Aplicar saldo pendente

Como aplicar o saldo pendente ao saldo disponível

Antes que os tokens possam ser transferidos de forma confidencial, o saldo público de tokens deve ser convertido em saldo confidencial. Essa conversão ocorre em duas etapas:

  1. Saldo Pendente Confidencial: Inicialmente, os tokens são "depositados" do saldo público para um saldo confidencial "pendente".
  2. Saldo Disponível Confidencial: O saldo pendente é então "aplicado" ao saldo disponível, tornando os tokens disponíveis para transferências confidenciais.

Esta seção explica a segunda etapa: aplicar o saldo pendente ao saldo disponível.

Quando os tokens são "depositados" do saldo público ou quando são transferidos de forma confidencial de um token account para outro, os tokens são inicialmente adicionados ao saldo confidencial pendente. Antes que os tokens possam ser usados para transferências confidenciais, o saldo pendente deve ser "aplicado" ao saldo disponível.

Apply Pending Balance

O diagrama a seguir mostra as etapas envolvidas na aplicação do saldo pendente ao saldo disponível:

Apply Pending Balance

Instrução Necessária

Para converter um saldo pendente em saldo disponível, invoque a instrução ConfidentialTransferInstruction::ApplyPendingBalance.

O crate spl_token_client fornece um método confidential_transfer_apply_pending_balance que constrói e envia uma transação com a instrução ApplyPendingBalance, conforme demonstrado no exemplo abaixo.

Código de Exemplo

O exemplo a seguir demonstra como aplicar o saldo confidencial pendente ao saldo confidencial disponível.

As transferências confidenciais dependem do programa ZK ElGamal Proof, que está habilitado na mainnet e na devnet. Um solana-test-validator padrão não o habilita, mas um validator local com fork da mainnet, como o Surfpool, sim. Execute o exemplo em um desses ambientes (o código usa a devnet) com um pagador com saldo, e substitua os endereços de mint e conta de exemplo pelos seus próprios.

Rust

const ZK_PROOF_PROGRAM_ID: Pubkey =
solana_pubkey::pubkey!("ZkE1Gama1Proof11111111111111111111111111111");
fn main() -> Result<()> {
let rpc_client = RpcClient::new_with_commitment(
String::from("https://api.devnet.solana.com"),
CommitmentConfig::confirmed(),
);
let owner = load_keypair()?;
let amount: u64 = 100;
let decimals: u8 = 2;
// Setup: create a confidential account with a pending balance.
let (_mint, token_account) = setup_pending_balance_account(&rpc_client, &owner, amount, decimals)?;
// Derive the owner's keys to decrypt the current balances.
let (elgamal_keypair, aes_key) = derive_confidential_keys(&owner, &token_account.to_bytes())
.map_err(|e| anyhow::anyhow!("derive confidential keys: {e}"))?;
let account_data = rpc_client.get_account(&token_account)?;
let account = StateWithExtensions::<TokenAccount>::unpack(&account_data.data)?;
let ct_extension = account.get_extension::<ConfidentialTransferAccount>()?;
// The pending balance is split into low (16 bits) and high parts, each small
// enough to recover with ElGamal's bounded decrypt_u32.
let pending_lo: ElGamalCiphertext = ct_extension
.pending_balance_lo
.try_into()
.map_err(|e| anyhow::anyhow!("pending_balance_lo: {e:?}"))?;
let pending_hi: ElGamalCiphertext = ct_extension
.pending_balance_hi
.try_into()
.map_err(|e| anyhow::anyhow!("pending_balance_hi: {e:?}"))?;
let pending_lo_amount = pending_lo
.decrypt_u32(elgamal_keypair.secret())
.context("decrypt pending_balance_lo")? as u64;
let pending_hi_amount = pending_hi
.decrypt_u32(elgamal_keypair.secret())
.context("decrypt pending_balance_hi")? as u64;
let pending_total = pending_lo_amount + (pending_hi_amount << 16);
// Read the current available balance from the AES-encrypted decryptable
// balance. ElGamal's decrypt_u32 only recovers values up to 2^32 raw units,
// so it fails for realistic balances; the AES field has no such limit.
let decryptable_balance: AeCiphertext = ct_extension
.decryptable_available_balance
.try_into()
.map_err(|e| anyhow::anyhow!("decryptable_available_balance: {e:?}"))?;
let current_available = decryptable_balance
.decrypt(&aes_key)
.context("decrypt available balance")?;
let new_available = current_available + pending_total;
// Re-encrypt the new available balance with the AES key for fast reads.
let new_decryptable: PodAeCiphertext = aes_key.encrypt(new_available).into();
// The expected counter guards against pending credits that arrive between
// building and processing this instruction.
let expected_counter: u64 = ct_extension.pending_balance_credit_counter.into();
let apply_ix = apply_pending_balance(
&spl_token_2022::id(),
&token_account,
expected_counter,
&new_decryptable,
&owner.pubkey(),
&[&owner.pubkey()],
)?;
let blockhash = rpc_client.get_latest_blockhash()?;
let transaction =
Transaction::new_signed_with_payer(&[apply_ix], Some(&owner.pubkey()), &[&owner], blockhash);
let signature = rpc_client.send_and_confirm_transaction(&transaction)?;
println!("Applied pending balance. New available: {new_available}. Tx: {signature}");
Ok(())
}

Typescript

const client = await createClient()
.use(signerFromFile(join(homedir(), ".config/solana/id.json")))
.use(
solanaRpc({
rpcUrl: "https://api.devnet.solana.com"
})
);
// The Solana CLI default keypair, used as fee payer, mint authority, and
// token account owner.
const owner = client.payer;
const amount = 100n;
const decimals = 2;
// Setup: create a confidential account with a pending balance.
const mint = await createConfidentialMint(client, owner, decimals);
const token = await createConfidentialTokenAccount(client, owner, mint);
await mintPublicTokens(client, owner, mint, token, amount);
await depositTokens(client, owner, mint, token, amount, decimals);
// Derive the owner's keys to decrypt the current balances.
const { elgamalSecretKey, aesKey } = await deriveConfidentialKeys(owner, mint);
// The helper decrypts the pending and available balances, re-encrypts the new
// available balance, and builds the ApplyPendingBalance instruction. No proof
// is required.
const tokenAccount = await fetchToken(client.rpc, token);
const applyInstruction = getApplyConfidentialPendingBalanceInstructionFromToken(
{
token,
tokenAccount: tokenAccount.data,
authority: owner,
elgamalSecretKey,
aesKey
}
);
const result = await client.sendTransaction([applyInstruction]);
console.log(
`Applied pending balance. New available: ${amount}. Tx: ${result.context.signature}`
);

Is this page helpful?

Índice

Editar Página
© 2026 Fundação Solana. Todos os direitos reservados.