Check it yourself
How to confirm the pool's admin powers, tell shields, private transfers and withdrawals apart, and re-check every proof, using only the public chain.
You don’t have to take this site’s word for anything on this page. Every check below reads Robinhood Chain directly, needs no wallet, and sends no transaction.
Run every check at once
verify-mainnet.mjs is a single read-only script. It checks the admin powers, lists every pool transaction with its kind, and sends each proof back to the deployed verifier. In an empty folder, with Node.js 20 or newer:
npm init -y && npm i viem
curl -O https://zeronym.lat/verify/verify-mainnet.mjs
node verify-mainnet.mjs
It uses Robinhood Chain’s public RPC. Set RPC_URL to use your own, and add --json for machine-readable output. The script is short: read it before you run it.
Admin powers
The pool’s source is verified on Sourcify with an exact match, so the code you read there is the code on the chain. It is also verified on the Blockscout explorer. The admin can call only two functions:
| Function | What it does |
|---|---|
setToken(token, supported) |
Turns deposits of a token on or off |
setAdmin(admin) |
Hands the admin role to another address |
Nothing else in the pool checks for the admin. Here is how to confirm the rest:
| Claim | How to check |
|---|---|
| The admin is the 2-of-3 Safe | Call admin() on the pool. It returns 0xFB4ff79d6BD9563b9f9c85Ce0Aa16271eA904Ae3. On that address, getThreshold() returns 2 and getOwners() returns 3 owners |
| It can’t block withdrawals | transact checks token support only when extAmount is positive (a deposit). Private transfers and withdrawals never read it |
| It can’t move funds | No admin function transfers tokens. Tokens leave the pool only inside transact, to the recipient and relayer fixed by a valid proof |
| It can’t change the verifier | verifier is immutable: set once at deploy, with no setter |
| It can’t cap deposits | The pool has no deposit limit and no setting for one |
| It can’t upgrade the pool | The pool is not a proxy: the EIP-1967 implementation, admin and beacon slots are empty, and its code has no DELEGATECALL or SELFDESTRUCT |
What each pool transaction looks like
Every pool action is one call to transact. Its extAmount and the token movements in its receipt tell you which kind it is. Open the pool on the explorer and compare.
| Kind | extAmount |
USDG movements | Pool events |
|---|---|---|---|
| Shield | Positive: the deposit | The deposit, from the sender’s wallet into the pool | 2 NewCommitment, 2 NewNullifier |
| Private transfer | 0 | None, or only the relayer fee (0.50 USDG) from the pool to the relayer | 2 NewCommitment, 2 NewNullifier |
| Withdrawal | Negative: the amount out | The amount, from the pool to the recipient, plus the relayer fee if it was relayed | 2 NewCommitment, 2 NewNullifier |
| Withdrawal received as ETH | Negative: the amount out | The amount, from the pool to a one-off forwarder, then through the ETH exit to Uniswap; ETH to the recipient. Plus the relayer fee | 2 NewCommitment, 2 NewNullifier, and a Settled event from the ETH exit with the ETH sent |
A withdrawal received as ETH is sent to the ETH exit contract, not the pool, and the exit contract calls transact. Decode it as withdraw(args, extData, order): the first two are the pool call, and order holds the ETH recipient and the least ETH it accepted.
Pool events
If you read the pool’s logs directly, topic0 tells you the event. Each is the keccak-256 hash of the event’s signature in the verified source.
topic0 |
Event | When |
|---|---|---|
0xf3843edd…411afd |
NewCommitment(bytes32 commitment, uint256 index, bytes encryptedOutput) |
2 per pool transaction: the new notes. encryptedOutput is the note encrypted to its owner, and only their key can read it |
0x5e58f77b…f297ce |
NewNullifier(bytes32 nullifier) |
2 per pool transaction: the notes spent |
0x9acd2543…e2b15e |
TokenSupport(address indexed token, bool supported) |
Admin turns deposits of a token on or off |
0x7ce7ec0b…188f5c |
AdminChanged(address admin) |
Admin role handed on |
So 10 NewCommitment and 10 NewNullifier events mean 5 pool transactions. The events look the same for a shield, a private transfer and a withdrawal; the kind comes from extAmount and the token movements, as in the table above.
The pool has no stored relayer. Each transaction names its relayer and fee in extData, and the proof binds them, so nobody can swap them. The Zeronym relayer is 0xc38e587856452610f41bbb57be25a14d2eb2959a, as /api/relay reports. You can also skip it and submit from your own wallet.
In a private transfer, nothing on the chain names the sender, the recipient or the amount. The new notes are encrypted, and the spent notes appear only as nullifiers, which can’t be traced back to the notes they spend. The one exception is who submits it: through the relayer, the sender on the chain is the relayer; from your own wallet, it is your wallet.
Re-check a proof
A pool transaction can only succeed if the verifier accepts its proof: transact reverts with “Invalid transaction proof” otherwise. You can also ask the verifier again at any time:
- Decode the transaction’s input as
transact(_args, _extData)using the pool’s verified ABI (or, for a withdrawal received as ETH, aswithdraw(args, extData, order)using the ETH exit’s verified ABI). - Call
verifyProof(_args, _extData.token)on the pool witheth_call. It forwards the proof and its eight public inputs to the verifier and returnstrue. - Change any public input, for example add 1 to
publicAmount, and call it again. It returnsfalse.
verifyProof checks only the proof, so it still answers for notes that are already spent. The script does steps 1 to 3 for every pool transaction.
Pool transactions so far
What the script reported as of 28 October 2026. Run it again for the current list.
| Transaction | Kind | Public amount | Proof |
|---|---|---|---|
| 0x51c11aa5… | Admin: turn on USDG | None | No proof needed |
| 0xec843b7f… | Admin: hand admin to the Safe | None | No proof needed |
| 0xf9644362… | Shield, from the sender’s wallet | 10 USDG in | Accepted. A changed copy is rejected |
| 0x565247b1… | Shield, from the sender’s wallet | 1 USDG in | Accepted. A changed copy is rejected |
| 0xabe3ea39… | Withdrawal received as ETH, through the relayer | 1 USDG out, swapped to 0.000428 ETH, plus 0.50 USDG relayer fee | Accepted. A changed copy is rejected |
| 0x48c43ded… | Withdrawal received as ETH, through the relayer | 5 USDG out, swapped to 0.00214 ETH, plus 0.50 USDG relayer fee | Accepted. A changed copy is rejected |
| 0x2ffa4b90… | Private transfer, through the relayer | None. Only the 0.50 USDG relayer fee leaves the pool; sender, recipient and amount stay encrypted | Accepted. A changed copy is rejected |