zk
Classes
IncrementalMerkleTree
Constructors
Constructor
new IncrementalMerkleTree(depth): IncrementalMerkleTree;Parameters
| Parameter | Type |
|---|---|
depth | number |
Returns
Properties
| Property | Modifier | Type |
|---|---|---|
depth | readonly | number |
Accessors
root
Get Signature
get root(): bigint;Returns
bigint
nextIndex
Get Signature
get nextIndex(): number;Returns
number
leaves
Get Signature
get leaves(): readonly bigint[];Returns
readonly bigint[]
Methods
insert()
insert(leaf): Promise<number>;Insert a new leaf. O(depth) hashes. Returns the inserted leaf’s
index. Throws if the tree is full (leaves.length === 2^depth).
Parameters
| Parameter | Type |
|---|---|
leaf | bigint |
Returns
Promise<number>
fromLeaves()
static fromLeaves(leaves, depth): Promise<IncrementalMerkleTree>;Build a fully-populated tree from an ordered leaf list. Useful for hydrating from chain state on startup.
Parameters
| Parameter | Type |
|---|---|
leaves | readonly bigint[] |
depth | number |
Returns
Promise<IncrementalMerkleTree>
proof()
proof(leafIndex): Promise<{
root: bigint;
pathElements: bigint[];
pathIndices: number[];
}>;Inclusion proof for a leaf at the given index. Walks the tree
level by level: at each level, the sibling is the hash of the
sibling subtree’s leaves (computed on demand) when those leaves
exist, otherwise the precomputed ZEROS[i]. Cost: O(N) hashes
per call in the worst case (sibling subtree is fully populated).
Memory stays at O(N) — the O(2^depth) a fully-padded tree would
require is the alternative.
TODO(perf): cache the layer arrays to drop per-proof cost to O(log N). Acceptable at projected near-term scale (~hundreds of commitments); revisit before mainnet.
Returns the same MerkleProof shape every spend circuit
consumes ({ root, pathElements, pathIndices }). Throws when
leafIndex is outside the inserted range.
Parameters
| Parameter | Type |
|---|---|
leafIndex | number |
Returns
Promise<{
root: bigint;
pathElements: bigint[];
pathIndices: number[];
}>
Interfaces
ClaimEntry
One claim distribution within an authorize proof — “send X of the buy token to recipient Y, releasable at time Z, secured by this per-claim secret.”
Properties
AuthorizeProofInput
Inputs to generateAuthorizeProof.
Properties
| Property | Type | Description |
|---|---|---|
note | CommitmentNote | Escrow note being spent (v2: includes BabyJub pubkey binding). |
leafIndex | number | Index of note’s commitment in the on-chain Merkle tree. |
allLeaves? | bigint[] | All commitment leaves in the pool. Required when merkleProof is omitted. |
merkleProof? | MerkleProof | Pre-computed Merkle proof for the note’s commitment. When provided, the expensive O(2^COMMIT_TREE_DEPTH) tree rebuild is skipped — recommended for any pool past a few thousand leaves. Apps should maintain an incremental tree and supply this. |
sellAmount | bigint | How much of note.token to sell. Must be ≤ note.amount. |
buyToken | string | Counterparty’s sell token. |
buyAmount | bigint | Minimum amount of buyToken we require — the price limit. |
maxFee | bigint | Relayer fee cap in basis points (100 = 1%). |
expiry | bigint | Order expiry as unix seconds; settle checks block.timestamp ≤ expiry. |
nonce | bigint | Replay-protection nonce — one per attempt. |
relayer | string | Address of the relayer this proof is bound to. |
eddsaPrivateKey | Uint8Array | Baby Jubjub private key (from deriveEdDSAKey). The local copy is wiped after signing. |
claims | ClaimEntry[] | Distribution of the proceeds. claims.length ≤ tier.cap. |
newSalt? | bigint | Salt for the residual (change) commitment. The page that pre-computed expectedChangeCommitment for the note file MUST pass the same salt here, otherwise the on-chain new commitment will differ from the stored one and the change UTXO will be effectively unspendable. Omit when sellAmount === note.amount (fully spent — no change). When change > 0 and this is omitted, the prover generates a random fallback but does not surface it back — callers should not rely on that path. |
AuthorizeProofResult
Output of generateAuthorizeProof. Public signals are returned
as bigints in circuit-declared order so they plug into the
Solidity verifier without further conversion. The named fields
duplicate signals from publicSignals for ergonomic use when
building the settleAuth calldata.
Properties
| Property | Type | Description |
|---|---|---|
proof | Groth16Proof | - |
publicSignals | readonly bigint[] | - |
pubKeyBind | bigint | First public signal of authorize.circom — the BabyJub pubkey bound to this proof. Off-chain relayer compliance checks read this without re-deriving from publicSignals. |
commitmentRoot | bigint | - |
nullifier | bigint | - |
nonceNullifier | bigint | - |
newCommitment | bigint | - |
claimsRoot | bigint | - |
totalLocked | bigint | - |
orderHash | bigint | - |
CancelProofInput
Properties
| Property | Type | Description |
|---|---|---|
note | CommitmentNote | The user’s escrow commitment note (v2 — includes BabyJub pubkey). |
leafIndex | number | Index of this commitment’s leaf in the on-chain Merkle tree. |
allLeaves? | bigint[] | All commitment leaves in the pool. Required when merkleProof is omitted. |
merkleProof? | MerkleProof | Pre-computed Merkle proof — recommended once the pool grows past a few thousand leaves; skipping the O(2^depth) rebuild is the difference between sub-second and multi-second cancel cost. |
nonce | bigint | Nonce of the order being cancelled. |
eddsaPrivateKey | Uint8Array | EdDSA private key bound into note’s commitment. Caller-owned; the prover wipes its own working copy after signing. |
submitter | string | Address of the wallet that will submit the cancel tx — always msg.sender of cancelPrivate. The contract binds this into the proof (pubSignals[4] = uint160(msg.sender)) and the EdDSA signature commits to it via cancelMsg = Poseidon(oldNonceNullifier, submitter), so a proof generated for one submitter address can’t be replayed by another wallet. Despite cancel’s “anyone can submit” framing in earlier versions, the contract has no relayer registry check on this path — cancelPrivate is permissionless and the user typically signs and broadcasts the cancel tx themselves from their own wallet. Pass the user’s address here. |
CancelProofResult
Properties
| Property | Type | Description |
|---|---|---|
proof | Groth16Proof | - |
publicSignals | readonly bigint[] | - |
commitmentRoot | bigint | - |
oldNullifier | bigint | - |
oldNonceNullifier | bigint | - |
newCommitment | bigint | - |
freshSalt | bigint | Fresh salt for the rotated commitment — the caller must persist this so the new note can be spent later (same balance, new salt). |
ClaimProofInput
Inputs to generateClaimProof. The secret and leafIndex
identify which slot in the settlement’s claims tree this proof
is releasing; the rest is exact-match material the circuit
re-hashes to verify the leaf.
Properties
| Property | Type | Description |
|---|---|---|
secret | bigint | Per-claim secret from the original ClaimEntry. |
recipient | bigint | Recipient address as a uint256. |
token | bigint | Token address as a uint256. |
amount | bigint | - |
releaseTime | bigint | Unix-seconds release time the original order set. |
leafIndex | number | Index of this claim within the settlement’s claims tree. |
allClaimLeaves | bigint[] | All 2^tier.claimsTreeDepth leaves of the claims tree (padded with 0n). Used to re-derive claimsRoot and the inclusion proof. Ignored when merkleProof is supplied. |
merkleProof? | MerkleProof | Optional fast path: when supplied, allClaimLeaves is ignored and the circuit takes this proof’s pathElements / pathIndices / root directly. |
ClaimProofResult
Output of generateClaimProof. Convenience fields duplicate
signals from publicSignals so callers building claim calldata
don’t have to re-derive them.
Properties
| Property | Type |
|---|---|
proof | Groth16Proof |
publicSignals | readonly bigint[] |
claimsRoot | bigint |
nullifier | bigint |
DeepRecoverRecipient
One recipient line for deep recovery, in leaf order. The operator supplies these (they created the payout); amounts are token-raw.
Properties
| Property | Type |
|---|---|
recipient | string |
amount | bigint |
DeepRecoverArgs
Properties
| Property | Type | Description |
|---|---|---|
seed | bigint | Per-payout seed — re-derived from the wallet via claimSeedFromKey. |
recipients | DeepRecoverRecipient[] | Recipients in their original leaf order (index matters: it feeds both the tree position and the secret derivation). |
token | string | - |
tierCap | number | Claims-tree capacity the settle used (16 |
targetClaimsRoot | string | The on-chain-registered root to match against (0x bytes32). |
startSec | number | Inclusive unix-second search window for the one unknown field, releaseTime (= claimFrom). |
endSec | number | - |
stepSec? | number | Search granularity in seconds (default 1). |
maxCandidates? | number | Safety cap on candidates so a fat-fingered window can’t spin forever. Default 200k (~2 days at 1s). Exceeding it throws. |
onProgress? | (scanned, total) => void | Progress callback (scanned, total) for a UI bar. |
signal? | AbortSignal | Abort signal so the UI can cancel a long scan. |
DeepRecoverClaim
Properties
| Property | Type |
|---|---|
recipient | string |
token | string |
amount | bigint |
releaseTime | bigint |
secret | bigint |
DeepRecoverResult
Properties
| Property | Type | Description |
|---|---|---|
releaseTime | bigint | - |
claims | DeepRecoverClaim[] | Reconstructed claims (with derived secrets) for the matched root — feed these to the package rebuilder. |
CircuitAssets
Wasm + zkey assets for a single circuit. snarkjs accepts URLs, ArrayBuffers, or Uint8Arrays for both — pass whatever the host has already loaded.
Properties
| Property | Type |
|---|---|
wasm | string | ArrayBuffer | Uint8Array<ArrayBufferLike> |
zkey | string | ArrayBuffer | Uint8Array<ArrayBufferLike> |
DepositProofResult
Properties
| Property | Type | Description |
|---|---|---|
commitment | bigint | Poseidon commitment derived from the note. Returned alongside the proof so callers don’t have to recompute it before sending the deposit transaction. |
proof | Groth16Proof | - |
publicSignals | readonly bigint[] | Public signals from the prover, in circuit-declared order. The deposit circuit emits one signal — the commitment — but we return the full array so callers can plug the result straight into the SDK’s ProveResult / ProverWorkerResponse shape without re-deriving it. |
PayoutRecipient
Recipient line for a multi-recipient payout. The token is shared across the whole payout — see SplitPayoutOpts.token.
Properties
| Property | Type | Description |
|---|---|---|
recipient | string | Ethereum address (0x-prefixed). |
amount | bigint | Amount of opts.token to send to this recipient. |
releaseTime | bigint | Earliest unix-second the recipient can claim. |
secret? | bigint | Optional pre-computed per-claim secret. When omitted, splitPayout draws one from SplitPayoutOpts.generateSecret (defaulting to randomFieldElement). |
SplitPayoutOpts
Properties
| Property | Type | Description |
|---|---|---|
token | string | Token address that goes into every ClaimEntry.token. The protocol enforces claim.token === buyToken per used claim, so callers should pass the same value as their AuthorizeProofInput.buyToken. |
tier? | CircuitTier | Tier to chunk against. Defaults to pickActiveTier(recipients.length) so the smallest active circuit covers the run; pass an explicit tier when the caller needs to pin to a specific verifier (e.g. tests, or a Pay UI that wants to multi-batch on a smaller tier even when a larger one is active). |
generateSecret? | () => bigint | Override the per-claim secret generator. Useful for tests that need deterministic output. Defaults to randomFieldElement. |
PayoutBatch
One settle-sized batch of claims. The caller plugs claims
into AuthorizeProofInput.claims and totalAmount into both
sellAmount and buyAmount (self-pay USDC → USDC pattern).
Each batch needs its own AuthorizeProofInput and signature.
Properties
| Property | Type | Description |
|---|---|---|
totalAmount | bigint | Sum of amount across claims. |
claims | ClaimEntry[] | Up to tier.cap fully-formed entries, ready to pass straight to generateAuthorizeProof. |
tier | CircuitTier | Tier this batch was sized for. The caller forwards it to generateAuthorizeProof so the circuit’s claimsTreeDepth matches the chunking. Every batch from one splitPayout call carries the same tier. |
WithdrawProofInput
Properties
| Property | Type | Description |
|---|---|---|
note | CommitmentNote | The note being spent. |
merkleProof | MerkleProof | Live merkle proof for note’s commitment in the pool tree. |
withdrawAmount | bigint | Raw token amount to withdraw. Must satisfy 0 < amount <= note.amount. |
recipient | string | Recipient EOA — receives the withdrawn tokens. |
relayer? | string | Relayer address paid out of the withdraw amount. Pass 0x000…000 for self-pay (no relayer). |
eddsaPrivateKey | Uint8Array | EdDSA private key bound into note’s commitment via pubKeyAx/Ay. Required since the EdDSA gate was added to the withdraw circuit — copying the note file alone is not sufficient to spend; the original wallet’s signing capability must be present to sign Poseidon(nullifierHash, recipient). Caller-owned; the prover wipes its own working copy after signing. |
WithdrawProofResult
Properties
| Property | Type | Description |
|---|---|---|
proof | Groth16Proof | - |
publicSignals | readonly bigint[] | - |
newCommitment | bigint | 0n for full-amount withdraws; otherwise the freshly-computed commitment of the change UTXO. Callers must persist the matching changeNote before broadcasting. |
changeNote | CommitmentNote | null | Change-UTXO preimage. null for full-amount withdraws (no residue to persist). |
root | bigint | - |
nullifierHash | bigint | - |
tokenHash | bigint | - |
CommitmentNote
A commitment note — the secret material backing one escrow entry.
The full v2 commitment binds the BabyJub signing pubkey ([issue #128]):
commitment = Poseidon( TAG_COMMITMENT_V2, ownerSecret, token, amount, salt, pubKeyAx, pubKeyAy )
Losing either ownerSecret or the EdDSA private key behind
pubKeyAx/pubKeyAy makes the funds unspendable. Wallets must
back both up together.
Properties
MerkleProof
Merkle inclusion proof for a commitment in the on-chain pool.
Properties
| Property | Type |
|---|---|
root | bigint |
pathElements | bigint[] |
pathIndices | number[] |
PoseidonModule()
Opaque Poseidon module handle. Hot loops (e.g. building a
depth-20 Merkle tree → 1M+ hashes) should fetch this once via
getPoseidonModule() and call poseidonHashWith(p, inputs)
synchronously inside the loop, rather than awaiting
poseidonHash per hash and paying 1M microtasks.
PoseidonModule(inputs): unknown;Opaque Poseidon module handle. Hot loops (e.g. building a
depth-20 Merkle tree → 1M+ hashes) should fetch this once via
getPoseidonModule() and call poseidonHashWith(p, inputs)
synchronously inside the loop, rather than awaiting
poseidonHash per hash and paying 1M microtasks.
Parameters
| Parameter | Type |
|---|---|
inputs | bigint[] |
Returns
unknown
Properties
| Property | Type |
|---|---|
F | { toObject: bigint; } |
F.toObject | bigint |
CircuitTier
Tier descriptor for an authorize.circom variant. The same Pay/Pro flow can run against any tier — the only differences are the compiled wasm/zkey assets, the on-chain verifier address, and how many claims fit per settlement. The 15 Groth16 public signals are shared across tiers (claimsRoot already aggregates the variable- length claims set inside the circuit).
All three protocol tiers (16 / 64 / 128) are live; the proof
helpers (generateAuthorizeProof, generateClaimProof,
splitPayout) take a CircuitTier parameter and default
to TIER_16 only for legacy callers. MAX_CLAIMS_PER_SIDE
stays as a deprecated re-export for the same reason.
Properties
EdDSAKeyPair
Properties
| Property | Type | Description |
|---|---|---|
privateKey | Uint8Array | 32-byte Baby Jubjub private key (the keccak of the ECDSA sig). |
publicKey | readonly [bigint, bigint] | Public point on Baby Jubjub: [Ax, Ay]. |
EdDSASignature
Properties
| Property | Type |
|---|---|
S | bigint |
R8x | bigint |
R8y | bigint |
BuiltTree
A built Poseidon Merkle tree. layers[0] is the leaf layer
(padded to 2^depth); layers[depth] is the root layer (one
element). Suitable input for getMerkleProof.
Properties
| Property | Type |
|---|---|
root | bigint |
layers | bigint[][] |
MerklePathProof
Inclusion proof for a single leaf in a BuiltTree.
Properties
| Property | Type |
|---|---|
pathElements | bigint[] |
pathIndices | number[] |
MockProverOpts
Properties
SnarkjsRawProof
Raw Groth16 proof shape returned by snarkjs.
Properties
| Property | Type |
|---|---|
pi_a | [string, string, string] |
pi_b | [[string, string], [string, string], [string, string]] |
pi_c | [string, string, string] |
ProveTiming
Properties
| Property | Type | Description |
|---|---|---|
circuit | ZkCircuit | - |
durationMs | number | Raw float — sub-ms precision retained so the same hook can time fast ops (hashing, witness-only) later without lossy rounding here. |
ok | boolean | - |
Prover
Generates Groth16 proofs.
Implementations:
createWebWorkerProver(browser) — runs snarkjs in a Web Worker so 30-second proofs don’t freeze the UI.createMockProver(dev/test) — returns deterministic dummy proofs so UI flows can be exercised without circuit assets.createWebViewProver(mobile, planned) — bridges to a React Native WebView running the same snarkjs build.
The interface is deliberately minimal: callers don’t care which platform is producing the proof, only that they get one back.
Methods
ready()
ready(): Promise<void>;Resolve once the prover is ready to accept jobs (circuits loaded, worker spawned, etc.). Idempotent: subsequent calls return the same promise.
Returns
Promise<void>
prove()
prove(req, opts?): Promise<ProveResult>;Generate one proof. Concurrent calls are serialized by every built-in implementation — proving is CPU-heavy and parallel jobs would just thrash.
Parameters
| Parameter | Type |
|---|---|
req | ProveRequest |
opts? | ProveOpts |
Returns
Promise<ProveResult>
dispose()
dispose(): void;Release any worker / WebView / circuit memory. The prover is unusable after this. Calling twice is a no-op.
Returns
void
Groth16Proof
Groth16 proof in the shape every Solidity verifier expects: two G1 elements (a, c) and one G2 element (b). Tuples — not arrays — so positional access is type-checked at call sites.
Properties
| Property | Type |
|---|---|
a | readonly [bigint, bigint] |
b | readonly [readonly [bigint, bigint], readonly [bigint, bigint]] |
c | readonly [bigint, bigint] |
ProveResult
Output of one proof job. publicSignals carries the public
inputs in circuit-declared order so callers can splice them into
the verifier call. meta is an optional side-channel for private
outputs the worker computed but the circuit doesn’t expose as a
public signal — e.g. cancel’s freshSalt, which the rotated note
needs persisted client-side but the on-chain call doesn’t take.
Field-element values; same BigInt structuredClone path as the
proof itself. Workers that don’t need the channel just omit it.
Properties
| Property | Type |
|---|---|
proof | Groth16Proof |
publicSignals | readonly bigint[] |
meta? | Readonly<Record<string, bigint>> |
ProveOpts
Optional knobs for a single prove call. Every implementation must
honor signal for cancellation; onProgress is best-effort.
Properties
ProveRequest
A request to generate one proof. The input shape is per-circuit
and validated by the implementation; this layer only carries it.
Properties
| Property | Type | Description |
|---|---|---|
circuitId | CircuitId | - |
input | Record<string, unknown> | Field-by-field circuit input. BigInts are accepted directly for prime-field values; strings/numbers are coerced by the implementation. Concrete shapes are exposed by per-circuit helpers in higher-level modules (e.g. zk/deposit). |
tier? | CircuitTier | Optional circuit-tier hint for per-tier provers (e.g. authorize 16 / 64 / 128). CircuitTier is a plain data object so it survives postMessage’s structured clone unchanged. Workers fall back to TIER_16 when omitted, preserving the historical single-tier behavior. |
WebWorkerProverOpts
Properties
| Property | Type | Description |
|---|---|---|
createWorker | () => Worker | Factory for the worker. Lazy so we don’t spawn until the first prove (or ready()) call. |
label? | string | Optional logger label used in fallback warnings and errors. |
fallbackProve? | (req, opts?) => Promise<ProveResult> | Main-thread fallback when the Worker constructor throws (most often: SSR, COOP/COEP misconfiguration, very old browsers). Without one, the prover surfaces the worker error to the caller. |
LazyWorkerProverOpts
Properties
| Property | Type | Description |
|---|---|---|
circuit | ZkCircuit | Circuit name — used for the timing wrapper’s label and as the WebWorkerProverOpts.label for fallback warnings. |
createWorker | () => Worker | Spawn the per-circuit worker. Lazily invoked on first ready() / prove() so apps that never use the prover don’t pay the ~24 MB asset fetch. |
ProverWorkerHandlers
What a circuit-specific worker file plugs into setupProverWorker.
Methods
prove()
prove(req): Promise<{
proof: Groth16Proof;
publicSignals: readonly bigint[];
meta?: Readonly<Record<string, bigint>>;
}>;Run the proof for one job. May throw — the runtime turns that
into a { type: "error" } message back to the main thread.
meta is an optional side-channel for private outputs the
circuit doesn’t expose as a public signal (e.g. cancel’s
freshSalt). See ProveResult.meta.
Parameters
| Parameter | Type |
|---|---|
req | ProverWorkerRequest |
Returns
Promise<{
proof: Groth16Proof;
publicSignals: readonly bigint[];
meta?: Readonly<Record<string, bigint>>;
}>
preload()?
optional preload(): Promise<void>;Optional asset / Poseidon warmup, run once before the worker
signals “ready”. Errors here are reported as a worker-scope
error AND posted as a sentinel error message so the main
thread surfaces a clear failure instead of waiting forever
for a ready that never comes.
Returns
Promise<void>
Type Aliases
AuthorizeProofMetaKey
type AuthorizeProofMetaKey = typeof AUTHORIZE_PROOF_META_KEYS[number];AuthorizeProofMeta
type AuthorizeProofMeta = Record<AuthorizeProofMetaKey, bigint>;TierAssetPaths
type TierAssetPaths = CircuitAssets;Asset URL pair for one circuit tier. Re-exports CircuitAssets under a tier-flavoured name for callsites that surface tiers explicitly; consumers can pass either type interchangeably.
ZkCircuit
type ZkCircuit = "authorize" | "cancel" | "claim" | "deposit" | "withdraw";ProveReporter
type ProveReporter = (timing) => void;Parameters
| Parameter | Type |
|---|---|
timing | ProveTiming |
Returns
void
CircuitId
type CircuitId =
| "deposit"
| "authorize"
| "claim"
| string & {
};Identifier for a Groth16 circuit shipped with the protocol.
Open string-literal union — the well-known names get IDE
autocomplete, but any string is accepted so consumers shipping
private circuits don’t have to fork the SDK. (Type aliases
can’t be declaration-merged; widening via (string & {}) is
the workaround.)
ProverWorkerRequest
type ProverWorkerRequest = {
type: "prove";
jobId: number;
circuitId: string;
input: Record<string, unknown>;
tier?: CircuitTier;
};Wire-format messages exchanged with a prover Web Worker.
The worker side is not part of this SDK — each Phase 2b+ circuit module ships its own worker that responds to these messages. The split keeps circuit-specific snarkjs code (large wasm, per-circuit input shapes) out of any bundle that doesn’t use it.
BigInts cross the boundary as native BigInt values. The HTML structuredClone algorithm has supported BigInt across all browsers and Node ≥ 17 for years, so the older string-encoding workaround is unnecessary and just slowed proofs down.
Properties
| Property | Type | Description |
|---|---|---|
type | "prove" | - |
jobId | number | - |
circuitId | string | - |
input | Record<string, unknown> | - |
tier? | CircuitTier | Optional circuit tier hint. CircuitTier is a plain data object (no methods), so it survives postMessage’s structured clone unchanged. Worker handlers default to TIER_16 when omitted, preserving the single-tier behavior pre-multi-tier callers expect. |
ProverWorkerResponse
type ProverWorkerResponse =
| {
type: "ready";
}
| {
type: "progress";
jobId: number;
message: string;
}
| {
type: "result";
jobId: number;
proof: Groth16Proof;
publicSignals: readonly bigint[];
meta?: Readonly<Record<string, bigint>>;
}
| {
type: "error";
jobId: number;
message: string;
};Union Members
Type Literal
{
type: "ready";
}Type Literal
{
type: "progress";
jobId: number;
message: string;
}Type Literal
{
type: "result";
jobId: number;
proof: Groth16Proof;
publicSignals: readonly bigint[];
meta?: Readonly<Record<string, bigint>>;
}type
type: "result";jobId
jobId: number;proof
proof: Groth16Proof;publicSignals
publicSignals: readonly bigint[];meta?
optional meta?: Readonly<Record<string, bigint>>;See ProveResult.meta.
Type Literal
{
type: "error";
jobId: number;
message: string;
}Variables
AUTHORIZE_PROOF_META_KEYS
const AUTHORIZE_PROOF_META_KEYS: readonly ["pubKeyBind", "commitmentRoot", "nullifier", "nonceNullifier", "newCommitment", "claimsRoot", "totalLocked", "orderHash"];Extracted scalars an authorize-circuit Web Worker should pass back
via ProveResult.meta. They duplicate fields the worker already
has on AuthorizeProofResult so the main thread can pack
SettleAuthSide without re-deriving by public-signal index.
CANCEL_PUBLIC_SIGNALS
const CANCEL_PUBLIC_SIGNALS: readonly ["commitmentRoot", "oldNullifier", "oldNonceNullifier", "newCommitment", "submitter"];Cancel circuit’s public-input order, in the same order the
.circom declares them. Mirrored by the contract verifier’s
scalar arguments. Change one, change both — the constant lives
here so consumers can’t drift from it.
CANCEL_META_FRESH_SALT
const CANCEL_META_FRESH_SALT: "freshSalt";Meta-channel key under which cancel.worker returns the
private freshSalt to assembleCancelProofResult.
CLAIMS_TREE_SIZE
const CLAIMS_TREE_SIZE: number;Tier-16 claims-tree size, kept as the historical export.
Deprecated
Derive size from the picked tier
(1 << tier.claimsTreeDepth) so it follows the source settlement’s
circuit; this constant is hard-pinned to TIER_16 and will silently
return the wrong value on a tier-64 / tier-128 settlement once
those ship.
FIELD_MODULUS
const FIELD_MODULUS: 21888242871839275222246405745257275088548364400416034343698204186575808495617n = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;BN254 scalar field modulus — every Poseidon output and every random field element must live below this.
COMMIT_TREE_DEPTH
const COMMIT_TREE_DEPTH: 20 = 20;Depth of the on-chain commitment Merkle tree (2^20 ≈ 1M leaves).
TIER_16
const TIER_16: CircuitTier;Tier 16 — depth 4 → 16 leaves, ~23K authorize constraints, ptau pot15. The default + smallest tier; what every Pay run uses for ≤ 16 recipients.
TIER_64
const TIER_64: CircuitTier;Tier 64 — depth 6 → 64 leaves, ~56K authorize constraints, ptau
pot16. Picked by pickActiveTier for runs of 17–64 recipients.
Registered on PrivateSettlement via setAuthorizeVerifier(64, addr) + setClaimVerifier(64, addr).
TIER_128
const TIER_128: CircuitTier;Tier 128 — depth 7 → 128 leaves, ~101K authorize constraints,
ptau pot17. Picked by pickActiveTier for runs of 65–128
recipients. Heaviest prove time (~6–12 s on a mid-tier laptop;
mobile-borderline) — Pay’s wizard surfaces the trade-off in the
Privacy plan.
TIERS
const TIERS: readonly CircuitTier[];Public registry of every tier the SDK knows about, ordered by capacity. Use pickTier to select one — direct indexing is fine when the tier is fixed (e.g. tests).
ACTIVE_TIERS
const ACTIVE_TIERS: readonly CircuitTier[];Tiers that have a live verifier today. Production code should
validate against this list before submitting; everything outside
it will revert on-chain with TierNotConfigured(tier).
Ordering invariant: must stay sorted by cap ascending.
pickActiveTier relies on the order — the first match wins
(smallest tier that covers n), and the multi-batch fallback uses
the last entry as the largest available cap. New tiers must be
inserted in sorted position; the assertion below enforces it at
module load to keep silent ordering bugs from changing tier
selection.
MAX_CLAIMS_PER_SIDE
const MAX_CLAIMS_PER_SIDE: 16 | 64 | 128 = TIER_16.cap;Maximum number of claim leaves per side in a single settlement.
Deprecated
Use CircuitTier.cap from a tier picked via pickTier instead. Hardcoding the tier-16 cap in callers blocks the multi-tier rollout. The constant stays as a transitional re-export of TIER_16.cap so legacy paths keep compiling.
CLAIMS_TREE_DEPTH
const CLAIMS_TREE_DEPTH: 4 | 6 | 7 = TIER_16.claimsTreeDepth;Depth of the per-settlement claims Merkle tree (2^4 = 16 leaves).
Deprecated
Use CircuitTier.claimsTreeDepth via pickTier. Re-exported as the tier-16 depth during the multi-tier migration.
DEFAULT_DERIVE_MESSAGE
const DEFAULT_DERIVE_MESSAGE: "Sign to generate your zkScatter trading key.\n\nThis key is used to sign orders privately.\nIt does not grant access to your funds." = "Sign to generate your zkScatter trading key.\n\nThis key is used to sign orders privately.\nIt does not grant access to your funds.";Default fixed message used by deriveEdDSAKey when callers don’t
pass their own. Keeping this stable across SDK versions matters:
changing it would invalidate every existing user’s EdDSA key.
TAG_ESCROW_NULL
const TAG_ESCROW_NULL: 0n = 0n;Shared Poseidon domain-separation tags.
Mirrors the canonical definitions in circuits/tags.circom. Every
Poseidon-based commitment, nullifier, or bound hash prepends one
of these tags as its first input so the preimage spaces are
disjoint.
CONSENSUS-CRITICAL: any change to these values must land in lock-step with:
circuits/tags.circomzk-relayer/src/core/tags.tsfrontend/app/lib/zk/tags.ts(legacy duplicate, to be removed) Disagreement here causes proofs to verify against a different preimage space than the contract expects — silent data loss.
TAG_NONCE_NULL
const TAG_NONCE_NULL: 1n = 1n;TAG_CLAIM_NULL
const TAG_CLAIM_NULL: 2n = 2n;TAG_COMMITMENT_V2
const TAG_COMMITMENT_V2: 3n = 3n;PRELOAD_ERROR_JOB_ID
const PRELOAD_ERROR_JOB_ID: -1 = -1;Sentinel jobId used by preload-failure error messages. The main client uses this to surface init failures even when no job is in flight; a real prove jobId is always > 0.
Functions
hashAuthorizeOrder()
function hashAuthorizeOrder(order): Promise<bigint>;Compute the order hash that the circuit and contract both sign over. Field order is consensus-critical:
Poseidon(sellToken, buyToken, sellAmount, buyAmount, maxFee, expiry, nonce, claimsRoot, relayer)
Including claimsRoot prevents claim-bag swapping; including
relayer enables the trustless fee split.
Parameters
| Parameter | Type |
|---|---|
order | { sellToken: bigint; buyToken: bigint; sellAmount: bigint; buyAmount: bigint; maxFee: bigint; expiry: bigint; nonce: bigint; claimsRoot: bigint; relayer: bigint; } |
order.sellToken | bigint |
order.buyToken | bigint |
order.sellAmount | bigint |
order.buyAmount | bigint |
order.maxFee | bigint |
order.expiry | bigint |
order.nonce | bigint |
order.claimsRoot | bigint |
order.relayer | bigint |
Returns
Promise<bigint>
generateAuthorizeProof()
function generateAuthorizeProof(
input,
assets,
tier?): Promise<AuthorizeProofResult>;Generate a Groth16 authorize proof for a private limit order.
Pure function — no globals, no caching. Workers / app code own asset loading and re-use. The circuit is ~22.5K constraints; one proof is ~1–2 s on desktop and ~5–9 s on phone-class hardware.
The function pre-computes the intermediate values
(commitmentRoot, nullifiers, claimsRoot, orderHash) so the
caller can build the settleAuth calldata without re-deriving
them from publicSignals.
Parameters
| Parameter | Type | Default value |
|---|---|---|
input | AuthorizeProofInput | undefined |
assets | CircuitAssets | undefined |
tier | CircuitTier | TIER_16 |
Returns
Promise<AuthorizeProofResult>
authorizeMetaFrom()
function authorizeMetaFrom(result): AuthorizeProofMeta;Pluck the worker-meta subset out of an AuthorizeProofResult.
Used inside the worker to populate ProveResult.meta.
Parameters
| Parameter | Type |
|---|---|
result | AuthorizeProofResult |
Returns
assembleAuthorizeProofResult()
function assembleAuthorizeProofResult(proveResult): AuthorizeProofResult;Reassemble an AuthorizeProofResult from a ProveResult whose
worker populated meta via authorizeMetaFrom. Validates
every required field so callers don’t paper over a worker that
forgot to surface a scalar — the alternative is a chain of !
non-null asserts at the call site.
Parameters
| Parameter | Type |
|---|---|
proveResult | ProveResult |
Returns
assembleCancelProofResult()
function assembleCancelProofResult(envelope): CancelProofResult;Reconstruct a rich CancelProofResult from the slim
{ proof, publicSignals, meta? } envelope a Web Worker passes
back. The cancel circuit’s private freshSalt is not in
publicSignals — it isn’t needed by the on-chain cancelPrivate
call (the contract doesn’t take it), but vault rotation
persistence (writing the rotated note with the new salt) needs
it. The worker carries it back through the meta channel; if a
worker omits it, this returns freshSalt: 0n and rotation
persistence stays gated off in the caller.
Parameters
| Parameter | Type |
|---|---|
envelope | { proof: Groth16Proof; publicSignals: readonly bigint[]; meta?: Readonly<Record<string, bigint>>; } |
envelope.proof | Groth16Proof |
envelope.publicSignals | readonly bigint[] |
envelope.meta? | Readonly<Record<string, bigint>> |
Returns
generateCancelProof()
function generateCancelProof(input, assets): Promise<CancelProofResult>;Parameters
| Parameter | Type |
|---|---|
input | CancelProofInput |
assets | CircuitAssets |
Returns
Promise<CancelProofResult>
singleClaimTree()
function singleClaimTree(
entry,
leafIndex,
tier?): Promise<{
claimLeaf: bigint;
allClaimLeaves: bigint[];
}>;Build an N-leaf claims tree with a single entry at leafIndex,
rest zero-padded. tier defaults to TIER_16 — the only live
authorize tier today — but a settlement produced by a higher tier
must pass that tier so the claims tree size matches. The shape
every single-claim flow needs; centralised so the inline
poseidonHash + Array(N).fill(0n) pattern lives in one place.
Note: recipient and token are passed as BigInt — call sites
with 0x-prefixed hex strings should BigInt(addr) before
passing, since the circuit hashes them as field elements.
Parameters
| Parameter | Type | Default value |
|---|---|---|
entry | { secret: bigint; recipient: bigint; token: bigint; amount: bigint; releaseTime: bigint; } | undefined |
entry.secret | bigint | undefined |
entry.recipient | bigint | undefined |
entry.token | bigint | undefined |
entry.amount | bigint | undefined |
entry.releaseTime | bigint | undefined |
leafIndex | number | undefined |
tier | CircuitTier | TIER_16 |
Returns
Promise<{
claimLeaf: bigint;
allClaimLeaves: bigint[];
}>
buildClaimsTree()
function buildClaimsTree(claims, tier?): Promise<{
root: bigint;
layers: bigint[][];
leaves: bigint[];
}>;N-leaf variant of singleClaimTree for an entire batch — hashes
every claim into its leaf, zero-pads up to CLAIMS_TREE_SIZE,
and builds the Poseidon tree. Returns the layers so callers can
pull a per-leaf inclusion proof via getMerkleProof(layers, i)
without rebuilding. The hash recipe matches the authorize circuit
exactly — keep in lock-step with generateAuthorizeProof’s
internal claim-leaf construction.
Parameters
| Parameter | Type | Default value |
|---|---|---|
claims | readonly { secret: bigint; recipient: string | bigint; token: string | bigint; amount: bigint; releaseTime: bigint; }[] | undefined |
tier | CircuitTier | TIER_16 |
Returns
Promise<{
root: bigint;
layers: bigint[][];
leaves: bigint[];
}>
generateClaimProof()
function generateClaimProof(
input,
assets,
tier?): Promise<ClaimProofResult>;Generate a Groth16 claim proof for one slot of a settlement.
Pre-checks:
leafIndexin range (slow path) or non-negative (fast path)- claim data hashes to the leaf at
leafIndex(slow path) — catches “wrong claim file” / “wrong settlement” mistakes loudly instead of after a 2 s proof allClaimLeaves.length === 2^tier.claimsTreeDepth
Parameters
| Parameter | Type | Default value |
|---|---|---|
input | ClaimProofInput | undefined |
assets | CircuitAssets | undefined |
tier | CircuitTier | TIER_16 |
Returns
Promise<ClaimProofResult>
deepRecoverReleaseTime()
function deepRecoverReleaseTime(args): Promise<DeepRecoverResult | null>;Recover the one fuzzy field — releaseTime — of a payout whose
claim links were lost, by reconstructing the claims tree for each
candidate timestamp and matching it against the on-chain root.
Everything else is known: the seed is re-derivable from the wallet,
and the operator supplies recipients + amounts + order. releaseTime
is run-wide (one value for the whole payout), so it’s a single scalar
to scan, with the on-chain claimsRoot as an exact-match oracle.
Returns the matched releaseTime + reconstructed claims, or null if
no candidate in the window reproduces the target root.
Parameters
| Parameter | Type |
|---|---|
args | DeepRecoverArgs |
Returns
Promise<DeepRecoverResult | null>
generateDepositProof()
function generateDepositProof(note, assets): Promise<DepositProofResult>;Generate a Groth16 deposit proof for a CommitmentNote.
Pure function — no globals, no caching. Callers (typically a Web Worker) decide where the wasm/zkey come from and whether to cache them across calls.
The commitment is derived from the note’s preimage internally,
so a caller can’t accidentally pair a note with a mismatched
commitment and then debug an opaque on-chain InvalidProof
revert. The function also re-checks the prover’s first public
signal against the derived commitment as a defence-in-depth
assertion.
Parameters
| Parameter | Type |
|---|---|
note | CommitmentNote |
assets | CircuitAssets |
Returns
Promise<DepositProofResult>
withDeterministicSecrets()
function withDeterministicSecrets(
recipients,
seed,
token): Promise<PayoutRecipient[]>;Fill each recipient’s secret deterministically from a per-payout
seed (see deriveClaimSecret), returning a new array ready
for splitPayout. Use this instead of the default random
generator whenever the caller wants the resulting claimsRoot to be
reproducible across retries / recovery — the secret for recipient i
is deriveClaimSecret(seed, recipient, token, amount, releaseTime, i),
indexed by the recipient’s position in the full payout (preserved by
splitPayout’s in-order chunking). token must match
SplitPayoutOpts.token. A recipient that already carries a
secret is left untouched.
Parameters
| Parameter | Type |
|---|---|
recipients | PayoutRecipient[] |
seed | bigint |
token | string |
Returns
Promise<PayoutRecipient[]>
splitPayout()
function splitPayout(recipients, opts): PayoutBatch[];Chunk a recipient list into batches that fit the picked circuit
tier’s cap. Order is preserved — recipient i always lands in
batch floor(i / tier.cap) — and per-claim secrets are drawn for
any recipient that didn’t supply one.
Tier defaults to pickActiveTier(recipients.length) so a
caller that doesn’t care about tiers gets the smallest live
circuit that covers the run; with only TIER_16 active today, that
matches the historical behavior. When a future tier ships and is
added to ACTIVE_TIERS, the same call automatically routes a
17-recipient run through one tier-64 batch instead of two
tier-16 batches.
This is a pure helper. The caller still selects which note(s)
to spend per batch, manages the residual change UTXO, and
drives N proofs / N signatures. splitPayout only
exists so apps don’t re-implement chunking + secret-generation
on top of generateAuthorizeProof.
Parameters
| Parameter | Type |
|---|---|
recipients | PayoutRecipient[] |
opts | SplitPayoutOpts |
Returns
authorizeAssetPaths()
function authorizeAssetPaths(tier, baseDir): {
wasm: string;
zkey: string;
};Resolve the per-tier authorize-circuit asset URLs for a given static-asset base directory.
- TIER_16 →
<baseDir>/authorize.wasm+<baseDir>/authorize_final.zkey(legacy filename, present in every existing deploy). - Higher tiers →
<baseDir>/authorize_<cap>.wasm+<baseDir>/authorize_<cap>_final.zkey(e.g.authorize_64.wasm).
Today only the TIER_16 artifacts exist in circuits/build/; the
higher-tier paths will 404 until the corresponding ceremony ships
the matching .wasm / .zkey files. Production paths can’t reach
those URLs accidentally — pickActiveTier only returns tiers in
ACTIVE_TIERS, which the deploy controls. The intentional
side effect: the moment a TIER_64 / TIER_128 ceremony drops the
files alongside the tier-16 ones and updates ACTIVE_TIERS, every
caller picks up the new artifacts with no code change.
Pass baseDir like "/zk" (Pay’s public/zk static folder) or
the per-app deploy path. No trailing slash.
Parameters
| Parameter | Type |
|---|---|
tier | CircuitTier |
baseDir | string |
Returns
{
wasm: string;
zkey: string;
}wasm
wasm: string;zkey
zkey: string;claimAssetPaths()
function claimAssetPaths(tier, baseDir): {
wasm: string;
zkey: string;
};Same idea as authorizeAssetPaths but for the claim circuit. Claim circuits are tier-specific because the claims tree depth matches the source settlement’s tier — a tier-64 settlement hands out claim packages whose proofs need a depth-6 claim circuit.
Parameters
| Parameter | Type |
|---|---|
tier | CircuitTier |
baseDir | string |
Returns
{
wasm: string;
zkey: string;
}wasm
wasm: string;zkey
zkey: string;generateWithdrawProof()
function generateWithdrawProof(input, assets): Promise<WithdrawProofResult>;Generate a Groth16 withdraw proof. Mirrors generateDepositProof’s
shape — pure function, no caching. The commitment / nullifier /
tokenHash / change-UTXO commitment are derived locally and
cross-checked against the prover’s public signals so a malformed
prove can’t slip through with a mismatched root or recipient.
Defense-in-depth: the on-chain pool re-verifies all of these via the verifier contract; the local checks just surface circuit-vs- app drift earlier and with clearer error messages.
Parameters
| Parameter | Type |
|---|---|
input | WithdrawProofInput |
assets | CircuitAssets |
Returns
Promise<WithdrawProofResult>
getPoseidonModule()
function getPoseidonModule(): Promise<PoseidonModule>;Get (and lazily build) the cached Poseidon module. The in-flight Promise is memoized so concurrent callers share one initialization.
Returns
Promise<PoseidonModule>
warmupPoseidon()
function warmupPoseidon(): Promise<void>;Eagerly build the Poseidon round-constant table so the first
proof job doesn’t pay the build cost on the user’s hot path.
Worker preload hooks should call this on startup.
Returns
Promise<void>
poseidonHash()
function poseidonHash(inputs): Promise<bigint>;Generic Poseidon hash — convenient for one-off calls. Awaits
the cached module each call, so use poseidonHashWith inside
hot loops instead.
Parameters
| Parameter | Type |
|---|---|
inputs | bigint[] |
Returns
Promise<bigint>
poseidonHashWith()
function poseidonHashWith(poseidon, inputs): bigint;Synchronous Poseidon hash given an already-built module. Use inside hot loops where awaiting per call would dominate the cost.
Parameters
| Parameter | Type |
|---|---|
poseidon | PoseidonModule |
inputs | bigint[] |
Returns
bigint
randomFieldElement()
function randomFieldElement(): bigint;Generate a cryptographically random field element strictly less
than the BN254 scalar modulus. Uses crypto.getRandomValues
(globalThis.crypto works in browser, Node 19+, Deno, Bun).
Sampling: top byte is masked with 0x3f so the candidate is at
most 254 bits (since FIELD_MODULUS < 2^254). The do-while
rejects the ~24% of candidates that still land in [FIELD_MODULUS, 2^254), leaving a uniform distribution over [0, FIELD_MODULUS)
with no modulo bias. We pay slightly more rejections than the
legacy 0x1f mask did, but get a full ~254 bits of entropy
rather than ~253.
Returns
bigint
claimSecretPreimage()
function claimSecretPreimage(
seed,
recipient,
token,
amount,
releaseTime,
index): bigint[];Poseidon preimage for a deterministic claim secret — the single
source of truth shared by deriveClaimSecret (one-off) and the
batched withDeterministicSecrets (Poseidon module fetched once), so
the two can never drift. A drift would make a recovery run regenerate
the wrong secrets.
Parameters
| Parameter | Type |
|---|---|
seed | bigint |
recipient | string |
token | string |
amount | bigint |
releaseTime | bigint |
index | number |
Returns
bigint[]
deriveClaimSecret()
function deriveClaimSecret(
seed,
recipient,
token,
amount,
releaseTime,
index): Promise<bigint>;Parameters
| Parameter | Type |
|---|---|
seed | bigint |
recipient | string |
token | string |
amount | bigint |
releaseTime | bigint |
index | number |
Returns
Promise<bigint>
generateNote()
function generateNote(
token,
amount,
pubKey): CommitmentNote;Build a fresh CommitmentNote bound to the caller’s BabyJub
signing pubkey.
Parameters
| Parameter | Type |
|---|---|
token | string |
amount | bigint |
pubKey | readonly [bigint, bigint] |
Returns
computeCommitment()
function computeCommitment(note): Promise<bigint>;v2 commitment: Poseidon(TAG_COMMITMENT_V2, ownerSecret, token, amount, salt, pubKeyAx, pubKeyAy)
Parameters
| Parameter | Type |
|---|---|
note | CommitmentNote |
Returns
Promise<bigint>
computeNullifier()
function computeNullifier(note): Promise<bigint>;Escrow nullifier (withdraw + settle): Poseidon(0, ownerSecret, salt).
Parameters
| Parameter | Type |
|---|---|
note | CommitmentNote |
Returns
Promise<bigint>
computeNonceNullifier()
function computeNonceNullifier(ownerSecret, nonce): Promise<bigint>;Nonce nullifier (settle replay protection): Poseidon(1, ownerSecret, nonce).
Parameters
| Parameter | Type |
|---|---|
ownerSecret | bigint |
nonce | bigint |
Returns
Promise<bigint>
computeClaimNullifier()
function computeClaimNullifier(
secret,
leafIndex,
claimsRoot): Promise<bigint>;Claim nullifier: Poseidon(2, secret, leafIndex, claimsRoot).
claimsRoot is bound into the preimage so the same (secret, leafIndex)
in two different settled claims groups yields two DISTINCT nullifiers —
the on-chain claimNullifiers set is global, and a maker derives every
recipient’s claim secret, so without this binding a maker could settle a
throwaway group with a colliding leaf and permanently brick the honest
recipient’s claim. Must stay byte-identical to claim_template.circom’s
nullifier derivation.
Parameters
| Parameter | Type |
|---|---|
secret | bigint |
leafIndex | bigint |
claimsRoot | bigint |
Returns
Promise<bigint>
computeTokenHash()
function computeTokenHash(token): Promise<bigint>;Token hash for circuit public inputs: Poseidon(token).
Parameters
| Parameter | Type |
|---|---|
token | string |
Returns
Promise<bigint>
toBytes32Hex()
function toBytes32Hex(value): string;Format a bigint as a 0x-prefixed bytes32 hex string.
Parameters
| Parameter | Type |
|---|---|
value | bigint |
Returns
string
pickTier()
function pickTier(recipientCount): CircuitTier;Pick the smallest tier that fits recipientCount. Returns the
matching CircuitTier or throws when no tier covers the
request — capping the upper bound is intentional, the on-chain
cap mirrors it.
Callers should pad the actual claims array up to tier.cap with
dummy entries (see padClaims) to keep per-tier batches
visually identical and protect the per-tier anonymity set.
This is the theoretical picker — it considers every tier the protocol defines, including ones whose verifier is not yet deployed. Production callers want pickActiveTier, which filters to ACTIVE_TIERS and falls back to the largest active tier with multi-batch when no active tier covers the request.
Parameters
| Parameter | Type |
|---|---|
recipientCount | number |
Returns
pickActiveTier()
function pickActiveTier(recipientCount): CircuitTier;Pick the smallest active tier that fits recipientCount —
i.e. one whose verifier is wired on-chain today (see
ACTIVE_TIERS). When no active tier covers the request, the
largest active tier is returned so the caller can chunk the
recipients into multiple batches of that tier; this is the
multi-batch fallback the Pay app uses while tier 64 / 128 are not
yet live.
Throws when ACTIVE_TIERS is empty (a misconfigured deployment).
Use this in production paths that actually generate proofs and submit on-chain; reserve pickTier for design-level reasoning that should ignore deployment status.
Parameters
| Parameter | Type |
|---|---|
recipientCount | number |
Returns
padClaims()
function padClaims<T>(
claims,
tier,
dummy): T[];Pad a claims array up to tier.cap by appending the same dummy
value in every empty slot. The returned array is a new outer
array (the input is not mutated), but each padded slot shares
one reference to dummy — pass a frozen / immutable sentinel
when T is an object type, or callers will see ghost mutations
across slots. Throws when the input already exceeds the tier
capacity (the caller picked the wrong tier for this list).
Padding is mandatory for anonymity: every tier-N settlement looks like every other tier-N settlement at the on-chain layer because the claims tree always carries N leaves. Tight-packing leaks the recipient count via the calldata size and proof timing.
Type Parameters
| Type Parameter |
|---|
T |
Parameters
| Parameter | Type |
|---|---|
claims | readonly T[] |
tier | CircuitTier |
dummy | T |
Returns
T[]
claimSeedFromKey()
function claimSeedFromKey(privateKey): bigint;Domain-separated derivation of a per-payout claim-secret seed from the
wallet’s deterministic EdDSA private key bytes (keccak256 of the
fixed-message ECDSA signature). Because the key is re-derivable from
the wallet alone, so is the seed — there is no random seed to lose,
and a recovery run can regenerate every claim secret just by
re-signing. The domain tag keeps this value independent of the EdDSA
key’s own uses. Reduced into the BN254 scalar field for use as a
Poseidon input.
Note: the seed is per-wallet (not per-payout); per-claim uniqueness
comes from the leaf fields in deriveClaimSecret. Two byte-identical
payouts (same recipients, amounts, releaseTime, order) would collide,
but that surfaces as a ClaimsGroupAlreadyExists revert on the second
settle — self-detecting, never silent loss.
Parameters
| Parameter | Type |
|---|---|
privateKey | Uint8Array |
Returns
bigint
warmupEddsa()
function warmupEddsa(): Promise<void>;Eagerly initialise the EdDSA + BabyJub tables. Worker preload
hooks should call this on startup so the first signing job
doesn’t pay the build cost on the user’s hot path.
Returns
Promise<void>
deriveEdDSAKey()
function deriveEdDSAKey(signerOrSignature, opts?): Promise<{
keyPair: EdDSAKeyPair;
signature: string;
}>;Derive the EdDSA keypair from a wallet signature.
Pass either an ethers.Signer (we’ll prompt for signMessage)
or a hex-encoded signature you already have. Returning the
signature lets callers cache it for later flows that need the
same material (e.g. AES-GCM key-wrapping for vault backup).
When a string signature is passed, opts.message has no effect
(the signature was produced against whatever message the caller
used). Passing both is treated as a programmer error — throws
rather than silently ignoring the override.
Parameters
| Parameter | Type |
|---|---|
signerOrSignature | string | Signer |
opts | DeriveOpts |
Returns
Promise<{
keyPair: EdDSAKeyPair;
signature: string;
}>
signEdDSA()
function signEdDSA(privateKey, message): Promise<EdDSASignature>;Sign a message (a field element — usually a Poseidon order hash) with EdDSA.
Parameters
| Parameter | Type |
|---|---|
privateKey | Uint8Array |
message | bigint |
Returns
Promise<EdDSASignature>
serializeKeyPair()
function serializeKeyPair(kp): string;Serialize an EdDSA keypair to a JSON string for storage. The private key is hex-encoded; pubkey field elements are decimal strings (BigInt-safe).
Parameters
| Parameter | Type |
|---|---|
kp | EdDSAKeyPair |
Returns
string
deserializeKeyPair()
function deserializeKeyPair(json): EdDSAKeyPair;Inverse of serializeKeyPair. Throws a single
"deserializeKeyPair: malformed JSON" for any structural issue
— invalid JSON, wrong shape, non-string fields, BigInt parse
failure — so callers don’t have to discriminate between the
underlying parse / coercion errors.
Parameters
| Parameter | Type |
|---|---|
json | string |
Returns
buildMerkleTree()
function buildMerkleTree(leaves, depth): Promise<BuiltTree>;Build a Poseidon Merkle tree of the given depth from leaves.
The leaf array is padded with the leaf-level zero (0n) up to
2^depth; internal zero values are derived by hashing the level
below.
Awaits the Poseidon module once at the top, then hashes
synchronously inside the loop. Awaiting per hash would cost a
microtask per node — at COMMIT_TREE_DEPTH = 20 (~1 M hashes)
that becomes the dominant cost.
Apps that maintain an incremental tree should pre-compute
MerkleProofs and pass them through AuthorizeProofInput .merkleProof instead of asking the prover to rebuild from
scratch each call.
Parameters
| Parameter | Type |
|---|---|
leaves | bigint[] |
depth | number |
Returns
Promise<BuiltTree>
getMerkleProof()
function getMerkleProof(layers, leafIndex): MerklePathProof;Walk a BuiltTree from leaf to root, collecting siblings. The
returned pathIndices[i] is 1 when the original leaf is on
the right side of its sibling at level i, 0 otherwise —
matching the convention every Circom Merkle template uses.
Parameters
| Parameter | Type |
|---|---|
layers | bigint[][] |
leafIndex | number |
Returns
createMockProver()
function createMockProver(opts?): Prover;A Prover that returns deterministic dummy proofs after a small
delay. Useful for:
- exercising UI flows without shipping circuit assets
- integration tests that don’t want to set up snarkjs
- storybooks / visual regression
The returned proof is structurally valid (BigInts in the right shape) but cryptographically nonsense. Verifiers will reject it.
Parameters
| Parameter | Type |
|---|---|
opts | MockProverOpts |
Returns
formatGroth16Proof()
function formatGroth16Proof(raw): Groth16Proof;Convert snarkjs’s raw proof shape to the SDK’s Groth16Proof
tuple (which mirrors what every Solidity verifier expects).
The G2 element’s limb order is reversed (pi_b[i][1] first, then
pi_b[i][0]) — that’s the pairing-curve convention every
circomlibjs-generated verifier was scaffolded against. Centralizing
the swap here means each per-circuit prover (deposit / authorize /
claim) doesn’t get a chance to reverse the limbs differently and
break verification in a way that’s painful to debug.
Parameters
| Parameter | Type |
|---|---|
raw | SnarkjsRawProof |
Returns
setProveReporter()
function setProveReporter(fn): void;Parameters
| Parameter | Type |
|---|---|
fn | ProveReporter |
Returns
void
timeProve()
function timeProve<T>(circuit, run): Promise<T>;Type Parameters
| Type Parameter |
|---|
T |
Parameters
| Parameter | Type |
|---|---|
circuit | ZkCircuit |
run | () => Promise<T> |
Returns
Promise<T>
wrapProverWithTimer()
function wrapProverWithTimer(circuit, inner): Prover;Wrap a Prover so each prove() call is bracketed by
timeProve(circuit, ...). Telemetry runs on the main thread —
workers can’t dispatch the zk-perf:prove window event, and the
postMessage round-trip is in the noise on a 1–9 s proof.
Parameters
| Parameter | Type |
|---|---|
circuit | ZkCircuit |
inner | Prover |
Returns
wipeBytes()
function wipeBytes(buf): void;Best-effort overwrite of secret material in a Uint8Array.
Useful for keypair / signature buffers that we want to keep out of GC scan paths once we’re done with them. Does not protect against:
- copies the runtime made for V8 small-string interning
- JIT optimization passes that re-materialize the buffer
- same-process attackers with arbitrary read
It’s worth the call (cheap, makes core dumps less interesting) but should not be treated as a strong erasure primitive.
Parameters
| Parameter | Type |
|---|---|
buf | Uint8Array |
Returns
void
createWebWorkerProver()
function createWebWorkerProver(opts): Prover;Build a Prover backed by a Web Worker.
Concurrency: jobs are processed strictly in order. Calling
prove() while another job is in flight enqueues the new
request — no parallel execution (proving is CPU-bound; parallel
jobs would just contend) and no thundering-herd wake-up.
Cancellation: an aborted signal rejects immediately whether
the job is queued or running. A running job’s worker is
terminated (cheapest way to stop snarkjs mid-flight) and a
fresh one spawns on the next call.
Worker errors: a runtime error from the worker tears down the
instance — the worker is often in a bad state after an error
and reusing it produces unpredictable failures. The next call
spawns a fresh worker (or hits fallbackProve).
Parameters
| Parameter | Type |
|---|---|
opts | WebWorkerProverOpts |
Returns
createLazyWorkerProver()
function createLazyWorkerProver(opts): Prover;Lazy-singleton authorize/claim/deposit/etc prover, wrapped with
the standard timing reporter. Apps used to hand-roll this five-line
pattern per circuit (apps/pro/app/lib/{authorize,cancel,claim, deposit}Prover.ts); this single helper supersedes them. The
worker URL stays at the call site because Webpack/Turbopack
resolves import.meta.url only when seen as a literal there.
Parameters
| Parameter | Type |
|---|---|
opts | LazyWorkerProverOpts |
Returns
setupProverWorker()
function setupProverWorker(handlers): {
postProgress: void;
};Wire a Web Worker script to the SDK’s prover protocol.
Usage from a circuit-specific worker file (consumer-owned):
// apps/<name>/workers/deposit.worker.ts
import {
setupProverWorker,
warmupPoseidon,
generateDepositProof,
} from "@zkscatter/sdk/zk";
setupProverWorker({
preload: () => warmupPoseidon(),
prove: async (req) => {
const { proof, publicSignals } = await generateDepositProof(
req.input as never,
{ wasm: "/zk/deposit.wasm", zkey: "/zk/deposit.zkey" },
);
return { proof, publicSignals };
},
});The runtime handles message decoding, request validation, and
error wrapping. Per-job progress messages can be sent with the
returned postProgress helper.
Parameters
| Parameter | Type |
|---|---|
handlers | ProverWorkerHandlers |
Returns
postProgress()
postProgress(jobId, message): void;Send a progress message to the main thread for a job in flight. No-op outside a Web Worker context.
Parameters
| Parameter | Type |
|---|---|
jobId | number |
message | string |
Returns
void
prefetchAssets()
function prefetchAssets(urls): Promise<void>;Best-effort prefetch — never throws so callers can
void prefetchAssets(...) from a fire-and-forget context.
Parameters
| Parameter | Type |
|---|---|
urls | readonly string[] |
Returns
Promise<void>
withCachedAssets()
function withCachedAssets<T>(paths, run): Promise<T>;Resolve wasm + zkey to Blob URLs (or fall back to canonical), hand
them to run, and revoke when run settles. resolveAsset always
fulfils (errors fall back to the canonical URL with a no-op
revoke), so a plain Promise.all is equivalent to and clearer than
Promise.allSettled here.
Type Parameters
| Type Parameter |
|---|
T |
Parameters
| Parameter | Type |
|---|---|
paths | { wasm: string; zkey: string; } |
paths.wasm | string |
paths.zkey | string |
run | (urls) => Promise<T> |
Returns
Promise<T>
warmProverAssets()
function warmProverAssets(paths): Promise<void>;Worker preload one-liner: resolves snarkjs + warms Poseidon +
prefetches the circuit’s wasm/zkey in parallel. Replaces the four
near-identical preload bodies that lived in *-worker.ts.
Parameters
| Parameter | Type |
|---|---|
paths | { wasm: string; zkey: string; } |
paths.wasm | string |
paths.zkey | string |
Returns
Promise<void>