Comment appliquer le solde en attente au solde disponible
Avant que les tokens puissent être transférés de manière confidentielle, le solde public de tokens doit être converti en solde confidentiel. Cette conversion s'effectue en deux étapes :
- Solde confidentiel en attente : Dans un premier temps, les tokens sont « déposés » depuis le solde public vers un solde confidentiel « en attente ».
- Solde confidentiel disponible : Le solde en attente est ensuite « appliqué » au solde disponible, rendant les tokens disponibles pour les transferts confidentiels.
Cette section explique la deuxième étape : l'application du solde en attente au solde disponible.
Lorsque des tokens sont « déposés » depuis le solde public ou lorsqu'ils sont transférés de manière confidentielle d'un token account à un autre, les tokens sont initalement ajoutés au solde confidentiel en attente. Avant que les tokens puissent être utilisés pour des transferts confidentiels, le solde en attente doit être « appliqué » au solde disponible.
Le diagramme suivant illustre les étapes impliquées dans l'application du solde en attente au solde disponible :
Instruction requise
Pour convertir un solde en attente en solde disponible, invoquez l'instruction ConfidentialTransferInstruction::ApplyPendingBalance.
La crate spl_token_client fournit une méthode
confidential_transfer_apply_pending_balance qui construit et envoie une
transaction avec l'instruction ApplyPendingBalance, comme illustré dans
l'exemple ci-dessous.
Exemple de code
L'exemple suivant montre comment appliquer le solde confidentiel en attente au solde confidentiel disponible.
Les transferts confidentiels dépendent du programme ZK ElGamal Proof, qui est
activé sur le mainnet et le devnet. Un validator standard
solana-test-validator ne l'active pas, mais un validator local avec fork du
mainnet tel que Surfpool le fait. Exécutez l'exemple sur
l'un d'eux (le code utilise le devnet) avec un payeur disposant de fonds, et
remplacez les adresses de mint et de compte fictives par les vôtres.
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?