> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Deposit

> Guide to deposit SOL, SPL, or Token 2022 assets into a private balance with a full code example.

1. A deposit moves tokens from the user's public Solana balance into their own or someone else's private balance.
2. Deposits are sent in a single Solana transaction to a Solana wallet address.

#### Deposit: What Is Private

| Field | Visibility |
| - | - |
| Asset | Public. Visible in the public deposit. |
| Amount | Public. Visible in the public deposit. |
| Sender | Public. The source wallet address is visible in the public deposit. |
| Recipient | Public. The destination wallet address is visible in the public deposit. |
| Memo (optional) | Public. Emitted with the public deposit. |
| Resulting private balance | Private. Encrypted onchain. |

> [!NOTE]
>
> 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 Deposit Works

A deposit is a public Solana transfer with a resulting private balance.

1. The user's SOL or SPL balance is public onchain.
2. The wallet sets amount and recipient, builds the deposit, and the owner signs. A deposit does not request a ZK proof.
3. The Solana runtime verifies the signatures and invokes the Solana Privacy Program.
4. The app tracks status via the Solana transaction hash.

```mermaid theme={"system"}
%%{init: {
  'theme': 'base',
  'themeVariables': {
    'lineColor':           '#FF6B35',
    'primaryTextColor':    '#737373',
    'primaryBorderColor':  '#9CA3AF',
    'actorBkg':            '#FFFFFF',
    'actorBorder':         '#9CA3AF',
    'actorTextColor':      '#737373',
    'signalColor':         '#FF6B35',
    'signalTextColor':     '#737373',
    'labelBoxBkgColor':    '#FF6B351F',
    'labelBoxBorderColor': '#FF6B35',
    'noteBkgColor':        '#F5F5F5',
    'noteTextColor':       '#737373',
    'noteBorderColor':     '#9CA3AF'
  }
}}%%
sequenceDiagram
    participant Wallet
    participant RPC as RPC Provider
    participant Solana

    Wallet->>RPC: Get public balance
    RPC-->>Wallet: Public state
    Note over Wallet: Set amount and recipient
    Note over Wallet: Build deposit, sign
    Wallet->>RPC: Submit Transaction
    RPC->>Solana: Forward transaction
    Note over Solana: Verify signatures
    Note over Solana: CPI Solana Privacy Program
    RPC-->>Wallet: Transaction signature
```

> [accordion] Compare to Solana Transfer
>
> 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.
>
> ```mermaid theme={"system"}
> %%{init: {
>   'theme': 'base',
>   'themeVariables': {
>     'lineColor':           '#FF6B35',
>     'primaryTextColor':    '#737373',
>     'primaryBorderColor':  '#9CA3AF',
>     'actorBkg':            '#FFFFFF',
>     'actorBorder':         '#9CA3AF',
>     'actorTextColor':      '#737373',
>     'signalColor':         '#FF6B35',
>     'signalTextColor':     '#737373',
>     'labelBoxBkgColor':    '#FF6B351F',
>     'labelBoxBorderColor': '#FF6B35',
>     'noteBkgColor':        '#F5F5F5',
>     'noteTextColor':       '#737373',
>     'noteBorderColor':     '#9CA3AF'
>   }
> }}%%
> sequenceDiagram
>     participant Wallet
>     participant RPC
>     participant Solana
>
>     Wallet->>RPC: Get public balance
>     RPC-->>Wallet: Public state
>     Note over Wallet: Build transfer, owner signs
>     Wallet->>RPC: sendTransaction
>     RPC->>Solana: Forward transaction
>     Note over Solana: Verify signatures
>     Note over Solana: CPI System / Token Program
>     Note over Solana: Update balance
>     RPC-->>Wallet: Transaction signature
> ```

> [!NOTE]
>
> This is the high-level transaction flow for the permissionless confidential Ring.
> Compare to custom Rings in [High-Level Transaction Flow](https://www.helius.dev/docs/privacy/concepts/overview.md#high-level-transaction-flow).

## Get Started

> [tab] TypeScript Client
>
> **Step 1**
>
> ### Prerequisites
>
> > [!NOTE]
> >
> > The TypeScript examples require Node.js 24 or later, pnpm 11.18.0, and the Solana CLI.
>
> ```bash theme={"system"}
> pnpm add @heliuslabs/zolana@^0.5.0-alpha @solana/kit@^8.3.0
> ```
>
> Source: [sdk-libs/ts](https://github.com/helius-labs/zolana/tree/ts-sdk-v0.5.0-alpha/sdk-libs/ts)
>
> > [accordion] Connect to Endpoints
> >
> > > [tab] Devnet
> > >
> > > ```bash theme={"system"}
> > > pnpm install
> > > cp .env.example .env
> > > ```
> > >
> > > Add a [Helius API key](https://dashboard.helius.dev/):
> > >
> > > ```bash .env theme={"system"}
> > > API_KEY=YOUR_API_KEY
> > > ZOLANA_PAYER_KEYPAIR=~/.config/solana/id.json
> > > ```
> > >
> > > ```ts theme={"system"}
> > > import { createZolanaClient } from "@heliuslabs/zolana";
> > >
> > > const client = await createZolanaClient({
> > >   solanaRpcUrl: "https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY",
> > > });
> > > ```
> > >
> > > The examples use the Solana CLI wallet as the payer by default. The payer must hold devnet SOL. See [How to Get Devnet SOL](https://www.helius.dev/docs/rpc/devnet-sol.md).
> >
> > > [tab] Localnet
> > >
> > > On localnet, the SDK starts the local test validator (`:8899`), Photon indexer (`:8784`), and prover (`:3001`), and the
> > > client connects to them automatically without needing endpoint configuration.
> > >
> > > ```bash theme={"system"}
> > > cargo install --git https://github.com/helius-labs/zolana --tag v0.5.0-alpha zolana-cli
> > > zolana dev start
> > > ```
> > >
> > > ```ts theme={"system"}
> > > import { createZolanaClient } from "@heliuslabs/zolana";
> > >
> > > const client = await createZolanaClient({});
> > > ```
>
> **Step 2**
>
> ### Deposit to a Private Balance
>
> > [accordion] Solana Kit send helper
> >
> > ```typescript theme={"system"}
> > import {
> >   appendTransactionMessageInstructions,
> >   assertIsTransactionWithBlockhashLifetime,
> >   createTransactionMessage,
> >   getSignatureFromTransaction,
> >   pipe,
> >   sendTransactionWithoutConfirmingFactory,
> >   setTransactionMessageConfig,
> >   setTransactionMessageFeePayerSigner,
> >   setTransactionMessageLifetimeUsingBlockhash,
> >   signTransactionMessageWithSigners,
> >   type Instruction,
> >   type Signature,
> >   type TransactionSigner,
> > } from "@solana/kit";
> > import { createZolanaClient } from "@heliuslabs/zolana";
> >
> > type Client = Awaited<ReturnType<typeof createZolanaClient>>;
> >
> > export interface ConfirmedTransaction {
> >   readonly signature: Signature;
> >   readonly slot: bigint;
> > }
> >
> > export function sendAndConfirmFactory(
> >   client: Client,
> >   feePayer: TransactionSigner,
> > ): (instructions: readonly Instruction[]) => Promise<ConfirmedTransaction> {
> >   const sendTransaction = sendTransactionWithoutConfirmingFactory({
> >     rpc: client.solanaRpc,
> >   });
> >
> >   return async function sendAndConfirm(
> >     instructions: readonly Instruction[],
> >   ): Promise<ConfirmedTransaction> {
> >     const { value: lifetime } = await client.solanaRpc
> >       .getLatestBlockhash()
> >       .send();
> >     const signed = await signTransactionMessageWithSigners(
> >       pipe(
> >         createTransactionMessage({ version: 1 }),
> >         (message) => setTransactionMessageFeePayerSigner(feePayer, message),
> >         (message) =>
> >           setTransactionMessageLifetimeUsingBlockhash(lifetime, message),
> >         (message) =>
> >           setTransactionMessageConfig(
> >             {
> >               computeUnitLimit: 450_000,
> >               loadedAccountsDataSizeLimit: 64 * 1024 * 1024,
> >             },
> >             message,
> >           ),
> >         (message) =>
> >           appendTransactionMessageInstructions(instructions, message),
> >       ),
> >     );
> >     assertIsTransactionWithBlockhashLifetime(signed);
> >     await sendTransaction(signed, { commitment: "confirmed" });
> >     const signature = getSignatureFromTransaction(signed);
> >     const slot = await client.confirmTransaction(signature);
> >     return { signature, slot };
> >   };
> > }
> > ```
> >
> > * 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.
>
> Each SPL mint needs a token interface to hold deposited tokens. If the interface does not exist, create it in the same transaction as the deposit. The creator pays the account rent.
>
> > [tab] Deposit Tokens with an Existing Interface
> >
> > > [tab] SOL
> > >
> > > ```typescript theme={"system"}
> > > import {
> > >   depositInstruction,
> > >   DepositAsset,
> > > } from "@heliuslabs/zolana/interface";
> > > import { sendAndConfirmFactory } from "../src/lib.js";
> > >
> > >
> > >   const sendAndConfirm = sendAndConfirmFactory(
> > >     client,
> > >     senderSigner,
> > >   );
> > >   const senderViewTag =
> > >     senderAddress.confidentialViewTag();
> > >   const depositIx = await depositInstruction({
> > >     tree: client.tree,
> > >     depositor: senderSigner,
> > >     deposits: [
> > >       {
> > >         asset: DepositAsset.sol(),
> > >         viewTag: senderViewTag,
> > >         recipientOwnerHash:
> > >           senderAddress.ownerHash(),
> > >         amount: DEPOSIT_AMOUNT,
> > >       },
> > >     ],
> > >   });
> > >
> > >   const depositTx = await sendAndConfirm([
> > >     depositIx,
> > >   ]);
> > > ```
> >
> > > [tab] SPL
> > >
> > > ```typescript theme={"system"}
> > > import {
> > >   depositInstruction,
> > >   DepositAsset,
> > > } from "@heliuslabs/zolana/interface";
> > > import { sendAndConfirmFactory } from "../src/lib.js";
> > >
> > >
> > >   const sendAndConfirm = sendAndConfirmFactory(
> > >     client,
> > >     senderSigner,
> > >   );
> > >   const senderViewTag =
> > >     senderAddress.confidentialViewTag();
> > >   const depositIx = await depositInstruction({
> > >     tree: client.tree,
> > >     depositor: senderSigner,
> > >     deposits: [
> > >       {
> > >         asset: DepositAsset.spl({
> > >           mint: spl.mint,
> > >           sourceTokenAccount: spl.sourceTokenAccount,
> > >           tokenProgram: spl.tokenProgram,
> > >         }),
> > >         viewTag: senderViewTag,
> > >         recipientOwnerHash:
> > >           senderAddress.ownerHash(),
> > >         amount: DEPOSIT_AMOUNT,
> > >       },
> > >     ],
> > >   });
> > >
> > >   const depositTx = await sendAndConfirm([
> > >     depositIx,
> > >   ]);
> > > ```
> >
> > > [accordion] 1. Move public tokens into the sender's private balance
> > >
> > > ```typescript theme={"system"}
> > > import {
> > >   depositInstruction,
> > >   DepositAsset,
> > > } from "@heliuslabs/zolana/interface";
> > >
> > > const senderViewTag =
> > >   senderAddress.confidentialViewTag();
> > > const depositIx = await depositInstruction({
> > >   tree: client.tree,
> > >   depositor: senderSigner,
> > >   deposits: [
> > >     {
> > >       asset: DepositAsset.sol(),
> > >       viewTag: senderViewTag,
> > >       recipientOwnerHash:
> > >         senderAddress.ownerHash(),
> > >       amount: DEPOSIT_AMOUNT,
> > >     },
> > >   ],
> > > });
> > > ```
> > >
> > > * `senderAddress` is the recipient's Shielded Address (The public key bundle (signing, nullifier, and viewing keys) published in a user's registry record. Not an onchain address.). This example deposits to the sender. The recipient can be the sender or a third-party wallet.
> > > * `senderViewTag` is `senderAddress.confidentialViewTag()`, the sender's Solana public key in confidential rings. The indexer uses it to return matching encrypted outputs.
> > > * `client.tree` is the state Merkle tree that receives the new output commitment.
> > > * `depositor` / `senderSigner` funds and signs the public-to-private deposit.
> > > * `DepositAsset.sol()` selects SOL. `DepositAsset.spl` takes the mint, source token account, and token program for SPL and Token 2022 assets.
> > > * `recipientOwnerHash` is `senderAddress.ownerHash()`. That hash of the recipient's signing and nullifier public keys is the owner field committed in the new private token account.
> > > * `DEPOSIT_AMOUNT` uses lamports for SOL, or the token's base units for SPL and Token 2022 assets.
> >
> > > [accordion] 2. Send like any Solana transaction
> > >
> > > ```typescript theme={"system"}
> > > import { sendAndConfirmFactory } from "../src/lib.js";
> > >
> > > const depositTx = await sendAndConfirm([
> > >   depositIx,
> > > ]);
> > > ```
> > >
> > > * `sendAndConfirm` signs and submits `depositIx` as a Solana transaction.
> > > * Confirmation yields the landed slot used to gate the indexer fetch.
>
> > [tab] Deposit Tokens without an Interface
> >
> > ```mermaid theme={"system"}
> > %%{init: {
> >   'theme': 'base',
> >   'themeVariables': {
> >     'primaryColor': '#FFFFFF',
> >     'primaryTextColor': '#737373',
> >     'primaryBorderColor': '#9CA3AF',
> >     'lineColor': '#FF6B35',
> >     'edgeLabelBackground': '#FFFFFF'
> >   }
> > }}%%
> > flowchart LR
> >     GetAccount["Get account"] --> Exists{"Interface PDA exists?"}
> >     Exists -->|Yes| Deposit["Deposit to private balance"]
> >     Exists -->|No| Create["Create interface PDA<br/>and token account"]
> >     Create --> Deposit
> > ```
> >
> > ```typescript theme={"system"}
> > import { getSplAssetVaultAddress } from "@heliuslabs/zolana/addresses";
> > import { getCreateSplInterfaceInstructionAsync } from "@heliuslabs/zolana/instructions";
> > import { depositInstruction, DepositAsset } from "@heliuslabs/zolana/interface";
> > import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
> > import type { Instruction } from "@solana/kit";
> >
> > // 1. Check whether the mint already has an interface PDA, the escrow that holds deposited tokens.
> > // A second create fails the transaction, so skip step 2 when the account exists.
> > const interfaceAddress =
> >   await getSplAssetVaultAddress(mint);
> > const interfaceAccount =
> >   await client.getAccount(interfaceAddress);
> > const instructions: Instruction[] = [];
> >
> > // 2. Create the mint registry PDA and interface token account.
> > // The authority signer pays their rent.
> > if (!interfaceAccount) {
> >   const createInterfaceIx =
> >     await getCreateSplInterfaceInstructionAsync(
> >       {
> >         authority: senderSigner,
> >         mint,
> >         tokenProgram: TOKEN_PROGRAM_ADDRESS,
> >       },
> >     );
> >   instructions.push(createInterfaceIx);
> > }
> >
> > // 3. Move public tokens into the sender's private balance.
> > // A deposit from a public balance reveals sender, recipient, asset and amount.
> > const depositIx = await depositInstruction({
> >   tree: client.tree,
> >   depositor: senderSigner,
> >   deposits: [
> >     {
> >       asset: DepositAsset.spl({
> >         mint,
> >         sourceTokenAccount: sourceToken,
> >         tokenProgram: TOKEN_PROGRAM_ADDRESS,
> >       }),
> >       viewTag:
> >         senderAddress.confidentialViewTag(),
> >       recipientOwnerHash:
> >         senderAddress.ownerHash(),
> >       amount: DEPOSIT_AMOUNT,
> >     },
> >   ],
> > });
> >
> > // 4. Send the instructions in one transaction.
> > instructions.push(depositIx);
> > const depositTx =
> >   await sendAndConfirm(instructions);
> > ```
> >
> > > [!NOTE]
> > >
> > > Full example: [**TypeScript**](https://github.com/helius-labs/zolana-examples/blob/main/typescript-client/examples/deposit_with_interface_setup.ts).
>
> ### Full Code Example
>
> Clone and run the example:
>
> ```bash theme={"system"}
> git clone https://github.com/helius-labs/zolana-examples.git
> cd zolana-examples
> cd typescript-client
> pnpm install
> pnpm example examples/deposit_transfer_withdraw.ts
> ```
>
> > [!NOTE]
> >
> > The examples use a confidential Ring on local/devnet [here](https://github.com/helius-labs/zolana-examples/blob/main/typescript-client/examples/deposit_transfer_withdraw.ts).
>
> ```typescript deposit_transfer_withdraw.ts expandable theme={"system"}
> import {
>   SOL_MINT,
>   ShieldedKeypair,
>   createZolanaClient,
> } from "@heliuslabs/zolana";
> import {
>   LocalKeys,
>   atSlot,
> } from "@heliuslabs/zolana/client";
> import {
>   depositInstruction,
>   transactInstruction,
>   DepositAsset,
>   TransactWithdrawal,
> } from "@heliuslabs/zolana/interface";
> import {
>   AssetRegistry,
>   ConfidentialTransfer,
>   ProofInputUtxo,
>   decryptToBalances,
>   WithdrawalTarget,
> } from "@heliuslabs/zolana/transaction";
>
> import {
>   cliKeypair,
>   sendAndConfirmFactory,
>   setup,
> } from "../src/lib.js";
>
> const DEPOSIT_AMOUNT = 10_000_000n;
> const TRANSFER_AMOUNT = 3_000_000n;
> const WITHDRAW_AMOUNT = 3_000_000n;
>
> async function main(): Promise<void> {
>   const { clientConfig } = await setup();
>
>   const client =
>     await createZolanaClient(clientConfig);
>   // localnet: const client = await createZolanaClient({});
>
>   // Initialize the sender's private wallet and local authority
>   // to decrypt transactions and sync balances.
>   // The Solana signer and private wallet are derived from the same Ed25519 seed.
>   const sender = ShieldedKeypair.fromKeypair(
>     await cliKeypair(),
>   );
>   const recipient = ShieldedKeypair.generate();
>   const senderSigner = sender.toSolanaSigner();
>   const senderAddress = sender.shieldedAddress();
>   const senderKeys = LocalKeys.fromKeypair(
>     sender,
>     client.proofService,
>   );
>
>   // The SDK hands back instructions; the app owns signing and sending.
>   const sendAndConfirm = sendAndConfirmFactory(
>     client,
>     senderSigner,
>   );
>
>   // Mints that are registered with Solana Privacy Rings for privacy.
>   const assets = new AssetRegistry();
>
>   // Deposit SOL into the sender's private balance.
>   // A deposit from a public balance reveals
>   // sender, recipient, asset and amount.
>   // Alternatively, you can onramp fiat directly to a private balance.
>
>   // 1. Move public SOL into the sender's private balance.
>   // The view tag is the sender's Solana public key in confidential rings.
>   // Used by the indexer to fetch the sender's outputs.
>   const senderViewTag =
>     senderAddress.confidentialViewTag();
>   const depositIx = await depositInstruction({
>     tree: client.tree,
>     depositor: senderSigner,
>     deposits: [
>       {
>         asset: DepositAsset.sol(),
>         viewTag: senderViewTag,
>         recipientOwnerHash:
>           senderAddress.ownerHash(),
>         amount: DEPOSIT_AMOUNT,
>       },
>     ],
>   });
>
>   // 2. Send and confirm like any Solana transaction; confirmation yields the landed slot.
>   const depositTx = await sendAndConfirm([
>     depositIx,
>   ]);
>
>   // 3. Fetch this transaction's outputs, gated on its confirmed slot.
>   const depositResponse =
>     await client.getShieldedTransactionsBySignature(
>       depositTx.signature,
>       atSlot(depositTx.slot),
>     );
>
>   // 4. The sender decrypts the transaction outputs locally to read the funds deposited in this run.
>   const balancesAfterDeposit =
>     await decryptToBalances({
>       keypair: sender,
>       registry: assets,
>       transactions:
>         depositResponse.transactions.map(
>           ({ transaction }) => transaction,
>         ),
>     });
>   const depositBalance =
>     balancesAfterDeposit.balance(SOL_MINT);
>   if (depositBalance.amount !== DEPOSIT_AMOUNT) {
>     throw new Error(
>       `expected deposit amount ${DEPOSIT_AMOUNT}, got ${depositBalance.amount}`,
>     );
>   }
>   if (depositBalance.utxos.length !== 1) {
>     throw new Error(
>       `expected 1 deposit utxo, got ${depositBalance.utxos.length}`,
>     );
>   }
>
>   // Confidential SOL transfer to the recipient's private balance.
>   // A confidential transfer reveals only sender and recipient,
>   // not the asset or amount.
>
>   // 1. Select private token accounts (UTXOs) that make up the private balance for the transfer.
>   const transferUtxo = depositBalance.utxos[0]!;
>
>   // 2. Prepare the selected UTXOs as inputs for the zero-knowledge proof.
>   const transferInput =
>     ProofInputUtxo.fromKeypair(
>       transferUtxo,
>       sender,
>     );
>
>   // 3. Build and sign the confidential transfer.
>   // Signing encrypts the asset and amount and produces the proof inputs for the ZK prover.
>   const transfer = new ConfidentialTransfer(
>     senderAddress,
>     [transferInput],
>     senderSigner.address,
>   );
>   transfer.send(
>     recipient.shieldedAddress(),
>     SOL_MINT,
>     TRANSFER_AMOUNT,
>   );
>   const transferProofInputs = transfer.sign(
>     sender,
>     assets,
>   );
>
>   // 4. Fetch the ZK proof to prove the sender can spend the balance without revealing asset and amount.
>   const transferData = await client.proveTransact(
>     transferProofInputs,
>     senderKeys,
>   );
>
>   // 5. Build the instruction with the state Merkle tree and Solana accounts required for the transfer.
>   // Private transfers move balances only between private token accounts, not public token accounts.
>   const transferInstruction =
>     await transactInstruction({
>       payer: senderSigner,
>       ownerSigners: [senderSigner],
>       inputTree: client.tree,
>       outputTree: client.tree,
>       data: transferData,
>     });
>
>   // 6. Send and confirm like any Solana transaction; confirmation yields the landed slot.
>   const transferTx = await sendAndConfirm([
>     transferInstruction,
>   ]);
>
>   // 7. Fetch this transaction's outputs, gated on its confirmed slot.
>   const transferResponse =
>     await client.getShieldedTransactionsBySignature(
>       transferTx.signature,
>       atSlot(transferTx.slot),
>     );
>   const balancesAfterTransfer =
>     await decryptToBalances({
>       keypair: sender,
>       registry: assets,
>       transactions:
>         transferResponse.transactions.map(
>           ({ transaction }) => transaction,
>         ),
>     });
>   const transferBalance =
>     balancesAfterTransfer.balance(SOL_MINT);
>   if (
>     transferBalance.amount !==
>     DEPOSIT_AMOUNT - TRANSFER_AMOUNT
>   ) {
>     throw new Error(
>       `expected remaining amount from this run ${DEPOSIT_AMOUNT - TRANSFER_AMOUNT}, got ${transferBalance.amount}`,
>     );
>   }
>   if (transferBalance.utxos.length !== 1) {
>     throw new Error(
>       `expected 1 transfer utxo, got ${transferBalance.utxos.length}`,
>     );
>   }
>
>   // Withdraw SOL from the sender's private balance to their public balance.
>   // A withdrawal reveals the sender, recipient, asset, and amount.
>
>   // 1. Select private token accounts (UTXOs) that make up the private balance for the withdrawal.
>   const withdrawalUtxo =
>     transferBalance.utxos[0]!;
>
>   // 2. Prepare the selected UTXOs as inputs for the zero-knowledge proof.
>   const withdrawalInput =
>     ProofInputUtxo.fromKeypair(
>       withdrawalUtxo,
>       sender,
>     );
>
>   // 3. Build and sign the private-to-public withdrawal.
>   // Signing encrypts the asset and amount of the remaining private balance
>   // and produces the proof inputs for the ZK prover.
>   const withdrawal = new ConfidentialTransfer(
>     senderAddress,
>     [withdrawalInput],
>     senderSigner.address,
>   );
>   withdrawal.withdraw(
>     SOL_MINT,
>     WITHDRAW_AMOUNT,
>     WithdrawalTarget.sol({
>       recipient: senderSigner.address,
>     }),
>   );
>   const withdrawalProofInputs = withdrawal.sign(
>     sender,
>     assets,
>   );
>
>   // 4. Fetch the ZK proof to prove the sender can spend the balance.
>   const withdrawalData =
>     await client.proveTransact(
>       withdrawalProofInputs,
>       senderKeys,
>     );
>
>   // 5. Build the instruction with the state Merkle tree and Solana accounts required for the withdrawal.
>   const withdrawalInstruction =
>     await transactInstruction({
>       payer: senderSigner,
>       ownerSigners: [senderSigner],
>       inputTree: client.tree,
>       outputTree: client.tree,
>       withdrawal: TransactWithdrawal.sol({
>         recipient: senderSigner.address,
>       }),
>       data: withdrawalData,
>     });
>
>   // 6. Send and confirm like any Solana transaction; confirmation yields the landed slot.
>   const withdrawalTx = await sendAndConfirm([
>     withdrawalInstruction,
>   ]);
>
>   // 7. Fetch this transaction's outputs, gated on its confirmed slot.
>   const withdrawalResponse =
>     await client.getShieldedTransactionsBySignature(
>       withdrawalTx.signature,
>       atSlot(withdrawalTx.slot),
>     );
>   const balancesAfterWithdrawal =
>     await decryptToBalances({
>       keypair: sender,
>       registry: assets,
>       transactions:
>         withdrawalResponse.transactions.map(
>           ({ transaction }) => transaction,
>         ),
>     });
>   const withdrawalBalance =
>     balancesAfterWithdrawal.balance(SOL_MINT);
>   if (
>     withdrawalBalance.amount !==
>     DEPOSIT_AMOUNT -
>       TRANSFER_AMOUNT -
>       WITHDRAW_AMOUNT
>   ) {
>     throw new Error(
>       `expected remaining amount from this run ${DEPOSIT_AMOUNT - TRANSFER_AMOUNT - WITHDRAW_AMOUNT}, got ${withdrawalBalance.amount}`,
>     );
>   }
>   if (withdrawalBalance.utxos.length !== 1) {
>     throw new Error(
>       `expected 1 withdrawal utxo, got ${withdrawalBalance.utxos.length}`,
>     );
>   }
>
>   // 8. Read remaining private balance and the public balance.
>   const solanaBalance = await client.getBalance(
>     senderSigner.address,
>   );
>   console.log(
>     `withdraw private_balance=${withdrawalBalance.amount} ` +
>       `solana_balance=${solanaBalance} tx=${withdrawalTx.signature}`,
>   );
> }
>
> await main();
> ```

> [tab] Rust Client
>
> **Step 1**
>
> ### Prerequisites
>
> > [!NOTE]
> >
> > The Rust examples require Rust 1.98.1 and the Solana CLI v4.0.2. See the [Solana installation guide](https://solana.com/docs/intro/installation).
>
> ```toml Cargo.toml theme={"system"}
> [dependencies]
> zolana-client = { git = "https://github.com/helius-labs/zolana", tag = "v0.5.0-alpha", features = ["solana-rpc"] }
> zolana-interface = { git = "https://github.com/helius-labs/zolana", tag = "v0.5.0-alpha" }
> zolana-program = { git = "https://github.com/helius-labs/zolana", tag = "v0.5.0-alpha" }
> zolana-keypair = { git = "https://github.com/helius-labs/zolana", tag = "v0.5.0-alpha" }
> zolana-transaction = { git = "https://github.com/helius-labs/zolana", tag = "v0.5.0-alpha" }
> zolana-test-utils = { git = "https://github.com/helius-labs/zolana", tag = "v0.5.0-alpha" }
> ```
>
> Source: [sdk-libs/client](https://github.com/helius-labs/zolana/tree/v0.5.0-alpha/sdk-libs/client)
>
> > [accordion] Connect to Endpoints
> >
> > > [tab] Devnet
> > >
> > > Add a [Helius API key](https://dashboard.helius.dev/):
> > >
> > > ```bash .env theme={"system"}
> > > API_KEY=YOUR_API_KEY
> > > ZOLANA_PAYER_KEYPAIR=~/.config/solana/id.json
> > > ```
> > >
> > > ```rust theme={"system"}
> > > use zolana_client::{SolanaRpc, ZolanaClient};
> > > use zolana_interface::pda;
> > >
> > > let tree = pda::tree(0);
> > > let url = "https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY";
> > > let client = ZolanaClient::from_urls(SolanaRpc::new(url), url, url)?;
> > > ```
> > >
> > > The examples use the Solana CLI wallet as the payer by default. The payer must hold devnet SOL. See [How to Get Devnet SOL](https://www.helius.dev/docs/rpc/devnet-sol.md).
> >
> > > [tab] Localnet
> > >
> > > ```bash theme={"system"}
> > > cargo install --git https://github.com/helius-labs/zolana --tag v0.5.0-alpha zolana-cli
> > > zolana dev start
> > > ```
> > >
> > > ```rust theme={"system"}
> > > use zolana_client::{SolanaRpc, ZolanaClient};
> > > use zolana_interface::pda;
> > >
> > > let tree = pda::tree(0);
> > > let client = ZolanaClient::from_urls(
> > >     SolanaRpc::new("http://127.0.0.1:8899"),
> > >     "http://127.0.0.1:8784",
> > >     "http://127.0.0.1:3001",
> > > )?;
> > > ```
>
> **Step 2**
>
> ### Deposit to a Private Balance
>
> Each SPL mint needs a token interface to hold deposited tokens. If the interface does not exist, create it in the same transaction as the deposit. The creator pays the account rent.
>
> > [tab] Deposit Tokens with an Existing Interface
> >
> > ```rust theme={"system"}
> > use zolana_program::instruction::{AssetDeposit, Deposit, DepositAsset};
> >
> >
> > let deposit_ix = Deposit {
> >     tree,
> >     depositor: sender.pubkey(),
> >     deposits: vec![AssetDeposit {
> >         asset: DepositAsset::Sol,
> >         // SPL: asset: DepositAsset::Spl(zolana_program::instruction::DepositSplAccounts {
> >         // SPL:     mint: spl.mint,
> >         // SPL:     user_token: spl.user_token_account,
> >         // SPL:     token_program: spl.token_program,
> >         // SPL: }),
> >         view_tag: sender_shielded_address.confidential_view_tag()?,
> >         owner: sender_shielded_address.owner_hash()?,
> >         amount: DEPOSIT_AMOUNT,
> >         memo: None,
> >     }],
> > }
> > .instruction()?;
> > ```
> >
> > > [accordion] 1. Move public SOL into the sender's private balance
> > >
> > > ```rust theme={"system"}
> > > use zolana_program::instruction::{AssetDeposit, Deposit, DepositAsset};
> > >
> > >
> > >     // 1. Move public SOL into the sender's private balance.
> > >         let deposit_ix = Deposit {
> > >             tree,
> > >             depositor: sender.pubkey(),
> > >             deposits: vec![AssetDeposit {
> > >                 asset: DepositAsset::Sol,
> > >                 // SPL: asset: DepositAsset::Spl(zolana_program::instruction::DepositSplAccounts {
> > >                 // SPL:     mint: spl.mint,
> > >                 // SPL:     user_token: spl.user_token_account,
> > >                 // SPL:     token_program: spl.token_program,
> > >                 // SPL: }),
> > >                 view_tag: sender_shielded_address.confidential_view_tag()?,
> > >                 owner: sender_shielded_address.owner_hash()?,
> > >                 amount: DEPOSIT_AMOUNT,
> > >                 memo: None,
> > >             }],
> > >         }
> > >         .instruction()?;
> > > ```
> > >
> > > * `sender_shielded_address` is the recipient's Shielded Address (The public key bundle (signing, nullifier, and viewing keys) published in a user's registry record. Not an onchain address.). This example deposits to the sender. The recipient can be the sender or a third-party wallet.
> > > * `view_tag` is `sender_shielded_address.confidential_view_tag()`, the sender's Solana public key in confidential rings. The indexer uses it to return matching encrypted outputs.
> > > * `tree` is the state Merkle tree that receives the new output commitment.
> > > * `depositor` / `sender` funds and signs the public-to-private deposit.
> > > * `DepositAsset::Sol` selects SOL. The `// SPL:` comments show `DepositAsset::Spl` with mint, source token account, and token program for SPL and Token 2022 assets.
> > > * `owner` is `sender_shielded_address.owner_hash()`. That hash of the recipient's signing and nullifier public keys is the owner field committed in the new private token account.
> > > * `DEPOSIT_AMOUNT` uses lamports for SOL, or the token's base units for SPL and Token 2022 assets. `memo` is `None`.
> >
> > > [accordion] 2. Send like any Solana transaction
> > >
> > > ```rust theme={"system"}
> > > use zolana_client::Rpc;
> > >
> > > let signature = client.create_and_send_transaction(
> > >     &[deposit_ix],
> > >     sender.pubkey(),
> > >     &[&sender],
> > >     client.compute_budget(),
> > > )?;
> > > let slot = landed_slot(&client, signature)?;
> > > ```
> > >
> > > * `create_and_send_transaction` signs and submits `deposit_ix` as a Solana transaction.
> > > * `landed_slot` reads the confirmation slot used to gate the indexer fetch.
> > > * `sender` pays the fee and authorizes the public-to-private deposit.
>
> > [tab] Deposit Tokens without an Interface
> >
> > ```mermaid theme={"system"}
> > %%{init: {
> >   'theme': 'base',
> >   'themeVariables': {
> >     'primaryColor': '#FFFFFF',
> >     'primaryTextColor': '#737373',
> >     'primaryBorderColor': '#9CA3AF',
> >     'lineColor': '#FF6B35',
> >     'edgeLabelBackground': '#FFFFFF'
> >   }
> > }}%%
> > flowchart LR
> >     GetAccount["Get account"] --> Exists{"Interface PDA exists?"}
> >     Exists -->|Yes| Deposit["Deposit to private balance"]
> >     Exists -->|No| Create["Create interface PDA<br/>and token account"]
> >     Create --> Deposit
> > ```
> >
> > ```rust theme={"system"}
> > use zolana_interface::pda;
> > use zolana_program::instruction::{
> >     AssetDeposit, CreateSplInterface, Deposit, DepositAsset, DepositSplAccounts,
> > };
> >
> > // 1. Check whether the mint already has an interface PDA, the escrow that holds deposited tokens.
> > // A second create fails the transaction, so skip step 2 when the account exists.
> > let interface_address = pda::spl_interface(&mint);
> > let interface_account = client.get_account(interface_address)?;
> > let mut instructions = Vec::new();
> >
> > // 2. Create the mint registry PDA and interface token account.
> > // The authority signer pays their rent.
> > if interface_account.is_none() {
> >     let create_interface_ix = CreateSplInterface {
> >         authority: sender_pubkey,
> >         mint,
> >         token_program,
> >     }
> >     .instruction();
> >     instructions.push(create_interface_ix);
> > }
> >
> > // 3. Move public tokens into the sender's private balance.
> > // A deposit from a public balance reveals sender, recipient, asset and amount.
> > let deposit_ix = Deposit {
> >     tree,
> >     depositor: sender_pubkey,
> >     deposits: vec![AssetDeposit {
> >         asset: DepositAsset::Spl(DepositSplAccounts {
> >             mint,
> >             user_token: source_token,
> >             token_program,
> >         }),
> >         view_tag: sender_shielded_address.confidential_view_tag()?,
> >         owner: sender_shielded_address.owner_hash()?,
> >         amount: DEPOSIT_AMOUNT,
> >         memo: None,
> >     }],
> > }
> > .instruction()?;
> >
> > // 4. Send the instructions in one transaction.
> > instructions.push(deposit_ix);
> > let signature = client.create_and_send_transaction(
> >     &instructions,
> >     sender_pubkey,
> >     &[&sender_solana_keypair],
> >     client.compute_budget(),
> > )?;
> > let slot = landed_slot(&client, signature)?;
> > ```
> >
> > > [!NOTE]
> > >
> > > Full example: [**Rust**](https://github.com/helius-labs/zolana-examples/blob/main/rust-client/examples/deposit_with_interface_setup.rs).
>
> ### Full Code Example
>
> Clone and run the example:
>
> ```bash theme={"system"}
> git clone https://github.com/helius-labs/zolana-examples.git
> cd zolana-examples
> cd rust-client
> cargo run -p rust-client-example --example deposit_transfer_withdraw
> ```
>
> > [!NOTE]
> >
> > The examples use a confidential Ring on local/devnet [here](https://github.com/helius-labs/zolana-examples/blob/main/rust-client/examples/deposit_transfer_withdraw.rs).
>
> ```rust deposit_transfer_withdraw.rs expandable theme={"system"}
> use anyhow::{anyhow, Result};
> use rust_client_example::{cli_keypair, landed_slot, setup};
> use solana_address::Address;
> use solana_keypair::Keypair;
> use solana_signer::Signer;
> use zolana_client::{IndexerRpcConfig, Rpc, SolanaRpc, ZolanaClient};
> use zolana_keypair::ShieldedKeypair;
> use zolana_program::instruction::{
>     AssetDeposit, Deposit, DepositAsset, Transact, TransactInterfaceTransferAccounts,
>     TransactSolTransferAccounts,
> };
> use zolana_transaction::{
>     decrypt_spendable, instructions::transact::ConfidentialTransaction, AssetRegistry, SOL_MINT,
> };
>
> const DEPOSIT_AMOUNT: u64 = 10_000_000;
> const TRANSFER_AMOUNT: u64 = 3_000_000;
> const WITHDRAW_AMOUNT: u64 = 3_000_000;
>
> fn main() -> Result<()> {
>     let context = setup()?;
>     let client = context.client()?;
>     run(&client, context.tree, &cli_keypair()?)
> }
>
> pub fn run(client: &ZolanaClient<SolanaRpc>, tree: Address, payer: &Keypair) -> Result<()> {
>     // Mints that are registered with Solana Privacy Rings for privacy.
>     let assets = AssetRegistry::default();
>     // SPL: assets.insert(spl.asset_id, spl.mint)?;
>
>     // Initialize the sender's private wallet and local authority
>     // to decrypt transactions and sync balances.
>     // The Solana signer and private wallet are derived from the same Ed25519 seed.
>     let sender = ShieldedKeypair::from_keypair(payer)?;
>     let recipient = ShieldedKeypair::from_keypair(&Keypair::new())?;
>     let sender_shielded_address = sender.shielded_address()?;
>
>     // Deposit SOL into the sender's private balance.
>     // A deposit from a public balance reveals
>     // sender, recipient, asset and amount.
>     // Alternatively, you can onramp fiat directly to a private balance.
>
>     // 1. Move public SOL into the sender's private balance.
>     let sender_balances_after_deposit = {
>         let deposit_ix = Deposit {
>             tree,
>             depositor: sender.pubkey(),
>             deposits: vec![AssetDeposit {
>                 asset: DepositAsset::Sol,
>                 // SPL: asset: DepositAsset::Spl(zolana_program::instruction::DepositSplAccounts {
>                 // SPL:     mint: spl.mint,
>                 // SPL:     user_token: spl.user_token_account,
>                 // SPL:     token_program: spl.token_program,
>                 // SPL: }),
>                 view_tag: sender_shielded_address.confidential_view_tag()?,
>                 owner: sender_shielded_address.owner_hash()?,
>                 amount: DEPOSIT_AMOUNT,
>                 memo: None,
>             }],
>         }
>         .instruction()?;
>
>         // 2. Send and confirm like any Solana transaction; the landed slot gates
>         // the indexer fetch below.
>         let signature = client.create_and_send_transaction(
>             &[deposit_ix],
>             sender.pubkey(),
>             &[&sender],
>             client.compute_budget(),
>         )?;
>         let slot = landed_slot(client, signature)?;
>
>         // 3. Fetch this transaction's outputs, gated on its confirmed slot.
>         let response = client.get_shielded_transactions_by_signature(
>             signature,
>             Some(IndexerRpcConfig::at_slot(slot)),
>         )?;
>         let transactions = response
>             .transactions
>             .into_iter()
>             .map(|indexed| indexed.transaction)
>             .collect::<Vec<_>>();
>
>         // 4. The sender decrypts the transaction outputs locally to read the funds deposited in this run.
>         let balances = decrypt_spendable(&sender, &transactions, &assets)
>             .map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?
>             .balances;
>
>         let sender_balance = balances
>             .get_balance(SOL_MINT)
>             // SPL: .get_balance(spl.mint)
>             .expect("failed to fetch sender's utxo");
>         assert_eq!(sender_balance.amount, DEPOSIT_AMOUNT);
>         assert_eq!(sender_balance.utxos.len(), 1);
>
>         balances
>     };
>
>     // Confidential SOL transfer to the recipient's private balance.
>     // A confidential transfer reveals only sender and recipient,
>     // not the asset or amount.
>     let sender_balances_after_transfer = {
>         // 1. Select UTXOs that make up the private balance for the transfer.
>         let transfer_utxo = sender_balances_after_deposit
>             .get_balance(SOL_MINT)
>             // SPL: .get_balance(spl.mint)
>             .and_then(|balance| balance.utxos.first())
>             .expect("failed to fetch deposited utxo")
>             .clone();
>
>         // 2. Prepare the selected UTXOs as inputs for the zero-knowledge proof.
>         let mut transfer = ConfidentialTransaction::new(vec![transfer_utxo], sender.pubkey())?;
>
>         // 3. Build and encrypt the confidential transfer.
>         // Encryption hides the asset and amount and produces the proof inputs for the ZK prover.
>         transfer.transfer_sol(&recipient.shielded_address()?, TRANSFER_AMOUNT)?;
>         // SPL: transfer.transfer(&recipient.shielded_address()?, spl.mint, TRANSFER_AMOUNT)?;
>         let proof_inputs = transfer.encrypt(&sender)?;
>
>         // 4. Fetch the zk proof to prove the sender can spend the balance without revealing asset and amount.
>         let transfer_data = client.prove_transact(proof_inputs, None, &sender)?;
>
>         // 5. Construct the instruction.
>         let transfer_ix = Transact {
>             payer: sender.pubkey(),
>             input_trees: vec![tree],
>             output_tree: tree,
>             owner_signers: vec![sender.pubkey()],
>             interface_transfer_accounts: Vec::new(),
>             data: transfer_data,
>         }
>         .instruction();
>
>         // 6. Send and confirm like any Solana transaction; confirmation yields the landed slot.
>         let signature = client.create_and_send_transaction(
>             &[transfer_ix],
>             sender.pubkey(),
>             &[&sender],
>             client.compute_budget(),
>         )?;
>         let slot = landed_slot(client, signature)?;
>
>         // 7. Fetch this transaction's outputs, gated on its confirmed slot.
>         let response = client.get_shielded_transactions_by_signature(
>             signature,
>             Some(IndexerRpcConfig::at_slot(slot)),
>         )?;
>         let transactions = response
>             .transactions
>             .into_iter()
>             .map(|indexed| indexed.transaction)
>             .collect::<Vec<_>>();
>         let sender_balances = decrypt_spendable(&sender, &transactions, &assets)
>             .map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?
>             .balances;
>         let sender_balance = sender_balances
>             .get_balance(SOL_MINT)
>             // SPL: .get_balance(spl.mint)
>             .expect("failed to fetch sender's utxo");
>         assert_eq!(sender_balance.amount, DEPOSIT_AMOUNT - TRANSFER_AMOUNT);
>         assert_eq!(sender_balance.utxos.len(), 1);
>
>         sender_balances
>     };
>
>     // Withdraw SOL back to the sender's public balance.
>     // A withdrawal from a confidential balance reveals
>     // sender, recipient, asset and amount.
>     {
>         // 1. Select UTXOs that make up the private balance for the withdrawal.
>         let withdrawal_utxo = sender_balances_after_transfer
>             .get_balance(SOL_MINT)
>             // SPL: .get_balance(spl.mint)
>             .and_then(|balance| balance.utxos.first())
>             .expect("failed to fetch sender's utxo")
>             .clone();
>
>         // 2. Prepare the selected UTXOs as inputs for the zero-knowledge proof.
>         let mut withdrawal = ConfidentialTransaction::new(vec![withdrawal_utxo], sender.pubkey())?;
>
>         // 3. Build and encrypt the confidential withdrawal.
>         // Encryption hides the private change and produces the ZK prover inputs.
>         withdrawal.withdraw_sol(WITHDRAW_AMOUNT, sender.pubkey())?;
>         // SPL: withdrawal.withdraw(spl.mint, WITHDRAW_AMOUNT, spl.user_token_account)?;
>         let proof_inputs = withdrawal.encrypt(&sender)?;
>
>         // 4. Fetch the ZK proof to prove the sender can spend the balance.
>         let withdrawal_data = client.prove_transact(proof_inputs, None, &sender)?;
>
>         // 5. Combine the proof and withdrawal accounts in a single instruction.
>         let withdraw_ix = Transact {
>             payer: sender.pubkey(),
>             input_trees: vec![tree],
>             output_tree: tree,
>             owner_signers: vec![sender.pubkey()],
>             interface_transfer_accounts: vec![TransactInterfaceTransferAccounts::Sol(
>                 TransactSolTransferAccounts {
>                     recipient: sender.pubkey(),
>                 },
>             )],
>             // SPL: interface_transfer_accounts: vec![
>             // SPL:     TransactInterfaceTransferAccounts::SplWithdrawal(
>             // SPL:         zolana_program::instruction::TransactSplWithdrawalAccounts {
>             // SPL:             mint: spl.mint,
>             // SPL:             spl_interface: spl.vault,
>             // SPL:             user_token_account: spl.user_token_account,
>             // SPL:             token_program: spl.token_program,
>             // SPL:         },
>             // SPL:     ),
>             // SPL: ],
>             data: withdrawal_data,
>         }
>         .instruction();
>
>         // 6. Send and confirm like any Solana transaction.
>         let signature = client.create_and_send_transaction(
>             &[withdraw_ix],
>             sender.pubkey(),
>             &[&sender],
>             client.compute_budget(),
>         )?;
>         let slot = landed_slot(client, signature)?;
>
>         // 7. Fetch this transaction's outputs, gated on its confirmed slot.
>         let response = client.get_shielded_transactions_by_signature(
>             signature,
>             Some(IndexerRpcConfig::at_slot(slot)),
>         )?;
>         let transactions = response
>             .transactions
>             .into_iter()
>             .map(|indexed| indexed.transaction)
>             .collect::<Vec<_>>();
>         let sender_balances = decrypt_spendable(&sender, &transactions, &assets)
>             .map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?
>             .balances;
>         let sender_balance = sender_balances
>             .get_balance(SOL_MINT)
>             // SPL: .get_balance(spl.mint)
>             .expect("failed to fetch sender's utxo");
>         assert_eq!(
>             sender_balance.amount,
>             DEPOSIT_AMOUNT - TRANSFER_AMOUNT - WITHDRAW_AMOUNT
>         );
>         assert_eq!(sender_balance.utxos.len(), 1);
>
>         // 8. Read the funds remaining from this run and the public SOL balance.
>         let solana_balance = client.get_balance(sender.pubkey())?;
>         println!("withdraw solana_balance={solana_balance} tx={signature}");
>         // SPL: println!(
>         // SPL:     "withdraw user_token={} tx={signature}",
>         // SPL:     spl.user_token_account,
>         // SPL: );
>     }
>     Ok(())
> }
> ```

## Related Guides

> [card] [Create a Private Wallet](https://www.helius.dev/docs/privacy/integration/wallet.md#create-private-wallet)

> [card] [Transfer](https://www.helius.dev/docs/privacy/guides/transfer.md)

> [card] [Withdraw](https://www.helius.dev/docs/privacy/guides/withdraw.md)

> [card] [Read a Private Balance](https://www.helius.dev/docs/privacy/guides/read-balance.md)

> [card] [Read Private History](https://www.helius.dev/docs/privacy/guides/read-history.md)

## Didn't find what you were looking for?

Reach out! [Telegram](https://t.me/tilo_light) | [E-Mail](mailto:sales@helius.xyz) | [Contact](https://www.helius.dev/contact)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.