> ## 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.

# Transfer

> Panduan untuk mentransfer aset SOL, SPL, atau Token 2022 antar-saldo privat dengan contoh kode lengkap.

1. Transfer privat memindahkan token dalam sebuah Ring antar-dompet privat.
2. Transfer privat dikirim dalam satu transaksi Solana ke alamat dompet Solana.

#### Transfer: Apa yang Bersifat Privat

<Tabs>
  <Tab title="Permissionless Confidential Ring">
    | Bidang               | Visibilitas | Alasan                                                            |
    | -------------------- | ----------- | ----------------------------------------------------------------- |
    | Aset                 | Privat      | Aset dienkripsi secara onchain                                    |
    | Jumlah               | Privat      | Jumlah yang ditransfer dienkripsi secara onchain                  |
    | Dompet privat sumber | Publik      | Dalam Ring rahasia, dompet privat sumber terlihat secara onchain. |
    | Penerima             | Publik      | Dalam Ring rahasia, penerima terlihat secara onchain.             |
  </Tab>

  <Tab title="Custom Rings">
    Transfer dari dan di dalam sebuah Ring mengungkapkan ID program Ring khusus tersebut.

    Saldo dalam Ring Khusus dapat dipindahkan ke akun token SPL, Ring Default, atau Ring lain, selama kebijakan Ring sumber mengizinkannya.
    Ring default bersifat tanpa izin dan tidak memiliki kebijakan atau otoritas.

    Satu transaksi dapat menggabungkan saldo dari Ring Default dan satu Ring Khusus. Transfer antara dua Ring Khusus dirutekan melalui Ring Default.

    | Transfer privat        | Ring rahasia khusus | Ring anonim khusus                                                                                          | Ring rahasia Default ke atau dari Ring rahasia khusus | Ring rahasia (Default atau Khusus) ke Ring anonim khusus | Ring anonim khusus ke Ring rahasia (Default atau Khusus)                                                    |
    | ---------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
    | Jumlah                 | Privat              | Privat                                                                                                      | Privat                                                | Privat                                                   | Privat                                                                                                      |
    | Aset                   | Privat              | Privat                                                                                                      | Privat                                                | Privat                                                   | Privat                                                                                                      |
    | Dompet privat sumber   | Publik              | Privat. Relayer mengirimkan transaksi, sehingga buku besar publik tidak mengungkapkan dompet privat sumber. | Publik                                                | Publik                                                   | Privat. Relayer mengirimkan transaksi, sehingga buku besar publik tidak mengungkapkan dompet privat sumber. |
    | Penerima               | Publik              | Privat                                                                                                      | Publik                                                | Privat                                                   | Publik                                                                                                      |
    | ID program Ring Khusus | Publik              | Publik                                                                                                      | Publik                                                | Publik                                                   | Publik                                                                                                      |
  </Tab>
</Tabs>

<Info>
  Ring tanpa izin bersifat rahasia, dengan jumlah dan aset yang dienkripsi.
  Ring khusus dapat dikonfigurasi sebagai rahasia atau anonim (pengirim, penerima, aset, dan jumlah dienkripsi).
</Info>

## Cara Kerja Transfer

Transfer privat bekerja serupa dengan transfer Solana publik:

1. Saldo SOL atau SPL pengguna dienkripsi secara on-chain.

2. Pengguna mendekripsi status privat, dompet membuat transfer, dan pemilik menandatanganinya.

   * Ambil status terenkripsi dengan metode RPC khusus. Hanya pengguna yang dapat mendekripsi saldo secara lokal.
   * Dompet menetapkan jumlah dan penerima, lalu meminta bukti ZK. Secara default, penyedia RPC menghasilkan bukti ZK dan mengembalikannya.

3. Runtime Solana memverifikasi tanda tangan dan memanggil Solana Privacy Program, yang memverifikasi bukti ZK tanpa mengungkapkan status terenkripsi.

4. Aplikasi melacak status melalui hash transaksi Solana.

```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: Fetch encrypted state
    RPC-->>Wallet: Encrypted state
    Note over Wallet: Decrypt

    Note over Wallet: Set amount and recipient
    Wallet->>RPC: Request ZK proof
    Note over RPC: Generate ZK proof
    RPC-->>Wallet: ZK proof
    Note over Wallet: Build transaction, sign

    Wallet->>RPC: Submit Transaction
    RPC->>Solana: Forward transaction
    Note over Solana: Verify signatures
    Note over Solana: CPI Solana Privacy Program
    Note over Solana: Verify ZK proof
    RPC-->>Wallet: Transaction signature
```

<Accordion title="Compare to Solana Transfer">
  1. Saldo SOL atau SPL pengguna tersedia secara publik di on-chain.
  2. Dompet membaca status publik, membuat transfer, dan pemilik menandatanganinya.
  3. Runtime Solana memverifikasi tanda tangan dan memanggil System Program atau Token Program, yang memperbarui saldo publik.
  4. Aplikasi melacak status melalui hash transaksi Solana.

  ```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
  ```
</Accordion>

<Info>
  Ini adalah alur transaksi tingkat tinggi untuk Ring rahasia tanpa izin.
  Bandingkan dengan Ring khusus di [konsep](/docs/id/privacy/concepts#alur-transaksi-tingkat-tinggi).
</Info>

## Mulai

<Tabs>
  <Tab title="TypeScript Client">
    <Steps>
      <Step>
        ### Prasyarat

        <Info>
          Contoh TypeScript memerlukan Node.js 24 atau versi yang lebih baru, pnpm 11.18.0, dan Solana CLI.
        </Info>

        ```bash theme={"system"}
        pnpm add 'git+ssh://git@github.com/helius-labs/zolana.git#v0.3.0-alpha&path:/sdk-libs/ts' @solana/kit@^8.3.0
        ```

        Sumber: [sdk-libs/ts](https://github.com/helius-labs/zolana/tree/v0.3.0-alpha/sdk-libs/ts)

        <Accordion title="Connect to Endpoints">
          <Tabs>
            <Tab title="Devnet">
              ```bash theme={"system"}
              pnpm install
              cp .env.example .env
              ```

              Tambahkan [kunci API Helius](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",
                indexerUrl: "https://d2xah7tnhdhcom.cloudfront.net",
                proverUrl: "https://d21ni15goiip6l.cloudfront.net",
              });
              ```

              Contoh-contoh ini menggunakan dompet CLI Solana sebagai pembayar secara default. Pembayar harus memiliki SOL devnet. Lihat [Cara Mendapatkan SOL Devnet](/docs/id/rpc/devnet-sol).
            </Tab>

            <Tab title="Localnet">
              Di localnet, SDK memulai validator pengujian lokal (`:8899`), pengindeks Photon (`:8784`), dan prover (`:3001`), lalu
              klien terhubung secara otomatis tanpa memerlukan konfigurasi endpoint.

              ```bash theme={"system"}
              cargo install --git https://github.com/helius-labs/zolana --tag v0.3.0-alpha zolana-cli
              zolana dev start
              ```

              ```ts theme={"system"}
              import { createZolanaClient } from "@heliuslabs/zolana";

              const client = await createZolanaClient({});
              ```
            </Tab>
          </Tabs>
        </Accordion>
      </Step>

      <Step>
        ### Transfer ke Saldo Privat

        <Accordion title="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 };
            };
          }
          ```

          * SDK mengembalikan instruksi. Aplikasi menandatangani dan mengirimkannya.
          * `sendAndConfirmFactory` membuat transaksi Kit, mengirimkannya, lalu mengembalikan tanda tangan beserta slot tempat transaksi masuk.
        </Accordion>

        <Tabs>
          <Tab title="SOL">
            ```typescript theme={"system"}
            import { LocalKeys } from "@heliuslabs/zolana/client";
            import { SOL_MINT } from "@heliuslabs/zolana";
            import { transactInstruction } from "@heliuslabs/zolana/interface";
            import {
              ConfidentialTransfer,
              ProofInputUtxo,
            } from "@heliuslabs/zolana/transaction";
            import { sendAndConfirmFactory } from "../src/lib.js";


              const sendAndConfirm = sendAndConfirmFactory(
                client,
                senderSigner,
              );
              const transferUtxo = depositBalance.utxos[0]!;
              const transferInput =
                ProofInputUtxo.fromKeypair(
                  transferUtxo,
                  sender,
                );
              const transfer = new ConfidentialTransfer(
                senderAddress,
                [transferInput],
                senderSigner.address,
              );
              transfer.send(
                recipient.shieldedAddress(),
                SOL_MINT,
                TRANSFER_AMOUNT,
              );
              const transferProofInputs = transfer.sign(
                sender,
                assets,
              );
              const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
              const transferData = await client.proveTransact(
                transferProofInputs,
                senderKeys,
              );
              const transferInstruction = await transactInstruction(
                {
                  payer: senderSigner,
                  inputTree: client.tree,
                  outputTree: client.tree,
                  data: transferData,
                },
              );
              const transferTx = await sendAndConfirm([
                transferInstruction,
              ]);
            ```
          </Tab>

          <Tab title="SPL">
            ```typescript theme={"system"}
            import { LocalKeys } from "@heliuslabs/zolana/client";
            import { transactInstruction } from "@heliuslabs/zolana/interface";
            import {
              ConfidentialTransfer,
              ProofInputUtxo,
            } from "@heliuslabs/zolana/transaction";
            import { sendAndConfirmFactory } from "../src/lib.js";


              const sendAndConfirm = sendAndConfirmFactory(
                client,
                senderSigner,
              );
              const transferUtxo = depositBalance.utxos[0]!;
              const transferInput =
                ProofInputUtxo.fromKeypair(
                  transferUtxo,
                  sender,
                );
              const transfer = new ConfidentialTransfer(
                senderAddress,
                [transferInput],
                senderSigner.address,
              );
              transfer.send(
                recipient.shieldedAddress(),
                spl.mint,
                TRANSFER_AMOUNT,
              );
              const transferProofInputs = transfer.sign(
                sender,
                assets,
              );
              const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
              const transferData = await client.proveTransact(
                transferProofInputs,
                senderKeys,
              );
              const transferInstruction = await transactInstruction(
                {
                  payer: senderSigner,
                  inputTree: client.tree,
                  outputTree: client.tree,
                  data: transferData,
                },
              );
              const transferTx = await sendAndConfirm([
                transferInstruction,
              ]);
            ```
          </Tab>
        </Tabs>

        <AccordionGroup>
          <Accordion title="1. Select private token accounts to spend">
            ```typescript theme={"system"}
            import { SOL_MINT } from "@heliuslabs/zolana";

            const transferUtxo = depositBalance.utxos[0]!;
            ```

            * Contoh ini membelanjakan Private Solana Token Account yang dibuat oleh deposit sebelumnya. Transfer dapat membelanjakan beberapa UTXO.
            * `transferUtxo` memilih satu Private Solana Token Account dari saldo tersebut.
          </Accordion>

          <Accordion title="2. Prepare proof inputs">
            ```typescript theme={"system"}
            import { ProofInputUtxo } from "@heliuslabs/zolana/transaction";

            const transferInput =
              ProofInputUtxo.fromKeypair(
                transferUtxo,
                sender,
              );
            ```

            * `ProofInputUtxo.fromKeypair` menyiapkan UTXO yang dipilih sebagai input bukti dengan pasangan kunci dompet privat milik pengirim.
            * Pasangan kunci menghasilkan nullifier yang menandai UTXO input sebagai telah dibelanjakan, sementara aset dan jumlah tetap terenkripsi.
          </Accordion>

          <Accordion title="3. Build and sign the confidential transfer">
            ```typescript theme={"system"}
            import { SOL_MINT } from "@heliuslabs/zolana";
            import { ConfidentialTransfer } from "@heliuslabs/zolana/transaction";

            const transfer = new ConfidentialTransfer(
              senderAddress,
              [transferInput],
              senderSigner.address,
            );
            transfer.send(
              recipient.shieldedAddress(),
              SOL_MINT,
              TRANSFER_AMOUNT,
            );
            const transferProofInputs = transfer.sign(
              sender,
              assets,
            );
            ```

            * `senderAddress` adalah <Tooltip tip="The public key bundle (signing, nullifier, and viewing keys) published in a user's registry record. Not an onchain address.">Shielded Address</Tooltip> milik pengirim. Transfer membelanjakan dana dari dompet ini.
            * `[transferInput]` mencantumkan UTXO yang dipilih pengirim. Transfer dapat membelanjakan beberapa UTXO.
            * `senderSigner.address` adalah alamat Solana pembayar biaya. Sponsor gas dapat membayar biaya tersebut.
            * `recipient.shieldedAddress()` adalah Shielded Address milik penerima. Output transfer dienkripsi menggunakan kunci tampilan penerima.
            * `SOL_MINT` memilih SOL. Transfer SPL atau Token 2022 meneruskan mint token.
            * `TRANSFER_AMOUNT` dinyatakan dalam unit dasar aset. SOL menggunakan lamport. Aset SPL dan Token 2022 menggunakan unit dasar token.
            * `transfer.sign` mengotorisasi transisi status, mengenkripsi aset dan jumlah, serta menghasilkan input untuk pembukti zero-knowledge.
            * `assets` adalah registri aset yang digunakan untuk menemukan aset privat yang didukung.
          </Accordion>

          <Accordion title="4. Fetch the zero-knowledge proof">
            ```typescript theme={"system"}
            import { LocalKeys } from "@heliuslabs/zolana/client";

            const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
            const transferData = await client.proveTransact(
              transferProofInputs,
              senderKeys,
            );
            ```

            * `senderKeys` menggunakan `LocalKeys.fromKeypair(sender, client.proofService)` untuk mengotorisasi pembuatan bukti dengan kunci pengirim.
            * `client.proveTransact` menghasilkan bukti zero-knowledge dari transfer yang telah ditandatangani dan mengembalikan data instruksi berseri.
            * Bukti tersebut menunjukkan bahwa pengirim memiliki dan dapat membelanjakan input tanpa mengungkapkan aset atau jumlah yang dienkripsi.
          </Accordion>

          <Accordion title="5. Build the transfer instruction">
            ```typescript theme={"system"}
            import { transactInstruction } from "@heliuslabs/zolana/interface";

            const transferInstruction = await transactInstruction(
              {
                payer: senderSigner,
                inputTree: client.tree,
                outputTree: client.tree,
                data: transferData,
              },
            );
            ```

            * `payer` menandatangani dan membayar transaksi Solana. Sponsor gas dapat membayar biayanya.
            * `inputTree` dan `outputTree` adalah `client.tree`, yaitu pohon Merkle status yang berisi UTXO yang dibelanjakan serta menerima output penerima dan kembalian pengirim.
            * `await transactInstruction` menghasilkan alamat akun nullifier secara lokal dan mengembalikan instruksi. Akun nullifier menandai UTXO input sebagai telah dibelanjakan sehingga saldo privat tidak dapat dibelanjakan dua kali.
            * `data` berisi bukti zero-knowledge dan output terenkripsi yang dihasilkan pada langkah sebelumnya.
            * Transfer privat hanya memindahkan aset antar-saldo privat. Transfer ini tidak meneruskan akun Solana publik atau akun token.
          </Accordion>

          <Accordion title="6. Send like any Solana transaction">
            ```typescript theme={"system"}
            import { sendAndConfirmFactory } from "../src/lib.js";

            const transferTx = await sendAndConfirm([
              transferInstruction,
            ]);
            ```

            * `sendAndConfirm` menandatangani dan mengirimkan `transferInstruction` sebagai transaksi Solana.
            * Konfirmasi menghasilkan slot tempat transaksi masuk yang digunakan untuk membatasi pengambilan data oleh pengindeks.
          </Accordion>
        </AccordionGroup>
      </Step>
    </Steps>

    ### Contoh Kode Lengkap

    Kloning dan jalankan contoh:

    ```bash theme={"system"}
    git clone https://github.com/helius-labs/zolana-examples.git
    cd zolana-examples
    git checkout v0.3.0-alpha
    cd typescript-client
    pnpm install
    pnpm example examples/deposit_transfer_withdraw.ts
    ```

    <Info>
      Contoh menggunakan Ring rahasia di local/devnet [di sini](https://github.com/helius-labs/zolana-examples/blob/v0.3.0-alpha/typescript-client/examples/deposit_transfer_withdraw.ts).
    </Info>

    ```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();

      // Connect to Helius devnet RPC plus the Photon indexer and prover.
      const client =
        await createZolanaClient(clientConfig);

      // 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 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 UTXOs.
      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 transaction outputs from the indexer, gated on the deposit's slot.
      // The indexer returns encrypted outputs by transaction signature.
      const depositResponse =
        await client.getShieldedTransactionsBySignature(
          depositTx.signature,
          atSlot(depositTx.slot),
        );

      // 4. The sender decrypts the transaction outputs locally to read the private balance.
      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,
          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 the sender's UTXOs from this transaction, gated on the transfer's slot,
      // and read the remaining private balance.
      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,
          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 the sender's UTXOs from this transaction, gated on the withdrawal's slot,
      // and read the remaining private balance.
      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>

  <Tab title="Rust Client">
    <Steps>
      <Step>
        ### Prasyarat

        <Info>
          Contoh Rust memerlukan Rust 1.98.1 dan Solana CLI v4.0.2. Lihat [panduan instalasi Solana](https://solana.com/docs/intro/installation).
        </Info>

        ```toml Cargo.toml theme={"system"}
        [dependencies]
        zolana-client = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha", features = ["indexer-api", "solana-rpc"] }
        zolana-interface = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
        zolana-program = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
        zolana-keypair = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
        zolana-transaction = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
        zolana-wallet = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
        ```

        Sumber: [sdk-libs/client](https://github.com/helius-labs/zolana/tree/v0.3.0-alpha/sdk-libs/client)

        <Accordion title="Connect to Endpoints">
          <Tabs>
            <Tab title="Devnet">
              Tambahkan [kunci API Helius](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 client = ZolanaClient::from_urls(
                  SolanaRpc::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY"),
                  "https://d2xah7tnhdhcom.cloudfront.net",
                  "https://d21ni15goiip6l.cloudfront.net",
              )?;
              ```

              Contoh-contoh ini menggunakan dompet Solana CLI sebagai pembayar secara default. Pembayar harus memiliki SOL devnet. Lihat [Cara Mendapatkan SOL Devnet](/docs/id/rpc/devnet-sol).
            </Tab>

            <Tab title="Localnet">
              ```bash theme={"system"}
              cargo install --git https://github.com/helius-labs/zolana --tag v0.3.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",
              )?;
              ```
            </Tab>
          </Tabs>
        </Accordion>
      </Step>

      <Step>
        ### Transfer ke Saldo Privat

        ```rust theme={"system"}
        use zolana_program::instruction::Transact;
        use zolana_transaction::{instructions::transact::ConfidentialTransaction, SOL_MINT};

        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();

        let mut transfer = ConfidentialTransaction::new(vec![transfer_utxo], sender.pubkey())?;

        transfer.transfer_sol(&recipient.shielded_address()?, TRANSFER_AMOUNT)?;
        // SPL: transfer.transfer(&recipient.shielded_address()?, spl.mint, TRANSFER_AMOUNT)?;
        let proof_inputs = transfer.encrypt(&sender)?;

        let transfer_data = client.prove_transact(proof_inputs, None, &sender)?;

        let transfer_ix = Transact {
            payer: sender.pubkey(),
            input_trees: vec![tree],
            output_tree: tree,
            owner_signers: Vec::new(),
            interface_transfer_accounts: Vec::new(),
            data: transfer_data,
        }
        .instruction();
        ```

        <AccordionGroup>
          <Accordion title="1. Select private token accounts to spend">
            ```rust theme={"system"}
            use zolana_transaction::SOL_MINT;

            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();
            ```

            * Contoh ini membelanjakan Private Solana Token Account yang dibuat oleh deposit sebelumnya. Transfer dapat membelanjakan beberapa UTXO.
            * `transfer_utxo` adalah UTXO pertama yang dapat dibelanjakan untuk aset tersebut. Komentar `// SPL:` menampilkan `get_balance(spl.mint)`.
          </Accordion>

          <Accordion title="2. Prepare proof inputs">
            ```rust theme={"system"}
            use zolana_transaction::instructions::transact::ConfidentialTransaction;

            let mut transfer = ConfidentialTransaction::new(vec![transfer_utxo], sender.pubkey())?;
            ```

            * `ConfidentialTransaction::new` menerima UTXO yang dipilih secara langsung.
            * `vec![transfer_utxo]` mencantumkan input. `sender.pubkey()` adalah pembayar biaya transaksi.
          </Accordion>

          <Accordion title="3. Build and encrypt the confidential transfer">
            ```rust theme={"system"}
            use solana_signer::Signer;

            transfer.transfer_sol(&recipient.shielded_address()?, TRANSFER_AMOUNT)?;
            // SPL: transfer.transfer(&recipient.shielded_address()?, spl.mint, TRANSFER_AMOUNT)?;
            let proof_inputs = transfer.encrypt(&sender)?;
            ```

            * `recipient.shielded_address()?` adalah Shielded Address milik penerima. Output transfer dienkripsi menggunakan kunci tampilan penerima.
            * `transfer.transfer_sol` memilih SOL. Komentar `// SPL:` menampilkan mint token untuk aset SPL dan Token 2022.
            * `TRANSFER_AMOUNT` dinyatakan dalam unit dasar aset. SOL menggunakan lamport. Aset SPL dan Token 2022 menggunakan unit dasar token.
            * `transfer.encrypt(&sender)` mengenkripsi output dan menghasilkan input untuk pembukti zero-knowledge.
          </Accordion>

          <Accordion title="4. Fetch the zero-knowledge proof">
            ```rust theme={"system"}
            use zolana_client::Rpc;

            let transfer_data = client.prove_transact(proof_inputs, None, &sender)?;
            ```

            * `client.prove_transact` menghasilkan bukti zero-knowledge dari transfer terenkripsi dan mengembalikan data instruksi berseri.
            * `sender` menyediakan kunci yang digunakan untuk mengotorisasi bukti. UTXO input mengidentifikasi pohon statusnya.
            * Bukti tersebut menunjukkan bahwa pengirim memiliki dan dapat membelanjakan input tanpa mengungkapkan aset atau jumlah yang dienkripsi.
          </Accordion>

          <Accordion title="5. Build the transfer instruction">
            ```rust theme={"system"}
            use zolana_program::instruction::Transact;

            let transfer_ix = Transact {
                payer: sender.pubkey(),
                input_trees: vec![tree],
                output_tree: tree,
                owner_signers: Vec::new(),
                interface_transfer_accounts: Vec::new(),
                data: transfer_data,
            }
            .instruction();
            ```

            * `payer` menandatangani dan membayar transaksi Solana. Sponsor gas dapat membayar biayanya.
            * `input_trees` mengidentifikasi pohon Merkle status yang berisi UTXO yang dibelanjakan.
            * `output_tree` mengidentifikasi pohon Merkle status yang menerima komitmen untuk output penerima dan kembalian pengirim.
            * `interface_transfer_accounts` kosong karena transfer privat hanya memindahkan aset antar-saldo privat dan tidak berinteraksi dengan saldo publik dalam akun Solana atau akun token.
            * `owner_signers` kosong untuk transfer rahasia ini.
            * `data` berisi bukti zero-knowledge dan output terenkripsi yang dihasilkan pada langkah sebelumnya.
          </Accordion>

          <Accordion title="6. Send like any Solana transaction">
            ```rust theme={"system"}
            use zolana_client::Rpc;

            let signature = client.create_and_send_transaction(
                &[transfer_ix],
                sender.pubkey(),
                &[&sender],
                client.compute_budget(),
            )?;
            let slot = landed_slot(&client, signature)?;
            ```

            * `create_and_send_transaction` menandatangani dan mengirimkan `transfer_ix` sebagai transaksi Solana.
            * `landed_slot` membaca slot konfirmasi yang digunakan untuk membatasi pengambilan data oleh pengindeks.
            * `sender` membayar biaya dan mengotorisasi transfer.
          </Accordion>
        </AccordionGroup>
      </Step>
    </Steps>

    ### Contoh Kode Lengkap

    Kloning dan jalankan contoh:

    ```bash theme={"system"}
    git clone https://github.com/helius-labs/zolana-examples.git
    cd zolana-examples
    git checkout v0.3.0-alpha
    cd rust-client
    cargo run -p rust-client-example --example deposit_transfer_withdraw
    ```

    <Info>
      Contoh menggunakan Ring rahasia di local/devnet [di sini](https://github.com/helius-labs/zolana-examples/blob/v0.3.0-alpha/rust-client/examples/deposit_transfer_withdraw.rs).
    </Info>

    ```rust deposit_transfer_withdraw.rs expandable theme={"system"}
    use anyhow::{anyhow, Result};
    use rust_client_example::{cli_keypair, landed_slot, setup, SetupContext};
    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 SetupContext {
            rpc_url,
            indexer_url,
            prover_url,
            tree,
        } = setup()?;

        // Connect to the RPC, indexer, and prover.
        let client = ZolanaClient::from_urls(SolanaRpc::new(rpc_url), &indexer_url, prover_url)?;

        // Mints that are registered with Solana 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(&cli_keypair()?)?;
        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 transaction outputs from the indexer, gated on the deposit's slot.
            // The indexer returns encrypted outputs by transaction signature.
            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 update the private balance.
            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::new(),
                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 the sender's UTXOs from this transaction, gated on the transfer's slot,
            // and read the remaining private balance.
            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::new(),
                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 the sender's UTXOs from this transaction, gated on the withdrawal's slot,
            // and read the remaining private balance.
            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 remaining private balance 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(())
    }
    ```
  </Tab>
</Tabs>

## Panduan Terkait

<CardGroup cols={2}>
  <Card title="Deposit" icon="arrow-down-to-bracket" href="/docs/id/privacy/guides/deposit" horizontal />

  <Card title="Withdraw" icon="arrow-up-from-bracket" href="/docs/id/privacy/guides/withdraw" horizontal />

  <Card title="Read a Private Balance" icon="wallet" href="/docs/id/privacy/guides/read-balance" horizontal />

  <Card title="Read Private History" icon="clock-rotate-left" href="/docs/id/privacy/guides/read-history" horizontal />
</CardGroup>

## Tidak menemukan yang Anda cari?

<Callout type="info">
  Hubungi kami! [Telegram](https://t.me/tilo_light) | [Email](mailto:sales@helius.xyz) | [Kontak](https://www.helius.dev/contact)
</Callout>
