Как применить отложенный баланс к доступному балансу
Прежде чем токены можно будет передавать конфиденциально, публичный баланс токенов необходимо преобразовать в конфиденциальный баланс. Это преобразование происходит в два этапа:
- Конфиденциальный отложенный баланс: Изначально токены «вносятся» из публичного баланса в «отложенный» конфиденциальный баланс.
- Конфиденциальный доступный баланс: Затем отложенный баланс «применяется» к доступному балансу, делая токены доступными для конфиденциальных переводов.
В этом разделе объясняется второй этап: применение отложенного баланса к доступному балансу.
Когда токены «вносятся» из публичного баланса или конфиденциально переводятся с одного token account на другой, токены изначально добавляются в конфиденциальный отложенный баланс. Прежде чем токены можно будет использовать для конфиденциальных переводов, отложенный баланс должен быть «применён» к доступному балансу.
На следующей диаграмме показаны шаги, выполняемые при применении отложенного баланса к доступному балансу:
Необходимая инструкция
Чтобы преобразовать отложенный баланс в доступный баланс, используйте инструкцию ConfidentialTransferInstruction::ApplyPendingBalance.
Крейт spl_token_client предоставляет метод
confidential_transfer_apply_pending_balance, который формирует и отправляет
транзакцию с инструкцией ApplyPendingBalance, как показано в примере ниже.
Пример кода
Следующий пример демонстрирует, как применить конфиденциальный отложенный баланс к конфиденциальному доступному балансу.
Конфиденциальные переводы зависят от программы ZK ElGamal Proof, которая
включена в mainnet и devnet. Стандартный solana-test-validator не включает её,
однако локальный validator с форком mainnet, например
Surfpool, поддерживает её. Запустите пример с одним из
них (в коде используется devnet), используя аккаунт с достаточным балансом, и
замените placeholder-адреса минта и аккаунта на собственные.
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?