Skip to main content

Overview

After a Stealth Pool Note is written, the SDK publishes an X25519-AES ciphertext on-chain addressed either to the sender (self-burnable) or to the recipient (receiver-burnable). To find your burnable notes, the scanner:
  1. Discovers every active stealth-pool tree.
  2. Loads scan progress from client.utxoDataStore and computes the unscanned ranges.
  3. Fetches the encrypted note data for those ranges from the indexer.
  4. Tries to decrypt each ciphertext with your X25519 + viewing keys.
  5. Persists progress.
  6. Returns the successful decryptions, grouped by (kind, source).
The V18 scanner is zero-arg — the cursor is fully SDK-managed.
The scanner requires the indexer. Ensure indexerApiEndpoint is set when constructing the client. The browser scanner is several orders of magnitude faster when utxoDataStore and nullifierStore are wired — without them, every call re-scans every active tree from genesis.

Usage

Return Value

The four note buckets are exhaustive — every burnable note maps onto exactly one. Within each bucket, the notes are not Merkle-proof-bundled: proofs are fetched per batch at burn time so a single proof set is shared across an entire burn batch (much cheaper than fetching one proof per note).

Example: Run the scanner

Idempotent — call as often as you like

The scanner is idempotent: subsequent calls only fetch and decrypt new leaves since the last call. The cursor lives in client.utxoDataStore.getScanProgress(treeIndex) / client.utxoDataStore.addScannedRange(treeIndex, start, end). Re-mounting the scanner factory does not reset state. A typical browser app calls the scanner on every page load and on a 30s timer while the page is open.

How decryption works

The SDK derives your X25519 private key from the master seed, then for each note ciphertext:
  1. Extracts the writer’s ephemeral X25519 public key from the ciphertext header.
  2. Computes an X25519 ECDH shared secret.
  3. Derives an AES-GCM key.
  4. Attempts to decrypt the payload.
  5. If decryption succeeds, reads the 12-byte domain separator and routes the note into the matching (kind, source) bucket.
Your private key never leaves your device.

Custom indexer fetchers

If you are running a private indexer mirror, override the data fetchers via deps:
(The burner factory’s per-batch fetchBatchMerkleProof is a separate dep on the burner — see Burning.)

Error Handling

An empty result (all buckets empty) is not an error — it means no notes addressable by your viewing keys exist in the trees the scanner walked. Errors are reserved for infrastructure failures.

Burn the result

Pass the scanned buckets directly to the matching burner factory — the burner fetches per-batch proofs internally:
See Burning for full burner factory setup, batching behaviour, and dropped-callback recovery.