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

# 提款

> 指南，从私人余额中提取SOL、SPL或Token 2022资产到公共Solana账户，附完整代码示例。

1. 提款将代币从私人余额转移到公共Solana余额。
2. 提款通过单一Solana交易发送到Solana钱包地址。

#### 提款：什么是私密的

| Field                     | Visibility | Why                                                                  |
| ------------------------- | ---------- | -------------------------------------------------------------------- |
| Source private wallet     | Public     | In a confidential Ring the source private wallet is visible onchain. |
| Asset                     | Public     | The asset is visible onchain                                         |
| Amount                    | Public     | The withdrawn amount is visible onchain                              |
| Destination public wallet | Public     | The destination wallet address is visible onchain                    |
| Resulting public balance  | Public     | The resulting public balance is visible onchain                      |
| Remaining private balance | Private    | The remaining balance is encrypted onchain                           |

<Info>
  无许可的Ring隐私性高，金额和资产加密。
  自定义Ring可以配置为保密或匿名（加密的发送者、接收者、资产和金额）。
</Info>

## 提款是如何工作的

提款的行为类似于公共Solana转账：

1. 用户的SOL或SPL余额在链上加密。

2. 用户解密私有状态，钱包构建提款，所有者签名。

   * 使用专用的RPC方法获取加密状态。只有用户可以在本地解密余额。
   * 钱包设置金额和接收者，然后请求ZK证明。RPC提供者默认生成ZK证明并返回。

3. Solana运行时验证签名并调用Solana隐私程序，该程序验证ZK证明而不揭示加密状态。

4. 应用通过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="与Solana转账比较">
  1. 用户的SOL或SPL余额在链上公开。
  2. 钱包读取公共状态，构建转账，并由所有者签名。
  3. Solana运行时验证签名并调用系统程序或代币程序，更新公共余额。
  4. 应用通过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>
  这是无许可的保密Ring的高级交易流程。
  与自定义Rings在[概念](/docs/zh/privacy/concepts#高级交易流程)中比较。
</Info>

# 开始

<Tabs>
  <Tab title="TypeScript 客户端">
    <Steps>
      <Step>
        ### 先决条件

        <Info>
          The TypeScript examples require Node.js 24 or later, pnpm 11.18.0, and the Solana CLI.
        </Info>

        ```bash theme={"system"}
        pnpm add @heliuslabs/zolana @solana/kit
        ```

        Source: [sdk-libs/ts](https://github.com/helius-labs/zolana/tree/main/sdk-libs/ts)

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

              Add a [Helius API key](https://dashboard.helius.dev/):

              ```bash .env theme={"system"}
              API_KEY=YOUR_API_KEY
              ZOLANA_PAYER_KEYPAIR=~/.config/solana/id.json
              ```

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

              const client = await createZolanaClient({
                solanaRpcUrl: "https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY",
                indexerUrl: "http://zolnet-devnet-1779374825.eu-north-1.elb.amazonaws.com",
                proverUrl: "http://zolnet-devnet-1779374825.eu-north-1.elb.amazonaws.com:3001",
                allowInsecureHttp: true,
              });
              ```

              The examples use the Solana CLI wallet as the payer by default. The payer must hold devnet SOL. See [How to Get Devnet SOL](/docs/rpc/devnet-sol).
            </Tab>

            <Tab title="Localnet">
              On localnet the SDK starts the local test validator (`:8899`), Photon indexer (`:8784`), and prover (`:3001`), and the
              client connects to them automatically without needing endpoint configuration.

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

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

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

      <Step>
        ### 提款到公共余额

        <Accordion title="Solana Kit send helper">
          ```typescript theme={"system"}
          import {
            appendTransactionMessageInstructions,
            assertIsTransactionWithBlockhashLifetime,
            createTransactionMessage,
            getSignatureFromTransaction,
            pipe,
            sendTransactionWithoutConfirmingFactory,
            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: 0 }),
                  (message) => setTransactionMessageFeePayerSigner(feePayer, message),
                  (message) =>
                    setTransactionMessageLifetimeUsingBlockhash(lifetime, 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返回指令。应用签名并发送它们。
          * `sendAndConfirmFactory`构建一个Kit交易，提交它，并返回签名和落地槽。
        </Accordion>

        <Tabs>
          <Tab title="SOL">
            ```typescript theme={"system"}
            import { SOL_MINT } from "@heliuslabs/zolana";
            import {
              transactInstruction,
              TransactWithdrawal,
            } from "@heliuslabs/zolana/interface";
            import {
              ConfidentialTransfer,
              ProofInputUtxo,
              WithdrawalTarget,
            } from "@heliuslabs/zolana/transaction";

            const withdrawalUtxo =
              transferBalance.utxos[0]!;
            const withdrawalInput =
              ProofInputUtxo.fromKeypair(
                withdrawalUtxo,
                senderKeypair,
              );
            const withdrawal = new ConfidentialTransfer(
              senderAddress,
              [withdrawalInput],
              senderSigner.address,
            );
            withdrawal.withdraw(
              SOL_MINT,
              WITHDRAW_AMOUNT,
              WithdrawalTarget.sol({
                recipient: senderSigner.address,
              }),
            );
            const withdrawalProofInputs = withdrawal.sign(
              senderKeypair,
              assets,
            );
            const withdrawalData =
              await client.proveTransact(
                withdrawalProofInputs,
              );
            const withdrawalInstruction =
              transactInstruction({
                payer: senderSigner,
                inputTree: client.tree,
                outputTree: client.tree,
                withdrawal: TransactWithdrawal.sol({
                  recipient: senderSigner.address,
                }),
                data: withdrawalData,
              });
            const withdrawalTx = await sendAndConfirm([
              withdrawalInstruction,
            ]);
            ```
          </Tab>

          <Tab title="SPL">
            ```typescript theme={"system"}
            import {
              transactInstruction,
              TransactWithdrawal,
            } from "@heliuslabs/zolana/interface";
            import {
              ConfidentialTransfer,
              ProofInputUtxo,
              WithdrawalTarget,
            } from "@heliuslabs/zolana/transaction";

            const withdrawalUtxo =
              transferBalance.utxos[0]!;
            const withdrawalInput =
              ProofInputUtxo.fromKeypair(
                withdrawalUtxo,
                senderKeypair,
              );
            const withdrawal = new ConfidentialTransfer(
              senderAddress,
              [withdrawalInput],
              senderSigner.address,
            );
            withdrawal.withdraw(
              spl.mint,
              WITHDRAW_AMOUNT,
              WithdrawalTarget.spl({
                userTokenAccount: spl.userTokenAccount,
                splTokenInterface: spl.splTokenInterface,
                vaultBump: spl.vaultBump,
              }),
            );
            const withdrawalProofInputs = withdrawal.sign(
              senderKeypair,
              assets,
            );
            const withdrawalData =
              await client.proveTransact(
                withdrawalProofInputs,
              );
            const withdrawalInstruction =
              transactInstruction({
                payer: senderSigner,
                inputTree: client.tree,
                outputTree: client.tree,
                withdrawal: TransactWithdrawal.spl({
                  mint: spl.mint,
                  splTokenInterface: spl.splTokenInterface,
                  userTokenAccount: spl.userTokenAccount,
                  tokenProgram: spl.tokenProgram,
                }),
                data: withdrawalData,
              });
            const withdrawalTx = await sendAndConfirm([
              withdrawalInstruction,
            ]);
            ```
          </Tab>
        </Tabs>

        <AccordionGroup>
          <Accordion title="1. 选择要花费的私人代币账户">
            ```typescript theme={"system"}
            import { SOL_MINT } from "@heliuslabs/zolana";

            const withdrawalUtxo =
              transferBalance.utxos[0]!;
            ```

            * 示例花费了上一次转账后剩下的私人Solana代币账户。提款可以花费多个UTXO。
            * `withdrawalUtxo`从该余额中选择一个私人Solana代币账户。
          </Accordion>

          <Accordion title="2. 准备证明输入">
            ```typescript theme={"system"}
            import { ProofInputUtxo } from "@heliuslabs/zolana/transaction";

            const withdrawalInput =
              ProofInputUtxo.fromKeypair(
                withdrawalUtxo,
                senderKeypair,
              );
            ```

            * `ProofInputUtxo.fromKeypair`准备选定的UTXO作为证明输入，与发送者的私人钱包密钥对。
            * 密钥对派生出标记输入UTXO为已用的空值，而输入资产和金额保持加密。
          </Accordion>

          <Accordion title="3. 构建并签署提款">
            ```typescript theme={"system"}
            import { SOL_MINT } from "@heliuslabs/zolana";
            import {
              ConfidentialTransfer,
              WithdrawalTarget,
            } from "@heliuslabs/zolana/transaction";

            const withdrawal = new ConfidentialTransfer(
              senderAddress,
              [withdrawalInput],
              senderSigner.address,
            );
            withdrawal.withdraw(
              SOL_MINT,
              WITHDRAW_AMOUNT,
              WithdrawalTarget.sol({
                recipient: senderSigner.address,
              }),
            );
            const withdrawalProofInputs = withdrawal.sign(
              senderKeypair,
              assets,
            );
            ```

            * `senderAddress`是发送者的<Tooltip tip="发布在用户注册记录中的公钥包（签名、空值和查看密钥）。不是链上地址。">屏蔽地址</Tooltip>。提款从这个钱包花费。
            * `[withdrawalInput]`列出发送者选择的UTXO。提款可以花费多个UTXO。
            * `senderSigner.address`是费用支付者的Solana地址。机密提款的气体赞助商可以扮演此角色。
            * `WithdrawalTarget.sol`是公共Solana接收者。接收者可以是所有者或第三方。
            * `SOL_MINT`选择SOL。SPL或Token 2022提款传递代币铸造。
            * `WITHDRAW_AMOUNT`以资产的基本单位计价。SOL使用lamports。SPL和Token 2022资产使用代币的基本单位。
            * `withdrawal.sign`授权状态转换，加密剩余的私人变动，并生成零知识证明器的输入。
            * `assets`是资产注册表，用于解析支持的私人资产。
          </Accordion>

          <Accordion title="4. 获取零知识证明">
            ```typescript theme={"system"}
            import { createZolanaClient } from "@heliuslabs/zolana";

            const withdrawalData =
              await client.proveTransact(
                withdrawalProofInputs,
              );
            ```

            * `client.proveTransact`从签署的提款中生成零知识证明并返回序列化的指令数据。
            * 证明表明发件人拥有并可以花费输入。提取的资产和金额是公开的。输入金额和变动保持加密。
          </Accordion>

          <Accordion title="5. 构建提款指令">
            ```typescript theme={"system"}
            import {
              transactInstruction,
              TransactWithdrawal,
            } from "@heliuslabs/zolana/interface";

            const withdrawalInstruction =
              transactInstruction({
                payer: senderSigner,
                inputTree: client.tree,
                outputTree: client.tree,
                withdrawal: TransactWithdrawal.sol({
                  recipient: senderSigner.address,
                }),
                data: withdrawalData,
              });
            ```

            * `payer`为Solana交易签名并支付费用。机密提款的气体赞助商可以扮演此角色。
            * `inputTree`和`outputTree`是`client.tree`，包含已用UTXO并接收发送者的私人变动的状态Merkle树。
            * `withdrawal`是`TransactWithdrawal.sol`，公共Solana接收者账户。
            * `data`包含在上一步生成的零知识证明和加密变动。
            * 提款将资产从私人余额转移到公共Solana账户。它传递公共接收账户。
          </Accordion>

          <Accordion title="6. 像任何Solana交易一样发送">
            ```typescript theme={"system"}
            import { sendAndConfirmFactory } from "../src/lib.js";

            const withdrawalTx = await sendAndConfirm([
              withdrawalInstruction,
            ]);
            ```

            * `sendAndConfirm`签名并提交`withdrawalInstruction`作为Solana交易。
            * 确认产生用于索引器提取的落地槽。
          </Accordion>
        </AccordionGroup>
      </Step>
    </Steps>

    ### 完整代码示例

    克隆并运行示例：

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

    <Info>
      这些示例在本地/devnet上使用保密Ring [here](https://github.com/helius-labs/zolana-examples/blob/main/typescript-client/examples/deposit_transfer_withdraw.ts)。
    </Info>

    ```typescript deposit_transfer_withdraw.ts expandable theme={"system"}
    import {
      SOL_MINT,
      createZolanaClient,
    } from "@heliuslabs/zolana";
    import { atSlot } from "@heliuslabs/zolana/client";
    import {
      depositInstruction,
      transactInstruction,
      DepositAsset,
      TransactWithdrawal,
    } from "@heliuslabs/zolana/interface";
    import { randomBlinding } from "@heliuslabs/zolana/keypair";
    import {
      AssetRegistry,
      ConfidentialTransfer,
      ProofInputUtxo,
      decryptToBalances,
      WithdrawalTarget,
    } from "@heliuslabs/zolana/transaction";

    import {
      sendAndConfirmFactory,
      setup,
    } from "../src/lib.js";

    const DEPOSIT_AMOUNT = 1_000_000_000n;
    const TRANSFER_AMOUNT = 300_000_000n;
    const WITHDRAW_AMOUNT = 300_000_000n;

    async function main(): Promise<void> {
      const {
        sender: senderKeypair,
        recipient: recipientKeypair,
        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 senderSigner =
        senderKeypair.toSolanaSigner();
      const senderAddress =
        senderKeypair.shieldedAddress();
      const recipient =
        recipientKeypair.shieldedAddress();

      // 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(),
            blinding: randomBlinding(),
            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 view tag.
      const depositResponse =
        await client.getShieldedTransactionsByTags(
          { tags: [senderViewTag] },
          atSlot(depositTx.slot),
        );

      // 4. The sender decrypts the transaction outputs locally to read the private balance.
      const balancesAfterDeposit =
        await decryptToBalances({
          keypair: senderKeypair,
          registry: assets,
          transactions: depositResponse.transactions,
        });
      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,
          senderKeypair,
        );

      // 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,
        SOL_MINT,
        TRANSFER_AMOUNT,
      );
      const transferProofInputs = transfer.sign(
        senderKeypair,
        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,
      );

      // 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 = 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 again, gated on the transfer's slot,
      // and read the remaining private balance.
      const transferResponse =
        await client.getShieldedTransactionsByTags(
          { tags: [senderViewTag] },
          atSlot(transferTx.slot),
        );
      const balancesAfterTransfer =
        await decryptToBalances({
          keypair: senderKeypair,
          registry: assets,
          transactions: transferResponse.transactions,
        });
      const transferBalance =
        balancesAfterTransfer.balance(SOL_MINT);
      if (
        transferBalance.amount !==
        DEPOSIT_AMOUNT - TRANSFER_AMOUNT
      ) {
        throw new Error(
          `expected remaining amount ${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,
          senderKeypair,
        );

      // 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(
        senderKeypair,
        assets,
      );

      // 4. Fetch the ZK proof to prove the sender can spend the balance.
      const withdrawalData =
        await client.proveTransact(
          withdrawalProofInputs,
        );

      // 5. Build the instruction with the state Merkle tree and Solana accounts required for the withdrawal.
      const withdrawalInstruction =
        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 again, gated on the withdrawal's slot,
      // and read the remaining private balance.
      const withdrawalResponse =
        await client.getShieldedTransactionsByTags(
          { tags: [senderViewTag] },
          atSlot(withdrawalTx.slot),
        );
      const balancesAfterWithdrawal =
        await decryptToBalances({
          keypair: senderKeypair,
          registry: assets,
          transactions:
            withdrawalResponse.transactions,
        });
      const withdrawalBalance =
        balancesAfterWithdrawal.balance(SOL_MINT);
      if (
        withdrawalBalance.amount !==
        DEPOSIT_AMOUNT -
          TRANSFER_AMOUNT -
          WITHDRAW_AMOUNT
      ) {
        throw new Error(
          `expected remaining amount ${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 客户端">
    <Steps>
      <Step>
        ### 先决条件

        <Info>
          The Rust examples require the latest stable Rust toolchain and the Solana CLI v4.0.2. See the [Solana installation guide](https://solana.com/docs/intro/installation).
        </Info>

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

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

        <Accordion title="Connect to Endpoints">
          <Tabs>
            <Tab title="Devnet">
              Add a [Helius API key](https://dashboard.helius.dev/):

              ```bash .env theme={"system"}
              API_KEY=YOUR_API_KEY
              ZOLANA_PAYER_KEYPAIR=~/.config/solana/id.json
              ```

              ```rust theme={"system"}
              use solana_address::Address;
              use zolana_client::{SolanaRpc, ZolanaClient};
              use zolana_interface::DEFAULT_TREE_ADDRESS;

              let tree: Address = DEFAULT_TREE_ADDRESS.parse()?;
              let client = ZolanaClient::from_urls_allowing_insecure_http(
                  SolanaRpc::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY"),
                  "http://zolnet-devnet-1779374825.eu-north-1.elb.amazonaws.com",
                  "http://zolnet-devnet-1779374825.eu-north-1.elb.amazonaws.com:3001",
                  tree,
              );
              ```

              The examples use the Solana CLI wallet as the payer by default. The payer must hold devnet SOL. See [How to Get Devnet SOL](/docs/rpc/devnet-sol).
            </Tab>

            <Tab title="Localnet">
              ```bash theme={"system"}
              cargo install --git https://github.com/helius-labs/zolana --tag v0.1.0-alpha zolana-cli
              zolana dev start
              ```

              ```rust theme={"system"}
              use solana_address::Address;
              use zolana_client::{SolanaRpc, ZolanaClient};
              use zolana_interface::DEFAULT_TREE_ADDRESS;

              let tree: Address = DEFAULT_TREE_ADDRESS.parse()?;
              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",
                  tree,
              )?;
              ```
            </Tab>
          </Tabs>
        </Accordion>
      </Step>

      <Step>
        ### 提款到公共余额

        ```rust theme={"system"}
        use zolana_interface::instruction::{
            Transact, TransactInterfaceTransferAccounts, TransactSolTransferAccounts,
        };
        use zolana_transaction::{
            instructions::{
                transact::{ConfidentialTransfer, SettlementTarget},
                types::SppProofInputUtxo,
            },
            SOL_MINT,
        };

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

        let withdrawal_input_utxo = SppProofInputUtxo::new(withdrawal_utxo, &sender);

        let mut withdrawal = ConfidentialTransfer::new(
            sender_shielded_address,
            vec![withdrawal_input_utxo],
            sender_solana_keypair.pubkey(),
        );
        withdrawal.withdraw(
            SOL_MINT,
            WITHDRAW_AMOUNT,
            SettlementTarget::Sol {
                user_sol_account: sender_solana_keypair.pubkey(),
            },
        )?;
        // SPL: withdrawal.withdraw(
        // SPL:     spl.mint,
        // SPL:     WITHDRAW_AMOUNT,
        // SPL:     SettlementTarget::Spl {
        // SPL:         user_spl_token: spl.user_token_account,
        // SPL:         spl_token_interface: spl.vault,
        // SPL:     },
        // SPL: )?;
        let proof_inputs = withdrawal.sign(&sender, &assets)?;

        let withdrawal_data = client.prove_transact(tree, proof_inputs, None)?;

        let withdraw_ix = Transact {
            payer: sender_solana_keypair.pubkey(),
            input_tree: tree,
            output_tree: tree,
            owner_signers: Vec::new(),
            interface_transfer_accounts: vec![TransactInterfaceTransferAccounts::Sol(
                TransactSolTransferAccounts {
                    recipient: sender_solana_keypair.pubkey(),
                },
            )],
            // SPL: interface_transfer_accounts: vec![
            // SPL:     TransactInterfaceTransferAccounts::SplWithdrawal(
            // SPL:         zolana_interface::instruction::TransactSplWithdrawalAccounts {
            // SPL:             mint: spl.mint,
            // SPL:             vault: spl.vault,
            // SPL:             user_token_account: spl.user_token_account,
            // SPL:             token_program: spl.token_program,
            // SPL:         },
            // SPL:     ),
            // SPL: ],
            data: withdrawal_data,
        }
        .instruction();
        ```

        <AccordionGroup>
          <Accordion title="1. 选择要花费的私人代币账户">
            ```rust theme={"system"}
            use zolana_transaction::SOL_MINT;

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

            * 示例花费了上一次转账后剩下的私人Solana代币账户。提款可以花费多个UTXO。
            * `withdrawal_utxo`是该资产的第一个可花费UTXO。`// SPL:`注释显示`get_balance(spl.mint)`。
          </Accordion>

          <Accordion title="2. 准备证明输入">
            ```rust theme={"system"}
            use zolana_transaction::instructions::types::SppProofInputUtxo;

            let withdrawal_input_utxo = SppProofInputUtxo::new(withdrawal_utxo, &sender);
            ```

            * `SppProofInputUtxo::new`准备选定的UTXO作为证明输入，与发送者的私人钱包密钥对。
            * 密钥对派生出标记输入UTXO为已用的空值，而输入资产和金额保持加密。
          </Accordion>

          <Accordion title="3. 构建并签署提款">
            ```rust theme={"system"}
            use zolana_transaction::{
                instructions::transact::{ConfidentialTransfer, SettlementTarget},
                SOL_MINT,
            };

            let mut withdrawal = ConfidentialTransfer::new(
                sender_shielded_address,
                vec![withdrawal_input_utxo],
                sender_solana_keypair.pubkey(),
            );
            withdrawal.withdraw(
                SOL_MINT,
                WITHDRAW_AMOUNT,
                SettlementTarget::Sol {
                    user_sol_account: sender_solana_keypair.pubkey(),
                },
            )?;
            // SPL: withdrawal.withdraw(
            // SPL:     spl.mint,
            // SPL:     WITHDRAW_AMOUNT,
            // SPL:     SettlementTarget::Spl {
            // SPL:         user_spl_token: spl.user_token_account,
            // SPL:         spl_token_interface: spl.vault,
            // SPL:     },
            // SPL: )?;
            let proof_inputs = withdrawal.sign(&sender, &assets)?;
            ```

            * `sender_shielded_address`是发送者的<Tooltip tip="发布在用户注册记录中的公钥包（签名、空值和查看密钥）。不是链上地址。">屏蔽地址</Tooltip>。提款从这个钱包花费。
            * `vec![withdrawal_input_utxo]`列出发送者选择的UTXO。提款可以花费多个UTXO。
            * `sender_solana_keypair.pubkey()`是交易费用支付者。机密提款的气体赞助商可以扮演此角色。
            * `SettlementTarget::Sol`是公共Solana接收者。接收者可以是所有者或第三方。`// SPL:`注释显示`SettlementTarget::Spl`。
            * `SOL_MINT`选择SOL。`// SPL:`注释显示SPL和Token 2022资产的代币铸造。
            * `WITHDRAW_AMOUNT`以资产的基本单位计价。SOL使用lamports。SPL和Token 2022资产使用代币的基本单位。
            * `withdrawal.sign`授权状态转换，加密剩余的私人变动，并生成零知识证明器的输入。
            * `assets`是资产注册表，用于解析支持的私人资产。
          </Accordion>

          <Accordion title="4. 获取零知识证明">
            ```rust theme={"system"}
            use zolana_client::Rpc;

            let withdrawal_data = client.prove_transact(tree, proof_inputs, None)?;
            ```

            * `client.prove_transact`从签署的提款中生成零知识证明并返回序列化的指令数据。
            * `tree`识别状态Merkle树的根，用于证明输入UTXO的成员身份。
            * 证明表明发件人拥有并可以花费输入。提取的资产和金额是公开的。输入金额和变动保持加密。
          </Accordion>

          <Accordion title="5. 构建提款指令">
            ```rust theme={"system"}
            use zolana_interface::instruction::{
                Transact, TransactInterfaceTransferAccounts, TransactSolTransferAccounts,
            };

            let withdraw_ix = Transact {
                payer: sender_solana_keypair.pubkey(),
                input_tree: tree,
                output_tree: tree,
                owner_signers: Vec::new(),
                interface_transfer_accounts: vec![TransactInterfaceTransferAccounts::Sol(
                    TransactSolTransferAccounts {
                        recipient: sender_solana_keypair.pubkey(),
                    },
                )],
                // SPL: interface_transfer_accounts: vec![
                // SPL:     TransactInterfaceTransferAccounts::SplWithdrawal(
                // SPL:         zolana_interface::instruction::TransactSplWithdrawalAccounts {
                // SPL:             mint: spl.mint,
                // SPL:             vault: spl.vault,
                // SPL:             user_token_account: spl.user_token_account,
                // SPL:             token_program: spl.token_program,
                // SPL:         },
                // SPL:     ),
                // SPL: ],
                data: withdrawal_data,
            }
            .instruction();
            ```

            * `payer`为Solana交易签名并支付费用。机密提款的气体赞助商可以扮演此角色。
            * `input_tree`识别包含已用UTXO的状态Merkle树。
            * `output_tree`识别接收发送者的私人变动承诺的状态Merkle树。
            * `interface_transfer_accounts`提供`TransactInterfaceTransferAccounts::Sol`，公共Solana接收者。`// SPL:`注释显示`SplWithdrawal`。
            * `owner_signers`对这个机密提款为空。
            * `data`包含在上一步生成的零知识证明和加密变动。
          </Accordion>

          <Accordion title="6. 像任何Solana交易一样发送">
            ```rust theme={"system"}
            use zolana_client::Rpc;

            let signature = client.create_and_send_transaction(
                &[withdraw_ix],
                sender_solana_keypair.pubkey(),
                &[&sender_solana_keypair],
            )?;
            let slot = landed_slot(&client, signature)?;
            ```

            * `create_and_send_transaction`签名并提交`withdraw_ix`作为Solana交易。
            * `landed_slot`读取用于索引器提取的确认槽。
            * `sender_solana_keypair`支付费用并授权提款。
          </Accordion>
        </AccordionGroup>
      </Step>
    </Steps>

    ## 完整代码示例

    克隆并运行示例：

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

    <Info>
      这些示例在本地/devnet上使用保密Ring [here](https://github.com/helius-labs/zolana-examples/blob/main/rust-client/examples/deposit_transfer_withdraw.rs)。
    </Info>

    ```rust deposit_transfer_withdraw.rs expandable theme={"system"}
    use anyhow::{anyhow, Result};
    use rust_client_example::{setup, SetupContext};
    use solana_signature::Signature;
    use solana_signer::Signer;
    use zolana_client::{IndexerRpcConfig, Rpc, SolanaRpc, ZolanaClient};
    use zolana_interface::instruction::{
        AssetDeposit, Deposit, DepositAsset, Transact, TransactInterfaceTransferAccounts,
        TransactSolTransferAccounts,
    };
    use zolana_keypair::random_blinding;
    use zolana_transaction::{
        decrypt_transactions,
        instructions::{
            transact::{ConfidentialTransfer, SettlementTarget},
            types::SppProofInputUtxo,
        },
        AssetRegistry, SOL_MINT,
    };

    const DEPOSIT_AMOUNT: u64 = 1_000_000_000;
    const TRANSFER_AMOUNT: u64 = 300_000_000;
    const WITHDRAW_AMOUNT: u64 = 300_000_000;

    fn main() -> Result<()> {
        let SetupContext {
            rpc_url,
            indexer_url,
            prover_url,
            tree,
            sender,
            recipient_address,
        } = setup()?;

        // Load the funded fee payer and devnet settings, then connect.
        // Photon and the prover are HTTP on this ALB, so the constructor permits that.
        let client = ZolanaClient::from_urls_allowing_insecure_http(
            SolanaRpc::new(rpc_url),
            &indexer_url,
            prover_url,
            tree,
        );

        // 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_solana_keypair = sender.to_solana_keypair()?;
        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_solana_keypair.pubkey(),
                deposits: vec![AssetDeposit {
                    asset: DepositAsset::Sol,
                    // SPL: asset: DepositAsset::Spl(zolana_interface::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()?,
                    blinding: random_blinding(),
                    amount: DEPOSIT_AMOUNT,
                    utxo_data: None,
                    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_solana_keypair.pubkey(),
                &[&sender_solana_keypair],
            )?;
            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 view tag, the sender's public key in Confidential Rings.
            let sender_tag = sender_shielded_address.confidential_view_tag()?;
            let response = client.get_shielded_transactions_by_tags(
                vec![sender_tag],
                None,
                Some(50),
                Some(IndexerRpcConfig::at_slot(slot)),
            )?;

            // 4. The sender decrypts the transaction outputs locally to update the private balance.
            let balances = decrypt_transactions(&sender, &response.transactions, &assets)
                .map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?;

            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 transfer_input_utxo = SppProofInputUtxo::new(transfer_utxo, &sender);

            // 3. Build and sign the confidential transfer.
            // Signing encrypts the asset and amount and produces the proof inputs for the ZK prover.
            let mut transfer = ConfidentialTransfer::new(
                sender_shielded_address,
                vec![transfer_input_utxo],
                sender_solana_keypair.pubkey(),
            );
            transfer.send(&recipient_address, SOL_MINT, TRANSFER_AMOUNT)?;
            // SPL: transfer.send(&recipient_address, spl.mint, TRANSFER_AMOUNT)?;
            let proof_inputs = transfer.sign(&sender, &assets)?;

            // 4. Fetch the zk proof to prove the sender can spend the balance without revealing asset and amount.
            let transfer_data = client.prove_transact(tree, proof_inputs, None)?;

            // 5. Construct the instruction.
            let transfer_ix = Transact {
                payer: sender_solana_keypair.pubkey(),
                input_tree: 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_solana_keypair.pubkey(),
                &[&sender_solana_keypair],
            )?;
            let slot = landed_slot(&client, signature)?;

            // 7. Sync the sender's wallet, gated on the transfer's slot, and read
            // the remaining private balance.
            let sender_tag = sender_shielded_address.confidential_view_tag()?;
            let response = client.get_shielded_transactions_by_tags(
                vec![sender_tag],
                None,
                Some(50),
                Some(IndexerRpcConfig::at_slot(slot)),
            )?;
            let sender_balances = decrypt_transactions(&sender, &response.transactions, &assets)
                .map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?;
            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 withdrawal_input_utxo = SppProofInputUtxo::new(withdrawal_utxo, &sender);

            // 3. Build and sign the confidential withdrawal.
            // Signing encrypts the private change and produces the ZK prover inputs.
            let mut withdrawal = ConfidentialTransfer::new(
                sender_shielded_address,
                vec![withdrawal_input_utxo],
                sender_solana_keypair.pubkey(),
            );
            withdrawal.withdraw(
                SOL_MINT,
                WITHDRAW_AMOUNT,
                SettlementTarget::Sol {
                    user_sol_account: sender_solana_keypair.pubkey(),
                },
            )?;
            // SPL: withdrawal.withdraw(
            // SPL:     spl.mint,
            // SPL:     WITHDRAW_AMOUNT,
            // SPL:     SettlementTarget::Spl {
            // SPL:         user_spl_token: spl.user_token_account,
            // SPL:         spl_token_interface: spl.vault,
            // SPL:     },
            // SPL: )?;
            let proof_inputs = withdrawal.sign(&sender, &assets)?;

            // 4. Fetch the ZK proof to prove the sender can spend the balance.
            let withdrawal_data = client.prove_transact(tree, proof_inputs, None)?;

            // 5. Combine the proof and withdrawal accounts in a single instruction.
            let withdraw_ix = Transact {
                payer: sender_solana_keypair.pubkey(),
                input_tree: tree,
                output_tree: tree,
                owner_signers: Vec::new(),
                interface_transfer_accounts: vec![TransactInterfaceTransferAccounts::Sol(
                    TransactSolTransferAccounts {
                        recipient: sender_solana_keypair.pubkey(),
                    },
                )],
                // SPL: interface_transfer_accounts: vec![
                // SPL:     TransactInterfaceTransferAccounts::SplWithdrawal(
                // SPL:         zolana_interface::instruction::TransactSplWithdrawalAccounts {
                // SPL:             mint: spl.mint,
                // SPL:             vault: 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_solana_keypair.pubkey(),
                &[&sender_solana_keypair],
            )?;
            let slot = landed_slot(&client, signature)?;

            // 7. Sync the sender's wallet, gated on the withdrawal's slot, and read
            // the remaining private balance.
            let sender_tag = sender_shielded_address.confidential_view_tag()?;
            let response = client.get_shielded_transactions_by_tags(
                vec![sender_tag],
                None,
                Some(50),
                Some(IndexerRpcConfig::at_slot(slot)),
            )?;
            let sender_balances = decrypt_transactions(&sender, &response.transactions, &assets)
                .map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?;
            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_solana_keypair.pubkey())?;
            println!("withdraw solana_balance={solana_balance} tx={signature}");
            // SPL: println!(
            // SPL:     "withdraw user_token={} tx={signature}",
            // SPL:     spl.user_token_account,
            // SPL: );
        }
        Ok(())
    }

    /// Slot the confirmed transaction landed in, which drives the indexer
    /// freshness gate on the fetches that read the transaction back.
    fn landed_slot(client: &ZolanaClient<SolanaRpc>, signature: Signature) -> Result<u64> {
        client
            .get_signature_statuses(vec![signature])?
            .first()
            .and_then(|status| status.as_ref())
            .map(|status| status.slot)
            .ok_or_else(|| anyhow!("transaction status missing after confirmation"))
    }
    ```
  </Tab>
</Tabs>

## 相关指南

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

  <Card title="转账" icon="arrow-right-arrow-left" href="/docs/zh/privacy/guides/transfer" horizontal />

  <Card title="读取私人余额" icon="wallet" href="/docs/zh/privacy/guides/read-balance" horizontal />

  <Card title="读取私人历史记录" icon="clock-rotate-left" href="/docs/zh/privacy/guides/read-history" horizontal />
</CardGroup>

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

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