@rail20/cli
v0.3.5
Published
Private payments for onchain agents. CLI for RAIL20 - shield, send, swap, bridge with zero-knowledge privacy on Base and Robinhood Chain.
Maintainers
Readme
@rail20/cli
Private payments for onchain agents. One env var or one hidden prompt, then shield / send / swap / bridge with zero-knowledge privacy on Base and Robinhood Chain. Wraps the same API docs.rail20.org/agents uses - no ZK client needed on your side.
Every command takes an optional --chain base|robinhood flag (default base). On Base the stablecoin is USDC; on Robinhood it's USDG.
Install
npm i -g @rail20/cli
# or
npx @rail20/cli helpSetup
Two ways to provide your key, depending on who's running it.
Interactive (humans / local demo) - key never shown
rail20 login
# private key: ●●●●●●●●●● <- hidden input, like sudo password
# ✓ wallet stored at ~/.rail20/wallet.json (chmod 600)The key is prompted with hidden input (no echo, never enters shell history), then stored locally at ~/.rail20/wallet.json with chmod 600 (owner-only). It is never transmitted - only the derived signature is sent to the relayer.
Env var (agents / CI) - no prompt
export RAIL20_KEY=0xyour_agent_private_key
rail20 login # detects env var, skips the prompt, stores nothing to diskAgents pull the key from their own secret manager / vault into RAIL20_KEY. The CLI reads it directly and does not write it to disk.
Logout
rail20 logout # removes ~/.rail20/wallet.json + config.jsonKey resolution order: RAIL20_KEY env var → ~/.rail20/wallet.json → error.
Commands
rail20 balance # private + public balances (both pools), Base
rail20 balance --chain robinhood # balances on Robinhood (ETH + USDG)
rail20 balance --pool eth # just private ETH
rail20 deposit 5 --pool usdc # shield 5 USDC from public to private (Base)
rail20 deposit 5 --pool usdg --chain robinhood # shield 5 USDG on Robinhood
rail20 send 0xRECIPIENT 2.5 # send 2.5 USDC privately (no gas from you)
rail20 send 0xRECIPIENT 0.01 --pool eth # send private ETH
rail20 send 0xRECIPIENT 2.5 --chain robinhood # send 2.5 USDG privately on Robinhood
rail20 swap 0.001 --from eth # private same-chain swap ETH -> USDC (Uniswap V3)
rail20 swap 3 --from usdc # private same-chain swap USDC -> ETH
rail20 bridge arb_usdc 0xRECIPIENT 2.5 # bridge cross-chain privately to Arbitrum
rail20 bridge bsc_usdt 0xRECIPIENT 5 # bridge to BSC (destinations: rail20 assets)
rail20 assets # list all supported bridge destinations
rail20 recover # sweep any stranded funds from burner slots
rail20 recover --to 0xADDR # sweep to a different destinationHow it works
Each command follows the pattern docs.rail20.org/agents documents.
Login / balance / send / bridge - you sign once, the relayer builds the Groth16 proof server-side, and broadcasts. You pay no gas. The relayer is compensated via the protocol fee (flat + 0.35% per withdraw / swap / bridge).
Deposit - your public wallet signs and broadcasts the shield tx (this is the only step where you pay gas, because the pool must see funds coming from a public address). For ERC-20 pools (USDC), an approve runs first; the CLI polls the on-chain allowance before broadcasting the deposit (same fix as the frontend for load-balanced RPC lag).
Swap same-chain - mirrors the frontend burner flow exactly:
- Derive a fresh burner wallet deterministically from your signature (
keccak256("rail20-burner-v2:" + keccak(sig) + ":" + nonce), scan slots 0-19). - Private withdraw from your pool → burner via
/api/swap-private. - For USDC origin: relayer tops up burner gas via
/api/burner-gas. - Burner approves + swaps on Uniswap V3 (SwapRouter02, 0.01% fee tier).
- WETH is unwrapped to ETH if ETH is the destination.
- Pre-flight
eth_callsimulation of the reshield (catches reverts cheap). - Burner reshields the swapped output back to your private balance via
/api/deposit/prepare.
If any step fails between (2) and (7), funds remain on the burner (which is deterministic from your signature). Run rail20 recover to sweep them home.
Bridge cross-chain - private withdraw to a NEAR Intents 1Click solver via /api/swap, then poll /api/intent-status until SUCCESS (or REFUNDED, in which case the solver returns your funds automatically). No burner involved for bridges.
Recover - scans burner slots 0-19 for stranded USDC / ETH and sweeps them back to your public wallet (or --to <addr>). Handles the wallets swap may have left behind if a later step reverted. Uses Base-realistic gas params (0.05 gwei) so the burner's tiny ETH top-up is enough for the sweep tx.
Environment
RAIL20_KEY- agent private key (0x-hex). Optional if you've runrail20 login.RAIL20_API- API base URL (defaulthttps://api.rail20.org).RAIL20_RPC/RAIL20_BASE_RPC- Base RPC endpoint (defaulthttps://mainnet.base.org). Set to your own Alchemy / QuickNode URL for faster + more reliable gas estimation on the burner-heavy swap flow.RAIL20_ROBINHOOD_RPC- Robinhood Chain RPC endpoint (defaulthttps://rpc.mainnet.chain.robinhood.com), used when--chain robinhood.
Config files
~/.rail20/wallet.json- stored private key (chmod 600, only if you usedrail20 logininteractively).~/.rail20/config.json- cached RAIL20 signatures per wallet (chmod 600).
Verified live on Base mainnet
Every command in this CLI has been exercised end-to-end against api.rail20.org with real on-chain transactions:
deposit→0x7ae2dde7…(3 USDC shielded)send→0x2108ae58…(1.49125 USDC delivered privately, fee 1.00875)swap→0x1bd8d68d…(0.001 ETH → 1.170797 USDC private, full reshield)bridge→0xdfc495fa…(2.5 USDC Base → 1.484306 USDC delivered to Arbitrum)recover→ 1.9895 USDC swept back from a stranded burner
Cross-chain destinations
Run rail20 assets for the full live list. Currently: Base, Ethereum, BSC, Arbitrum - ETH / WETH / USDC / USDT / cbBTC / ARB / BNB depending on the chain.
Roadmap
@rail20/sdk- TypeScript SDK for programmatic use inside an agent.- Mandate limits - human-signed spend cap per window (aligns with the "supervised agent" pattern).
- Optional wallet encryption at rest - passphrase-protected
wallet.json.
Support
Docs: docs.rail20.org/agents · Site: rail20.org
