The pool, the proofs and the relayer: read how Lethe works →

Contracts, trusted setup and verifying a proof

Contracts

There is one pool per asset. Solidity 0.8.26, compiled for Cancun, sources published at /contracts on this site. The pools have no upgrade path and no admin function can touch a note.

HYPE poolpublished here when deployed
USDC poolpublished here when deployed
Groth16 verifierpublished here when deployed
Poseidon (T3)published here when deployed

Sources: LethePool.sol, LetheVerifier.sol, PoseidonT3.sol.

What the owner can and cannot do

The exit fee accrues inside the pool and collectFees() pays it out. A withdrawal never depends on the fee address, so no choice of that address can block an exit. The contracts are unaudited at launch.

The circuit

Transaction(20, 2, 2) in circom 2: two notes in, two out, a Poseidon tree of depth 20, 7 public signals, 12,698 constraints, a join-split in the Tornado Nova design. sum(in) + publicAmount = sum(out), with publicAmount = extAmount - fee.

Trusted setup

Phase 1 is the perpetual powers of tau ceremony run by the Privacy and Scaling Explorations team, 80 contributions (ppot_0080_14, 2^14). Phase 2 is one contribution made for Lethe, labelled lethe-1, with its entropy read once and never stored, followed by a public beacon labelled lethe beacon, built from the text lethe.fi lethe pool v1 08-11-2026.

A Groth16 setup is safe if at least one contributor discarded their randomness. Phase 2 here has one contributor. If you want not to rely on that, verify phase 1 yourself and run your own phase 2; the zkey below is what the pools verify against.

transaction_final.zkeyf8a59059e1df4f45e47647f898d9ae9d97585fa3724726b068329bba6ff73312
transaction.wasm4c6f5b60bc7d74c080d45b70b7cadcd236c023997a4249bc6ef1e062cd5df4ce
verification_key.json031702e57d0e0409e20ee4e3204662ac0e41a5356b3940a09ddb1ae7a51ba1b5

Verifying a proof

The verifying key is served from /zk/verification_key.json, and the proving files are hashed above. With snarkjs and a proof you made:

curl -O {site{"}"}/zk/verification_key.json
snarkjs groth16 verify verification_key.json public.json proof.json

And on chain, read the root and check a nullifier directly:

cast call <pool> "getLastRoot()(uint256)"
cast call <pool> "nullifierHashes(uint256)(bool)" <nullifier>
cast call <pool> "exitFeeBps()(uint256)"