Smart contracts
HestiaPool, the history-keeping Merkle tree, the association registry, the generated verifiers, and on-chain Poseidon.
Settlement lives in a small set of Solidity contracts on Base. They verify proofs, keep the commitment tree and nullifier set, gate spends on approved association roots, and move the underlying ERC-20 / ETH. They never see a plaintext note.
The contracts
| Contract | Role |
|---|---|
HestiaPool | The pool. Holds funds; handles shield and the transact entrypoints. |
MerkleTreeWithHistory | The depth-32 commitment tree with a rolling window of the last 64 roots. |
AssociationSetRegistry | Records ASP-published association roots and revocations. |
TransactionVerifier1x2 / …2x2 | Generated Groth16 verifiers, one per arity. |
Poseidon (T2/T3/T6) | circomlib-generated hash bytecode, deployed via CREATE. |
Live deployment — Base mainnet
The contracts are live on Base mainnet (chain 8453). The Labs Console and the SDK in this
app are wired to these addresses.
| Contract | Address |
|---|---|
HestiaPool | 0x38e8131d9A6A2Fc89489F21Fc01bB4E17c70B1f6 |
AssociationSetRegistry | 0x378d2e9985AC04c51De9Ae8396474FC6c382CA3f |
TransactionVerifier1x2 | 0x9D47926a6b967E6195a7B4d15dc1A0b29DbA2EF6 |
TransactionVerifier2x2 | 0x7Db8dCF4D20B289Ddc9bae0B08e1E380539891AD |
Poseidon T2 (label) | 0xfAB6723206b16A0AC2cd6608031cEe141564a06b |
Poseidon T3 (tree) | 0xb0f4646bcE5F95252cB3504dA687d8b4dA917b6D |
Poseidon T6 (commitment) | 0xE0be8A7B4882E73e35053d09a47780A8EDBd9635 |
| USDC (Base) | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
The pool is non-custodial and immutable — its verifiers, Poseidon hashers, registry, and USDC
address are constructor-set immutables, so the wiring above is fixed for the life of the
contract. The Groth16 keys behind the on-chain verifiers come from a fresh setup made
specifically for this deployment — see circuits. All four
source contracts are verified on Basescan; Poseidon is CREATE-deployed bytecode (no source).
shield works as soon as the pool is live. Spends (send / unshield) additionally require
the spend's association root to be approved in the registry — the deployer is authorized as the
ASP and the operator publishes roots as the pool grows. See
enabling spends.
HestiaPool
The pool exposes a deposit entrypoint and two spend entrypoints (the human-readable ABI used by the indexer and relayer):
function shield(
address token, uint256 amount, uint256 ownerSK, uint256 randomness, bytes encryptedNote
) payable returns (uint256 leafIndex, uint256 commitment);
struct TransactData {
uint256 root; uint256 associationRoot; uint256 withdrawAmount;
address token; address recipient; uint256 feeAmount; address relayer;
}
function transact1x2(
uint256[2] a, uint256[2][2] b, uint256[2] c,
uint256[1] nullifiers, uint256[2] outCommitments,
TransactData d, bytes[2] encryptedNotes
);
function transact2x2(/* … uint256[2] nullifiers … */);
function getLastRoot() view returns (uint256);
function nextLeafIndex() view returns (uint256);shield pulls amount of token (or accepts ETH as msg.value when the token is the
zero address), computes the commitment from ownerSK + randomness + the deposit's
label, appends a leaf, stores the encrypted blob, and emits
Shield.
transact1x2 / transact2x2 are the spend paths. Each:
- checks
d.rootis one of the last 64 known tree roots; - checks
d.associationRootis approved by the registry; - verifies the Groth16 proof
(a, b, c)against the public signals via the generated verifier; - ensures each nullifier is unspent, then records it;
- appends
outCommitmentsas new leaves and storesencryptedNotes; and - pays
withdrawAmounttorecipientandfeeAmounttorelayer(both zero for a pure private send).
Because recipient, withdrawAmount, feeAmount, and relayer are part of the verified
public signals, no submitter can change them.
Events
The indexer reconstructs all off-chain state from these:
event Shield(uint256 indexed commitment, uint256 leafIndex, uint256 label, address token, uint256 amount, bytes encryptedNote);
event Commitment(uint256 indexed commitment, uint256 leafIndex, bytes encryptedNote);
event Nullified(uint256 indexed nullifier);
event Unshield(address indexed token, address indexed recipient, uint256 amount, address relayer, uint256 fee);Shield and Commitment both carry (commitment, leafIndex, encryptedNote) — they are the
two leaf sources (deposits and transaction outputs). Nullified marks a spent note. Unshield
records a public withdrawal.
MerkleTreeWithHistory
The pool's tree keeps not just the current root but the last 64. A proof built against any of them is accepted, so a transaction has a window to be proven and mined even as other deposits advance the tree. See the commitment tree.
AssociationSetRegistry
ASPs publish and revoke association roots here; the pool consults it on every spend:
event RootPublished(address indexed asp, uint256 indexed root, string uri);
event RootRevoked(address indexed by, uint256 indexed root);
function isValidRoot(uint256 root) view returns (bool);The uri lets an ASP point to the off-chain set behind a root (its membership and policy).
On-chain Poseidon
The pool hashes with the same Poseidon the circuits use. The permutations (T2, T3,
T6) are circomlib-generated bytecode deployed via CREATE, so the contract's commitment and
tree hashes match @hestia/common and the circuit exactly. Conformance tests assert this
equality; it is what lets an SDK-generated proof verify on-chain.
Deployment notes
- Deploy order and wiring are scripted (Foundry). The pool must be pointed at the deployed verifiers, the Poseidon contracts, and the registry.
- Verify the USDC address for the target chain at deploy time —
baseandbaseSepoliadiffer (see parameters). The live deployment uses Base-mainnet USDC, confirmed against the deployed pool'susdc()immutable. - The on-chain verifiers are generated from the ceremony
zkey. The open-source repo ships a reproducible dev ceremony; the live deployment above was built from a separate fresh-entropy setup whose secret was discarded — see circuits. - The contracts have not had an external audit; treat large mainnet value accordingly.
