RAIL20 for Agents
Give any autonomous agent a private balance and private payments on Base. Integration is an HTTP API plus one signature - no circuit knowledge, no client-side proving, no new wallet.
Why agents need this
Agents that hold and move value on public chains broadcast everything: treasury size, payment timing, and the full counterparty graph. That leaks strategy, invites front-running, and exposes agent-to-agent (A2A) relationships. RAIL20 shields the balance and the payment while keeping the agent's normal signing key.
The integration model
An agent never handles zero-knowledge proofs. It signs a message, and the RAIL20 relayer derives the account, builds the proof, pays gas, and broadcasts. On-chain, only a commitment and a nullifier appear - no sender, recipient, or amount.
- No new wallet. The private account is derived deterministically from the agent's existing signature.
- No gas. The relayer fronts gas and deducts a fee in the transacted token.
- No server-side account. Nothing to register; balance is reconstructed from on-chain notes with a key only the agent can derive.
- Recoverable. Because keys derive from the signature, a crashed or restarted agent can always resume.
Three steps to integrate
1Derive the private account
The agent signs the fixed RAIL20 message once with its existing key. That signature deterministically derives the agent's private account keys. Cache the signature for the session; it is the only secret the agent needs.
// The exact message string is fixed. Sign it once, cache the result. const RAIL20_SIGN_MSG = "RAIL20 Private Account Sign-in\n\nSigning this message derives your account keys.\nNo transaction will be sent." const signature = await agent.signMessage(RAIL20_SIGN_MSG)
2Fund the private balance
Either deposit ETH/USDC into the pool, or simply receive private notes from another agent. To deposit, request an unsigned transaction from the relayer and broadcast it from the agent's wallet.
const prep = await post("/api/deposit/prepare", { signature, address: agent.address, amount: "50", pool: "usdc" }) // prep = { to, data, value, approveTx? } - sign & broadcast from the agent wallet if (prep.approveTx) await agent.sendTransaction(prep.approveTx) await agent.sendTransaction({ to: prep.to, data: prep.data, value: prep.value })
3Transact privately
Read the private balance any time (reconstructed locally, nothing hits the chain), then pay another agent, swap, or bridge with one signed API call. The relayer builds the proof and broadcasts.
// Read private balance - derived from on-chain notes, no tx const { balance } = await post("/api/balance", { signature, address: agent.address, pool: "usdc" }) // Pay a peer agent privately. Relayer broadcasts; agent pays no gas. await post("/api/withdraw", { signature, recipient: peerAgent.address, amount: "25", // amount hidden on-chain pool: "usdc" }) // On-chain: one commitment + one nullifier. No sender, recipient, or amount.
API reference
All endpoints are served from https://api.rail20.org and accept a JSON body containing the agent's signature.
| Endpoint | Method | Purpose |
|---|---|---|
/api/balance | POST | Reconstruct the agent's private balance from its notes |
/api/deposit/prepare | POST | Build an unsigned deposit (shield) transaction |
/api/withdraw | POST | Private send to any address (up to 2 recipients) |
/api/swap-private | POST | Same-chain swap into a burner (step 1 of private swap) |
/api/burner-gas | POST | Relayer gas top-up for a burner mid-swap |
/api/swap | POST | Cross-chain bridge via NEAR Intents 1Click |
/api/bridge/quote | POST | Quote a swap or bridge (dry or real) |
/api/intent-status | GET | Poll cross-chain intent settlement |
Common agent patterns
Private treasury
An agent receives funds to its public wallet, deposits into the pool, and from then on manages balance privately. Withdraw to a fresh address when spending publicly - an observer sees deposits into the pool and withdrawals from the pool, but cannot connect the two.
Agent-to-agent (A2A) payments
Agents pay each other directly from private balance via /api/withdraw. Amount and counterparties stay hidden, so recurring A2A relationships and payment sizes never appear on-chain.
Private swap & rebalance
An agent rebalances ETH into USDC (or back) via /api/swap-private, which routes through a one-time burner and Uniswap V3 on-chain, then re-shields the result. Strategy shifts never leak.
Considerations
| Consideration | Detail |
|---|---|
| Signing | One personal_sign over the fixed message. Cache per session; it derives all account keys. |
| Gas | Relayer pays gas for withdrawals and private transactions. Deposits are broadcast by the agent wallet. |
| Fees | Flat + 0.35% in the transacted token. Flat: 0.00025 ETH (ETH pool) or 1 USDC (USDC pool). |
| Minimums | USDC pool has a 1 USDC minimum on withdrawals. Keep transacted amounts above the fee + minimum. |
| Recovery | Keys derive from the signature; a restarted agent resumes with no state loss. |
| Latency | Send: seconds. Same-chain swap: ~30s. Cross-chain bridge: poll intent status (~1-6 min). |