Overview
A plain swap on Robinhood Chain is one transaction from your wallet. A stranger with the explorer open can see what you bought and when, with your balance one click away. Ask a typical AI assistant to plan that swap, and the chat, address included, sits on the provider's servers.
Hush splits the job in two. On chain, your wallet deposits a fixed amount into a pool. At a time you choose, a relayer withdraws that amount through a Uniswap swap and pays a fresh address. Off chain, a model on Venice reads your sentence and Venice keeps no copy.
An observer sees the deposit and the exit, with no public field to join them. Venice sees one sentence for a few seconds, and our server strips any address from it before the call.
Threat model
Six parties touch a Hush swap, and each one sees a different slice of it.
- Chain observer
- Sees your wallet send 0.05, 0.5 or 5 ETH into a pool. At some later block it sees a relayer submit a withdrawal that swaps and pays a fresh address. Each pool takes one note size, so the observer can't pair deposit and exit by amount. In a thin pool a patient observer can pair them by timing, and the roadmap lists the planned defense.
- Relayer
- Receives your proof, the nullifier hash, the recipient and the exit parameters. It learns that someone holds a valid note and where the output goes, and it can't tell which deposit the note came from. If it edits a bound field, the pool rejects the proof.
- Venice
- Receives the sentence you typed, minus anything shaped like an address, for the seconds a reply takes. Venice says it stores neither prompts nor completions, and its TEE mode makes that claim attestable for the models served that way. Hush calls a standard model today.
- This site
- Runs no analytics and keeps no database. Your browser pulls prices from DexScreener and quotes from the Uniswap quoter contracts on chain 4663. Two routes run on our server: the planner forwards your sentence to Venice, and the relayer takes a finished proof and submits it. Neither route asks for your wallet address. The relayer learns the recipient, which the exit transaction makes public.
- Your wallet
- Signs the deposits. You connect through Privy or an injected wallet such as MetaMask or Rabby. Privy knows the login you used, and neither one sees your notes, which your browser keeps in local storage next to the copy you save.
- Your network
- Your ISP sees that you visited this site, DexScreener and the chain's RPC endpoints. Use a VPN or Tor if you want to hide the visit.
Layer 1 · the pool
The pool follows the commitment-and-nullifier design that Ethereum's shielded pools share, down to the incremental Merkle tree from Tornado Cash. We kept the cryptography standard on purpose. Hush adds one thing: the pool swaps on the way out.
Deposit
Your browser draws two random 31-byte numbers, a nullifier and a secret, and hashes them with Poseidon into a commitment. Your wallet sends deposit(commitment) with the pool's denomination as the value. The contract appends the commitment as the next leaf of a 20-level tree and keeps the last 30 roots, so a proof you made against a recent root verifies after new deposits land.
commitment = Poseidon(nullifier, secret)nullifierHash = Poseidon(nullifier)note = hush-eth-<wei>-<nullifier, 62 hex>-<secret, 62 hex>The note string carries both numbers. Hold it and you can withdraw the deposit from any browser; lose it and the deposit stays in the pool for good. The app saves a copy in your browser and asks you to keep another outside it.
Withdraw and swap
To spend a note, your browser rebuilds the tree from getLeaves and proves in zero knowledge that it knows a nullifier and secret whose commitment sits under a known root. The proof reveals nullifierHash. The contract records that hash and rejects it the second time, so you can spend each note once.
The proof commits to seven public signals, in this order:
| Signal | Binds |
|---|---|
| root | A Merkle root from the pool's last 30 |
| nullifierHash | Poseidon(nullifier), the note's spent marker |
| recipient | The address that receives the output |
| relayer | The address that receives the fee |
| fee | The relayer's cut in wei, paid in ETH |
| tokenOut | The token the exit buys, or zero for plain ETH |
| minOut | The smallest output the swap may return |
Change any of them after proving and the verifier rejects the proof. The venue and the pool tier sit outside the proof, so a relayer may route through a different pool, and minOut sets the floor that route has to clear.
enum Venue { None, V4, V3 }struct Exit { Venue venue; address tokenOut; uint256 minOut; uint24 fee; int24 tickSpacing; address hooks; } function withdraw( uint256[2] a, uint256[2][2] b, uint256[2] c, // Groth16 proof bytes32 root, bytes32 nullifierHash, address payable recipient, address payable relayer, uint256 fee, // paid to the relayer in ETH Exit calldata exit // venue, token, floor, pool key) external;The contract verifies the proof and marks the nullifier spent. It pays fee in ETH to the relayer and sends denomination - fee through one of three exits:
None- Plain ETH to the recipient.
tokenOuthas to be zero. V4- The pool calls
PoolManager.unlockand swaps native ETH in the Uniswap v4 pool with key(ETH, tokenOut, fee, tickSpacing, hooks). V3- The pool wraps the ETH into WETH and swaps in the pool the v3 factory returns for
(WETH, tokenOut, fee).
If the swap returns less than minOut, the transaction reverts and the note stays unspent. The whole exit fits in one transaction, and anyone holding the proof can submit it.
Fixed denominations
Each pool accepts one size: 0.05, 0.5 or 5 ETH. An observer can pair an odd-sized deposit with its withdrawal at a glance, so the contract rejects any other value.
The app splits a larger amount into notes, largest first, and you deposit and withdraw each note on its own. Each extra note costs one more gas payment and one more relayer fee. Anything under 0.05 ETH stays in your wallet.
| Amount | Notes | Count | Left in your wallet |
|---|---|---|---|
| 0.3 ETH | 6 × 0.05 ETH | 6 | 0 |
| 1.5 ETH | 3 × 0.5 ETH | 3 | 0 |
| 2.72 ETH | 5 × 0.5 + 4 × 0.05 ETH | 9 | 0.02 ETH |
| 7.2 ETH | 1 × 5 + 4 × 0.5 + 4 × 0.05 ETH | 9 | 0 |
Layer 2 · the agent
The agent turns a sentence into a plan you can check field by field. Our server and a language model read your intent. Your browser does the rest with data from chain 4663.
- 01Strip
The
/api/planroute removes anything shaped like an address (0x followed by 40 hex characters) and caps the text at 400 characters. - 02Parse
It sends the sentence to
zai-org-glm-5-2on Venice and gets back a JSON intent: amount, source token, target token and slippage. - 03Fall back
If Venice errors or the server has no key, a local parser reads the sentence. The response names the parser that answered.
- 04Quote
Your browser asks Uniswap QuoterV2 about the v3 WETH pools and V4Quoter about the native-ETH v4 pools, across the four fee tiers, and keeps the best output.
- 05Split
The app cuts the amount into notes, takes the relayer fee off each and shows the
minOuteach proof will bind.
The server route forwards your sentence without logging it, and the Venice API key stays in the server environment.
The case for Venice
- Zero retention
- Venice says it stores neither prompts nor completions. Your sentence exists in your tab and, for one request, in Venice's memory. Venice also offers TEE and end-to-end encrypted inference, where that claim is attestable in hardware. The Hush planner calls a standard model today, and the roadmap moves it to an attested one.
- Open weights
- Venice serves open-weight models with no moderation layer on top. A multi-step plan about a private swap won't stall on the word “private”, and no reviewer reads a queue of your chats.
- DIEM
- Stake VVV on Venice and you mint DIEM; each DIEM buys a dollar of inference per day. An agent that holds DIEM pays for its own calls, with no card on file.
The planner talks to Venice's OpenAI-compatible API, and you can swap the model with the VENICE_MODEL variable. Hush pays Venice like any other customer. Venice has not endorsed Hush. Read Venice's own description of its agent features at venice.ai/agents.
A swap, end to end
Take the example the app ships with: “1.5 ETH into USDG, quietest route”. Two of the five steps reach the chain.
- 01Say it
You type “1.5 ETH into USDG, quietest route” or fill in the form the sentence parses into.
- 02Plan
The agent reads 1.5 ETH to USDG. Your browser quotes the USDG pools on Uniswap v3 and v4, and the app cuts the amount into three 0.5 ETH notes. Venice keeps none of it.
- 03ShieldOn chain
Your wallet makes three deposits of 0.5 ETH, each with its own commitment. These are the last transactions your wallet signs in the flow.
- 04Prove
For each note, your browser builds a Groth16 proof in a few seconds and hands it to the relayer with the recipient and the exit parameters. Leave hours between the three exits, since three withdrawals in one minute read as one trade.
- 05ExitOn chain
The relayer submits each proof. The pool checks it and swaps 0.5 ETH minus the fee into USDG for your fresh address. The explorer shows three deposits and three relayed exits, with no field that joins them.
In the app, the planner and the in-tab proof work today, and the dry run checks each proof against a local tree with the real verification key. Deposit and the relayed exit switch on when the pools launch.
Fees
| Item | Rate | Paid to |
|---|---|---|
| Uniswap pool fee | 0.01%, 0.05%, 0.3% or 1% | Liquidity providers in the pool the route uses, by tier |
| Relayer fee | 0.15% floor · 15 bps | The relayer, in ETH, to cover gas and reverted submits |
| Protocol fee | none | Hush takes no cut |
| Gas | network rate | The relayer, or your wallet if you submit yourself |
The relayer takes its fee in ETH from the note before the swap, and the pool swaps the rest. At the floor, the numbers per pool come out like this:
| Pool | Relayer fee at the floor | Swapped |
|---|---|---|
| 0.05 ETH | 0.000075 ETH | 0.049925 ETH |
| 0.5 ETH | 0.00075 ETH | 0.49925 ETH |
| 5 ETH | 0.0075 ETH | 4.9925 ETH |
You can skip the relayer and submit from a connected wallet, and that wallet pays the gas and shows up on the exit. Pick one with no history tied to the wallet that deposited.
Status
The home page shows the same board, and we update this list first when a piece ships.
| Piece | State |
|---|---|
| Site, docs and app | Built |
| Live quotes from chain 4663 | Built |
| Circuit · Groth16, Poseidon, 20 levels | Built |
| Browser prover | Built |
| Venice planner | Built |
| MCP server | Built |
| Pool contracts | Written, tested on chain 4663 bytecode |
| Relayer | Built · switches on with the pools |
| Trusted setup | Dev ceremony · public ceremony before launch |
| Pools on chain | Launching soon |
| $HUSH token | Launching soon |
The contracts
At launch we will deploy a Poseidon hasher, a Groth16 verifier and one HushPool per denomination. The contracts have no owner and no upgrade path, so we can't pause a pool or redirect a note.
| Contract | Address on chain 4663 |
|---|---|
| Poseidon hasher | Launching soon |
| Groth16 verifier | Launching soon |
| HushPool · 0.05 ETH | Launching soon |
| HushPool · 0.5 ETH | Launching soon |
| HushPool · 5 ETH | Launching soon |
| $HUSH token | Launching soon |
MerkleTreeWithHistory- Tornado Cash's incremental tree, with Poseidon in place of MiMC. Its constructor derives the zero values from
keccak256("hush") mod p, and the tree keeps 30 roots. Groth16Verifier- The verifier snarkjs exports for the
withdrawcircuit, with seven public inputs. HushPool- One fixed denomination of native ETH. It holds the tree, the spent nullifiers and the three exits.
function deposit(bytes32 commitment) external payable; // msg.value == denominationfunction getLeaves(uint256 from, uint256 count) external view returns (uint256[] memory);function leafCount() external view returns (uint256);function isSpent(bytes32 nullifierHash) external view returns (bool);function isKnownRoot(bytes32 root) external view returns (bool);function getLastRoot() external view returns (bytes32); event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp);event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee, address tokenOut, uint256 amountOut);Your browser and the MCP server rebuild the tree with getLeaves over eth_call, so a withdrawal needs no event logs and works against the public RPCs.
Third-party contracts
The pools and the app call these Uniswap, WETH and token contracts on chain 4663. They exist today, and you can check each one on Blockscout.
| Contract | Role | Address |
|---|---|---|
| Uniswap v4 PoolManager | Swap venue for V4 exits | 0x8366a39cc670b4001a1121b8f6a443a643e40951 |
| Uniswap V4Quoter | Quotes the native-ETH pools | 0x8dc178efb8111bb0973dd9d722ebeff267c98f94 |
| Uniswap v3 factory | Finds the WETH pool for V3 exits | 0x1f7d7550b1b028f7571e69a784071f0205fd2efa |
| Uniswap QuoterV2 | Quotes the v3 pools | 0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7 |
| WETH | The pool wraps into it for V3 exits | 0x0bd7d308f8e1639fab988df18a8011f41eacad73 |
| USDG | Exit token · Global Dollar | 0x5fc5360d0400a0fd4f2af552add042d716f1d168 |
| TSLA | Exit token · Tesla, Robinhood Token | 0x322F0929c4625eD5bAd873c95208D54E1c003b2d |
| PONS | Exit token | 0x39dBED3a2bd333467115dE45665cC57F813C4571 |
| Multicall3 | Batches the app's reads | 0xcA11bde05977b3631167028862bE2a173976CA11 |
Tests and gas
The contract suite has 28 tests. It runs the pools against Uniswap bytecode from chain 4663 and feeds them real Groth16 proofs from the circuit your browser loads.
| Call | Gas |
|---|---|
| deposit | 884,960 |
| withdraw, plain ETH | 334,223 |
| withdraw and swap, v4 | 404,309 |
| withdraw and swap, v3 | 436,297 |
The trusted setup
Groth16 needs a ceremony for each circuit. The proving key the app uses today comes from a development ceremony with one contributor. We generated the powers of tau ourselves at size 2¹³ and finished each phase with the hash of Robinhood Chain block 60,376,679 as the beacon. The withdraw circuit compiles to 5,318 constraints.
That contributor could forge a withdrawal from any pool that trusts this key. We accept that risk for dry runs and refuse it for real deposits.
Before the pools launch, we will run a public multi-party ceremony and redeploy the verifier with the new key. The new key stays safe as long as one participant destroys their share of the randomness. We will publish the transcript and update this section that day.
| Parameter | Value |
|---|---|
| Proof system | Groth16 on BN254 |
| Hash | Poseidon |
| Tree | 20 levels · 1,048,576 notes per pool |
| Public signals | 7 |
| Constraints | 5,318 |
| Powers of tau | 2¹³ = 8,192 |
| Beacon | Hash of block 60,376,679 · chain 4663 |
| Contributors | 1 (development) |
| Tooling | circom 2.2.3 · snarkjs 0.7.6 |
For agents: the MCP server
The MCP server gives an agent the flow the app has. It runs on your machine over stdio, and Claude Code, Cursor or any other MCP client can drive it. The note secrets stay inside that process. The agent can see calldata, hashes and the finished proof, and the proof leaves your machine when you hand it to the relayer.
| Tool | Gives your agent |
|---|---|
| pools | Pool sizes, notes deposited, ETH held, relayer status and the $HUSH contract (launching soon). |
| quote | The note split for an amount, the output after fees and the minOut each proof will bind. |
| plan | An intent from the Venice planner, plus a quote. The planner receives the sentence and nothing else. |
| deposit | A new note, stored in ~/.hush/notes.json, and an unsigned deposit for your wallet to sign. |
| notes | Your local notes and their state on chain: not deposited, unspent or spent. |
| withdraw | A Groth16 proof made on your machine, then a relayer receipt or an unsigned withdraw transaction. |
Install
The server ships as one file. Download it, then point your client at it.
curl -O https://hushtrade.xyz/mcp/hush-mcp.mjscurl -O https://hushtrade.xyz/mcp/hush-mcp.mjs.sha256 && sha256sum -c hush-mcp.mjs.sha256claude mcp add hush -- node /path/to/hush-mcp.mjs{ "mcpServers": { "hush": { "command": "node", "args": ["/path/to/hush-mcp.mjs"] } }}The server needs Node 20 or newer. On the first withdrawal it downloads the proving key and the circuit wasm (5.2 MB) from the site and caches them for later runs.
| Variable | Default | Sets |
|---|---|---|
| HUSH_SITE | https://hushtrade.xyz | The deployment it calls for plans, relays and zk files |
| HUSH_HOME | ~/.hush | The folder that holds notes.json and the cached zk files |
| HUSH_ZK_DIR | unset | A folder that already holds withdraw.wasm, withdraw_final.zkey and verification_key.json. The server reads them there and downloads nothing |
The server holds no private key. deposit hands back an unsigned transaction for your wallet, and so does withdraw with the relayer off. With the relayer on, withdraw posts the proof to /api/relay, the same route the app calls.
Roadmap
- Public ceremony
- A multi-party ceremony for the zkey and a verifier redeploy, before the first deposit.
- Pools on chain
- Deploy the three pools and post their addresses here and on X. The relayer switches on with them.
- $HUSH
- The token contract launches soon. We will post the address on X first.
- Open relayers
- One relayer at launch, run by us, with a 0.15% floor. After that, an open set of relayers that compete on fee.
- Timing defense
- Random delays in the client between deposit and exit, and a minimum pool size before the app lets you spend a note.
- Planner source
- Publish the planner prompt and parser so you can check the address stripping against the code.
Last updated 2026-09-11