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

# Übertragung

> Anleitung zum Übertragen von SOL-, SPL- oder Token-2022-Assets zwischen privaten Salden mit einem vollständigen Codebeispiel.

1. Ein privater Transfer verschiebt Tokens in einem Ring zwischen privaten Wallets.
2. Private Transfers werden in einer einzigen Solana-Transaktion an eine Solana-Wallet-Adresse gesendet.

#### Übertragung: Was ist privat?

<Tabs>
  <Tab title="Permissionless Confidential Ring">
    | Field                 | Visibility | Why                                                                  |
    | --------------------- | ---------- | -------------------------------------------------------------------- |
    | Asset                 | Private    | The asset is encrypted onchain                                       |
    | Amount                | Private    | The transferred amount is encrypted onchain                          |
    | Source private wallet | Public     | In a confidential Ring the source private wallet is visible onchain. |
    | Recipient             | Public     | In a confidential Ring the recipient is visible onchain.             |
  </Tab>

  <Tab title="Custom Rings">
    Transfers from and within a ring reveal the program ID of the custom Ring.

    A balance in a Custom Ring can exit to an SPL token account, to the Default Ring, or to another Ring, as long as the source Ring's policy permits it.
    The default Ring is permissionless and does not have a policy or authority.

    One transaction can combine balances from the Default Ring and one Custom Ring. A transfer between two Custom Rings routes through the Default Ring.

    | Private transfer       | Custom confidential Rings | Custom anonymous Rings                                                                                      | Default confidential to or from Custom confidential | Confidential (Default or Custom) to Custom anonymous | Custom anonymous to confidential (Default or Custom)                                                        |
    | ---------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
    | Amount                 | Private                   | Private                                                                                                     | Private                                             | Private                                              | Private                                                                                                     |
    | Asset                  | Private                   | Private                                                                                                     | Private                                             | Private                                              | Private                                                                                                     |
    | Source private wallet  | Public                    | Private. A relayer submits the transaction, so the public ledger does not reveal the source private wallet. | Public                                              | Public                                               | Private. A relayer submits the transaction, so the public ledger does not reveal the source private wallet. |
    | Recipient              | Public                    | Private                                                                                                     | Public                                              | Private                                              | Public                                                                                                      |
    | Custom Ring program ID | Public                    | Public                                                                                                      | Public                                              | Public                                               | Public                                                                                                      |
  </Tab>
</Tabs>

<Info>
  Der genehmigungslose Ring ist vertraulich mit verschlüsselter Menge und Asset.
  Ein benutzerdefinierter Ring kann als vertraulich oder anonym (verschlüsselter Absender, Empfänger, Asset und Menge) konfiguriert werden.
</Info>

## So funktioniert eine Übertragung

Eine private Übertragung verhält sich ähnlich wie eine öffentliche Solana-Übertragung:

1. Der SOL- oder SPL-Saldo des Benutzers wird on-chain verschlüsselt.

2. Der Benutzer entschlüsselt den privaten Zustand, die Wallet erstellt eine Übertragung und der Eigentümer unterschreibt.

   * Abgerufener verschlüsselter Zustand mit dedizierten RPC-Methoden. Nur der Benutzer kann Salden lokal entschlüsseln.
   * Die Wallet legt die Menge und den Empfänger fest, dann wird ein ZK-Beweis angefordert. Der RPC-Anbieter generiert standardmäßig den ZK-Beweis und gibt ihn zurück.

3. Die Solana-Laufzeit überprüft die Signaturen und ruft das Solana-Privacy-Programm auf, das den ZK-Beweis überprüft, ohne den verschlüsselten Zustand preiszugeben.

4. Die App verfolgt den Status über den Solana-Transaktions-Hash.

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

    Wallet->>RPC: 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="Vergleich mit Solana-Übertragung">
  1. Der SOL- oder SPL-Saldo des Benutzers ist on-chain öffentlich.
  2. Die Wallet liest den öffentlichen Zustand, erstellt eine Übertragung und der Eigentümer unterschreibt.
  3. Die Solana-Laufzeit überprüft die Signaturen und ruft das System- oder Token-Programm auf, das den öffentlichen Saldo aktualisiert.
  4. Die App verfolgt den Status über den Solana-Transaktions-Hash.

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

      Wallet->>RPC: Get public balance
      RPC-->>Wallet: Public state
      Note over Wallet: Build transfer, owner signs
      Wallet->>RPC: sendTransaction
      RPC->>Solana: Forward transaction
      Note over Solana: Verify signatures
      Note over Solana: CPI System / Token Program
      Note over Solana: Update balance
      RPC-->>Wallet: Transaction signature
  ```
</Accordion>

<Info>
  Dies ist der Transaktionsfluss auf hoher Ebene für den genehmigungslosen vertraulichen Ring.
  Vergleiche mit benutzerdefinierten Ringen in [Konzepte](/docs/de/privacy/concepts#transaktionsfluss-auf-hoher-ebene).
</Info>

## Erste Schritte

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

        <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>
        ### Übertragung auf ein privates Guthaben

        <Accordion title="Solana Kit Send-Hilfe">
          ```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 };
            };
          }
          ```

          * Das SDK gibt Anweisungen zurück. Die App signiert und sendet sie.
          * `sendAndConfirmFactory` erstellt eine Kit-Transaktion, übermittelt sie und gibt die Signatur plus den gelandeten Slot zurück.
        </Accordion>

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


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

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


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

        <AccordionGroup>
          <Accordion title="1. Wählen Sie private Token-Konten zum Ausgeben">
            ```typescript theme={"system"}
            import { SOL_MINT } from "@heliuslabs/zolana";

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

            * Das Beispiel gibt das private Solana-Token-Konto aus, das durch die vorherige Einzahlung erstellt wurde. Eine Übertragung kann mehrere UTXOs ausgeben.
            * `transferUtxo` wählt ein privates Solana-Token-Konto aus diesem Guthaben aus.
          </Accordion>

          <Accordion title="2. Beweis-Eingaben vorbereiten">
            ```typescript theme={"system"}
            import { ProofInputUtxo } from "@heliuslabs/zolana/transaction";

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

            * `ProofInputUtxo.fromKeypair` bereitet das ausgewählte UTXO als Beweis-Eingabe mit dem privaten Wallet-Keypair des Absenders vor.
            * Das Keypair leitet den Nullifier ab, der das Eingabe-UTXO als ausgegeben markiert, während die Asset- und Mengeninformationen verschlüsselt bleiben.
          </Accordion>

          <Accordion title="3. Den vertraulichen Transfer erstellen und unterschreiben">
            ```typescript theme={"system"}
            import { SOL_MINT } from "@heliuslabs/zolana";
            import { ConfidentialTransfer } from "@heliuslabs/zolana/transaction";

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

            * `senderAddress` ist die <Tooltip tip="Der öffentliche Schlüsselbund (Signatur-, Nullifier- und Ansichtsschlüssel), veröffentlicht im Nutzerregistrierungseintrag. Keine On-Chain-Adresse.">geschützte Adresse</Tooltip> des Absenders. Der Transfer geht von dieser Wallet aus.
            * `[transferInput]` listet die ausgewählten UTXOs des Absenders auf. Ein Transfer kann mehrere UTXOs ausgeben.
            * `senderSigner.address` ist die Solana-Adresse des Gebührenzahlers. Ein Gassponsor kann diese Rolle bei einem vertraulichen Transfer übernehmen.
            * `recipient` ist die geschützte Adresse des Empfängers. Der übertragene Output wird zum Ansichtsschlüssel des Empfängers verschlüsselt.
            * `SOL_MINT` wählt SOL aus. Eine SPL- oder Token-2022-Übertragung übergibt das Token-Mint.
            * `TRANSFER_AMOUNT` ist in den Basiseinheiten des Assets angegeben. SOL verwendet Lamports. SPL- und Token-2022-Assets verwenden die Basiseinheiten des Tokens.
            * `transfer.sign` autorisiert den Zustandsübergang, verschlüsselt das Asset und die Menge und erzeugt die Eingaben für den Zero-Knowledge-Prover.
            * `assets` ist das Asset-Register, das zur Auflösung unterstützter privater Assets verwendet wird.
          </Accordion>

          <Accordion title="4. Zero-Knowledge-Proof abrufen">
            ```typescript theme={"system"}
            import { createZolanaClient } from "@heliuslabs/zolana";

            const transferData = await client.proveTransact(
              transferProofInputs,
            );
            ```

            * `client.proveTransact` generiert den Zero-Knowledge-Proof aus dem unterschriebenen Transfer und gibt serialisierte Anweisungsdaten zurück.
            * Der Beweis zeigt, dass der Absender die Eingaben besitzt und ausgeben kann, ohne das verschlüsselte Asset oder die Menge preiszugeben.
          </Accordion>

          <Accordion title="5. Die Transferanweisung erstellen">
            ```typescript theme={"system"}
            import { transactInstruction } from "@heliuslabs/zolana/interface";

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

            * `payer` signiert und bezahlt die Solana-Transaktion. Ein Gassponsor kann diese Rolle für einen vertraulichen Transfer übernehmen.
            * `inputTree` und `outputTree` sind `client.tree`, der Zustands-Merkle-Baum, der die verbrauchten UTXOs enthält und den Empfänger-Output und die Senderänderung erhält.
            * `data` enthält den Zero-Knowledge-Proof und die im vorherigen Schritt erzeugten verschlüsselten Ausgaben.
            * Ein privater Transfer bewegt das Asset nur zwischen privaten Salden. Er geht nicht über öffentliche Solana-Konten oder Token-Konten.
          </Accordion>

          <Accordion title="6. Senden wie jede Solana-Transaktion">
            ```typescript theme={"system"}
            import { sendAndConfirmFactory } from "../src/lib.js";

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

            * `sendAndConfirm` signiert und übermittelt `transferInstruction` als eine Solana-Transaktion.
            * Die Bestätigung liefert den gelandeten Slot, der verwendet wird, um den Index-Abruf zu steuern.
          </Accordion>
        </AccordionGroup>
      </Step>
    </Steps>

    ### Vollständiges Codebeispiel

    Klone und führe das Beispiel aus:

    ```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>
      Die Beispiele verwenden einen vertraulichen Ring auf Local/Devnet [hier](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-Client">
    <Steps>
      <Step>
        ### Voraussetzungen

        <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>
        ### Übertragung auf ein privates Guthaben

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

        let transfer_utxo = sender_balances_after_deposit
            .get_balance(SOL_MINT)
            // SPL: .get_balance(spl.mint)
            .and_then(|balance| balance.utxos.first())
            .expect("failed to fetch deposited utxo")
            .clone();

        let transfer_input_utxo = SppProofInputUtxo::new(transfer_utxo, &sender);

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

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

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

        <AccordionGroup>
          <Accordion title="1. Wählen Sie private Token-Konten zum Ausgeben">
            ```rust theme={"system"}
            use zolana_transaction::SOL_MINT;

            let transfer_utxo = sender_balances_after_deposit
                .get_balance(SOL_MINT)
                // SPL: .get_balance(spl.mint)
                .and_then(|balance| balance.utxos.first())
                .expect("failed to fetch deposited utxo")
                .clone();
            ```

            * Das Beispiel gibt das private Solana-Token-Konto aus, das durch die vorherige Einzahlung erstellt wurde. Eine Übertragung kann mehrere UTXOs ausgeben.
            * `transfer_utxo` ist das erste ausgebbare UTXO für dieses Asset. Der `// SPL:`-Kommentar zeigt `get_balance(spl.mint)`.
          </Accordion>

          <Accordion title="2. Beweis-Eingaben vorbereiten">
            ```rust theme={"system"}
            use zolana_transaction::instructions::types::SppProofInputUtxo;

            let transfer_input_utxo = SppProofInputUtxo::new(transfer_utxo, &sender);
            ```

            * `SppProofInputUtxo::new` bereitet das ausgewählte UTXO als Beweis-Eingabe mit dem privaten Wallet-Keypair des Absenders vor.
            * Das Keypair leitet den Nullifier ab, der das Eingabe-UTXO als ausgegeben markiert, während die Asset- und Mengeninformationen verschlüsselt bleiben.
          </Accordion>

          <Accordion title="3. Den vertraulichen Transfer erstellen und unterschreiben">
            ```rust theme={"system"}
            use zolana_transaction::{
                instructions::transact::ConfidentialTransfer,
                SOL_MINT,
            };

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

            * `sender_shielded_address` ist die <Tooltip tip="Der öffentliche Schlüsselbund (Signatur-, Nullifier- und Ansichtsschlüssel), veröffentlicht im Nutzerregistrierungseintrag. Keine On-Chain-Adresse.">geschützte Adresse</Tooltip> des Absenders. Der Transfer geht von dieser Wallet aus.
            * `vec![transfer_input_utxo]` listet die ausgewählten UTXOs des Absenders auf. Ein Transfer kann mehrere UTXOs ausgeben.
            * `sender_solana_keypair.pubkey()` ist der Gebührenzahler der Transaktion. Ein Gassponsor kann diese Rolle bei einem vertraulichen Transfer übernehmen.
            * `recipient_address` ist die geschützte Adresse des Empfängers. Der übertragene Output wird zum Ansichtsschlüssel des Empfängers verschlüsselt.
            * `SOL_MINT` wählt SOL aus. Der `// SPL:`-Kommentar zeigt das Token-Mint für SPL- und Token-2022-Assets.
            * `TRANSFER_AMOUNT` ist in den Basiseinheiten des Assets angegeben. SOL verwendet Lamports. SPL- und Token-2022-Assets verwenden die Basiseinheiten des Tokens.
            * `transfer.sign` autorisiert den Zustandsübergang, verschlüsselt das Asset und die Menge und erzeugt die Eingaben für den Zero-Knowledge-Prover.
            * `assets` ist das Asset-Register, das zur Auflösung unterstützter privater Assets verwendet wird.
          </Accordion>

          <Accordion title="4. Zero-Knowledge-Proof abrufen">
            ```rust theme={"system"}
            use zolana_client::Rpc;

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

            * `client.prove_transact` generiert den Zero-Knowledge-Proof aus dem unterschriebenen Transfer und gibt serialisierte Anweisungsdaten zurück.
            * `tree` identifiziert den Zustands-Merkle-Baum, dessen Wurzel verwendet wird, um die Mitgliedschaft des Eingabe-UTXO zu beweisen.
            * Der Beweis zeigt, dass der Absender die Eingaben besitzt und ausgeben kann, ohne das verschlüsselte Asset oder die Menge preiszugeben.
          </Accordion>

          <Accordion title="5. Die Transferanweisung erstellen">
            ```rust theme={"system"}
            use zolana_interface::instruction::Transact;

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

            * `payer` signiert und bezahlt die Solana-Transaktion. Ein Gassponsor kann diese Rolle für einen vertraulichen Transfer übernehmen.
            * `input_tree` identifiziert den Zustands-Merkle-Baum, der die verbrauchten UTXOs enthält.
            * `output_tree` identifiziert den Zustands-Merkle-Baum, der Verpflichtungen zum Empfänger-Output und zur Senderänderung erhält.
            * `interface_transfer_accounts` ist leer, da ein privater Transfer das Asset nur zwischen privaten Salden bewegt und nicht mit öffentlichen Salden in Solana-Konten oder Token-Konten interagiert.
            * `owner_signers` ist leer für diese vertrauliche Übertragung.
            * `data` enthält den Zero-Knowledge-Proof und die im vorherigen Schritt erzeugten verschlüsselten Ausgaben.
          </Accordion>

          <Accordion title="6. Senden wie jede Solana-Transaktion">
            ```rust theme={"system"}
            use zolana_client::Rpc;

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

            * `create_and_send_transaction` signiert und übermittelt `transfer_ix` als eine Solana-Transaktion.
            * `landed_slot` liest den Bestätigungsslot, der verwendet wird, um den Index-Abruf zu steuern.
            * `sender_solana_keypair` bezahlt die Gebühr und autorisiert den Transfer.
          </Accordion>
        </AccordionGroup>
      </Step>
    </Steps>

    ### Vollständiges Codebeispiel

    Klone und führe das Beispiel aus:

    ```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>
      Die Beispiele verwenden einen vertraulichen Ring auf Local/Devnet [hier](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>

## Verwandte Anleitungen

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

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

  <Card title="Privates Guthaben lesen" icon="wallet" href="/docs/de/privacy/guides/read-balance" horizontal />

  <Card title="Private Historie lesen" icon="clock-rotate-left" href="/docs/de/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>
