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

> Configure token accounts, move tokens between public and confidential balances, and send Token-2022 confidential transfers from a Solana wallet using Dynamic's Python SDK

## Overview

The Token-2022 confidential transfer extension keeps token balances and transfer amounts encrypted on chain while addresses stay public. A confidential token account holds a public balance and two encrypted balances: a pending balance that receives incoming funds, and an available balance that you can spend. `DynamicSvmWalletClient` builds, signs, and optionally broadcasts each operation for a Dynamic wallet, and it can decrypt the encrypted balances so your application can display them.

There are five operations, and each one is a single Solana transaction.

| Operation | What it does | Extra options |
| - | - | - |
| `configureAccount` | Enables confidential transfers on a token account. | None |
| `deposit` | Moves tokens from the public balance into the pending balance. | `amount`, `decimals` |
| `applyPendingBalance` | Moves the pending balance into the available balance. | None |
| `transfer` | Sends tokens from the available balance to another confidential token account, which receives them as pending. | `amount`, `decimals`, `destination_token_account` |
| `withdraw` | Moves tokens from the available balance back to the public balance. | `amount`, `decimals` |

Amounts are strings in the token's base units, so `"1000000"` is one token when the mint has 6 decimals.

## How signing works

Each call to `sign_confidential_transfer` runs the same sequence:

1. The SDK reads the token account state and the latest blockhash from the RPC endpoint you provide, because the secure enclave that builds the transaction cannot reach the network.
2. The enclave builds one unsigned transaction containing the confidential instruction and its zero-knowledge proofs, using the wallet's confidential seed.
3. The SDK checks 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. The SDK signs the transaction with the wallet's key share in a single signing session.
5. 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

* [A Solana wallet](https://www.dynamic.xyz/docs/python/svm/create-wallet.md) funded with SOL for network fees.
* A Token-2022 mint with the confidential transfer extension enabled.
* An associated token account for that mint, owned by each wallet that takes part. Your application creates these accounts. The SDK does not.
* `solders` installed to derive token account addresses.

## Step 1: Set up the client

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

from solders.pubkey import Pubkey

from dynamic_wallet_sdk import DynamicSDKError, 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"
RECIPIENT_ADDRESS = "RecipientWalletAddress"


def token_account_for(owner: str) -> str:
    seeds = [bytes(Pubkey.from_string(owner)), bytes(TOKEN_2022), bytes(Pubkey.from_string(MINT))]
    return str(Pubkey.find_program_address(seeds, ASSOCIATED_TOKEN)[0])
```

> [!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 when you leave it out of the options. It must use `https`. The SDK accepts `http` only for `localhost`, `127.0.0.1`, and `::1`.

## Step 2: Create the confidential seed

Create the confidential seed once for each wallet, before its first confidential operation. The enclave generates the seed and stores it encrypted. The method returns the wallet's ElGamal public key and the derivation version.

```python theme={"system"}
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)
        seed = await client.create_confidential_seed(wallet.wallet_id, wallet.account_address)
        print(seed["elgamalPubkey"], seed["derivationVersion"])
```

The remaining steps run inside the same `async with` block and use these values.

```python theme={"system"}
        wallet_id = wallet.wallet_id
        address = wallet.account_address
        token_account = token_account_for(address)
```

## Step 3: Configure the token account

`configureAccount` enables confidential transfers on the wallet's token account. Run it for every token account that will send or receive confidential funds, including the recipient's.

```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,
                broadcast=True,
                password=PASSWORD,
            ),
        )
        print(result["transactionIds"])
```

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

## Step 4: Deposit, apply, transfer, and withdraw

Every operation uses the same call with a different operation name. The wallet must hold a public balance of the token before it can deposit.

```python theme={"system"}
        def options(**extra) -> ConfidentialTransferOptions:
            return ConfidentialTransferOptions(
                rpc_url=RPC_URL,
                chain_id=CHAIN_ID,
                broadcast=True,
                password=PASSWORD,
                **extra,
            )

        # Public balance to pending balance
        await client.sign_confidential_transfer(
            wallet_id, "deposit", address, MINT, token_account,
            options=options(amount="40000000", decimals=DECIMALS),
        )

        # Pending balance to available balance
        await client.sign_confidential_transfer(
            wallet_id, "applyPendingBalance", address, MINT, token_account,
            options=options(),
        )

        # Available balance to another confidential token account
        await client.sign_confidential_transfer(
            wallet_id, "transfer", address, MINT, token_account,
            options=options(
                amount="10000000",
                decimals=DECIMALS,
                destination_token_account=token_account_for(RECIPIENT_ADDRESS),
            ),
        )

        # Available balance back to the public balance
        await client.sign_confidential_transfer(
            wallet_id, "withdraw", address, MINT, token_account,
            options=options(amount="10000000", decimals=DECIMALS),
        )
```

Each operation reads the current token account state, so wait for one operation to confirm before you start the next. With `broadcast=True` the call returns only after the transaction reaches the `confirmed` commitment level.

The recipient receives a transfer as a pending balance. The recipient's wallet must run `applyPendingBalance` before it can transfer or withdraw those funds.

## Read decrypted balances

`get_confidential_balance` decrypts both encrypted balances 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"])


asyncio.run(main())
```

The last line runs `main()`. Keep it at the end of the script, outside the function, when you add any of the steps that follow.

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`. Catch it inside the same `async with` block.

```python theme={"system"}
        try:
            await client.sign_confidential_transfer(
                wallet_id, "applyPendingBalance", address, MINT, token_account,
                options=options(),
            )
        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)


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