Skip to main content

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.
Always pre-check the recipient before a receiver-burnable create:

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 (createProofAccountcreateUtxo), 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 a CreateUtxoArgs object as the first argument:
Address
required
The wallet address that can unlock this note. For self-burnable, use client.signer.address. For receiver-burnable, use the recipient’s address.
Address
required
SPL or Token-2022 mint address. Must be a supported mint.
bigint
required
Amount in native token units. Protocol fees and Token-2022 transfer fees are subtracted from this amount before the commitment is written.
U256
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.
OptionalData32
32 bytes of opaque metadata stored alongside the note. Must be pre-hashed or pre-encrypted — never store plaintext identifiers.
Commitment
default:"\"confirmed\""
Commitment level for RPC account reads during note construction.
Commitment
default:"\"confirmed\""
(ATA-source only) Commitment level for epoch-info fetches (Token-2022 transfer-fee schedule).
ETAIntoStealthPoolNoteCreatorHooks | ATAIntoStealthPoolNoteCreatorHooks
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).
ZkProver
required
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.
The zkProver dependency is required and cannot be omitted. There is no built-in default. Use @umbra-privacy/sdk/zk-prover for the CDN-backed implementation.

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. Use isCreateUtxoError from @umbra-privacy/sdk/errors:
On transaction-send, do not immediately retry. Run the scanner first — the commitment may already be in the tree. If retrying an ETA-source create, you must pass the same generationIndex via options.generationIndex so the SDK’s closeProofAccount step can reclaim the orphan from your prior attempt (see Recovery).
See Error Handling for a full reference.

Concurrency rule

Never run note creates concurrently from the same client. The SDK auto-derives generationIndex 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 from amount before the commitment is written. The net amount committed (= what the unlocker receives at burn time) is amount − fees. See Pricing for current rates.