/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.
TL;DR
- Bump the package:
@umbra-privacy/sdk@4.x → @umbra-privacy/sdk@5.0.0-rc.3. Pin an exact version. - Delete
@umbra-privacy/web-zk-proverfrompackage.json. The prover ships inside the SDK at@umbra-privacy/sdk/zk-prover. - Move every operation factory import from the main barrel to its named subpath (
/registration,/deposit,/withdrawal,/burn,/query,/conversion,/compliance,/account). - Rename factories per the table below (UTXO → Stealth Pool Note, claim → burn, ATA → ATA, encrypted balance → ETA).
- Rewrite the scanner call: zero-arg in V5, results bucketed by
(kind, source)under long-form keys. - Burner factories take a new
relayerdep —{ submitBurn, pollBurnStatus, getRelayerAddress }— these are TypeScript aliases of the relayer client’s existingsubmitClaim/pollClaimStatus/getRelayerAddress, so plug in the same client unchanged. - (Browser) Wire
utxoDataStore+nullifierStorefrom@umbra-privacy/sdk/store-adapters. Without them everyscan()walks every active tree from genesis. - (Mainnet) Mint list adds
CASH. (Devnet) Mint list: wSOL, dUSDC, dUSDT, STREAMFLOW.
1. Package consolidation
V4^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}— includinggetUmbraRelayer, RPC providers, transaction forwarders.
| What you import | V4 path | V5 path |
|---|---|---|
getUmbraClient, signer factories, getUmbraRelayer | @umbra-privacy/sdk | @umbra-privacy/sdk (unchanged) |
getUserRegistrationFunction | @umbra-privacy/sdk | @umbra-privacy/sdk/registration |
| Direct depositor + 4 note creators | @umbra-privacy/sdk | @umbra-privacy/sdk/deposit |
| Direct withdrawer | @umbra-privacy/sdk | @umbra-privacy/sdk/withdrawal |
Scanner + 3 burner factories + enrichWithMerkleProof | @umbra-privacy/sdk | @umbra-privacy/sdk/burn |
| Account + balance queriers | @umbra-privacy/sdk | @umbra-privacy/sdk/query |
| MXE→Shared converter | @umbra-privacy/sdk | @umbra-privacy/sdk/conversion |
| Compliance grant + reencrypt + querier | @umbra-privacy/sdk | @umbra-privacy/sdk/compliance |
| Staged-token recoverers, key rotators, maintenance | @umbra-privacy/sdk | @umbra-privacy/sdk/account |
| ZK provers | @umbra-privacy/web-zk-prover | @umbra-privacy/sdk/zk-prover |
| CDN asset provider | @umbra-privacy/web-zk-prover/cdn | @umbra-privacy/sdk/zk-prover/cdn |
| Browser store adapters | (new) | @umbra-privacy/sdk/store-adapters |
UMBRA_MESSAGE_TO_SIGN | @umbra-privacy/sdk | @umbra-privacy/sdk/shared |
Branded type helpers (createU64, etc.) | @umbra-privacy/sdk | @umbra-privacy/sdk/types |
generateRandomNonce | @umbra-privacy/sdk/utils | @umbra-privacy/sdk/arcium (also re-exported from the main barrel) |
| Function-type aliases | @umbra-privacy/sdk/interfaces | the same subpath as the factory |
Error classes + is* guards | @umbra-privacy/sdk/errors | @umbra-privacy/sdk/errors (unchanged) |
BPS_DIVISOR | @umbra-privacy/sdk | @umbra-privacy/sdk/shared |
| mint lists | @umbra-privacy/sdk | @umbra-privacy/sdk/constants |
@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:UTXO→Stealth Pool Note(in factory names + result types; on-chain commitments and the indexer route are still called UTXOs).Claim→Burn(in factory names + lifecycle hooks; the wire endpoint is still/v1/claims).ATA/PublicBalance→AssociatedTokenAccount(ATA) in factory names.EncryptedBalance→EncryptedTokenAccount(ETA) in factory names.
Deposits
| V4 | V5 (subpath /deposit) |
|---|---|
getPublicBalanceToEncryptedBalanceDirectDepositorFunction | getATAIntoETADirectDepositorFunction |
getPublicBalanceToSelfClaimableUtxoCreatorFunction | getATAIntoSelfBurnableStealthPoolNoteCreatorFunction |
getPublicBalanceToReceiverClaimableUtxoCreatorFunction | getATAIntoReceiverBurnableStealthPoolNoteCreatorFunction |
getEncryptedBalanceToSelfClaimableUtxoCreatorFunction | getETAIntoSelfBurnableStealthPoolNoteCreatorFunction |
getEncryptedBalanceToReceiverClaimableUtxoCreatorFunction | getETAIntoReceiverBurnableStealthPoolNoteCreatorFunction |
Withdrawal
| V4 | V5 (subpath /withdrawal) |
|---|---|
getEncryptedBalanceToPublicBalanceDirectWithdrawerFunction | getETAIntoATAWithdrawerFunction |
Scan + burn
| V4 | V5 (subpath /burn) |
|---|---|
getClaimableUtxoScannerFunction | getBurnableStealthPoolNoteScannerFunction |
getReceiverClaimableUtxoToEncryptedBalanceClaimerFunction | getReceiverBurnableStealthPoolNoteIntoETABurnerFunction |
getSelfClaimableUtxoToEncryptedBalanceClaimerFunction | getSelfBurnableStealthPoolNoteIntoETABurnerFunction |
getSelfClaimableUtxoToPublicBalanceClaimerFunction | getSelfBurnableStealthPoolNoteIntoATABurnerFunction |
| (new in V5) | getTransferorFunction (in @umbra-privacy/sdk/transfer) — ETA-to-ETA direct transfer, 8 variants |
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
| V4 | V5 |
|---|---|
getNetworkEncryptionToSharedEncryptionConverterFunction (main barrel) | getNetworkEncryptionToSharedEncryptionConverterFunction (now @umbra-privacy/sdk/conversion) |
getMintEncryptionKeyRotatorFunction (main barrel) | getMintEncryptionKeyRotatorFunction (name unchanged; moved to @umbra-privacy/sdk/account) |
getRotateUserAccountX25519KeyFunction | getUserEncryptionKeyRotatorFunction (in @umbra-privacy/sdk/account) |
getRotateMvkX25519KeyFunction | getMasterViewingKeyRotatorFunction (in @umbra-privacy/sdk/account) |
Account recovery + maintenance
| V4 | V5 (subpath /account) |
|---|---|
getClaimStagedSolFromPoolFunction | getStagedSolRecovererFunction |
getClaimStagedSplFromPoolFunction | getStagedSplRecovererFunction |
getUpdateRandomGenerationSeedFunction | getUserEntropySeedRotatorFunction |
getUpdateTokenAccountRandomGenerationSeedFunction | getTokenEntropySeedRotatorFunction |
Compliance
| V4 | V5 (subpath /compliance) |
|---|---|
getComplianceGrantIssuerFunction | getComplianceGrantIssuerFunction (unchanged) |
getComplianceGrantRevokerFunction | getComplianceGrantRevokerFunction (unchanged) |
getUserComplianceGrantQuerierFunction | getUserComplianceGrantQuerierFunction (unchanged) |
getQueryNetworkMxeComplianceGrantFunction | getNetworkComplianceGrantQuerierFunction |
getQueryNetworkSharedComplianceGrantFunction | getSharedComplianceGrantQuerierFunction |
getReencryptMxeCiphertextsNetworkGrantFunction | getNetworkCiphertextReencryptorForNetworkGrantFunction |
getSharedCiphertextReencryptorForUserGrantFunction | getSharedCiphertextReencryptorForUserGrantFunction (unchanged) |
getSharedCiphertextReencryptorForNetworkGrantFunction | getSharedCiphertextReencryptorForNetworkGrantFunction (unchanged) |
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
| V4 result key | V5 result key |
|---|---|
selfBurnable | etaToStealthPoolSelfBurnable |
received | etaToStealthPoolReceiverBurnable |
publicSelfBurnable | ataToStealthPoolSelfBurnable |
publicReceived | ataToStealthPoolReceiverBurnable |
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
fetchBatchMerkleProof— passclient.fetchBatchMerkleProof(auto-wired whenindexerApiEndpointis 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 …IntoAssociatedTokenAccountResult 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 ≤4 per proof (MAX_STEALTH_POOL_NOTES_PER_PROOF = 4). Pass the whole array. - Self-burnable → ETA / ATA has
MAX_STEALTH_POOL_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 shipscreateBrowserStorageBackend, 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.
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 mint list: wSOL, dUSDC, dUSDT, STREAMFLOW (dUSDC / dUSDT from faucet.umbraprivacy.com).
- Localnet stayed wSOL only.
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:
9. Registration hooks rewrite
V5 registration replaces V4’scallbacks: { 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— baseEncryptedUserAccountPDA.registerX25519PublicKey— X25519 token-encryption key.registerAnonymousUsage— user-commitment registration via MPC + ZK.
| V4 callbacks slot | V5 hooks slot |
|---|---|
userAccountInitialisation | initUserAccount |
registerX25519PublicKey | registerX25519PublicKey (unchanged) |
registerUserForAnonymousUsage | registerAnonymousUsage |
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 exposesonMonitorStarted,onProgress,onFinalized,onRentReclaimSubmitted,onRentReclaimError(seeMpcTransactionStepHooks).
10. Options-shape change: callbacks: TransactionCallbacks → hooks (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 option | V5 |
|---|---|
priorityFees?: U64 | removed (priority fees are factory-time config) |
purpose?: number | removed |
awaitCallback?: boolean | removed (callback waiting is always on; the result’s callback? field is populated on success) |
skipPreflight?: boolean | removed |
maxRetries?: number | removed (configure deps.transactionForwarder at factory time) |
epochInfoCommitment?: Commitment | retained on deposit; removed on withdrawal |
callbacks?: TransactionCallbacks | replaced by hooks?: …DirectDepositHooks / …DirectWithdrawHooks |
| (new) | computationOffset?: U64 (withdrawal) |
| (new) | mpcCallbackDataOffset?: U128 (withdrawal) |
| (new) | feeVaultOffset?: U128 (withdrawal) |
| (new) | destinationProgram?: Address (withdrawal observer-CPI) |
| V4 | V5 |
|---|---|
convert(mints, optionalData, callbacks) | convert(mints, optionalData, hooks?, microLamportsPerAcu?) |
| V4 result field | V5 result field |
|---|---|
queueSignature: TransactionSignature | queueSignature: TransactionSignature (unchanged) |
callbackStatus?: "finalized" | "pruned" | "timed-out" | flattened into callback?: { status, signature?, elapsedMs } (discriminated) |
callbackSignature?: TransactionSignature | inside callback.signature (when status === "finalized") |
callbackElapsedMs?: number | inside callback.elapsedMs |
rentClaimSignature?: TransactionSignature | inside rentClaim?: { claimed: true, signature } | { claimed: false, reason } |
rentClaimError?: string | inside rentClaim.reason (when claimed: false) |
| (new) | signatures: readonly TransactionSignature[] — full submission list from OperationOutcome |
11. Error class names
Error class names are unchanged for backwards compatibility — even thoughClaimUtxoError 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"—enrichWithMerkleProoffailed to attach a proof entry to a scanned note.
stage enums are unchanged.
End-to-end before/after
A complete “scan → burn into ETA” flow. V4Compatibility checklist
After migrating, verify each:-
package.jsonpins@umbra-privacy/sdkto an exact version;@umbra-privacy/web-zk-proveris removed. - No imports from
@umbra-privacy/web-zk-prover(replaced with@umbra-privacy/sdk/zk-prover). - No imports of
@umbra-privacy/sdk/interfacesor@umbra-privacy/sdk/utils(V4-only subpaths). - Every V4 factory name above has been renamed to its V5 counterpart.
-
getUmbraClient(...)includesindexerApiEndpointand, on browser,utxoDataStore+nullifierStore. The relayer is built separately viagetUmbraRelayer({ apiEndpoint }). - Scanner is invoked with no arguments and result keys are dereferenced with the long-form names.
- Burner factories receive
fetchBatchMerkleProof: client.fetchBatchMerkleProof!and arelayer: { submitBurn, pollBurnStatus, getRelayerAddress }adapter. - Mainnet mint allowlist includes
CASH; devnet allowlist includes wSOL, dUSDC, dUSDT, STREAMFLOW. -
UMBRA_MESSAGE_TO_SIGNis imported from@umbra-privacy/sdk/shared. -
getEncryptedBalanceQuerierFunctionresultswitchcovers 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 useoptions.hooks(typed per-operation hooks interface); registration usesoptions.hooks: RegistrationHookswith{ onPreSend, onPostSend, onSkipped }per-step slots. - Removed dead options from deposit / withdraw call sites:
priorityFees,purpose,awaitCallback,skipPreflight,maxRetries. - Update
DepositResult/WithdrawResultconsumers: flatcallbackSignature/callbackStatus/rentClaimSignaturefields no longer exist — readresult.callback?.signature(only present whenresult.callback?.status === "finalized") andresult.rentClaim?.signature(only whenresult.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, andrelayer dep shape are stable.