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.
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.
| Step | What happens | Who does it |
|---|---|---|
| 1 · Shield | cToken.shield(amount) — your USDC goes into the wrapper, you get
cUSDC 1:1. From here the balance lives inside the enclave. | You |
| 2 · Deposit | cToken.setOperator(router) then router.deposit(amount)
— the router pulls your cUSDC (never USDC) into the open batch. | You |
| 3 · Execute | router.executeBatch() — once the window closes, the batch nets
and the net crosses. Permissionless: anyone can call it. | Anyone |
| 4 · Claim | router.claimShares(batchId) — you pull your confidential shares at
that batch's clearing price. | You |
| 5 · Exit | requestWithdraw(shares) → execute → claim(batchId) →
cUSDC back. unshield() if you want plain USDC. | You |
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) == totalShieldedholds at all times. The total is public; the breakdown is not. - Boundary amounts are public.
shieldandunshieldemit 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.
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()orcancelWithdraw()any time while it's open. - Executed. Once
batchOpenedAt + batchWindowhas passed, anyone callsexecuteBatch(). 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:
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.
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
observer — setObserver(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.
| Caller | sharesOf(you) | positionOf(you) |
|---|---|---|
| You | ✓ returns | ✓ returns |
| Your observer | ✓ returns | ✓ returns |
| Anyone else | ✗ NotAuthorizedToView() | ✗ 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:
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.
| Layer | What lives there |
|---|---|
| Public Arc EVM | The lending venues (Morpho, Aave, Arc-native protocols), plain USDC, everything visible. USDC is the native gas token — users never hold ETH. |
| APS · private pEVM | A 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 / unshield | The 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:
| Model | Trust | Cost |
|---|---|---|
| ZK | Cryptography only | Circuits, proving time, and — for amount privacy — fixed denominations, which drags you toward mixer semantics. |
| FHE | Cryptography only | Ciphertext types, a KMS, async decryption — powerful, but a heavier stack to build and operate. |
| TEE (APS) | Hardware vendor + attestation | Almost 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.
| Fact | Status |
|---|---|
| Your confidential balance | Private |
| Your shares, cost basis, earnings | Private |
| Who deposited how much, and when | Private |
| Your per-batch entry | Private |
| Shield / unshield amounts | Public |
| Total shielded, total shares, venue position | Public |
| The per-batch net (direction + amount) | Public |
| That an account exists and interacted | Public |
| The solvency invariant | Public |
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:
- Hide the owner (a proxy holds the position). The amount is still public.
- 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.
Depositing
Withdrawing
Reading your own position
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
address(0)) your view key.ConfidentialVaultRouter
(shares, deposited, currentValue). Owner or auditor only.(totalShares, backingAssets). Verify the invariant yourself.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
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.
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.
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 · Morphowraps 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
| Key | Value |
|---|---|
| Network | Arc Testnet |
| Chain ID | 5042002 |
| RPC | https://rpc.testnet.arc.network |
| Explorer | testnet.arcscan.app |
| USDC (native gas) | 0x3600…0000 — real Circle testnet USDC |
| Markets | 10 (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:
- Aave-style pools first — permissionless, and the risk parameters are governance's job, not ours.
- Morpho markets direct second — permissionless and immutable, with an explicit disclosure that supplier bad-debt risk is isolated to the chosen market.
- Curated vaults only where the gates are abdicated to
address(0), i.e. permanently permissionless.
FAQ
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.
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.
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.
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.
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
previewRedeem(totalShares) ≤ totalAssets() — public, checkable, fuzz-proven.