> ## 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 资产存入私人余额的指南，附完整代码示例。

1. 存款将代币从用户的公共 Solana 余额转移到他们自己或其他人的私人余额。
2. 通过单个 Solana 交易将存款发送到 Solana 钱包地址。

#### 存款：什么是私人

| Field                      | Visibility | Why                                                |
| -------------------------- | ---------- | -------------------------------------------------- |
| Source public wallet       | Public     | The source wallet address is visible onchain       |
| Asset                      | Public     | The asset is visible onchain                       |
| Amount                     | Public     | The deposited amount is visible onchain            |
| Destination wallet address | Public     | The destination wallet address is visible onchain  |
| Resulting private balance  | Private    | The resulting private balance is encrypted onchain |

<Info>
  无需许可的 Ring 是通过加密数量和资产来保密的。
  可以将自定义 Ring 配置为保密或匿名（加密发件人、接收者、资产和数量）。
</Info>

## 存款如何运作

存款是一个公开 Solana 转账，结果为私人余额。

1. 用户的 SOL 或 SPL 余额在链上是公开的。
2. 钱包设置金额和接收者，构建存款，所有者签名。存款不要求 ZK 证明。
3. Solana 运行时验证签名并调用 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 as RPC Provider
    participant Solana

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

<Accordion 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 的高级交易流程。
  与 [概念](/docs/zh/privacy/concepts#高级交易流程) 中的自定义 Ring 进行比较。
</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 套件发送助手">
          ```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 {
              depositInstruction,
              DepositAsset,
            } from "@heliuslabs/zolana/interface";
            import { randomBlinding } from "@heliuslabs/zolana/keypair";
            import { sendAndConfirmFactory } from "../src/lib.js";


              const sendAndConfirm = sendAndConfirmFactory(
                client,
                senderSigner,
              );
              const senderViewTag =
                senderAddress.confidentialViewTag();
              const depositIx = await depositInstruction({
                tree: client.tree,
                depositor: senderSigner,
                deposits: [
                  {
                    asset: DepositAsset.sol(),
                    viewTag: senderViewTag,
                    recipientOwnerHash:
                      senderAddress.ownerHash(),
                    blinding: randomBlinding(),
                    amount: DEPOSIT_AMOUNT,
                  },
                ],
              });

              const depositTx = await sendAndConfirm([
                depositIx,
              ]);
            ```
          </Tab>

          <Tab title="SPL">
            ```typescript theme={"system"}
            import {
              depositInstruction,
              DepositAsset,
            } from "@heliuslabs/zolana/interface";
            import { randomBlinding } from "@heliuslabs/zolana/keypair";
            import { sendAndConfirmFactory } from "../src/lib.js";


              const sendAndConfirm = sendAndConfirmFactory(
                client,
                senderSigner,
              );
              const senderViewTag =
                senderAddress.confidentialViewTag();
              const depositIx = await depositInstruction({
                tree: client.tree,
                depositor: senderSigner,
                deposits: [
                  {
                    asset: DepositAsset.spl({
                      mint: spl.mint,
                      sourceTokenAccount: spl.sourceTokenAccount,
                      tokenProgram: spl.tokenProgram,
                    }),
                    viewTag: senderViewTag,
                    recipientOwnerHash:
                      senderAddress.ownerHash(),
                    blinding: randomBlinding(),
                    amount: DEPOSIT_AMOUNT,
                  },
                ],
              });

              const depositTx = await sendAndConfirm([
                depositIx,
              ]);
            ```
          </Tab>
        </Tabs>

        <AccordionGroup>
          <Accordion title="1. 将公共代币移入发送者的私人余额">
            ```typescript theme={"system"}
            import {
              depositInstruction,
              DepositAsset,
            } from "@heliuslabs/zolana/interface";
            import { randomBlinding } from "@heliuslabs/zolana/keypair";

            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,
                },
              ],
            });
            ```

            * `senderAddress` 是接收者的 <Tooltip tip="在用户注册记录中发布的公钥包（签名、空白和查看密钥）。不是链上地址。">Shielded Address</Tooltip>。此示例将存款转给发送者。接收者可以是发送者或第三方钱包。
            * `senderViewTag` 是 `senderAddress.confidentialViewTag()`，发送者在保密环中使用的 Solana 公钥。索引器使用它返回匹配的加密输出。
            * `client.tree` 是接收新输出承诺的状态 Merkle 树。
            * `depositor` / `senderSigner` 为公对私存款提供资金并签名。
            * `DepositAsset.sol()` 选择 SOL。`DepositAsset.spl` 获取铸造、源代币账户和用于 SPL 和 Token 2022 资产的代币程序。
            * `recipientOwnerHash` 是 `senderAddress.ownerHash()`。接收者签名和空白公钥的哈希值是提交给新私人代币账户的所有者字段。
            * `randomBlinding()` 为私人代币账户承诺添加随机性。
            * `DEPOSIT_AMOUNT` 使用 SOL 的 lamports，或用于 SPL 和 Token 2022 资产的代币基础单位。
          </Accordion>

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

            const depositTx = await sendAndConfirm([
              depositIx,
            ]);
            ```

            * `sendAndConfirm` 签名并提交 `depositIx` 作为 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 [此处](https://github.com/helius-labs/zolana-examples/blob/main/typescript-client/examples/deposit_transfer_withdraw.ts) 使用保密 Ring。
    </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::{AssetDeposit, Deposit, DepositAsset};
        use zolana_keypair::random_blinding;


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

        <AccordionGroup>
          <Accordion title="1. 将公共 SOL 移入发送者的私人余额">
            ```rust theme={"system"}
            use zolana_interface::instruction::{AssetDeposit, Deposit, DepositAsset};
            use zolana_keypair::random_blinding;


                // 1. Move public SOL into the sender's private balance.
                    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()?;
            ```

            * `sender_shielded_address` 是接收者的 <Tooltip tip="在用户注册记录中发布的公钥包（签名、空白和查看密钥）。不是链上地址。">Shielded Address</Tooltip>。此示例将存款转给发送者。接收者可以是发送者或第三方钱包。
            * `view_tag` 是 `sender_shielded_address.confidential_view_tag()`，发送者在保密环中使用的 Solana 公钥。索引器使用它返回匹配的加密输出。
            * `tree` 是接收新输出承诺的状态 Merkle 树。
            * `depositor` / `sender_solana_keypair` 为公对私存款提供资金并签名。
            * `DepositAsset::Sol` 选择 SOL。`// SPL:` 注释展示 `DepositAsset::Spl`，带有 SPL 和 Token 2022 资产的铸造、源代币账户和代币程序。
            * `owner` 是 `sender_shielded_address.owner_hash()`。接收者签名和空白公钥的哈希值是提交给新私人代币账户的所有者字段。
            * `random_blinding()` 为私人代币账户承诺添加随机性。
            * `DEPOSIT_AMOUNT` 使用 SOL 的 lamports，或用于 SPL 和 Token 2022 资产的代币基础单位。`utxo_data` 和 `memo` 是 `None`。
          </Accordion>

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

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

            * `create_and_send_transaction` 签名并提交 `deposit_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 [此处](https://github.com/helius-labs/zolana-examples/blob/main/rust-client/examples/deposit_transfer_withdraw.rs) 使用保密 Ring。
    </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-right-arrow-left" href="/docs/zh/privacy/guides/transfer" horizontal />

  <Card title="提现" icon="arrow-up-from-bracket" href="/docs/zh/privacy/guides/withdraw" 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>
