Skip to Content
DocumentContractsCommitmentPool
Protocol reference

On-chain Poseidon Merkle tree of commitments — the protocol's central state.

CommitmentPool is the contract every flow reads from and writes to. It maintains an append-only Merkle tree of Poseidon commitments and a nullifier set — together they’re the protocol’s authoritative state.

State

FieldTypePurpose
Treeinherited from IncrementalMerkleTreeAppend-only Poseidon Merkle tree
nullifiersmapping(uint256 => bool)Replay protection (true = spent)
whitelistedTokensmapping(address => bool)Per-token deposit allowlist
pausedboolEmergency pause (deposits + withdrawals blocked)
authorizedSettlementaddressOnly this address can call insertCommitment
pendingSettlement / pendingSettlementActivateAtaddress / uint25624h timelock on settlement-address changes
withdrawVerifierIVerifier (immutable)Groth16 verifier for withdraw
depositVerifierIDepositVerifier (immutable)Groth16 verifier for deposit
sanctionsListISanctionsListOptional address screening

External entry points

// Deposit. Verifier checks the zk-deposit proof; the commitment must // already encode `(token, amount)` exactly. function deposit( uint[2] calldata proofA, uint[2][2] calldata proofB, uint[2] calldata proofC, uint256 commitment, address token, uint256 amount ) external; // Settlement-only entry: insert change leaves emitted by `settleAuth` // or `cancelPrivate`. Reverts unless `msg.sender == authorizedSettlement`. function insertCommitment(uint256 commitment) external returns (uint32); // Read state. function getLastRoot() external view returns (uint256); function isKnownRoot(uint256 r) external view returns (bool); function nextIndex() external view returns (uint32); function nullifiers(uint256 nullifier) external view returns (bool);

PrivateSettlement is the only contract allowed to call insertCommitment and transferToSettlement — the wiring is set through a 24h timelock for upgrades.

Events

event CommitmentInserted( uint256 indexed commitment, uint32 leafIndex, uint256 timestamp ); event Withdrawal( address indexed recipient, uint256 nullifierHash, uint256 newCommitment, uint256 amount );

The SDK subscribes to CommitmentInserted to maintain a local incremental tree — see Subscribe to pool events.

Errors

ErrorWhen
ContractPausedDeposit / withdraw attempted while paused
TokenNotWhitelisteddeposit with a token not in the allowlist
ZeroCommitment / ZeroAmount / ZeroAddressRequired arg is zero
FieldElementOutOfRangecommitment or amount exceeds BN254 scalar field
InvalidProofGroth16 verifier rejected
NullifierAlreadySpentWithdraw attempted on a spent leaf
UnknownRootWithdraw proof references a root not in recentRoots
FeeOnTransferTokenUnsupportedToken rebased / charged a fee on the transfer
NotAuthorizedSettlementOnly PrivateSettlement may call insertCommitment
AddressSanctionedSanctions list reject

Tree parameters

ParameterValueNotes
Depth20COMMIT_TREE_DEPTH constant in SDK
Capacity2^20 = 1,048,576Soft limit before rotation needed
HashPoseidon over BN254circomlibjs impl in SDK
Field modulusBN254 scalarFIELD_MODULUS constant in SDK

Reorg safety

The pool emits leaves at insertion. Apps that build proofs against tree state should wait for confirmations before treating an inserted leaf as final — typical thresholds: 12 blocks on Sepolia, 32 on mainnet. The SDK exposes the raw event stream and leaves the threshold to the app:

import { subscribeCommitmentInserted } from "@zkscatter/sdk";

Address by network

NetworkConfig.contracts.commitmentPool in the SDK. ABI ships as COMMITMENT_POOL_ABI from @zkscatter/sdk with a pre-parsed Interface at COMMITMENT_POOL_IFACE. See SDK reference / core.

Last updated on