Solana confidential transfers: how Token-2022 privacy works

Historically, solving the privacy problem has meant abandoning the Layer-1 execution environment. Developers have been forced to spin up complex Layer-2 Zero-Knowledge (ZK) rollups, manage off-chain provers, or fragment their liquidity into isolated privacy pools.
Solana’s Token-2022 standard changes this by bringing the cryptography directly to the base layer.
With the introduction of the Solana Confidential Transfers extension, Token-2022 embeds native Zero-Knowledge proofs and Twisted ElGamal encryption directly into the SPL token standard. Instead of moving assets to a separate network to obfuscate them, the tokens themselves carry encrypted state. The runtime mathematically proves to the network that a transfer is valid, ensuring no tokens were secretly minted or burned without ever revealing the actual transfer amount or the underlying account balances.
Just like the Interest-Bearing extension shifted yield from state mutation to pure computation, the Confidential Transfer extension shifts privacy from off-chain infrastructure to native, on-chain cryptography.
However, encrypting and decrypting state natively on the BPF runtime requires a radical shift in how we handle token routing. A standard transfer no longer just subtracts from Account A and adds to Account B. Instead, it introduces complex cryptographic states like “pending balances” and requires explicit ZK-proof verification instructions.
In this deep dive, we are going to unpack the architecture of the
ConfidentialTransferextension. We will break down the encrypted state layout and inspect the raw ciphertexts using the Solana CLI.
What are Solana confidential transfers
When developers first hear “Confidential Transfers,” they often assume it is a privacy coin mixer like Tornado Cash. It is not. Token-2022 Solana Confidential Transfers do not obfuscate the transaction graph.
If Alice sends a confidential transfer to Bob, the network (and anyone looking at a block explorer) can still see:
- The Sender: Alice’s wallet address.
- The Receiver: Bob’s wallet address.
- The Token: The specific Mint address being transferred.
What the network cannot see is the amount of tokens transferred and the underlying balances of Alice and Bob’s accounts.
To achieve this, Token-2022 introduces a dual-state architecture. A single token account now holds two entirely separate balances:
- The Public Balance: The standard
u64integer representing public tokens (fully transparent). - The Confidential Balance: Cryptographic ciphertexts representing encrypted tokens.
These two balances exist side-by-side. Tokens enter the encrypted ecosystem via a Deposit instruction, where a user converts their public tokens into an encrypted ciphertext. However, once those tokens are shielded, they can circulate privately forever. If you receive a confidential transfer from a friend or an employer, you never have to touch the public balance, you can simply hold, transfer, or spend those encrypted tokens using Zero-Knowledge proofs (will be explained shortly). If you ever want to exit the shielded ecosystem, you execute a Withdraw instruction, decrypting the ciphertext and moving the funds back to your public balance.
A high-level overview of the cryptography of encrypted state
To understand the architecture, let’s understand the core problem.
Imagine Alice wants to send Bob 10 tokens. On a standard SPL token, the runtime simply checks if Alice has >= 10 tokens and if so subtracts 10 from her balance, and adds 10 to Bob’s balance.
If we want to make this confidential, the most obvious solution is to encrypt the token balances before writing them to the account data. But this introduces an immediate architectural roadblock: if the Token-2022 program cannot read the raw numbers because they are encrypted, how can it execute the transfer? It cannot natively subtract 10 from Alice’s account if it doesn’t know what her balance is.
Linear homomorphism
To solve this, Token-2022 relies on a class of encryption called Linear Homomorphism.
A linearly homomorphic encryption scheme allows mathematical operations, like addition and subtraction to be performed directly on encrypted ciphertexts, yielding a result that perfectly matches the encrypted sum or difference of the underlying plaintexts.
When Alice initiates a confidential transfer, she doesn’t pass the raw number 10 to the program. Instead, she generates an encrypted ciphertext of the transfer amount. The Token-2022 program simply takes Alice’s encrypted account balance, subtracts the encrypted transfer amount, and saves the new ciphertext back to her state. It then takes Bob’s encrypted balance, adds the encrypted transfer amount, and saves it.
Encrypted(50) – Encrypted(10) = Encrypted(40)
Because the math is homomorphic, the Solana runtime successfully processes the transfer without ever knowing that the underlying numbers were 50, 10, and 40.
Zero-Knowledge Proofs – trust but verify
If the program never decrypts the underlying numbers, what stops a malicious user with an encrypted balance of 50 from sending an encrypted transfer of 70? Or what stops them from sending a negative amount to mathematically inflate their own balance?
Because the token program cannot natively see the hidden inputs, every confidential transfer must be accompanied by Zero-Knowledge (ZK) Proofs generated locally by the sender’s wallet:
- Range Proofs: The sender must prove that the transfer amount is a positive 64-bit integer, and that subtracting this amount from their current balance will leave a value >= 0.
- Equality Proofs: The transfer amount is encrypted twice, once under the sender’s public key to deduct it and once under the receiver’s public key to add it. The sender must mathematically prove that both ciphertexts encrypt the exact same underlying value.
Token-2022 passes these proofs to Solana’s native ZK Token Proof program. Once the ZK program verifies the math is sound, Token-2022 executes the homomorphic addition and subtraction.
📖 More detailed reference can be found here.
Initializing the cryptographic state via CLI
To truly understand how this dual-state architecture works, we need to look at it on-chain. Let’s use the Solana CLI to create a confidential token, fund a user’s account, and watch the state transition from public to encrypted.
Creating the mint
First, we initialize a Token-2022 mint and explicitly tell the program to allocate the ConfidentialTransfer extension.
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-confidential-transfers autoNote: The
autokeyword means that any token user can permissionlessly configure their account to perform confidential transfers.
Configuring the user’s Token Account
Next, we create a standard token account for our wallet. However, a standard token account can only hold a public u64 balance. We must explicitly configure the account to allocate the extra bytes required to hold the ElGamal ciphertexts.
# Create the base token account
spl-token create-account <YOUR_MINT_ADDRESS>
# Allocate the cryptographic extension and generate the ElGamal keypair
spl-token configure-confidential-transfer-account <YOUR_MINT_ADDRESS>Under the hood, this second command does something important, it generates a Twisted ElGamal keypair (will be explained shortly) locally on your machine and stores the public key directly on the token account. This is the key the Token-2022 program will use to encrypt your incoming transfers.
The public-to-confidential deposit
Now that our account is configured for cryptography, let’s give ourselves 100 public tokens.
spl-token mint <YOUR_MINT_ADDRESS> 100lets take a look at the token account state
spl-token display <TOKEN_ACCOUNT_ACCRESS>
We can see that the public balance is 100 and still not encrypted.
To enter the shielded state, we must deposit these public tokens into the encrypted balance.
spl-token deposit-confidential-tokens <YOUR_MINT_ADDRESS> 100and run the display again
spl-token display <TOKEN_ACCOUNT_ACCRESS>
This is where the magic happens. The Token-2022 program deducts the 100 from your public amount, encrypts the number 100 using your account’s ElGamal public key, and writes the resulting ciphertext to the ConfidentialTransferAccount TLV extension.
right now, your public balance will show 0, but your funds are not gone, they are locked inside the cryptographic state, ready to be transferred with Zero-Knowledge proofs.
Note: Even though your CLI wallet holds the ElGamal private key locally, the standard
spl-tokenCLI currently lacks a--decryptflag. Instead of a clean integer, you will be staring at raw, encrypted bytes, the actual Pedersen commitments and ciphertexts living on the BPF runtime. To see the human-readable number100, you must write a custom client-side script to manually fetch the state and decrypt the ciphertext locally.📖 Open Github issue: https://github.com/solana-program/token-2022/issues/145
Preventing cryptographic griefing with the split state architecture
Encrypting state and using ZK proofs creates a unique new attack vector that doesn’t exist in standard transparent tokens: Cryptographic Front-Running.
To submit a valid transfer, Alice must generate her ZK proofs locally on her machine. This proof is mathematically tied to her current encrypted account balance on-chain. Generating this proof takes computational time (often a few seconds on a standard client).
Imagine Alice generates a proof based on her encrypted balance of 50 tokens and submits the transaction to the network. However, milliseconds before her transaction lands, a malicious actor (or just a busy protocol) transfers 1 token to Alice.
Because of the homomorphic math, that incoming 1 token mutates Alice’s encrypted state on-chain. When Alice’s transaction finally hits the Token-2022 program a fraction of a second later, the ZK proof she generated is instantly rejected because it was built against the old ciphertext.
If an attacker continuously flooded Alice’s account with micro-transfers (dusting), they could constantly mutate her state, making it mathematically impossible for her to ever generate a valid ZK proof fast enough to send an outgoing transfer. Her account would be permanently bricked.
The solution: splitting the balance
To solve this, Token-2022 fundamentally alters the Token Account layout. The ConfidentialTransferAccount TLV extension splits the encrypted balance into two entirely separate ciphertexts:
available_balancepending_balance
When Alice sends tokens out, the Token-2022 program subtracts the ciphertext strictly from her available_balance. When someone sends tokens to Alice, the program adds the ciphertext strictly to her pending_balance.
This strict separation guarantees that nobody but Alice can mutate her available_balance. Because her ZK proofs are generated exclusively against her available_balance, incoming transfers can no longer invalidate her outgoing proofs. The attack vector is completely neutralized.
However, this architecture introduces an integration trap.
The mechanics of the pending balance
The split-state architecture solves the cryptographic front-running problem. Because incoming transfers only hit your pending_balance, your available_balance remains completely static. This ensures that the ZK proofs your client is busy generating locally will never be invalidated by unexpected network traffic.
However, this architectural safeguard introduces a necessary friction point in the token lifecycle: Incoming funds are not immediately spendable.
If Alice sends you 50 encrypted tokens, the Token-2022 program homomorphically adds the transfer ciphertext to your pending_balance ciphertext. Your available_balance remains untouched. Because outgoing transfers can only deduct from the available_balance, those 50 tokens are effectively parked in a cryptographic waiting room.
The state sweep – apply pending balance
To actually spend received funds, the account owner must explicitly instruct the Token-2022 program to merge the two ciphertexts. This is done via the ApplyPendingBalance instruction.
spl-token apply-pending-balance <MINT_ACCOUNT>
When this instruction is fired, it triggers a deterministic state transition on-chain. The Token-2022 processor:
- Takes the current
pending_balanceciphertext. - Homomorphically adds it to the
available_balanceciphertext. - Overwrites the
available_balancewith the new combined ciphertext. - Resets the
pending_balanceciphertext to a zero-value encryption.
Note: This is done manually because by forcing the account owner to explicitly call
ApplyPendingBalance, the protocol guarantees that theavailable_balanceonly ever mutates exactly when the user wants it to. You control your own state updates, ensuring your local ZK proof generation environment remains perfectly stable.
The transfer trap: failing vs succeeding hands-on
Let’s prove that the 100 tokens we just deposited are unspendable in their current state. We will try to send 50 confidential tokens to a friend.
Prerequisite: The Receiver Opt-In Handshake
If you try to send confidential tokens to a standard public wallet, the transfer will instantly fail because to send an encrypted transfer, your local client must encrypt the transfer amount using the receiver’s ElGamal public key.
If Bob hasn’t explicitly opted into the shielded ecosystem, he doesn’t have an ElGamal keypair attached to his account. You cannot force-send privacy tokens, it is a two-way handshake. Before we can route tokens to Bob, he must create an account and configure his cryptography:
# Bob creates his token account
spl-token create-account <YOUR_MINT_ADDRESS> --owner bob.json
# Bob generates his ElGamal keypair and appends it to his account
spl-token configure-confidential-transfer-account <YOUR_MINT_ADDRESS> --owner bob.jsonThe fail case
Now that Bob is mathematically ready to receive encrypted funds, let’s execute the transfer. We must pass the --confidential flag, otherwise the CLI will attempt a standard public routing.
spl-token transfer <MINT_ACCOUNT> 50 <BOB_TOKEN_ACCOUNT> --confidentialOutput

The program fails because it specifically attempts to deduct 50 tokens from your Available Balance, which is mathematically zero. Your 100 tokens are still stuck in the Pending Balance ciphertext.
The success case
To actually spend these received funds, you must explicitly instruct the Token-2022 program to mathematically merge the two ciphertexts. This is done via the ApplyPendingBalance instruction.
Note: If you pass your Token Account address directly to this command, the CLI will hallucinate a fake Associated Token Account and throw an
Account not founderror. You must either pass the Mint address
spl-token apply-pending-balance <MINT_ADDRESS>The Token-2022 takes the current pending_balance ciphertext, homomorphically adds it to the available_balance ciphertext, overwrites the state, and resets the pending balance to zero.
Now that the sweep is complete, our 100 tokens have safely moved into our Available Balance. If we run the exact same transfer command again
spl-token transfer <MINT_ACCOUNT> 50 <BOB_TOKEN_ACCOUNT> --confidentialOutput

The withdrawal instruction – exiting the shielded state
Finally, what happens when a user wants to exit the shielded ecosystem? Perhaps you want to view your raw balance on a standard block explorer, or you need to interact with legacy Solana infrastructure that doesn’t support the ConfidentialTransfer extension.
You must transition the state from encrypted back to public using the Withdraw instruction.
spl-token withdraw-confidential-tokens <MINT_ADDRESS> 100Output after running the display command

This instruction is the exact inverse of the Deposit we ran in preiously. When you deposit, you are just encrypting a public number. But when you withdraw, you are asking the public network to subtract a specific amount from an encrypted balance.
To execute this, your local client must do the heavy lifting:
- It uses your local ElGamal private key to decrypt your current
available_balance. - It verifies you actually have >= 100 tokens.
- It generates a ZK proof mathematically verifying that subtracting 100 from the ciphertext is valid and will not result in a negative number.
It sends this proof to the network. The Token-2022 program verifies the proof via the ZK Token Proof program, homomorphically subtracts the ciphertext of 100 from your available_balance, and explicitly adds the raw integer 100 back to your public amount field. Your funds are now public again.
Smart contract integration
If you are writing a smart contract, say, a payment router or an automated market maker, and it receives a confidential transfer, the contract cannot immediately route those tokens in the next instruction. The funds will be stuck in the contract’s
pending_balance. Your program must execute a Cross-Program Invocation to triggerApplyPendingBalanceto merge the ciphertexts before attempting any outgoing transfers. Forgetting this single CPI will cause every incoming transaction to your protocol to hard-fail.
Conclusion
The Token-2022 Confidential Transfer extension is a masterclass in natively integrating zero-knowledge cryptography into a high-performance runtime.
By pushing the encryption directly to the L1 standard:
- Liquidity Remains Unified: Developers no longer have to bridge assets to isolated Layer-2 ZK-rollups to achieve privacy.
- State is Protected: The
pendingvs.availablebalance split brilliantly neutralizes the cryptographic griefing vectors that plague other privacy networks. - Math Replaces Trust: Linear homomorphism ensures the base layer can blindly but flawlessly execute accounting without ever compromising user data.
Reliable Solana RPC infrastructure
Getting started with Solana on Chainstack is fast and straightforward. Developers can deploy a reliable Solana node within seconds through an intuitive Console — no complex setup or hardware management required.
Chainstack provides low-latency Solana RPC access and real-time gRPC data streaming via Yellowstone Geyser Plugin, ensuring seamless connectivity for building, testing, and scaling DeFi, analytics, and trading applications. With Solana low-latency endpoints powered by global infrastructure, you can achieve lightning-fast response times and consistent performance across regions.
Start for free, connect your app to a reliable Solana RPC endpoint, and experience how easy it is to build and scale on Solana with Chainstack – one of the best RPC providers.
FAQ
It is a Token-2022 transfer that hides the token amount and confidential balances while keeping the account addresses and mint public.
Public balances are standard visible token amounts. Confidential balances are encrypted values that can be transferred and validated with zero-knowledge proofs.
No. The sender, receiver, and token mint remain visible. Only the transferred amount and confidential balances are hidden.
Pending balances prevent invalid state transitions and help the protocol handle confidential transfers safely before balances become available.
Tokens are first deposited from the public balance into the confidential pending balance, then applied into the confidential available balance.
Yes. The withdraw instruction decrypts and moves tokens from confidential available balance back into the public balance.
The extension uses Twisted ElGamal encryption and zero-knowledge proofs to validate transfers without revealing token amounts.
Learn more about Solana architecture from our articles
- Solana Token-2022 Metadata
- Where Token Metadata Lives on Solana
- SPL Token Program Architecture
- Architecture & Parallel Transactions
- Solana Interest-Bearing Tokens: Inside the Mint Extension
- Account Model and Transactions
- Anchor Accounts: Seeds, Bumps, PDAs
- Instructions and Messages
- Transaction, Serialization, Signatures, Fees, and Runtime Execution





