Skip to Content
GuidesShield Assets

Shield Assets

The Shield page has two tabs: Deposit moves tokens into the shielded pool, Withdraw takes them back out to a public address.

Deposit

  1. Pick an asset pool — C2FLR, FXRP, or USDT0.
  2. Enter an amount.
  3. Confirm.

For ERC-20 assets the flow runs four steps:

StepWhat happens
Approve TokenStandard ERC-20 approval for the vault
Submit Depositshield() with your locally computed commitment
Confirm On-ChainWait for the receipt and the Shielded event
SettlementThe note is saved with its Merkle leaf index

Native C2FLR skips the approval step — shield() takes it directly as msg.value.

No zero-knowledge proof is generated here. Your browser computes Poseidon2(assetId, amount, ownerKey, blinding) and submits it alongside a perfectly public transfer. You’re publishing a commitment, not spending one.

What’s public

Everything about the deposit itself. The transfer amount, the asset, and your address are all visible on-chain — there is nothing secret about putting money in.

What’s not visible is which shielded note becomes yours afterwards. That link is what every later operation protects.

Withdraw

Withdrawal exits the pool publicly.

  1. Select one of your shielded notes.
  2. Enter a destination address.
  3. Confirm.
StepWhat happens
Generate ZK ProofThe withdraw circuit proves note ownership in your browser
Submit Withdrawalwithdraw() with the proof and public inputs
Confirm On-ChainVerifier checks the proof, tokens transfer
SettlementThe note’s nullifier is marked spent

Proving happens locally with Noir compiled to WASM and Barretenberg. It takes a moment — that’s real proof generation, not a simulated delay.

What’s revealed

withdraw publishes root, nullifierHash, amount, assetId, and recipient. Amount and asset are unavoidably public, since the payout is a plain ERC-20 transfer and the contract needs the real value to move real tokens.

What stays hidden is which prior deposit this withdrawal traces back to. Every note in the tree is a candidate.

Withdrawals are subject to sanction screening. If a ComplianceRegistry is configured, the vault reverts with RecipientNotScreened unless the destination address has a clear screen on file. See Compliance.

Notes are indivisible

You withdraw a whole note, not an arbitrary amount from a balance. If you want finer granularity, shield in the denominations you expect to use.

Withdrawing multiple notes at once

Unshield All withdraws every note you hold in the selected asset to one destination. Each note still needs its own proof — that part is unavoidable, one proof per note — but proving is local computation with no signature involved. Submission is batched through BatchWithdrawer, a small permissionless helper contract, so unshielding any number of notes costs one signature, not one per note. See Deployed Contracts for how it works and why it needs no privilege of its own.

One item failing (say, a note whose Merkle root has gone stale) doesn’t block the rest — you’ll see a count of how many succeeded and how many didn’t.

If you want privacy on both ends

Shielding is public and withdrawing to your own address is public — doing both links them. To actually benefit, put something private in between: a dark pool order, a private payment, or simply time and a different destination address.

Last updated on