Skip to Content
DocumentContractsPrivateSettlement
Protocol reference

Verifies half-proofs and atomically settles trades, claims, and cancellations.

PrivateSettlement is the contract that turns ZK proofs into on-chain state changes. Apps don’t usually call it directly — RelayerClient and the SDK’s contracts module wrap every entry point.

External entry points

The settle/cancel/swap entry points take struct params (full parameter shapes live in the contract source); claim takes positional args matching the verifier signature.

// Two-sided private settlement (relayer-only). Verifies maker + taker // half-proofs and writes both payout commitments atomically. function settleAuth(SettleAuthParams calldata p) external; // Single private settlement against a whitelisted external DEX router. function settleWithDex(SettleDexParams calldata p) external; // Cancel an active order — spends maker's leaf, writes a fresh // change commitment for the unspent balance. function cancelPrivate(CancelParams calldata p) external; // Claim a stealth-addressed payout. Verifier checks the `claim` // circuit; the contract enforces `releaseTime`. function claimWithProof( uint[2] calldata proofA, uint[2][2] calldata proofB, uint[2] calldata proofC, bytes32 claimsRoot, bytes32 claimNullifier, uint256 amount, address token, address recipient, uint256 releaseTime ) external; // Batch up to MAX_CLAIM_BATCH_SIZE claims atomically. function claimWithProofBatch(ClaimParams[] calldata claims) external;
CallerFunctionOutcome
RelayersettleAuthVerifies both half-proofs, advances both nullifiers, writes payout commitments
Relayer / usersettleWithDexHalf-proof + DEX swap in one tx
UserclaimWithProofVerifies claim circuit, transfers ERC-20 to recipient
UserclaimWithProofBatchAtomic batch claim — any revert rolls everything back
UsercancelPrivateSpends maker’s leaf, writes a fresh change leaf

Verifier wiring

Each circuit has its own deployed Groth16 verifier. PrivateSettlement holds the verifier addresses in storage (immutable for claim, admin-settable behind owner for the rest):

PrivateSettlement ├── claimVerifier (immutable) ├── authorizeVerifier (admin-settable via setAuthorizeVerifier) ├── cancelVerifier (admin-settable via setCancelVerifier) └── batchAuthorizeVerifier (admin-settable via setBatchAuthorizeVerifier) Deposit verifier lives on `CommitmentPool` and is called via that contract's `deposit` entry — settlement does not see it directly.

Events

event PrivateClaim( bytes32 indexed claimsRoot, bytes32 indexed nullifier, address indexed recipient, address token, uint256 amount ); event PrivateCancel( bytes32 indexed escrowNullifier, bytes32 indexed nonceNullifier, bytes32 newCommitment, address indexed relayer ); event PrivateSettledAuth( bytes32 indexed makerNullifier, bytes32 indexed takerNullifier, bytes32 claimsRootMaker, bytes32 claimsRootTaker, address indexed makerRelayer, address takerRelayer, address submitter, uint96 feeTokenMaker, uint96 feeTokenTaker ); event SettledWithDex(/* …see contract source for full shape… */);

The relayer indexer subscribes to PrivateSettledAuth to update order status; apps subscribe to PrivateClaim to mark local pending claims as fulfilled. Note Solidity’s 3-indexed-fields cap — for PrivateSettledAuth, takerRelayer is non-indexed; consumers filtering by taker side either scan + filter post-hoc or build a secondary index off submitter.

Errors

ErrorWhen
ContractPausedAny entry called while paused
UnknownRootHalf-proof references a root not in recentRoots
NullifierAlreadySpentOne side already spent
InvalidProofGroth16 verifier rejected
NotActiveRelayerSubmitted by an address not active in RelayerRegistry
NotMakerOrTakerRelayerCaller isn’t the maker or taker relayer in the proof
OrderExpiredblock.timestamp > expiry on either half
NotYetReleasableClaim attempted before releaseTime
TokenNotWhitelistedSettlement involves a non-allowlisted token
TokenSidesMismatch / SellBuyTokenMismatchMaker / taker token sides don’t pair
PriceMismatchMaker buy price ≠ taker sell price
FeeExceedsMaxPer-side fee above the maker’s maxFee
ClaimsCapExceededMore than MAX_CLAIMS_PER_SIDE claim entries
ExceedsTotalLocked / AmountOverflowNumeric guard rails
DuplicateClaimsRoot / ClaimsGroupAlreadyExists / ClaimsGroupNotFoundClaims-root invariants
AuthorizeVerifierNotSet / CancelVerifierNotSetVerifier missing — admin misconfiguration
EmptyBatch / BatchTooLargeclaimWithProofBatch arg shape
OnlyWETHNative-ETH settlement called with a non-WETH token
AddressSanctionedSanctions list reject

Address by network

NetworkConfig.contracts.privateSettlement in the SDK. ABI ships as PRIVATE_SETTLEMENT_ABI and PRIVATE_SETTLEMENT_IFACE (pre-parsed) from @zkscatter/sdk. See SDK reference / core.