Cách áp dụng số dư chờ xử lý vào số dư khả dụng
Trước khi token có thể được chuyển một cách bảo mật, số dư token công khai phải được chuyển đổi thành số dư bảo mật. Quá trình chuyển đổi này diễn ra theo hai giai đoạn:
- Số Dư Chờ Xử Lý Bảo Mật: Ban đầu, các token được "nạp" từ số dư công khai vào số dư bảo mật "chờ xử lý".
- Số Dư Khả Dụng Bảo Mật: Số dư chờ xử lý sau đó được "áp dụng" vào số dư khả dụng, giúp các token sẵn sàng cho các giao dịch bảo mật.
Phần này giải thích giai đoạn thứ hai: áp dụng số dư chờ xử lý vào số dư khả dụng.
Khi các token được "nạp" từ số dư công khai hoặc khi các token được chuyển bảo mật từ token account này sang token account khác, các token sẽ ban đầu được thêm vào số dư chờ xử lý bảo mật. Trước khi token có thể được sử dụng cho các giao dịch bảo mật, số dư chờ xử lý phải được "áp dụng" vào số dư khả dụng.
Sơ đồ sau đây mô tả các bước liên quan đến việc áp dụng số dư chờ xử lý vào số dư khả dụng:
Lệnh Bắt Buộc
Để chuyển đổi số dư chờ xử lý thành số dư khả dụng, hãy gọi lệnh ConfidentialTransferInstruction::ApplyPendingBalance.
Crate spl_token_client cung cấp phương thức
confidential_transfer_apply_pending_balance để xây dựng và gửi một giao dịch
với lệnh ApplyPendingBalance, như được minh họa trong ví dụ bên dưới.
Mã Ví Dụ
Ví dụ sau đây minh họa cách áp dụng số dư chờ xử lý bảo mật vào số dư khả dụng bảo mật.
Các giao dịch bảo mật phụ thuộc vào chương trình ZK ElGamal Proof, được kích
hoạt trên mainnet và devnet. Một solana-test-validator thông thường không kích
hoạt nó, nhưng một validator cục bộ fork mainnet như
Surfpool thì có. Chạy ví dụ với một trong những môi
trường đó (mã sử dụng devnet) cùng với một payer có đủ số dư, và thay thế địa
chỉ mint và tài khoản giữ chỗ bằng địa chỉ của bạn.
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?