Overview
Writing a Stealth Pool Note inserts a Poseidon commitment into the on-chain Indexed Merkle Tree and locks the corresponding tokens in the pool. The SDK also publishes an X25519 + AES-GCM ciphertext on-chain so the unlocker can later discover the note with their viewing keys. Choose the factory function that matches your source (ATA or ETA) and unlocker (self-burnable or receiver-burnable). All four factories live under @umbra-privacy/sdk/deposit.
Registration prerequisites differ by variant
This is the most common integration footgun.- Self-burnable creates encrypt the unlocker against the sender’s master-seed-derived key. Sender needs
isUserAccountX25519KeyRegistered. Recipient needs nothing. - Receiver-burnable creates encrypt against the recipient’s
userCommitment. The recipient must have completed all three registration sub-step flags (isInitialised,isUserAccountX25519KeyRegistered,isUserCommitmentRegistered) on-chain.
Factory Functions
Self-burnable from ATA
Single-tx, no MPC. Funds locked directly from your ATA. You unlock.Receiver-burnable from ATA
Single-tx, no MPC. Funds locked from your ATA. Recipient must be fully registered.Self-burnable from ETA
Two-tx pipeline (createProofAccount → createUtxo), MPC. Funds drawn from your ETA. You unlock.
Receiver-burnable from ETA
Two-tx pipeline, MPC. Funds drawn from your ETA. Recipient must be fully registered.Parameters
All four creator factories accept aCreateUtxoArgs object as the first argument:
The wallet address that can unlock this note. For self-burnable, use
client.signer.address. For receiver-burnable, use the recipient’s address.SPL or Token-2022 mint address. Must be a supported mint.
Amount in native token units. Protocol fees and Token-2022 transfer fees are subtracted from this amount before the commitment is written.
Optional deterministic generation index. Defaults to a random
U256 (CSPRNG). Pass the same generationIndex on retry to allow the V18 pipeline’s closeProofAccount step to reclaim a proof-account orphan from a prior failed attempt.32 bytes of opaque metadata stored alongside the note. Must be pre-hashed or pre-encrypted — never store plaintext identifiers.
Commitment level for RPC account reads during note construction.
(ATA-source only) Commitment level for epoch-info fetches (Token-2022 transfer-fee schedule).
Per-phase + per-step lifecycle hooks. Per-step slots use
{ onPreSend, onPostSend, onSkipped }. Step slot names: closeProofAccount (fires only when a stale proof account from a prior failed attempt is reclaimed), populateProofAccount, createStealthPoolNote (ATA-source pipeline only; ETA-source uses queueComputation instead).Circuit-specific Groth16 prover. Required for every variant — there is no built-in default. Provers ship at
@umbra-privacy/sdk/zk-prover.V4 fields
priorityFees, purpose, awaitCallback, skipPreflight, maxRetries do not exist on V5 note creators. The V4 callbacks: { createProofAccount, createUtxo, closeProofAccount } shape has been replaced by options.hooks (ATA-source) or deps.hooks (ETA-source) with onPreSend/onPostSend/onSkipped per-step events.Return Value
The return type depends on the source:- From ATA (ZK-only):
Promise<TransactionSignature[]>of length 1 —[noteWriteSig]. - From ETA (MPC pipeline):
Promise<TransactionSignature[]>of length 2 —[proofAccountSig, noteWriteSig].
The ZK prover dependency
Note creation requires a ZK prover function (zkProver in deps). This function generates a Groth16 proof that the commitment was constructed correctly.
The prover is CPU-intensive — generating a proof takes 2–8 seconds in the browser and 1–3 seconds in Node.js. For browser applications, run the prover in a Web Worker to avoid blocking the main thread. See ZK Provers for the canonical comlink pattern.
Anonymous payment fallback (receiver-burnable → self-burnable)
For one-shot payments to a recipient who may not be registered, prefer the self-burnable ATA variant + share a recovery secret out of band (or use the relayer’s escrow flow). This avoids the “recipient must register first” UX trap:Error Handling
Create-side failures have two distinguishing modes — ZK proof generation and stale on-chain state. UseisCreateUtxoError from @umbra-privacy/sdk/errors:
Concurrency rule
Never run note creates concurrently from the same client. The SDK auto-derivesgenerationIndex from on-chain account state during each call; two parallel Promise.all-style creates read the same generationIndex before either has incremented it, derive identical ephemeral keys, and collide silently (fund loss / scan failure). Serialise creates per (signer, network).
Protocol Fees
Fees are deducted fromamount before the commitment is written. The net amount committed (= what the unlocker receives at burn time) is amount − fees. See Pricing for current rates.