Smart Contracts
RAIL20 runs on Base (chain 8453), Robinhood Chain (chain 4663, an Arbitrum Orbit L2), Arbitrum (chain 42161), and BNB Smart Chain (chain 56). It runs one independent zero-knowledge privacy pool contract per token on each chain - there is no single shared vault. Each chain has a native-asset pool plus stablecoin pool(s): USDC on Base, USDG on Robinhood, USDC on Arbitrum, and both USDC + USDT on BSC (18-decimal Binance-Peg tokens).
Contract Addresses
Base (chain 8453)
| Contract | Address | Description |
|---|---|---|
| ETH Pool | 0x99205B045Fcf5ff2689ad6B038abF3dd35b79ddE | ZK privacy pool for ETH - UTXO commitments, Merkle tree, nullifiers |
| USDC Pool | 0x764FF96EabEeF2b197f845dD8D39441Ac68FDE11 | ZK privacy pool for USDC |
| USDC (token) | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | USDC token held by the USDC pool |
Robinhood Chain (chain 4663)
| Contract | Address | Description |
|---|---|---|
| ETH Pool | 0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163 | ZK privacy pool for ETH on Robinhood |
| USDG Pool | 0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0 | ZK privacy pool for USDG (Global Dollar) |
| USDG (token) | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 | Global Dollar token held by the USDG pool |
Arbitrum (chain 42161)
| Contract | Address | Description |
|---|---|---|
| ETH Pool | 0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163 | ZK privacy pool for ETH on Arbitrum |
| USDC Pool | 0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0 | ZK privacy pool for native USDC on Arbitrum |
| USDC (token) | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 | Native Circle USDC (6 decimals) held by the USDC pool |
Arbitrum and Robinhood share the same pool addresses because each pool was deployed from the same address at the same account nonce on both chains - the resulting contract address is deterministic and identical across chains. The deployments are independent contracts on separate chains; there is no shared state.
BNB Smart Chain (chain 56)
| Contract | Address | Description |
|---|---|---|
| BNB Pool | 0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163 | ZK privacy pool for native BNB |
| USDC Pool | 0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0 | ZK privacy pool for Binance-Peg USDC (18 decimals) |
| USDT Pool | 0x8d484006882fDe2604968D5BE24D092DbAE2C04b | ZK privacy pool for Binance-Peg USDT (18 decimals) |
| USDC (token) | 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d | Binance-Peg USDC token held by the USDC pool |
| USDT (token) | 0x55d398326f99059fF775485246999027B3197955 | Binance-Peg USDT token held by the USDT pool |
BSC's BNB and USDC pool proxies share the Robinhood/Arbitrum addresses (same deployer + nonce, deterministic CREATE); the USDT pool is unique to BSC. Binance-Peg USDC and USDT are both 18 decimals, unlike the 6-decimal USDC on Base/Arbitrum - the client and API pin TOKEN_DECIMALS=18 for these pools. Same-chain BNB ↔ USDC/USDT swaps route via PancakeSwap V3 (router 0x13f4EA83D0bd40E75C8222255bc855a974568Dd4).
Fee recipient: 0xd962c7a67b74EEe8D418189f7917A18577A4775d. Relayer fees for every pool are paid to this address.
Staking & Governance
The RAIL20 staking and governance system lets stakers lock $RAIL20 to earn a share of protocol fees paid in WETH and to gain voting power over on-chain governance. Live on Robinhood Chain (4663).
| Contract | Address | Description |
|---|---|---|
| $RAIL20 token | 0xda9176…e460 | Governance and staking token |
| Staking | 0x35F33E…fb5A | Per-position stake, daily snapshots, 30-day unlock cooldown |
| Voting | 0x0Ada81…f40d | Proposal lifecycle: create, sponsor, callVote, vote, execute |
| Delegator | 0x799067…9761 | Permissioned executor; owner is Voting |
| Treasury (proxy) | 0x73F795…C1BF | Holds protocol fees; admin is Delegator |
| GovernorRewards (proxy) | 0x260c4a…e7Bb | Distributes treasury funds to stakers by voting power |
| ProxyAdmin | 0x123D13…Fa1c | Upgrade admin for Treasury and GovernorRewards; owner is Delegator |
| Reward token (WETH) | 0x0Bd7D3…aD73 | Rewards are distributed in WETH |
Governance parameters (identical to upstream): sponsorship threshold 500,000 $RAIL20 within a 30-day window, voting quorum 2,000,000, 30-day unlock cooldown, snapshot interval 1 day, reward distribution interval 14 days, distribution rate 2% of the treasury balance per interval (intervalBP = 200).
Ownership chain: Treasury, ProxyAdmin, and GovernorRewards are all owned or admin-controlled by Delegator; Delegator is owned by Voting. All privileged actions execute only through passed governance proposals.
Privacy Pool
Each pool is a self-contained ZK privacy contract for a single token. It holds shielded balances as UTXO notes, maintains a Poseidon commitment Merkle tree, verifies zk-SNARK proofs, and tracks spent nullifiers. On-chain, a spend reveals only commitments and nullifiers - never amounts or recipients. The proving circuit produces at most 2 outputs per transaction: outputs[0] is the recipient note and outputs[1] is the change note.
// Shield (deposit) into the pool: create up to 2 output commitments.
// USDC pool: caller approves the pool first. ETH pool: send value.
function shield(uint256 amount, uint256[2] calldata outputCommitments) external payable;
// Spend a note by proving ownership. A zk-SNARK proof reveals only the
// Merkle root, nullifiers, and new output commitments - never amounts.
// outputs[0] = recipient note, outputs[1] = change note (max 2 outputs).
function transact(
uint256 root,
uint256[] calldata nullifiers,
uint256[2] calldata outputCommitments,
address recipient, // 0 = fully private (no on-chain withdraw)
uint256 withdrawAmount, // 0 = no public withdraw
bytes calldata proof
) external;UTXO Notes & Nullifiers
Shielding produces private UTXO notes. Each note commits to its value and owner as a Poseidon hash inserted into the pool's Merkle tree. To spend, the owner proves in zero knowledge that the note exists in the tree and publishes its nullifier; the nullifier marks the note spent without revealing which leaf it was. Double-spends are impossible because a nullifier can be published only once.
commitment = Poseidon(amount, pubkey, blinding, mintAddress)
nullifier = Poseidon(commitment, merklePath, signature) // signature = Poseidon(privKey, commitment, merklePath)
merkleRoot = Poseidon-based tree over all commitments (BN254 field)Relayer Fee
Users submit proofs to a relayer, which broadcasts the transaction so the user pays no gas and their wallet never touches the chain. The relayer takes a flat fee plus a percentage; there is no on-chain fee-splitting contract. All fees go to the fee recipient address above.
| Pool | Chain | Flat fee | Percentage |
|---|---|---|---|
| ETH pool | Base | 0.00025 ETH | 0.35% |
| USDC pool | Base | 1 USDC | 0.35% |
| ETH pool | Robinhood | 0.0002 ETH | 0.35% |
| USDG pool | Robinhood | 0.5 USDG | 0.35% |
| ETH pool | Arbitrum | 0.00002 ETH | 0.35% |
| USDC pool | Arbitrum | 0.5 USDC | 0.35% |
Integration Guide
import { createPublicClient, http } from "viem";
import { base } from "viem/chains";
const client = createPublicClient({ chain: base, transport: http() });
// Base (chain 8453)
const BASE_POOLS = {
ETH: "0x99205B045Fcf5ff2689ad6B038abF3dd35b79ddE",
USDC: "0x764FF96EabEeF2b197f845dD8D39441Ac68FDE11",
};
// Robinhood Chain (chain 4663)
const ROBINHOOD_POOLS = {
ETH: "0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163",
USDG: "0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0",
};
// Arbitrum (chain 42161) - same deterministic addresses as Robinhood
const ARBITRUM_POOLS = {
ETH: "0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163",
USDC: "0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0",
};
// BNB Smart Chain (chain 56) - BNB + USDC share Robinhood/Arbitrum addresses; USDT is unique.
const BSC_POOLS = {
BNB: "0xBf6a26CEF8f6251B46Cc09D03A0f12Df5972d163",
USDC: "0x04F8d8E4C3f23fE2BfB80c6C46b7d7dfE6f853F0",
USDT: "0x8d484006882fDe2604968D5BE24D092DbAE2C04b",
};
const POOLS = BASE_POOLS;
// Read the current Merkle root of the ETH pool
const root = await client.readContract({
address: POOLS.ETH,
abi: poolAbi,
functionName: "getLastRoot",
});Backend API
The RAIL20 backend at https://api.rail20.org serves proof, balance, and settlement data used by the app:
| Endpoint | Purpose |
|---|---|
/api/health | Service status |
/api/balance | Derive shielded balance from notes |
/api/assets | Supported tokens and pools |
/api/deposit/prepare | Prepare a shield (deposit) |
/api/withdraw | Relay a private withdraw |
/api/bridge/quote | Cross-chain bridge quote |
/api/swap, /api/swap-private | Swap-to-private flow |
/api/intent-status | Poll cross-chain intent settlement |