Docs
How it works.
ZKVENICE is two things bolted together with care: a shielded pool on Robinhood Chain that swaps on the way out, and an agent on Venice that plans the swap and keeps no record of it. This page explains each, what each one can and cannot hide, and which parts exist today.
Overview
A normal swap is one transaction from your wallet: anyone can see what you held, what you bought, and when. A normal AI assistant that helps you do it keeps the conversation on someone’s server, usually with your address in it.
ZKVENICE separates the two problems. On chain, your wallet deposits into a pool; later, a relayer withdraws from the pool and swaps, delivering to an address that never touched your wallet. Off chain, the planning happens on Venice, whose inference does not persist prompts or answers. The result is that no single party, and no combination of the public ones, can pair you with the trade.
Threat model
Who learns what, in the design:
- A chain observer sees a deposit from your wallet into the pool and, at some later time, a withdraw-and-swap submitted by a relayer to some fresh address. The amounts are fixed denominations, so size cannot pair them. Timing can, if the pool is thin; see the roadmap.
- The relayer receives a proof and a nullifier. It learns that some valid note is being spent and where the output goes. It cannot tell which deposit the note came from.
- Venice receives the intent text and the quotes it fetches for the seconds it takes to answer. Venice states it does not store prompts or completions; the prompt never contains your address. In TEE mode that claim is attestable at the hardware level.
- ZKVENICE the site has no analytics and no database. The app reads prices from DexScreener in your browser. Two routes run on the server: the planner, which forwards the sentence you typed to Venice and nothing else, and the relayer, which receives a finished proof and submits it. Neither is told your address; the relayer sees the recipient, as the chain will.
- Your ISP or a network observer sees that you visited this site and DexScreener. Use the network protection you already use.
What this does not hide: that you used the pool at all (the deposit is public), and, against a patient adversary, timing correlation when the pool has few participants.
Layer 1 · the pool
The pool is a standard commitment-and-nullifier design, the family every serious shielded pool descends from. There is no novelty in the cryptography, on purpose; the novelty is that the exit is a swap.
Deposit
The client picks a random secret and nullifier, computes the commitment C = H(secret ‖ nullifier), and calls deposit(C) with exactly one fixed denomination of the deposit token. The contract inserts C as a leaf in an incremental Merkle tree and emits the new root. Nothing about the sender is stored beyond what the chain records for every transaction.
Withdraw and swap
To spend a note, the client generates a zero-knowledge proof that it knows a secret and nullifier whose commitment is in the tree at a recent root, and reveals the hash N = H(nullifier) so the note cannot be spent twice. The proof also binds the recipient, the swap parameters, and the relayer fee, so a relayer cannot redirect the output.
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, Exit calldata exit // venue, tokenOut, minOut, fee tier ) external;
The contract verifies the proof, marks N spent, and routes denomination - feethrough Uniswap on Robinhood Chain: a v4 pool whose other side is native ETH (the singleton at 0x8366…0951), or a v3 WETH pool from the factory at 0x1f7d…2EfA (where USDG lives), sending tokenOut to recipient and fee in ETH to the relayer. With tokenOut = 0 it is a plain ETH withdrawal. One transaction, submitted and paid by whoever holds the proof.
Fixed denominations
Notes are 0.01, 0.1 and 1 ETH, one pool each. A swap for 0.4 ETH becomes four 0.1 notes spent over time. This is a cost, not a detail: variable amounts are the easiest way to pair a withdrawal with a deposit, and the app makes the cut visible before you sign anything.
Layer 2 · the agent
The agent is a small program that turns an intent into a plan: parse the sentence, fetch quotes, choose a route, cut the amount into notes, and hand the client the exact calls to make. It runs its language model calls on Venice for three reasons that no other provider offers together.
- Zero retention. Venice does not persist prompts or responses on its servers. The conversation about your trade exists in your tab and nowhere else. TEE and end-to-end encrypted inference modes exist for when the claim needs to be attestable rather than trusted.
- No policy layer. The open-weight models are served without a moderation layer on top. An agent that runs a multi-step task about “private” swaps does not get refused halfway, and there is no review queue reading the transcript.
- DIEM. Staking VVV mints DIEM, a tokenised right to one dollar of inference per day in perpetuity. The agent holds DIEM and pays for its own thinking. There is no API key that resolves to a person and no card on file.
The integration surface is Venice’s OpenAI-compatible API, which is also exposed as an MCP server and as one-line skills for agent frameworks. ZKVENICE uses the API directly; the planner is a few hundred lines and it is the least interesting part of the system, which is how it should be.
The agent is a customer of Venice. Nothing here is endorsed by Venice AI, and Venice’s own claims are documented at venice.ai/agents.
A swap, end to end
- 1. Intent. You type “0.4 ETH to USDG, best route, private” or fill the form; the form is what the sentence parses into.
- 2. Plan. The agent fetches quotes, picks the route, cuts the amount into notes, and returns the plan. Venice keeps nothing.
- 3. Shield. Your wallet deposits each note with its commitment. This is the first and last time your wallet touches the system.
- 4. Relay. For each note, your browser generates a proof and sends it to a relayer with the nullifier hash, the recipient and the swap parameters.
- 5. Swap and deliver. The relayer submits; the pool verifies, swaps, and delivers
tokenOutto the fresh address. The chain holds two facts it cannot join.
The app on this site runs all five steps once the pools are deployed: Plan calls the agent, Deposit sends from your wallet, Withdraw makes the proof in your tab and hands it to the relayer or to your wallet.
Fees
| Item | Rate | Goes to |
|---|---|---|
| Pool swap fee | 0.05% – 0.30% | Uniswap liquidity providers, by pool tier |
| Relayer fee | 0.10% | The relayer, for gas and the risk of a failed submit |
| Protocol fee | none | Nothing accrues to ZKVENICE at launch |
Status
This mirrors the block on the home page and is updated first.
| Piece | State |
|---|---|
| Site, design, docs | Built |
| Live quotes from chain 4663 | Built |
| Circuit (Groth16, Poseidon, 20 levels) | Built |
| Pool contracts | Written, 26 tests on real bytecode |
| Browser prover | Built |
| Relayer | Live |
| Agent on Venice | Live |
| Trusted setup | Development ceremony only |
| Pools on chain | Live, 3 denominations |
| $ZKVENICE | Not launched |
The contracts, as deployed
| Contract | Address on chain 4663 |
|---|---|
| Poseidon hasher | 0x915543d93b868b53a02c9cd9b445b3b4459aa92b |
| Groth16 verifier | 0x5a8c968663c5c0fb0f5d97c64d2ef7203b0d5422 |
| Pool 0.01 ETH | 0xf8ade805a67f572947eae239dc6f848eae3edb7c |
| Pool 0.1 ETH | 0x23a7c8382fa5c2c95f7622baf68b8f62244142c1 |
| Pool 1 ETH | 0xb41c395b2ad4490f03751ccc73a1938e21262732 |
Three contracts, no owner, no upgrade path. MerkleTreeWithHistory is Tornado’s tree with Poseidon and a constructor that computes its own zero values. Groth16Verifier is what snarkjs exported for the circuit. ZKVenicePool is one fixed denomination of native ETH; three are deployed at 0.01, 0.1 and 1 ETH.
function deposit(bytes32 commitment) external payable; // msg.value == denomination
function getLeaves(uint256 from, uint256 count) view returns (uint256[]); // rebuild the tree with eth_call, no logs
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, // the Groth16 proof
bytes32 root, bytes32 nullifierHash,
address payable recipient, address payable relayer, uint256 fee,
Exit exit // None = plain ETH, V4 = native pool, V3 = WETH pool
) external;The public signals of the proof are root, nullifierHash, recipient, relayer, fee, tokenOut, minOut, so a relayer can choose the venue and the fee tier but cannot change where the money goes, what it is paid, what it becomes, or the floor on how much. The tree is rebuilt by the client from getLeavesbecause the public RPCs of this chain do not serve logs reliably.
What the tests run
- The Uniswap v4 PoolManager is the runtime bytecode of the singleton at
0x8366…0951, and the v3 factory the bytecode of0x1f7d…2EfA, both pulled from chain 4663 and executed in-process on an EVM at the Cancun fork. - Every proof in the suite is a real Groth16 proof from the same wasm and zkey the browser uses; about two seconds each in Node.
- Covered: zero values and empty root match the client; three deposits produce the client’s root; wrong value and duplicate commitment revert; relayed withdraw-and-swap through a v4 native pool; fee paid; nullifier spent; double spend; the same proof with the recipient, fee, minOut or token changed is rejected; unknown root; minOut too high; plain ETH exit with no relayer; a proof against an older root still verifies after a new deposit; relayed withdraw-and-swap through a genuine v3 pool created by the real factory; a fee tier with no pool reverts.
| Call | Gas |
|---|---|
| deposit | ≈ 735,000 |
| withdraw as ETH | ≈ 265,000 |
| withdraw and swap (v4) | ≈ 411,000 |
| withdraw and swap (v3) | ≈ 405,000 |
The trusted setup, honestly
Groth16 needs a circuit-specific ceremony. The one in this repository is a single contribution made on a laptop over a locally generated powers-of-tau. Whoever ran it could, in principle, forge proofs against these pools. That is acceptable for testing and for small amounts on a new chain; it is not acceptable for a launch. Before any token or any real volume the zkey is regenerated in a public multi-party ceremony and the verifier redeployed, and this page will say so.
Roadmap
- Relayer: one to start, run by us, with a 0.1% floor; then an open set with a fee market.
- Agent: the planner already calls Venice when a key is present. Next: the agent holds DIEM and pays for itself, and the prompt is published so the claim “your address is never in it” can be checked against the code (it can already: the route is in the repository).
- Public ceremony for the zkey, then a verifier redeploy, before any launch.
- Timing defence: client-side random delays between deposit and withdraw, and a minimum anonymity set before the app lets a note be spent.
Last updated 2026-09-11, after the pools went on chain.