The Most Powerful Shielded Pool on Solana
Umbra’s Stealth Pool is the most powerful shielded pool on Solana today — and it is built to fit into any flow. Tokens can enter from a public ATA or an EncryptedTokenAccount, exit to a fresh address or stay private inside another ETA, and be unlocked by you or by any registered recipient. Every combination is a first-class path.The pool requires registration with
anonymous: true on the recipient side for receiver-burnable notes. Self-burnable notes require no recipient registration — the sender is the unlocker.Note structure
The pool operates on Stealth Pool Notes — commitments inserted into an on-chain Indexed Merkle Tree that represent locked tokens. Every note encodes three distinct roles:- Sender — the address that funded the note, locked the tokens into the pool, and fixed the recipient at creation time.
- Unlocker — the address authorised to burn the note’s nullifier on-chain and release the tokens, choosing whether they exit to an ATA or an ETA.
- Recipient — the final destination address, set by the sender and not modifiable by the unlocker.
Note variants
Self-burnable
You are both the sender and the unlocker. Burn the note on your own schedule and choose whether tokens exit to an ATA or stay in an ETA. Recipient needs zero on-chain state.
Receiver-burnable
The recipient is the unlocker. They burn the note themselves and decide the exit. Recipient must be fully registered (all three sub-step flags) — the unlocker is encrypted against their
userCommitment.Source options
Each variant can be funded from two sources:- From an ATA — tokens are transferred directly from your public ATA. Single tx, no MPC.
- From an ETA — tokens are drawn from your existing EncryptedTokenAccount before entering the pool. Two-tx pipeline (proof account → note write), MPC under the hood.
@umbra-privacy/sdk/deposit:
getATAIntoSelfBurnableStealthPoolNoteCreatorFunction— ATA source, you unlock.getATAIntoReceiverBurnableStealthPoolNoteCreatorFunction— ATA source, recipient unlocks.getETAIntoSelfBurnableStealthPoolNoteCreatorFunction— ETA source (MPC), you unlock.getETAIntoReceiverBurnableStealthPoolNoteCreatorFunction— ETA source (MPC), recipient unlocks.
Burn variants (V18)
Three burn factories are shipped, under@umbra-privacy/sdk/burn:
getReceiverBurnableStealthPoolNoteIntoETABurnerFunction— receiver-burnable → ETA. Natively batches: groups notes bydestinationAddress, chunks ≤5 per proof.getSelfBurnableStealthPoolNoteIntoETABurnerFunction— self-burnable → ETA.MAX_NOTES_PER_PROOF = 1; SDK loops internally.getSelfBurnableStealthPoolNoteIntoATABurnerFunction— self-burnable → ATA.MAX_NOTES_PER_PROOF = 1.
The Stealth Pool flow
Write a note
Choose a creator factory, supply the recipient pre-check / fallback (for receiver-burnable creates), and submit. The SDK locks tokens in the pool, inserts a commitment into the Merkle tree, and publishes the X25519-AES ciphertext on-chain so the unlocker can discover it.See Writing notes.
Wait for the anonymity set to grow
The more other users write notes into the same tree, the stronger your privacy guarantee. There is no enforced waiting period — this is a trade-off you manage in your application.
Scan for burnable notes
Run
getBurnableStealthPoolNoteScannerFunction({ client })(). The zero-arg scanner walks every active tree, decrypts every ciphertext addressable by your viewing keys, persists progress in client.utxoDataStore, and returns the notes grouped by (kind, source).See Scanning.Burn
Pass the notes to the matching burner factory. The burner fetches a per-batch Merkle proof, generates a Groth16 ZK proof, submits to the relayer, and polls until the on-chain burn lands. The nullifier is burned to prevent double-spending; tokens are released to the chosen destination.See Burning.
Infrastructure requirements
- Indexer — required for note discovery and Merkle proof fetching. Pass
indexerApiEndpointtogetUmbraClient. - Relayer — required for burning. Pass
relayerApiEndpointtogetUmbraClient, then build the burner factory’srelayerdep fromgetUmbraRelayer({ apiEndpoint }). - Store adapters — strongly recommended on the browser. Without
utxoDataStore/nullifierStore, everyscan()re-scans every active tree from genesis.