GHOSTRAIL Docs
Documentation

GhostRail

A confidential lending layer on Circle's Arc. Supply into the same public venues everyone else uses, earn the same yield, and keep your position private. This is how it works — including the parts that don't work yet.

What is GhostRail?

GhostRail is a layer, not a lending venue. It doesn't hold its own liquidity pool, set its own interest rates, or decide which collateral is safe. It sits between you and an existing venue — Morpho, Aave, or anything else that lends on Arc — and makes your position invisible on the way through.

The trade you're making is simple: you get the venue's exact yield, and in exchange for pooling with other depositors you get amount privacy that is otherwise impossible on a public chain.

In one sentence

Wrap USDC into cUSDC (a confidential dollar), deposit cUSDC into a router, receive confidential shares. The router pools everyone, nets deposits against withdrawals, and supplies only the net to the public venue as a single ordinary transfer. The venue sees one address. Never you.

What it is not

  • Not a mixer. You deposit and withdraw from the same address. We never claim your deposit can't be linked to your withdrawal. We hide sizes and positions, not identity.
  • Not a venue. We have no pool of our own, no curators, no risk parameters. Your credit risk is the venue's, unchanged.
  • Not a custodian. There is no admin key, no owner, no pause, no privileged path to funds.
  • Not private yet. Arc's Privacy Sector isn't live. See Honest limits.

Why it exists

On a public lending venue, the depositor table is public. Not leaked — published. Anyone can read every address, every size, every entry, for free, with no permission and no trace.

For retail that's a curiosity. For anyone running size it's a live disadvantage: your position, your entry, and the moment you start unwinding are all a public feed that others can trade against. This is one of the main reasons institutional capital still can't come on-chain at scale — and why the loudest request the big lending protocols report hearing is confidential DeFi.

The demand is not theoretical. A confidential USDC vault on Morpho climbed into the top-10 USDC vaults by deposits within weeks of launch — capital is ready to move through confidential rails. GhostRail brings that to Arc, generic across assets and venues rather than bound to one curated vault.

How it works

Five steps. The first two are yours; the third is anyone's; the rest is arithmetic.

StepWhat happensWho does it
1 · ShieldcToken.shield(amount) — your USDC goes into the wrapper, you get cUSDC 1:1. From here the balance lives inside the enclave.You
2 · DepositcToken.setOperator(router) then router.deposit(amount) — the router pulls your cUSDC (never USDC) into the open batch.You
3 · Executerouter.executeBatch() — once the window closes, the batch nets and the net crosses. Permissionless: anyone can call it.Anyone
4 · Claimrouter.claimShares(batchId) — you pull your confidential shares at that batch's clearing price.You
5 · ExitrequestWithdraw(shares) → execute → claim(batchId) → cUSDC back. unshield() if you want plain USDC.You
The key detail people miss

The router never touches plain USDC on the way in. You give it cUSDC and it gives you confidential shares. Plain USDC only appears at one moment: when executeBatch unshields the net and supplies it to the venue.

Confidential token (cUSDC)

A generic wrapper that turns any ERC-20 into a confidential mirror of itself. cUSDC is the flagship; the same contract with a different underlying gives cWETH, cEURC, cUSTB.

  • 1:1 and provable. underlying.balanceOf(wrapper) == totalShielded holds at all times. The total is public; the breakdown is not.
  • Boundary amounts are public. shield and unshield emit their amounts — they cross into and out of the public chain, so they must. Everything inside is not.
  • Confidential transfers emit no amount. ConfidentialTransfer(from, to) carries the parties, never the value.
  • Operator model. Instead of an ERC-20 allowance you grant a time-bounded operator (setOperator(spender, until)) — that's how the router pulls your cUSDC.
  • Immutable, no owner. Nothing about it can be changed after deployment.

The shape follows the ERC-7984 confidential-token interface. It is not a cryptographic implementation: under APS the enclave provides confidentiality and the Solidity stays plain.

Vault router

One router per (asset, venue) pair. It holds a single position in the public venue on behalf of everyone, and tracks who owns what internally — confidentially.

The router is the only contract that crosses the boundary, and it does so once per batch. It is immutable, has no owner, no pause and no privileged fund path. The auditor address it's deployed with can only read; it can never move a token.

// what the router knows about you — all gated on read mapping(address => uint256) _shares; // your confidential shares mapping(address => uint256) _costBasis; // what you put in, for "earned" mapping(address => uint256) _reserved; // shares locked in a pending exit // what the world knows — public, always uint256 public totalShares; function totalAssets() public view; // venue balance only function checkSolvency() public view; // (totalShares, backing)

Confidential shares

Your claim on the pool is a share balance, not an amount. Shares are minted at the batch's clearing price and their value grows as the venue pays yield — an ERC-4626-style accounting model, but with the per-user numbers private.

// minted at claim, priced at that batch's snapshot shares = amount × (totalShares + 1e3) / (totalAssets + 1) // redeemed the other way assets = shares × (totalAssets + 1) / (totalShares + 1e3)

Those constants are a virtual offset: 1,000 virtual shares against 1 virtual asset. They make the classic first-depositor inflation attack unprofitable — an attacker can't donate their way into owning the next depositor's money, because the offset dominates an empty pool.

Every division floors, and always in the protocol's favour. The dust that rounding leaves behind stays in the pool as extra backing. It can never round the other way.

Why totalAssets reads the venue only

totalAssets() returns the venue balance — never the router's idle cUSDC. That single choice closes the donation-inflation vector structurally: sending tokens to the router does not move the share price, because idle tokens were never counted as backing.

Batches & GhostGate netting

Inside the confidential zone, operations are instant. At the boundary — the one line where value becomes public — they are batched. That's GhostGate.

Lifecycle

  • Open. Deposits and withdrawal requests queue into the current batch. You can cancelDeposit() or cancelWithdraw() any time while it's open.
  • Executed. Once batchOpenedAt + batchWindow has passed, anyone calls executeBatch(). It snapshots the clearing price, nets, and crosses once.
  • Claimable. You then pull: claimShares(batchId) for a deposit, claim(batchId) for a withdrawal — both priced at that batch's stored snapshot.

What netting actually does

Say a batch has 1,510,000 of deposits and 310,000 of withdrawal value. Without netting, the public chain would see many transfers. With netting it sees exactly one:

net = deposits − withdrawalValue net > 0 → unshield(net); venue.deposit(net) // one supply net < 0 → venue.withdraw(−net); shield(−net) // one withdrawal net = 0 → nothing crosses at all

Withdrawals are funded from the deposits already sitting in the router; only the difference ever moves. When deposits and withdrawals happen to match, nothing touches the public chain — the entire batch settles inside the confidential zone.

Why execution is O(1)

executeBatch loops over nothing. It reads two running aggregates, mints/burns shares in aggregate, crosses once, and advances. Users then pull their own share. That's deliberate: an earlier design looped over batch participants, and an independent review found it could be griefed into a permanent brick. See Security.

View keys & selective disclosure

Privacy that an auditor can't pierce is useless to an institution. Every account can appoint one observersetObserver(address) on the token, grantViewKey on the ledger — who can read that account's confidential values exactly as the owner can.

This is the compliance story in one line: invisible to the market, legible to your auditor. It's per-account and revocable (setObserver(address(0))). There is no global backdoor: no key, no role, no address can read everyone.

CallersharesOf(you)positionOf(you)
You✓ returns✓ returns
Your observer✓ returns✓ returns
Anyone elseNotAuthorizedToView()NotAuthorizedToView()

The solvency invariant

Privacy that can hide insolvency is a scam waiting to happen. So the aggregate is public and checkable by anyone, at any time, without seeing a single position:

previewRedeem(totalShares) totalAssets()

In words: everything the pool owes, priced at the current share price, is covered by what the venue actually holds. checkSolvency() returns both numbers so you can verify it yourself.

This is not a claim — it's proven as a stateful fuzz invariant across 128,000 randomized calls, alongside token conservation and a no-value-creation property.

Architecture

The whole design rests on one fact about Arc that's easy to miss: Arc is not a private chain. It is a public EVM and a private one, side by side.

LayerWhat lives there
Public Arc EVMThe lending venues (Morpho, Aave, Arc-native protocols), plain USDC, everything visible. USDC is the native gas token — users never hold ETH.
APS · private pEVMA TEE-backed sector where contract state is sealed inside a hardware enclave. GhostRail's accounting lives here. You write plain Solidity; the enclave provides the confidentiality.
shield / unshieldThe only boundary between them — atomic, same-block, and generic over any Arc ERC-20. This is exactly where GhostRail nets.

Same-block atomic composability is what makes the layer viable: a confidential contract can call a public venue and settle in one transaction. Without it you'd need a bridge, and a bridge would leak the timing that netting is designed to hide.

Why TEE and not ZK or FHE

All three can hide a balance. They differ in what you trust and what you pay:

ModelTrustCost
ZKCryptography onlyCircuits, proving time, and — for amount privacy — fixed denominations, which drags you toward mixer semantics.
FHECryptography onlyCiphertext types, a KMS, async decryption — powerful, but a heavier stack to build and operate.
TEE (APS)Hardware vendor + attestationAlmost none. Plain Solidity, native composability. The trade is a hardware trust assumption instead of a purely cryptographic one.

GhostRail takes the proven confidential-DeFi patterns — netting, the two-speed boundary, the honest boundary table — onto the simpler substrate. We state the TEE trust assumption plainly rather than implying cryptographic guarantees we don't have.

Privacy model

We hide the things an institution needs hidden and keep public the things solvency requires. Nothing more, nothing less.

FactStatus
Your confidential balancePrivate
Your shares, cost basis, earningsPrivate
Who deposited how much, and whenPrivate
Your per-batch entryPrivate
Shield / unshield amountsPublic
Total shielded, total shares, venue positionPublic
The per-batch net (direction + amount)Public
That an account exists and interactedPublic
The solvency invariantPublic

Operational privacy, not unlinkability

We make a deliberately narrow claim. A mixer promises that nobody can link your deposit to your withdrawal — which requires a large anonymity set from day one, fixed denominations, and carries regulatory weight that a compliance-native product cannot absorb. We promise something smaller and actually deliverable: your size and your position are not readable, while the aggregate stays verifiable and your auditor stays authorized.

Why pooling is forced, not chosen

This is the load-bearing argument. A position that lives in a public contract has a public amount — there's no way around that. So there are exactly two moves:

  1. Hide the owner (a proxy holds the position). The amount is still public.
  2. Hide the breakdown (many owners share one position). The aggregate is public; who owns what is not.

If you want amount privacy, only (2) works. And (1) plus "make the amounts private too" just is (2) with extra steps. Splitting one depositor across many proxies to disguise size has a name in AML — structuring — and it's the last architecture a compliance-native product should adopt. So pooling isn't a design preference. It's the mechanism.

User flows

Getting USDC onto Arc (CCTP)

If your USDC is on another chain, the Bridge screen uses Circle's CCTP via Bridge Kit: it burns USDC on the source chain, waits for Circle's attestation, and mints native USDC on Arc. Not a wrapped bridge asset — real USDC. This is a genuine, live Circle integration on Arc testnet.

Base Sepolia USDC → burn (source) → Circle attestation → mint native USDC on Arc ← staged; not instant → shield → cUSDC → deposit → confidential shares

Depositing

// 1 — become confidential usdc.approve(cUSDC, amount); cUSDC.shield(amount); // public amount, one time // 2 — let the router pull your cUSDC cUSDC.setOperator(router, block.timestamp + 1 hours); router.deposit(amount); // queued into the open batch // 3 — after the window closes (anyone can do this) router.executeBatch(); // 4 — pull your shares router.claimShares(batchId);

Withdrawing

router.requestWithdraw(shares); // reserves them in the open batch router.executeBatch(); // after the window router.claim(batchId); // cUSDC back to you cUSDC.unshield(amount); // optional: back to plain USDC

Reading your own position

router.positionOf(you) → (shares, deposited, currentValue) // earned = currentValue − deposited // from any other address:revert NotAuthorizedToView()

In the app this sits behind a Sign to reveal step: an EIP-712 signature proving you own the balance. Today that gates the read client-side; under APS the same signature is what authorizes the enclave to decrypt for you.

Contract reference

ConfidentialToken

shield(uint256 amount)Public amount
Pull the underlying, credit your confidential balance 1:1.
unshield(uint256 amount)Public amount
Burn confidential balance, send the underlying back.
confidentialTransfer(address to, uint256 amount)No amount emitted
Move confidential balance. The event carries the parties, never the value.
setOperator(address spender, uint64 until)
Time-bounded spending authority — how the router pulls your cUSDC.
confidentialBalanceOf(address)Gated
Owner or observer only; everyone else reverts.
setObserver(address)
Appoint (or revoke, with address(0)) your view key.
totalShielded()Public
The aggregate. Must always equal the wrapper's underlying balance.

ConfidentialVaultRouter

deposit(uint256 amount)
Queue cUSDC into the open batch. Requires operator approval first.
requestWithdraw(uint256 shares)
Reserve shares for exit in the open batch.
executeBatch()Permissionless
After the window: snapshot the clearing price, net, cross once, advance. O(1) — no loop.
claimShares(uint256 batchId)
Mint your share of an executed deposit batch at its stored snapshot price.
claim(uint256 batchId)
Collect cUSDC for an executed withdrawal at its stored snapshot price.
cancelDeposit() / cancelWithdraw()
Back out of the current batch while it's still open. Real, not a stub.
positionOf(address)Gated
(shares, deposited, currentValue). Owner or auditor only.
checkSolvency()Public
(totalShares, backingAssets). Verify the invariant yourself.
previewRedeem(uint256 shares)Public
Price shares in assets at the current share price.

Security

What's structurally impossible

  • We can't take your funds. No owner, no admin, no pause, no upgrade, no privileged path. There is no operator role, so there's nothing to compromise.
  • The auditor can't move anything. The view key is read-only by construction.
  • Donations can't move the share price. totalAssets() reads the venue balance only.
  • Rounding can't drain the pool. Every division floors in the protocol's favour; dust accretes as backing.

What's covered by tests

56 tests across 7 suites, including 4 stateful fuzz invariants over 128,000 randomized calls: token conservation, router solvency, share-price monotonicity under yield, and no value creation. Plus a global event scan asserting no confidential value ever appears in a log, and a three-way access test on every gated surface (owner ✓ / auditor ✓ / stranger ✗).

A finding we found and fixed

Remediated · was HIGH

An earlier executeBatch looped over every participant in the batch. Because there was no minimum deposit, an attacker could queue 1-wei deposits from many addresses until execution exceeded the block gas limit. Since currentBatch only advanced at the end of a successful execution, that would have bricked the router permanently — with no admin to rescue it.

The fix was architectural, not a patch: the router became pull-based. Execution is now O(1) — it snapshots the clearing price and adjusts aggregates — and users pull their own share afterwards. The loop is gone, so the attack has nothing to grow. A regression test proves execution stays in bounded gas with hundreds of distinct depositors.

What an audit still has to cover

  • Share math under adversarial sequencing across many batches.
  • The venue adapter boundary once real Morpho/Aave adapters replace the mock.
  • The APS assumptions themselves — enclave attestation, view-key enforcement, revert sanitization.
The order we will not reverse

An independent audit completes before mainnet real funds — not after. The vault router is marked AUDIT-GATED in the source for exactly this reason.

Honest limits

Everything above describes the design. Here is what is actually true today.

Privacy is notional right now

APS is not live. On Arc testnet the confidential values sit in ordinary storage — readable with eth_getStorageAt — and the msg.sender gating isn't enforced against an eth_call with a spoofed from. The privacy is architectural, not yet cryptographic. Every place production diverges is marked // APS-SWAP in the source.

What is live: the protocol logic, deployed and running on Arc testnet; the USDC market wrapping real Arc testnet USDC; and a real Circle CCTP bridge. Those are not simulated.

  • Anonymity set. A batch with one participant is a batch whose net is that participant's amount. Privacy scales with participation — which is why the production batch window targets 12–24h rather than the 60s used on the preview, and why we'd rather run a few deep markets than many thin ones.
  • Boundary amounts are public. Shield and unshield sizes are visible. A large shield followed closely by a matching batch net is correlatable. Netting and a long window mitigate this; they don't erase it.
  • Custody risk is real. Pooling means a contract or enclave bug can drain the pool. This cannot be eliminated, only reduced (audit, immutability, the solvency invariant) and disclosed.
  • Venues are mocked on testnet. Morpho and Aave aren't deployed on Arc yet — Aave V4 is in Arc governance. Only cUSDC · Morpho wraps real USDC; the other markets are labelled Preview · simulated.
  • TEE trust. APS confidentiality rests on hardware attestation, not on cryptography alone. That's a real assumption, and it's the price of the simplicity.

The claim, precisely: protocol live on Arc testnet, USDC-integrated, architected for APS — confidentiality activates when APS ships.

Deployments

KeyValue
NetworkArc Testnet
Chain ID5042002
RPChttps://rpc.testnet.arc.network
Explorertestnet.arcscan.app
USDC (native gas)0x3600…0000 — real Circle testnet USDC
Markets10 (asset, venue) routers · 1 live, 9 simulated

Live addresses and the smoke-test transaction hashes are in deployments/arc-testnet.json — each market carries an explicit simulated flag. Verify them on Arcscan; don't take our word.

Roadmap

v1 — live now

Confidential deposit and yield. Generic token + router, multi-asset markets, GhostGate netting, zero admin, public solvency invariant, view keys. On Arc testnet with real USDC and real CCTP.

v2 — after an independent audit

  • Real venue adapters — Morpho and Aave once they're on Arc mainnet. Aave V4 is already in Arc governance with a USDC/EURC/cirBTC scope. We're built to integrate on day one.
  • Same-asset leverage — loop a vault-share position. Collateral and debt are the same asset family, so there's no cross-asset price-gap risk and the share price is monotone. The leverage ratio itself stays private. It's the clean, low-tail-risk way to add leverage.
  • Institutional compliance tier — auditor tooling on top of the view-key primitive: audit logs, report export, disclosure flows. This is the revenue layer. Retail stays near-free; the fee is a small yield-share, never a fee on principal or on shielding.

v3 — research / demand-gated

  • Cross-asset borrowing — deferred, and honestly so. Pooled cross-asset collateral carries an irreducible tail risk: if price gaps faster than internal liquidation can execute, the venue liquidates the aggregate and safe users share the loss. Ships only with a liquidation design that stays ahead of the external venue.
  • Confidential yield aggregator — auto-routing to the best confidential yield. Deferred because it fights the privacy model: aggregation splits liquidity across venues, and thinner pools mean weaker anonymity sets. The tension has to be resolved before it's safe to ship.

Venue selection policy

Not all venues are equally safe to build on. Aave-style pools are structurally permissionless — supply is open to anyone and only a global supply cap applies. Morpho markets are permissionless and immutable too, and governance cannot halt them. Morpho curated vaults are different: a curator can install gate contracts that block specific addresses from depositing. So:

  1. Aave-style pools first — permissionless, and the risk parameters are governance's job, not ours.
  2. Morpho markets direct second — permissionless and immutable, with an explicit disclosure that supplier bad-debt risk is isolated to the chosen market.
  3. Curated vaults only where the gates are abdicated to address(0), i.e. permanently permissionless.

FAQ

The obvious suspicion
Isn't this just a mixer?

No, and the difference is structural rather than cosmetic.

A mixer breaks the link between deposit and withdrawal: you put in from address A and take out to address B, and the whole point is that nobody can tell which deposit was yours. That requires fixed denominations (because amounts identify you) and a large anonymity set.

GhostRail does none of that. You deposit and withdraw from the same address. The link is never broken — anyone can see that you shielded, and that you interacted. What's hidden is your share of the pool, your position size, and your entry. And your auditor can be given a key that reveals all of it — the exact opposite of a mixer's guarantee.

Short version: a mixer hides who. We hide how much.

Couldn't you do this on any public chain, without APS?

Not with plain pooling — and this is the part that surprises people.

If you built a router on a normal public chain and pooled everyone's USDC, an observer wouldn't bother reading the venue. They'd read your router's events instead: "0xB81a deposited 8M into the router." One layer up, same information. Pooling by itself hides nothing.

For the pool to actually hide anything, the per-user balances must be genuinely unreadable. That needs a confidentiality substrate: ZK, FHE, or a TEE. APS isn't the only option — it's the simplest one, and it's native to Arc with same-block atomic composability into public venues.

Can you steal my funds?

Not by design, and not by key: the contracts are immutable and there is no owner, no admin, no pause and no upgrade path. There is no privileged function that moves user funds. The auditor role can only read.

What remains is honest to state: a bug in the router or the enclave could drain the pool. That risk exists in every pooled system and cannot be engineered away — only reduced (audit, immutability, the public solvency invariant) and disclosed. That's why real funds wait for an independent audit.

Mechanics
Do you take my USDC or my cUSDC?

cUSDC. The router never touches plain USDC on the way in. You shield first (USDC → cUSDC), grant the router an operator allowance, and it pulls cUSDC. In return you get confidential shares — not cUSDC back.

Plain USDC appears at exactly one moment: executeBatch unshields the net and supplies that to the venue. On the way out the reverse happens — the router shields what it withdraws, and pays you in cUSDC.

Why do I have to wait for a batch? Why not deposit instantly?

Because instant means visible. If your deposit crossed to the venue the moment you made it, the public chain would show a transfer with your exact amount, and the whole exercise would be pointless.

Batching lets deposits and withdrawals cancel each other so only the difference crosses. It also decorrelates timing: with a long window, your action and the public movement aren't the same event. The cost is latency — that's the honest trade.

You can always cancelDeposit() while the batch is still open.

What does the venue actually see?

One address — the router — supplying and withdrawing ordinary amounts. From Morpho's or Aave's perspective GhostRail is a single depositor with a single position. There's nothing exotic about the interaction; it's a normal supply.

Is my yield lower because I go through you?

No. Your shares accrue the venue's yield 1:1. We don't produce the yield and we don't skim it in v1 — there is no fee in the deployed contracts.

When a fee does arrive (v2), it will be a small share of yield, never a fee on principal and never a fee on shielding. A fee on shielding would be a tax on money that hasn't earned anything yet — and users could bypass it by using Arc's native shield directly anyway.

What happens if I'm the only one in a batch?

Then the batch's net is your amount, and the privacy of that batch is weak. We say this plainly rather than pretending otherwise: privacy scales with participation.

Mitigations are structural — a long production window (12–24h) so more participants accumulate, and a preference for a few deep markets over many thin ones. But in a nearly-empty pool, an observer correlating your shield with the batch net will often be right.

Can I lose money to rounding?

Not meaningfully, and never in the protocol's disfavour. Every division floors toward the pool, so the sum of what everyone can claim is always ≤ what the pool holds. The remainder — dust, at the 1e-6 level — stays as extra backing.

Privacy
Is my position private right now?

No. APS isn't live, so on Arc testnet the values live in normal storage and can be read directly with eth_getStorageAt. The gating is architectural today, not cryptographic.

What is live: the protocol logic, real USDC, real CCTP. When APS ships, the same contracts become genuinely confidential — that's what the // APS-SWAP markers in the source point at. We'd rather say this than let you find out later.

Who can see my position?

Under APS: you, and whoever you appoint as your observer (your auditor, your regulator, your fund admin). Nobody else — not us, not the validators, not the venue.

There is no global view key. No address can read everyone. Disclosure is per-account and revocable.

If shield amounts are public, what's actually hidden?

Shielding says "this account moved X dollars into the confidential zone" — once, at the boundary. It doesn't say what you did with it: how much you supplied, to which venue, at what price, when you entered, what you've earned, or when you're leaving. Those are the facts that let someone trade against you, and those stay inside.

It's the difference between knowing someone walked into a bank and knowing their balance, their positions and their exit plan.

What happens if APS never ships?

Then GhostRail on Arc stays what it is today: a working, deployed lending router without the confidentiality. The value proposition depends on APS, and we don't hide that dependency.

The design isn't wasted, though — the patterns are substrate-portable by design, which is what makes them straightforward to carry onto whatever confidentiality layer ships.

Design choices
Why not run your own vault instead of using Morpho/Aave?

Because our own vault would need its own liquidity, and bootstrapping liquidity from zero is the two-sided cold-start trap that kills most lending launches. Nobody supplies to an empty pool, and nobody borrows from one either.

The incumbents' liquidity, curators, borrowers and trust took years to build. We inherit all of it and add the one thing it lacks. That's the whole strategy: layer, not venue.

Why is there no borrowing?

Cross-asset borrowing against pooled collateral has an irreducible tail risk. If the price gaps faster than our internal liquidation can execute, the venue liquidates the router's aggregate position — and users who were nowhere near their own liquidation threshold share the loss.

Conservative buffers shrink that risk; they don't remove it. A user's priorities are safety first, yield second, privacy third — a product that trades safety for privacy has the order wrong. So borrowing waits (v3), while same-asset leverage (v2) is clean: same asset family, no cross-asset gap, monotone share price.

Why not a mixer, if it's more private?

Two reasons, and either is sufficient. First, a mixer's guarantee needs a large anonymity set on day one — which a new protocol doesn't have, making early users less private, not more. Second, unlinkability is regulatory poison for a product whose whole thesis is compliance-native institutional capital on Circle's chain.

Institutions aren't asking to disappear. They're asking not to be front-run while staying auditable. That's a different product.

Which venues can you support?

Any that lend on Arc. The router takes an ILendingVenue adapter, so it isn't bound to two names — Morpho and Aave are the first targets because they're the largest and because Aave V4 is already in Arc governance. Arc-native protocols slot in the same way.

One caveat worth knowing: Morpho's curated vaults can install gate contracts that block specific depositors. Morpho markets and Aave pools cannot. See the venue selection policy in Roadmap.

Why 5 assets? Why not everything?

Because liquidity fragments privacy. Every (asset, venue) pair is its own pool with its own anonymity set — ten thin markets are ten weak ones. The architecture has no ceiling; the deployment should, at least until there's enough participation to justify each pool.

Project
How is this different from a single confidential vault?

A single confidential vault is one curated market: deposit, earn that strategy's yield, one asset, one venue. That shape works and has already proven the demand for private on-chain lending.

GhostRail is a generic layer rather than a single vault: any ERC-20, any venue, on Arc, with the selective-disclosure surface built in from the start. Fewer moving parts, broader surface.

Is the code open source? Audited?

Open source, MIT, on GitHub — contracts, tests, deploy scripts and frontend.

Not independently audited yet. We ran our own review and it found a real HIGH — the executeBatch DoS described in Security — which we fixed architecturally. An independent audit is the gate before real funds on mainnet, not something we'll do afterwards.

Which Circle products does GhostRail use?
  • USDC Live — the flagship market wraps real Arc testnet USDC; it's also the gas token, so users never hold ETH.
  • CCTP Live — cross-chain onboarding via Circle's Bridge Kit: burn on the source chain, Circle attestation, native mint on Arc.
  • EURC — via the Euro market.
  • Gateway, Paymaster — on the roadmap, not claimed as integrated.

Glossary

Arc
Circle's stablecoin-native L1. USDC is the gas token. Public EVM plus a private sector (APS).
APS
Arc Privacy Sector — a TEE-backed private EVM where contract state is sealed inside a hardware enclave.
TEE
Trusted Execution Environment. A hardware enclave that runs code with sealed memory and proves what it's running via attestation.
cUSDC
Confidential USDC. A 1:1 wrapper whose balances are readable only by the owner and their observer.
Shield / unshield
Crossing into or out of the confidential zone. The amounts are public; that's the boundary.
GhostGate
The netting mechanism at the public boundary: deposits cancel withdrawals, only the net crosses.
Batch
A window during which deposits and withdrawals queue. Open → Executed → Claimable.
Net
Deposits minus withdrawal value in one batch. The only quantity that touches the public venue.
Confidential share
Your private claim on the pool. Value grows with venue yield; the balance is gated on read.
View key / observer
An address you authorize to read your confidential values. Per-account, revocable, read-only.
Solvency invariant
previewRedeem(totalShares) ≤ totalAssets() — public, checkable, fuzz-proven.
Virtual offset
1,000 virtual shares against 1 virtual asset, making first-depositor inflation attacks unprofitable.
Anonymity set
How many participants your amount hides among. Small set, weak privacy.
Operational privacy
Hiding sizes and positions while staying linkable and auditable — as opposed to unlinkability.
Structuring
Splitting one flow into many to disguise size. An AML red flag — and why we don't use per-user proxies.
CCTP
Circle's Cross-Chain Transfer Protocol. Burns USDC on one chain, mints native USDC on another.
APS-SWAP
A source marker at every point where the testnet simulation diverges from production APS.
© 2026 GhostRail · MIT · GitHub Testnet preview — APS not yet live. Not affiliated with Circle, Morpho, or Aave.