Skip to Content
DocumentArchitectureOverview
Architecture

Commitment pool, half-proof authorization, relayer economy, identity gating.

zkScatter is a privacy-preserving settlement protocol for compliant on-chain finance. Trades, payments, and identity-gated distributions all settle through the same primitives — they’re different flows on top of one shared engine, not separate protocols.

The engine is built around four primitives:

  1. A commitment pool — an on-chain Merkle tree of Poseidon hashes hiding owner, token, and amount. Every flow reads and writes here.
  2. Half-proof authorization — each side of an action independently proves ownership of a leaf and authorizes a state transition (trade, payment, claim).
  3. A relayer network — off-chain matchers/submitters that batch half-proofs and post settlement transactions for a basis-point fee.
  4. An identity gate — a zk-X509 attestation layer (IdentityGate) that controls who can deposit or claim, without revealing the identity on-chain.

Where the four flows fit

FlowReference appWhat’s specific to itWhat’s shared
Private OTC tradingPro peer-to-peer order match, dual half-proofs (settleAuth), shared orderbookpool, identity, relayer
Private paymentsPay one-sided send, recipient claims later to their walletpool, identity, relayer
Identity-gated dropsDrop bulk distribution, one-sided claim (no maker), batch claimspool, identity, relayer
Mobile walletMobilenative ZK prover, on-device key custody, biometric unlockall of the above

If you’re building something new on zkScatter, you’re almost certainly composing primitives — not re-implementing settlement.

Components

┌────────────┐ ┌────────────┐ ┌──────────────────┐ │ Wallet │──────▶│ Relayer │──────▶│ PrivateSettlement│ │ (browser / │ HTTP │ (matcher + │ tx │ + CommitmentPool│ │ mobile) │ │ prover) │ │ (contracts) │ └────────────┘ └────────────┘ └──────────────────┘ │ │ │ │ ▼ ▼ │ ┌──────────────┐ ┌──────────────┐ └───────────▶│ Shared │ │ Identity │ │ Orderbook │ │ Gate │ └──────────────┘ └──────────────┘
ComponentRoleSDK module
CommitmentPoolAppend-only Poseidon Merkle tree of commitmentscore
PrivateSettlementVerifies half-proofs and settles trades atomicallycontracts
RelayerRegistryOn-chain directory of bonded relayersrelayer
IdentityGateKYC/zk-X509 attestation verifiercore
Relayer nodeOff-chain order matcher + half-proof co-proverrelayer
Shared orderbookCross-relayer order discovery serviceorderbook

Why a commitment pool

Token balances live as commitmentsPoseidon(ownerSecret, token, amount, salt, pubKey) — appended to an incremental Merkle tree of depth 20 (1M leaves). Spending a balance produces a nullifier that the contract records to prevent double-spends.

Because the leaf hides token, amount, and owner, an outside observer sees only opaque insertions and nullifier publications. Recovery from seed is straightforward: scan the tree, recompute commitments, find your leaves.

Why half-proofs

A trade involves two parties. A naive design would wait for both parties to be online, build a joint witness, and prove it together — slow, brittle, and hostile to mobile.

zkScatter splits the proof. Each side independently proves:

  • They own a leaf in the current commitment tree.
  • They’ve signed an EdDSA authorization over (side, sellToken, buyToken, sellAmount, buyAmount, maxFee, expiry, relayer, nonce).
  • The matched counterparty’s authorization hashes to a value the relayer commits to in the order hash.

A relayer matches two compatible authorizations, collects both proofs, and submits them to settleAuth in a single transaction. The contract verifies both Groth16 proofs, advances both nullifiers, and writes new commitments for the payouts.

This means the user signs once and goes offline. Matching, proving, and settlement happen asynchronously.

The relayer economy

Relayers are bonded operators registered on RelayerRegistry. Each relayer:

  • Posts a TON bond (returned in full on exit; no on-chain slashing — accountability runs through the planned Dispute Registry + reputation indexer).
  • Sets a fee in basis points.
  • Runs a matcher service exposing /api/info and /api/orders/:address (history is the same endpoint with ?status=&limit=&offset= query params; per-order detail and cancel use /api/orders/:address/:nonce).
  • Probes the shared orderbook for cross-relayer matches.
  • Earns fees on every settlement they submit.

To exit, a relayer requests exit, waits 7 days (EXIT_COOLDOWN_SECONDS), then withdraws their bond. See Run a relayer.

Apps discover relayers with loadRelayersWithApiInfo — combining on-chain registry rows with live /api/info probes.

Failure modes

FailureDetectionMitigation
Relayer goes offline mid-matchProbe timeout in loadRelayersWithApiInfoResubmit to another relayer — orders are idempotent by nonce
Order expiresexpiry < block.timestampCancel and roll forward with a cancel proof
Pool reorgIndexer falls behindConsume raw subscribeCommitmentInserted events and apply an app-level confirmation threshold (e.g. 12 blocks on Sepolia, 32 on mainnet) before treating inserts as final
Lost noteUser lost local storageRebuild from seed by scanning the pool
Last updated on