Core Features

Shield & Send

Deposit tokens into the privacy pool and send them privately using zero-knowledge proofs.

How Shielding Works

Shielding deposits tokens into a token pool. You receive a shielded note (a UTXO) - a cryptographic commitment that proves ownership without revealing the amount or your identity. Each token has its own pool; there is no shared vault.

  1. Your account keys are derived deterministically from a one-time wallet signature, in the browser. Nothing is stored on a server.
  2. A note commitment is computed as Poseidon(amount, pubkey, blinding, mintAddress).
  3. The deposit merges any existing note with the new amount, producing your updated private balance (a transaction has up to 2 inputs and 2 outputs).
  4. The token pool pulls the tokens and inserts the new commitment into its Merkle tree.
  5. The note's encrypted output is written on-chain, so you can always recover your balance from just your signature - no external backup needed.
Your keys are derived from your signature and never leave your browser. The on-chain commitment reveals nothing about the amount or owner.
Shielding native ETH: pick ETH in the app and your deposit goes into that chain's dedicated ETH pool. On Base the pools are ETH (0x99205B045Fcf5ff2689ad6B038abF3dd35b79ddE) and USDC (0x764FF96EabEeF2b197f845dD8D39441Ac68FDE11); on Robinhood Chain they are ETH (0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163) and USDG (0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0); on Arbitrum they are ETH (0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163) and USDC (0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0); on BNB Smart Chain they are BNB (0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163), USDC (0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0) and USDT (0x8d484006882fDe2604968D5BE24D092DbAE2C04b). Each token has its own independent pool - there is no shared vault. The Base and Robinhood ETH pools have a 0.005 ETH minimum deposit; stablecoin pools have their own floors (20 USDC on Base, 10 units elsewhere). The floor exists because the withdraw fee is a flat amount plus 0.35%, so a deposit close to the flat fee would be largely consumed on the way out.

Shielding Example

javascript
// Alice shields 100 USDC into the USDC pool.
// The app derives her keys from a wallet signature, builds the note,
// and prepares the deposit transaction via the API.
const amount = parseUnits("100", 6); // USDC has 6 decimals

const prep = await fetch("https://api.rail20.org/api/deposit/prepare", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ signature, address, amount: "100", pool: "usdc" }),
}).then(r => r.json());

// prep.approveTx (ERC-20 approve) then prep (the deposit tx) are signed
// and broadcast by the user's wallet. The output note is encrypted on-chain.
await wallet.sendTransaction(prep.approveTx);
await wallet.sendTransaction({ to: prep.to, data: prep.data, value: prep.value });

How Private Sending Works

A zero-knowledge proof proves:

Transfer Flow

  1. Input note - Alice's existing note (e.g., 100 USDC)
  2. Output notes - Bob (60 USDC) + Alice change (40 USDC); at most 2 outputs per tx
  3. ZK proof - valid without revealing amounts or addresses
  4. Relayer - submits the proof, pays gas, collects a flat + 0.35% fee
  5. Pool - verifies, marks the nullifier spent, inserts the new commitments
javascript
// Alice sends 60 USDC to Bob (recipient + change = 2 outputs).
// The relayer derives keys from the signature, builds the proof, and broadcasts.
const withdraw = await fetch("https://api.rail20.org/api/withdraw", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    signature,          // derives Alice's account keys
    pool: "usdc",
    amount: "60",
    recipient: bobShieldedAddress,
  }),
}).then(r => r.json());
// The relayer broadcasts and pays gas; Alice's wallet is never the sender.

Privacy Model

Privacy strength = anonymity set size. Every shielded note is a commitment hash - indistinguishable on-chain.

What's visible on-chain

DataVisible?Details
Shield deposit amountYesPublic ERC-20 / ETH transfer into the pool
Commitment hashesYesPoseidon hashes - reveal nothing
Nullifier hashesYesProve a note is spent, not which one
Transfer amountsNoHidden by the ZK proof
Sender identityNoThe relayer submits the transaction
Recipient identityNoThe recipient decrypts the note locally

Best practices