Skip to main content
V5 is a vocabulary + ergonomics overhaul. The on-chain protocol and wire formats are unchanged — relayer paths are still /v1/claims, the on-chain instruction is still burn_* under the hood, and a handful of TypeScript aliases bridge the two namespaces so most callers can rename without rewriting logic. This page walks you through every breaking change. Work top-to-bottom; nothing later depends on something earlier you skipped.
Current release: @umbra-privacy/sdk@5.0.0-rc.6 (pins @umbra-privacy/umbra-codama@3.0.0-rc.6). Nothing extra to wire up — the earlier rc.3/rc.4 workarounds are gone: the codama PDA bug (ConstraintSeeds 2006 on deposit) and the columnar scan() BigInt bug are both fixed in rc.6. No package-manager override, no postinstall dist patch.

TL;DR

  1. Bump the package: @umbra-privacy/sdk@4.x → @umbra-privacy/sdk@5.0.0-rc.6. Pin an exact version (codama is pinned by the SDK — no override needed).
  2. Delete @umbra-privacy/web-zk-prover from package.json. The prover ships inside the SDK at @umbra-privacy/sdk/zk-prover.
  3. Move every operation factory import from the main barrel to its named subpath (/registration, /deposit, /withdrawal, /burn, /query, /conversion, /compliance, /account).
  4. Rename factories per the table below (UTXO → Stealth Pool Note, claim → burn, PublicBalance → ATA, encrypted balance → ETA).
  5. Rewrite the scanner call: zero-arg in V5, results bucketed by (kind, source) under long-form keys.
  6. Burner factories take a new relayer dep — { submitBurn, pollBurnStatus, getRelayerAddress } — these are TypeScript aliases of the relayer client’s existing submitClaim / pollClaimStatus / getRelayerAddress, so plug in the same client unchanged.
  7. (Browser) Wire utxoDataStore + nullifierStore from @umbra-privacy/sdk/store-adapters. Without them every scan() walks every active tree from genesis.
  8. (Mainnet) Mint list adds CASH. (Devnet) Mint list drops to wSOL only.
The rest of this page expands each step with before/after code.

1. Package consolidation

V4
V5
Why pin? ZK assets are versioned in lockstep with the package. A floating version (^5.0.0) can resolve a manifest that points at a different circuit hash than the one your wallet’s master seed was set up for. Prover naming: The burner prover factories (getClaim…) retained their V4 spellings. The creator prover factories were renamed — getATAIntoStealthPoolNoteCreatorProver (ATA-source) and getETAIntoStealthPoolNoteCreatorProver (ETA-source). If you also imported the package’s CJS build paths or had a webpack alias for web-zk-prover, remove them. If your scaffold has a Next.js transpilePackages block, remove the web-zk-prover entry:

2. Subpath imports

The V5 main barrel re-exports only:
  • getUmbraClient, the four signer factories, the error hierarchy, Result<T,E> helpers.
  • Everything from infrastructure/{arcium, indexer, relayer, solana, zk-prover} — including getUmbraRelayer, getUmbraIndexer, RPC providers, transaction forwarders.
Every operation factory now lives behind a named subpath. There is no @umbra-privacy/sdk/interfaces re-export in V5. Function-type aliases (SelfBurnableStealthPoolNoteFromEncryptedTokenAccountCreatorFunction, etc.) ship alongside their factories — import them from the same subpath.

3. Factory renames

Names lengthened to spell out the data flow end-to-end. The vocabulary swap:
  • UTXOStealth Pool Note (in factory names + result types; on-chain commitments and the indexer route are still called UTXOs).
  • ClaimBurn (in factory names + lifecycle hooks; the wire endpoint is still /v1/claims).
  • PublicBalanceAssociatedTokenAccount (ATA) in factory names.
  • EncryptedBalanceEncryptedTokenAccount (ETA) in factory names.

Deposits

Withdrawal

Scan + burn

The getReceiverClaimableUtxoToPublicBalanceClaimerFunction variant from V4 is not yet shipped in V5 — the on-chain instruction exists, but the SDK factory has not been written. Use receiver-into-ETA, then a regular withdrawal, until it lands.

Conversion + key rotation

Account recovery + maintenance

Compliance


4. Scanner API rewrite

V4’s scanner took three positional args (treeIndex, start, end?) and returned proof-bundled UTXO data under four short-form keys. V5’s scanner is zero-arg: it walks every active tree automatically, with the cursor persisted in client.utxoDataStore. The scanner no longer bundles Merkle proofs. Proofs are fetched per batch at burn time so a single proof set is shared across an entire batch — far cheaper than fetching one proof per note. The burner factory handles the per-batch fetch internally; you do not need to call enrichWithMerkleProof yourself unless you are building a custom burn pipeline. V4
V5
Bucket key rename map: The result object also gains scannedTrees: readonly ScannedTreeProgress[] so you can display per-tree progress in your UI.

5. Burner factory relayer dep

V5’s burner factories accept a new relayer dep — a three-property adapter built from the relayer client. The property names use the V5 vocabulary but are TypeScript aliases of the claim equivalents, so the relayer client’s existing methods plug in directly. V4
V5
Two new required deps to be aware of:
  • fetchBatchMerkleProof — pass client.fetchBatchMerkleProof (auto-wired when indexerApiEndpoint is set on the client). Replaces V4’s pre-fetched proof bundle.
  • relayer: { submitBurn, pollBurnStatus, getRelayerAddress } — explicit three-property adapter. V4 accepted the full relayer client; V5 wants only these three methods.

Burn result shape

V4 claimers returned { signatures: Record<number, TransactionSignature[]> }. V5 burners return BurnStealthPoolNoteIntoETAResult (or BurnStealthPoolNoteIntoATAResult for the ATA variant):
completed / callback_received indicate success. The signature to surface to the user is callbackSignature ?? txSignature. A failureReason containing NullifierAlreadyBurnt is idempotent success — the burner factory handles this internally.

Burner function signature

V5 burners take three positional args (one more than V4):

Batching is unchanged conceptually

  • Receiver-burnable → ETA batches natively: groups notes by destinationAddress, chunks to ≤5 per proof. Pass the whole array.
  • Self-burnable → ETA / ATA has MAX_NOTES_PER_PROOF = 1. The SDK loops internally — caller still passes an array.

6. Store adapters (browser)

V4 had no opinionated cursor / nullifier store — every scanner call walked every tree from genesis. V5 ships createBrowserStorageBackend, createShardedUtxoDataStore, and createShardedNullifierStore for browser persistence; and createFileStorageBackend for Node.js file-backed persistence. On the browser, wire these. Without them every scan() re-scans every active tree from genesis — fine on devnet, crippling on mainnet.
Key the IndexedDB database per-wallet (e.g. dbName: \umbra-$“). A wallet swap mid-session must not load the previous wallet’s stores. On Node.js or short-lived scripts, you can omit the stores — the scanner falls back to in-memory state.

7. Mint list changes

  • Mainnet gained CASH (CASHx9KJUStyftLFWGvEVf59SGeG9sh5FfcnZMVPCASH). Mainnet list (5): wSOL, USDC, USDT, UMBRA, CASH.
  • Devnet dropped to wSOL only.
  • Localnet stayed wSOL only.
If your code hard-codes a mint allowlist, update it. The authoritative source at runtime is relayer.getSupportedMints() — call it once at app boot and cache for the session.

8. UMBRA_MESSAGE_TO_SIGN moved

The deterministic consent message used for master-seed derivation now ships under the /shared subpath:
The bytes are unchanged — your existing users’ master seeds re-derive identically. Only the import path changed.

9. Registration hooks rewrite

V5 registration replaces V4’s callbacks: { pre, post } shape with hooks: RegistrationHooks — named per-step slots with { onPreSend, onPostSend, onSkipped } plus top-level phase hooks. The on-chain instructions also changed: V4 emitted up to three transactions (account init, X25519 key registration, user commitment registration). V5 has the same three logical steps, but the underlying instructions now init_if_needed the user account, so the init step is usually folded into the X25519 step when account creation and key registration happen together. V5 registration hooks expose three skippable slots — each fires onSkipped when on-chain state already satisfies it:
  • initUserAccount — base EncryptedUserAccount PDA.
  • registerX25519PublicKey — X25519 token-encryption key.
  • registerAnonymousUsage — user-commitment registration via MPC + ZK.
V4 → V5 slot rename map: Event-shape changes:
  • pre(tx)onPreSend({ signedTransaction }).
  • post(tx, sig)onPostSend({ signature }).
  • New: onSkipped({ reason }) fires when a step is a no-op because on-chain state already satisfies it.
  • New: anonymous-usage step is an MPC step (SkippableMpcTransactionStepHooks) and additionally exposes onMonitorStarted, onProgress, onFinalized, onRentReclaimSubmitted, onRentReclaimError (see MpcTransactionStepHooks).

10. Options-shape change: callbacks: TransactionCallbackshooks (per-operation)

V4 deposits, withdrawals, conversions, and registrations all took a generic callbacks: { pre, post } object. V5 replaces it with a per-operation hooks object whose shape is specific to that operation — named per-phase / per-step slots, each with a typed event object. V4 → V5 option-field changes on the direct deposit / withdraw: V4 → V5 option-field changes on conversion: V4 → V5 result-shape change on deposit / withdraw:

11. Error class names

Error class names are unchanged for backwards compatibility — even though ClaimUtxoError is now thrown by burner factories that operate on “Stealth Pool Notes”, the class kept its V4 spelling so existing isClaimUtxoError(err) guards still work. The FetchUtxosStage enum gained two values in V5 (no V4 equivalents):
  • "proof-fetch" — the burner’s per-batch Merkle proof fetch failed.
  • "proof-enrichment"enrichWithMerkleProof failed to attach a proof entry to a scanned note.
All other operation stage enums are unchanged.

End-to-end before/after

A complete “scan → burn into ETA” flow. V4
V5

Compatibility checklist

After migrating, verify each:
  • package.json pins @umbra-privacy/sdk to an exact version; @umbra-privacy/web-zk-prover is removed.
  • No imports from @umbra-privacy/web-zk-prover (replaced with @umbra-privacy/sdk/zk-prover).
  • No imports of @umbra-privacy/sdk/interfaces or @umbra-privacy/sdk/utils (V4-only subpaths).
  • Every V4 factory name above has been renamed to its V5 counterpart.
  • getUmbraClient(...) includes relayerApiEndpoint and, on browser, utxoDataStore + nullifierStore.
  • Scanner is invoked with no arguments and result keys are dereferenced with the long-form names.
  • Burner factories receive fetchBatchMerkleProof: client.fetchBatchMerkleProof! and a relayer: { submitBurn, pollBurnStatus, getRelayerAddress } adapter.
  • Mainnet mint allowlist includes CASH; devnet allowlist is wSOL-only.
  • UMBRA_MESSAGE_TO_SIGN is imported from @umbra-privacy/sdk/shared.
  • getEncryptedBalanceQuerierFunction result switch covers the discriminator strings "shared", "mxe", "uninitialized", "non_existent" (was { state: "exists", data: { mode } } in V4 — flattened in V5).
  • Removed callbacks: { pre, post } from every call site. Deposits / withdrawals / conversions use options.hooks (typed per-operation hooks interface); registration uses options.hooks: RegistrationHooks with { onPreSend, onPostSend, onSkipped } per-step slots.
  • Removed dead options from deposit / withdraw call sites: priorityFees, purpose, awaitCallback, skipPreflight, maxRetries.
  • Update DepositResult / WithdrawResult consumers: flat callbackSignature / callbackStatus / rentClaimSignature fields no longer exist — read result.callback?.signature (only present when result.callback?.status === "finalized") and result.rentClaim?.signature (only when result.rentClaim?.claimed === true).
  • Run the SDK against devnet and verify a full register → deposit → write note → scan → burn round-trip succeeds before deploying to mainnet.

Forward-compatibility note

The V5 surface is the target shape going forward. Future minor releases will add factories (e.g. receiver-burnable → ATA), additional store backends, and ergonomic helpers — but the existing V5 factory names, subpath layout, and relayer dep shape are stable.