Skip to main content
  1. A withdrawal moves tokens from a private balance to a public Solana balance.
  2. Withdrawals are sent in a single Solana transaction to a Solana wallet address.

Withdraw: What Is Private

The permissionless Ring is confidential with encrypted amount and asset. A custom Ring can be configured as confidential or anonymous (encrypted sender, recipient, asset, and amount).

How a Withdrawal Works

A withdrawal behaves similarly to a public Solana transfer:
  1. The user’s SOL or SPL balance is encrypted onchain.
  2. The user decrypts private state, the wallet builds a withdrawal, and the owner signs.
    • Fetch encrypted state with dedicated RPC methods. Only the user can decrypt balances locally.
    • The wallet sets amount and recipient, then requests a ZK proof. The RPC provider generates the ZK proof by default and returns it.
  3. The Solana runtime verifies the signatures and invokes the Solana Privacy Program, which verifies the ZK proof without revealing the encrypted state.
  4. The app tracks status via the Solana transaction hash.
  1. The user’s SOL or SPL balance is public onchain.
  2. The wallet reads public state, builds a transfer, and the owner signs.
  3. The Solana runtime verifies the signatures and invokes the System Program or Token Program, which updates the public balance.
  4. The app tracks status via the Solana transaction hash.
This is the high-level transaction flow for the permissionless confidential Ring. Compare to custom Rings in concepts.

Get Started

1

Prerequisites

The TypeScript examples require Node.js 24 or later, pnpm 11.18.0, and the Solana CLI.
Source: sdk-libs/ts
Add a Helius API key:
.env
The examples use the Solana CLI wallet as the payer by default. The payer must hold devnet SOL. See How to Get Devnet SOL.
2

Withdraw to a Public Balance

  • The SDK returns instructions. The app signs and sends them.
  • sendAndConfirmFactory builds a Kit transaction, submits it, and returns the signature plus the landed slot.
  • The example spends the Private Solana Token Account remaining after the preceding transfer. A withdrawal can spend multiple UTXOs.
  • withdrawalUtxo selects one Private Solana Token Account from that balance.
  • ProofInputUtxo.fromKeypair prepares the selected UTXO as a proof input with the sender’s private wallet keypair.
  • The keypair derives the nullifier that marks the input UTXO as spent while the input asset and amount remain encrypted.
  • senderAddress is the sender’s . The withdrawal spends from this wallet.
  • [withdrawalInput] lists the sender’s selected UTXOs. A withdrawal can spend multiple UTXOs.
  • senderSigner.address is the fee payer’s Solana address. A gas sponsor can fill this role for a confidential withdrawal.
  • WithdrawalTarget.sol is the public Solana recipient. The recipient can be the owner or a third party.
  • SOL_MINT selects SOL. An SPL or Token 2022 withdrawal passes the token mint.
  • WITHDRAW_AMOUNT is denominated in the asset’s base units. SOL uses lamports. SPL and Token 2022 assets use the token’s base units.
  • withdrawal.sign authorizes the state transition, encrypts the remaining private change, and produces the inputs for the zero-knowledge prover.
  • assets is the asset registry used to resolve supported private assets.
  • client.proveTransact generates the zero-knowledge proof from the signed withdrawal and returns serialized instruction data.
  • The proof demonstrates that the sender owns and can spend the inputs. The withdrawn asset and amount are public. Input amounts and change stay encrypted.
  • payer signs and pays for the Solana transaction. A gas sponsor can fill this role for a confidential withdrawal.
  • inputTree and outputTree are client.tree, the state Merkle tree that contains the spent UTXOs and receives the sender’s private change.
  • withdrawal is TransactWithdrawal.sol, the public Solana recipient account.
  • data contains the zero-knowledge proof and encrypted change produced in the previous step.
  • A withdrawal moves the asset from a private balance to a public Solana account. It passes the public recipient account.
  • sendAndConfirm signs and submits withdrawalInstruction as a Solana transaction.
  • Confirmation yields the landed slot used to gate the indexer fetch.

Full Code Example

Clone and run the example:
The examples use a confidential Ring on local/devnet here.
deposit_transfer_withdraw.ts

Deposit

Transfer

Read a Private Balance

Read Private History

Didn’t find what you were looking for?

Reach out! Telegram | E-Mail | Contact