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

# Chuyển tài sản

> Hướng dẫn chuyển SOL, SPL hoặc tài sản Token 2022 giữa các số dư riêng tư, kèm ví dụ mã đầy đủ.

1. Một giao dịch chuyển riêng tư di chuyển token trong một Ring giữa các ví riêng tư.
2. Giao dịch chuyển riêng tư được gửi trong một giao dịch Solana duy nhất đến địa chỉ ví Solana.

#### Chuyển tài sản: Nội dung nào là riêng tư

<Tabs>
  <Tab title="Permissionless Confidential Ring">
    | Trường            | Khả năng hiển thị | Lý do                                                          |
    | ----------------- | ----------------- | -------------------------------------------------------------- |
    | Tài sản           | Riêng tư          | Tài sản được mã hóa trên chuỗi                                 |
    | Số lượng          | Riêng tư          | Số lượng được chuyển được mã hóa trên chuỗi                    |
    | Ví riêng tư nguồn | Công khai         | Trong một Ring bảo mật, ví riêng tư nguồn hiển thị trên chuỗi. |
    | Người nhận        | Công khai         | Trong một Ring bảo mật, người nhận hiển thị trên chuỗi.        |
  </Tab>

  <Tab title="Custom Rings">
    Các giao dịch chuyển từ và trong một Ring sẽ tiết lộ ID chương trình của Ring tùy chỉnh.

    Số dư trong một Ring tùy chỉnh có thể được chuyển sang tài khoản token SPL, Ring mặc định hoặc một Ring khác, miễn là chính sách của Ring nguồn cho phép.
    Ring mặc định không cần cấp quyền và không có chính sách hoặc cơ quan quản lý.

    Một giao dịch có thể kết hợp số dư từ Ring mặc định và một Ring tùy chỉnh. Giao dịch chuyển giữa hai Ring tùy chỉnh sẽ được định tuyến qua Ring mặc định.

    | Chuyển khoản riêng tư              | Ring bảo mật tùy chỉnh | Ring ẩn danh tùy chỉnh                                                                                | Từ Ring bảo mật mặc định đến Ring bảo mật tùy chỉnh hoặc ngược lại | Từ Ring bảo mật (mặc định hoặc tùy chỉnh) đến Ring ẩn danh tùy chỉnh | Từ Ring ẩn danh tùy chỉnh đến Ring bảo mật (mặc định hoặc tùy chỉnh)                                  |
    | ---------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
    | Số lượng                           | Riêng tư               | Riêng tư                                                                                              | Riêng tư                                                           | Riêng tư                                                             | Riêng tư                                                                                              |
    | Tài sản                            | Riêng tư               | Riêng tư                                                                                              | Riêng tư                                                           | Riêng tư                                                             | Riêng tư                                                                                              |
    | Ví riêng tư nguồn                  | Công khai              | Riêng tư. Một bên chuyển tiếp gửi giao dịch, vì vậy sổ cái công khai không tiết lộ ví riêng tư nguồn. | Công khai                                                          | Công khai                                                            | Riêng tư. Một bên chuyển tiếp gửi giao dịch, vì vậy sổ cái công khai không tiết lộ ví riêng tư nguồn. |
    | Người nhận                         | Công khai              | Riêng tư                                                                                              | Công khai                                                          | Riêng tư                                                             | Công khai                                                                                             |
    | ID chương trình của Ring tùy chỉnh | Công khai              | Công khai                                                                                             | Công khai                                                          | Công khai                                                            | Công khai                                                                                             |
  </Tab>
</Tabs>

<Info>
  Ring không cần cấp quyền có tính bảo mật, với số lượng và tài sản được mã hóa.
  Có thể cấu hình Ring tùy chỉnh ở chế độ bảo mật hoặc ẩn danh (mã hóa người gửi, người nhận, tài sản và số lượng).
</Info>

## Cách hoạt động của giao dịch chuyển tài sản

Một giao dịch chuyển riêng tư hoạt động tương tự giao dịch chuyển Solana công khai:

1. Số dư SOL hoặc SPL của người dùng được mã hóa trên chuỗi.

2. Người dùng giải mã trạng thái riêng tư, ví tạo giao dịch chuyển và chủ sở hữu ký.

   * Truy xuất trạng thái đã mã hóa bằng các phương thức RPC chuyên dụng. Chỉ người dùng mới có thể giải mã số dư cục bộ.
   * Ví thiết lập số lượng và người nhận, sau đó yêu cầu bằng chứng ZK. Theo mặc định, nhà cung cấp RPC tạo và trả về bằng chứng ZK.

3. Môi trường thực thi Solana xác minh chữ ký và gọi Solana Privacy Program. Chương trình này xác minh bằng chứng ZK mà không tiết lộ trạng thái đã mã hóa.

4. Ứng dụng theo dõi trạng thái thông qua hàm băm giao dịch 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. Số dư SOL hoặc SPL của người dùng được công khai trên chuỗi.
  2. Ví đọc trạng thái công khai, tạo giao dịch chuyển và chủ sở hữu ký.
  3. Môi trường thực thi Solana xác minh chữ ký và gọi System Program hoặc Token Program để cập nhật số dư công khai.
  4. Ứng dụng theo dõi trạng thái thông qua hàm băm giao dịch 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>
  Đây là luồng giao dịch cấp cao dành cho Ring bảo mật không cần cấp quyền.
  So sánh với các Ring tùy chỉnh trong phần [khái niệm](/docs/vi/privacy/concepts#luồng-giao-dịch-cấp-cao).
</Info>

## Bắt đầu

<Tabs>
  <Tab title="TypeScript Client">
    <Steps>
      <Step>
        ### Điều kiện tiên quyết

        <Info>
          Các ví dụ TypeScript yêu cầu Node.js 24 trở lên, pnpm 11.18.0 và 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
        ```

        Nguồn: [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
              ```

              Thêm [khóa 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",
              });
              ```

              Theo mặc định, các ví dụ sử dụng ví Solana CLI làm bên thanh toán. Bên thanh toán phải có SOL trên devnet. Xem [Cách nhận SOL trên devnet](/docs/vi/rpc/devnet-sol).
            </Tab>

            <Tab title="Localnet">
              Trên localnet, SDK khởi động trình xác thực kiểm thử cục bộ (`:8899`), trình lập chỉ mục Photon (`:8784`) và trình chứng minh (`:3001`), đồng thời
              máy khách tự động kết nối với chúng mà không cần cấu hình điểm cuối.

              ```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>
        ### Chuyển đến số dư riêng tư

        <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 trả về các lệnh. Ứng dụng ký và gửi các lệnh đó.
          * `sendAndConfirmFactory` tạo một giao dịch Kit, gửi giao dịch đó và trả về chữ ký cùng slot đã được ghi nhận.
        </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]!;
            ```

            * Ví dụ sử dụng Tài khoản Token Solana Riêng tư được tạo từ khoản nạp trước đó. Một giao dịch chuyển có thể sử dụng nhiều UTXO.
            * `transferUtxo` chọn một Tài khoản Token Solana Riêng tư từ số dư đó.
          </Accordion>

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

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

            * `ProofInputUtxo.fromKeypair` chuẩn bị UTXO đã chọn làm đầu vào bằng chứng bằng cặp khóa ví riêng tư của người gửi.
            * Cặp khóa tạo ra nullifier để đánh dấu UTXO đầu vào là đã được sử dụng, trong khi tài sản và số lượng vẫn được mã hóa.
          </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` là <Tooltip tip="The public key bundle (signing, nullifier, and viewing keys) published in a user's registry record. Not an onchain address.">Địa chỉ được bảo vệ</Tooltip> của người gửi. Giao dịch chuyển sử dụng tài sản từ ví này.
            * `[transferInput]` liệt kê các UTXO đã chọn của người gửi. Một giao dịch chuyển có thể sử dụng nhiều UTXO.
            * `senderSigner.address` là địa chỉ Solana của người trả phí. Nhà tài trợ phí gas có thể thanh toán khoản phí này.
            * `recipient.shieldedAddress()` là Địa chỉ được bảo vệ của người nhận. Đầu ra được chuyển sẽ được mã hóa bằng khóa xem của người nhận.
            * `SOL_MINT` chọn SOL. Với giao dịch chuyển SPL hoặc Token 2022, hãy truyền mint của token.
            * `TRANSFER_AMOUNT` được tính theo đơn vị cơ sở của tài sản. SOL sử dụng lamport. Tài sản SPL và Token 2022 sử dụng đơn vị cơ sở của token.
            * `transfer.sign` cho phép chuyển đổi trạng thái, mã hóa tài sản và số lượng, đồng thời tạo đầu vào cho trình tạo bằng chứng không tiết lộ.
            * `assets` là sổ đăng ký tài sản dùng để phân giải các tài sản riêng tư được hỗ trợ.
          </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` sử dụng `LocalKeys.fromKeypair(sender, client.proofService)` để cho phép tạo bằng chứng bằng các khóa của người gửi.
            * `client.proveTransact` tạo bằng chứng không tiết lộ từ giao dịch chuyển đã ký và trả về dữ liệu lệnh được tuần tự hóa.
            * Bằng chứng cho thấy người gửi sở hữu và có thể sử dụng các đầu vào mà không tiết lộ tài sản hoặc số lượng đã mã hóa.
          </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` ký và thanh toán cho giao dịch Solana. Nhà tài trợ phí gas có thể thanh toán khoản phí này.
            * `inputTree` và `outputTree` là `client.tree`, cây Merkle trạng thái chứa các UTXO đã sử dụng và nhận đầu ra của người nhận cùng tiền thừa của người gửi.
            * `await transactInstruction` tạo cục bộ địa chỉ tài khoản nullifier và trả về lệnh. Các tài khoản nullifier đánh dấu UTXO đầu vào là đã được sử dụng để số dư riêng tư không thể bị chi tiêu hai lần.
            * `data` chứa bằng chứng không tiết lộ và các đầu ra đã mã hóa được tạo ở bước trước.
            * Giao dịch chuyển riêng tư chỉ di chuyển tài sản giữa các số dư riêng tư. Giao dịch không truyền tài khoản Solana hoặc tài khoản token công khai.
          </Accordion>

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

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

            * `sendAndConfirm` ký và gửi `transferInstruction` dưới dạng một giao dịch Solana.
            * Quá trình xác nhận trả về slot đã được ghi nhận, dùng để kiểm soát thời điểm truy xuất từ trình lập chỉ mục.
          </Accordion>
        </AccordionGroup>
      </Step>
    </Steps>

    ### Ví dụ mã đầy đủ

    Sao chép kho lưu trữ và chạy ví dụ:

    ```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>
      Các ví dụ sử dụng một Ring bảo mật trên local/devnet [tại đây](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>
        ### Điều kiện tiên quyết

        <Info>
          Các ví dụ Rust yêu cầu Rust 1.98.1 và Solana CLI v4.0.2. Xem [hướng dẫn cài đặt 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" }
        ```

        Nguồn: [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">
              Thêm [khóa 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",
              )?;
              ```

              Theo mặc định, các ví dụ sử dụng ví Solana CLI làm tài khoản thanh toán. Tài khoản thanh toán phải có SOL trên devnet. Xem [Cách nhận SOL trên devnet](/docs/vi/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>
        ### Chuyển đến số dư riêng tư

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

            * Ví dụ sử dụng Tài khoản Token Solana Riêng tư được tạo từ khoản nạp trước đó. Một giao dịch chuyển có thể sử dụng nhiều UTXO.
            * `transfer_utxo` là UTXO có thể sử dụng đầu tiên cho tài sản đó. Chú thích `// SPL:` minh họa `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` nhận trực tiếp các UTXO đã chọn.
            * `vec![transfer_utxo]` liệt kê các đầu vào. `sender.pubkey()` là người trả phí giao dịch.
          </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()?` là Địa chỉ được bảo vệ của người nhận. Đầu ra được chuyển sẽ được mã hóa bằng khóa xem của người nhận.
            * `transfer.transfer_sol` chọn SOL. Chú thích `// SPL:` minh họa mint của token cho tài sản SPL và Token 2022.
            * `TRANSFER_AMOUNT` được tính theo đơn vị cơ sở của tài sản. SOL sử dụng lamport. Tài sản SPL và Token 2022 sử dụng đơn vị cơ sở của token.
            * `transfer.encrypt(&sender)` mã hóa các đầu ra và tạo đầu vào cho trình tạo bằng chứng không tiết lộ.
          </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` tạo bằng chứng không tiết lộ từ giao dịch chuyển đã mã hóa và trả về dữ liệu lệnh được tuần tự hóa.
            * `sender` cung cấp các khóa dùng để cho phép tạo bằng chứng. Các UTXO đầu vào xác định cây trạng thái của chúng.
            * Bằng chứng cho thấy người gửi sở hữu và có thể sử dụng các đầu vào mà không tiết lộ tài sản hoặc số lượng đã mã hóa.
          </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` ký và thanh toán cho giao dịch Solana. Nhà tài trợ phí gas có thể thanh toán khoản phí này.
            * `input_trees` xác định cây Merkle trạng thái chứa các UTXO đã sử dụng.
            * `output_tree` xác định cây Merkle trạng thái nhận các cam kết đối với đầu ra của người nhận và tiền thừa của người gửi.
            * `interface_transfer_accounts` để trống vì giao dịch chuyển riêng tư chỉ di chuyển tài sản giữa các số dư riêng tư và không tương tác với số dư công khai trong tài khoản Solana hoặc tài khoản token.
            * `owner_signers` để trống cho giao dịch chuyển bảo mật này.
            * `data` chứa bằng chứng không tiết lộ và các đầu ra đã mã hóa được tạo ở bước trước.
          </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` ký và gửi `transfer_ix` dưới dạng một giao dịch Solana.
            * `landed_slot` đọc slot xác nhận dùng để kiểm soát thời điểm truy xuất từ trình lập chỉ mục.
            * `sender` thanh toán phí và cho phép giao dịch chuyển.
          </Accordion>
        </AccordionGroup>
      </Step>
    </Steps>

    ### Ví dụ mã đầy đủ

    Sao chép kho lưu trữ và chạy ví dụ:

    ```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>
      Các ví dụ sử dụng một Ring bảo mật trên local/devnet [tại đây](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>

## Hướng dẫn liên quan

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

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

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

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

## Không tìm thấy nội dung bạn đang tìm kiếm?

<Callout type="info">
  Hãy liên hệ với chúng tôi! [Telegram](https://t.me/tilo_light) | [Email](mailto:sales@helius.xyz) | [Liên hệ](https://www.helius.dev/contact)
</Callout>
