> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.umbraprivacy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stealth Pool Overview

> Stealth Pool overview (V18): 4 note-creation variants (ATA/ETA × self/receiver-burnable), 3 shipped burn variants (receiver→ETA, self→ETA, self→ATA). Self-burnable vs receiver-burnable flows.

## 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.

<Note>
  The pool requires [registration](/sdk/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>

## 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.

The sender controls who receives. The unlocker controls when and how the tokens exit. Separating these two roles is what makes deep mixing possible — the sender's involvement ends at note-write time.

## Note variants

<CardGroup cols={2}>
  <Card title="Self-burnable" icon="user">
    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.**
  </Card>

  <Card title="Receiver-burnable" icon="users">
    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`.
  </Card>
</CardGroup>

Receiver-burnable notes produce stronger anonymity sets. Because the sender's actions and the exit are fully decoupled, timing correlation between write and burn becomes significantly harder.

## 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.

This gives four creator factories — all under `@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.

See [Writing notes](/sdk/mixer/creating-utxos) for full API details and the recipient pre-check rubric.

## Burn variants (V18)

Three burn factories are shipped, under `@umbra-privacy/sdk/burn`:

* `getReceiverBurnableStealthPoolNoteIntoETABurnerFunction` — receiver-burnable → ETA. Natively batches: groups notes by `destinationAddress`, 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`.

(Receiver-burnable → ATA is supported on-chain but is not yet shipped in the SDK — open issue.)

## The Stealth Pool flow

<Steps>
  <Step title="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](/sdk/mixer/creating-utxos).
  </Step>

  <Step title="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.
  </Step>

  <Step title="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](/sdk/mixer/fetching-utxos).
  </Step>

  <Step title="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](/sdk/mixer/claiming-utxos).
  </Step>
</Steps>

## Infrastructure requirements

* **Indexer** — required for note discovery and Merkle proof fetching. Pass `indexerApiEndpoint` to `getUmbraClient`.
* **Relayer** — required for burning. Pass `relayerApiEndpoint` to `getUmbraClient`, then build the burner factory's `relayer` dep from `getUmbraRelayer({ apiEndpoint })`.
* **Store adapters** — strongly recommended on the browser. Without `utxoDataStore` / `nullifierStore`, every `scan()` re-scans every active tree from genesis.

```typescript theme={null}
import { getUmbraClient } from "@umbra-privacy/sdk";
import {
  createBrowserStorageBackend,
  createShardedUtxoDataStore,
  createShardedNullifierStore,
} from "@umbra-privacy/sdk/store-adapters";

const storageBackend = createBrowserStorageBackend({ dbName: "umbra" });
const utxoDataStore  = createShardedUtxoDataStore({ storageBackend });
const nullifierStore = createShardedNullifierStore({ storageBackend });

const client = await getUmbraClient({
  signer,
  network: "mainnet",
  rpcUrl: "...",
  rpcSubscriptionsUrl: "...",
  indexerApiEndpoint: "https://utxo-indexer.api.umbraprivacy.com",
  relayerApiEndpoint: "https://relayer.api.umbraprivacy.com",
  utxoDataStore,
  nullifierStore,
});
```

## Privacy Considerations

**Burn into an ETA by default.** When a note is burnt into an ETA, the burned amount is hidden, the destination is not revealed, and no observable exit event is produced on-chain. At burn time, the sender is completely unlinkable — there is no visible amount, no destination address, and no signal that can tie the burn back to any specific note write. ATA burns should be the opt-out, not the default.

**Use round, pool-common amounts when burning publicly.** If a burn must exit to an ATA, the amount becomes visible on-chain. Unusual or fragmented amounts let an observer eliminate most of the tree and narrow in on the matching commitment. Standardise on amounts that are common in the pool (e.g. exactly 100 USDC) to stay indistinguishable within the largest possible set.

**Burn to a fresh address when going public.** Burning into an address with existing on-chain history re-establishes a link. Use an address that has never appeared on-chain before.

For a complete breakdown of all source × destination combinations — visible vs hidden state, timing risk, recommended use cases — see [Privacy Analysis](/sdk/mixer/privacy-analysis).
