Overview
Umbra supports two distinct models for transferring tokens privately. They differ in what they hide, what infrastructure they require, and how strong their privacy guarantees are.Unlinkable transfers (via the Stealth Pool)
The strongest privacy model. The on-chain link between the sender’s note write and the recipient’s burn is broken entirely — an observer cannot connect the two events by examining the chain. Tokens enter the pool as a Stealth Pool Note commitment inserted into an on-chain Indexed Merkle Tree. The note is later burned with a Groth16 ZK proof that proves ownership of the note without revealing which commitment is being spent. The note write and the burn are indistinguishable from every other note write and burn in the same tree. Note writes have four variants —(source × unlocker):
- From an ATA — tokens enter the pool directly from an
AssociatedTokenAccount. Single tx, no MPC. - From an ETA — tokens are drawn from an
EncryptedTokenAccountbefore entering the pool. Two-tx pipeline (proof account → note write), MPC under the hood.
userCommitment — recipient must have completed all three registration sub-steps).
Burns have three shipped variants:
- Receiver-burnable → ETA — anonymous payment into the recipient’s encrypted balance.
- Self-burnable → ETA — write a note, then burn it back into an encrypted balance from any wallet.
- Self-burnable → ATA — write a note, then burn it out to a plaintext ATA from any wallet.
Receiver-burnable notes require the recipient to have completed full registration (
confidential: true and anonymous: true). Self-burnable notes have no recipient-side prerequisites — they’re the right choice for one-shot transfers when the recipient hasn’t onboarded yet.Confidential-only transfers (via EncryptedTokenAccounts)
A lighter-weight privacy model that hides token amounts and balances on-chain, but does not break the link between sender and recipient. The transfer is confidential — amounts are hidden under MPC encryption — but the participating addresses remain observable. This covers scenarios where amount privacy is the goal and linkability is an acceptable trade-off: for example, moving tokens between accounts you control without exposing the amounts, or settling a payment where both parties know each other but want the value hidden. The on-chain program supports several confidential-transfer variants. For direct ETA-to-ETA transfers, usegetTransferorFunction documented below. For moving funds between ATAs and your own ETAs, use deposit / withdraw / convert.
ETA-to-ETA Direct Transfer
getTransferorFunction from @umbra-privacy/sdk/transfer performs a direct confidential transfer between two EncryptedTokenAccounts. A single MPC instruction moves the amount from the sender’s encrypted balance to the receiver’s, creating or initialising the receiver’s token account if needed.
When to use this vs the Stealth Pool: Transfer settles immediately (one MPC instruction) but the on-chain event is linkable — an observer can see that two Umbra users interacted. The Stealth Pool breaks that link entirely but requires a note write followed by a separate burn, which takes longer and involves the relayer. Use Transfer when speed matters and the relationship between sender and receiver is already known; use the Stealth Pool when on-chain anonymity is the goal.
Import
Factory pattern
Recipient pre-check
The recipient must have anEncryptedTokenAccount (or the transfer will create one, provided they have an x25519PublicKey registered). Verify before calling:
Calling the function
TransferVariant — the 8 on-chain variants
The SDK auto-selects the correct variant by fetching all four accounts in one RPC round-trip:- Sender mode —
network(MXE-only balance) orshared(user-decryptable shared balance) - Receiver state —
existing_network,existing_shared,new_network,new_shared
TransferResult
The result is a discriminated union onkind:
"submitted"— returned forshared_to_*variants; the SDK ran the full queue-computation pipeline."prepared"— returned fornetwork_to_*variants; the SDK built and returned the preparation bundle and the caller decides what to do with it (typically for advanced flows or batching).