> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Confidential Transfers

> Use Solana's Token-2022 Confidential Balances extension from Dynamic's Python SDK: configure a token account, deposit, apply a pending balance, transfer, and withdraw with encrypted balances

> [!NOTE]
>
> Confidential transfers require `dynamic-wallet-sdk` version **0.7.0** or later.

## Overview

Solana's Token-2022 [Confidential Balances](https://solana.com/docs/tokens/extensions/confidential-transfer) extension, also called the confidential transfer extension, keeps token balances and transfer amounts encrypted on chain. Wallet and token account addresses stay public.

A token account with the extension enabled holds three balances:

| Balance | On chain | What it holds |
| - | - | - |
| Public balance | Visible | The token's ordinary balance. |
| Pending balance | Encrypted | Funds you deposited or received that you cannot spend yet. |
| Available balance | Encrypted | Funds you can spend confidentially. |

Incoming funds always land in the pending balance. Before you can spend them, you apply the pending balance, which moves it to the available balance. Solana keeps the two apart so that anyone sending you tokens cannot interfere with a transaction you are preparing.

`DynamicSvmWalletClient` builds, signs, and optionally broadcasts each operation for a Dynamic wallet. The operation names match the Token-2022 instructions, and each one is a single Solana transaction.

| Operation | What it does | Options |
| - | - | - |
| `configureAccount` | Enables confidential transfers on a token account. | None |
| `deposit` | Moves tokens from the public balance to the pending balance. | `amount`, `decimals` |
| `applyPendingBalance` | Moves the pending balance to the available balance. | None |
| `transfer` | Moves tokens from your available balance to another token account's pending balance. | `amount`, `decimals`, `destination_token_account` |
| `withdraw` | Moves tokens from the available balance back to the public balance. | `amount`, `decimals` |

`amount` is a string in the token's base units, so `"1000000"` is one token for a mint with 6 decimals. `decimals` must match the mint.

## How it works

Each call to `sign_confidential_transfer` runs the same steps:

1. **Read.** The SDK reads the token account and the latest blockhash from your Solana RPC endpoint, because the secure enclave that builds the transaction cannot reach the network.
2. **Build.** The enclave builds one unsigned transaction. It holds the Token-2022 instruction and the zero-knowledge proofs that the ZK ElGamal Proof program verifies on chain, and it uses the wallet's confidential seed to derive the ElGamal keypair and AES key.
3. **Check.** The SDK verifies the transaction before signing. It refuses to sign unless the transaction is a version 1 transaction, uses only the Token-2022, associated token account, System, ZK ElGamal Proof, and Compute Budget programs, has the wallet as its only signer and fee payer, and contains exactly one confidential instruction that matches the operation, mint, token account, destination, amount, and decimals you passed.
4. **Sign.** The SDK signs the transaction with the wallet's key share in a single signing session.
5. **Broadcast.** If you set `broadcast=True`, the SDK submits the transaction and waits for the `confirmed` commitment level.

The result is a dictionary with `serializedTransactions` (the signed transaction as base64), `signatures` (its base58 signature), and `transactionIds` when you broadcast.

## Prerequisites

* The SDK and `solders`, which derives token account addresses:

  ```bash theme={"system"}
  pip install "dynamic-wallet-sdk>=0.7.0" solders
  ```

* A Solana wallet funded with SOL. The wallet pays the transaction fee, and `configureAccount` also pays the rent to create and grow the token account. See [create a Solana wallet](https://www.dynamic.xyz/docs/python/svm/create-wallet.md).

* A Token-2022 mint with the Confidential Transfer extension enabled. See Solana's [issuer guide](https://solana.com/docs/tokens/extensions/confidential-transfer/issuer-guide). If the mint does not auto-approve new accounts, its confidential transfer authority must approve each token account after `configureAccount`. The SDK does not send that instruction.

* A public balance of the token in the wallet's associated token account before you `deposit`.

* A recipient for `transfer` whose token account for the mint has already run `configureAccount`. The transfer fails if the destination token account does not have confidential transfers enabled.

## Quick Start

This script creates a wallet and its confidential seed, then runs every operation and reads the decrypted balances. It pauses so that you can fund the new wallet.

```python theme={"system"}
import asyncio
import os

from solders.pubkey import Pubkey

from dynamic_wallet_sdk import DynamicSvmWalletClient
from dynamic_wallet_sdk.svm import ConfidentialTransferOptions

TOKEN_2022 = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
ASSOCIATED_TOKEN = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")

RPC_URL = "https://api.devnet.solana.com"
CHAIN_ID = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"  # devnet
MINT = "YourConfidentialMintAddress"
DECIMALS = 6
PASSWORD = "your-secure-password"
# A wallet whose token account for MINT has already run configureAccount.
RECIPIENT_ADDRESS = "RecipientWalletAddress"


def token_account_for(owner: str) -> str:
    """The associated token account of `owner` for MINT."""
    seeds = [bytes(Pubkey.from_string(owner)), bytes(TOKEN_2022), bytes(Pubkey.from_string(MINT))]
    return str(Pubkey.find_program_address(seeds, ASSOCIATED_TOKEN)[0])


async def main():
    async with DynamicSvmWalletClient(
        os.environ["DYNAMIC_ENV_ID"], default_rpc_url=RPC_URL
    ) as client:
        await client.authenticate_api_token(os.environ["DYNAMIC_API_TOKEN"])

        wallet = await client.create_wallet_account(password=PASSWORD)
        wallet_id, address = wallet.wallet_id, wallet.account_address
        token_account = token_account_for(address)

        # Once per wallet, before its first confidential operation.
        await client.create_confidential_seed(wallet_id, address)

        input(f"Send {address} some SOL and some of the token, then press Enter. ")

        async def run(operation: str, **params) -> str:
            result = await client.sign_confidential_transfer(
                wallet_id,
                operation,
                address,
                MINT,
                token_account,
                options=ConfidentialTransferOptions(
                    rpc_url=RPC_URL,
                    chain_id=CHAIN_ID,
                    password=PASSWORD,
                    broadcast=True,
                    **params,
                ),
            )
            return result["transactionIds"][0]

        print("configureAccount", await run("configureAccount"))
        print("deposit", await run("deposit", amount="40000000", decimals=DECIMALS))
        print("applyPendingBalance", await run("applyPendingBalance"))
        print(
            "transfer",
            await run(
                "transfer",
                amount="10000000",
                decimals=DECIMALS,
                destination_token_account=token_account_for(RECIPIENT_ADDRESS),
            ),
        )
        print("withdraw", await run("withdraw", amount="10000000", decimals=DECIMALS))

        balances = await client.get_confidential_balance(
            wallet_id, address, MINT, token_account, rpc_url=RPC_URL
        )
        print(balances["availableBalance"], balances["pendingBalance"])


asyncio.run(main())
```

The snippets in the sections below run inside `main()`, in the `async with` block after the steps above. That is where `client`, `wallet_id`, `address`, `token_account`, and `run` are defined.

> [!WARNING]
>
> `chain_id` defaults to Solana mainnet (`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`). The cluster in `chain_id` must match the cluster behind `rpc_url`. Devnet is `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`.

`rpc_url` falls back to the `default_rpc_url` you pass to the client constructor. It must use `https`. The SDK accepts `http` only for `localhost`, `127.0.0.1`, and `::1`.

## Operations

### Configure a token account

`configureAccount` enables confidential transfers on the wallet's associated token account, and creates the account first if it does not exist. Run it for every token account that will send or receive confidential funds, including the recipient's.

### Deposit, apply, transfer, and withdraw

All five operations use the same `sign_confidential_transfer` call with a different operation name, as in the script above.

* The recipient of a `transfer` receives it as a pending balance. The recipient runs `applyPendingBalance` before they can transfer or withdraw those funds.
* Each operation reads the current token account state, so wait for one to confirm before you start the next. With `broadcast=True` the call returns only after the transaction reaches `confirmed`.
* Pass the `password` you set when you created the wallet. If you hold the wallet's key shares yourself, pass them as `key_shares` instead. For a wallet that requires multi-factor authentication, pass `mfa_token`.

## Read decrypted balances

The pending and available balances are ciphertexts that only the owner's keys can read. `get_confidential_balance` decrypts both inside the enclave and returns them as decimal strings in base units. It reads the token account at the `confirmed` commitment level, so the result includes any operation you have already confirmed.

```python theme={"system"}
balances = await client.get_confidential_balance(
    wallet_id, address, MINT, token_account, rpc_url=RPC_URL
)
print(balances["availableBalance"], balances["pendingBalance"])
```

The public balance is not encrypted. Read it with the `getTokenAccountBalance` JSON-RPC method.

## Sign without broadcasting

Leave out `broadcast` to get the signed transaction back without submitting it. Submit it yourself, and wait for confirmation before the next operation.

```python theme={"system"}
result = await client.sign_confidential_transfer(
    wallet_id,
    "configureAccount",
    address,
    MINT,
    token_account,
    options=ConfidentialTransferOptions(
        rpc_url=RPC_URL, chain_id=CHAIN_ID, password=PASSWORD
    ),
)
signed_transaction = result["serializedTransactions"][0]
```

## Supply account state yourself

By default the SDK fetches account state and the blockhash from `rpc_url`. If you already have them, set `account_state`, `destination_account_state`, `recent_blockhash`, and `last_valid_block_height` on the options and omit `rpc_url`. A call that broadcasts still needs an RPC URL. `account_state` is a dictionary with the keys `data`, `lamports`, and `owner`, where `data` is the base64 account data and `lamports` and `owner` are strings.

## Error handling

SDK errors raise `DynamicSDKError`.

```python theme={"system"}
from dynamic_wallet_sdk import DynamicSDKError

try:
    await run("applyPendingBalance")
except DynamicSDKError as e:
    print(f"Confidential operation failed: {e}")
```

Two common causes are a missing RPC URL, which you fix by passing `rpc_url` or setting `default_rpc_url` on the client, and a transaction the SDK refuses to sign because it does not match the request. In the second case, check `mint`, `token_account`, `amount`, and `decimals`.

## Next steps

* [Sign SVM transactions](https://www.dynamic.xyz/docs/python/svm/sign-transactions.md)
* [Create a Solana wallet](https://www.dynamic.xyz/docs/python/svm/create-wallet.md)
* [Confidential Balances](https://solana.com/docs/tokens/extensions/confidential-transfer) in the Solana documentation


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.