كيفية تطبيق الرصيد المعلّق على الرصيد المتاح
قبل أن يمكن نقل الرموز المشفرة بشكل سري، يجب تحويل رصيد الرمز العام إلى رصيد سري. يتم هذا التحويل على مرحلتين:
- الرصيد السري المعلّق: في البداية، يتم "إيداع" الرموز من الرصيد العام إلى رصيد سري "معلّق".
- الرصيد السري المتاح: يتم بعد ذلك "تطبيق" الرصيد المعلّق على الرصيد المتاح، مما يجعل الرموز متاحة للتحويلات السرية.
يشرح هذا القسم المرحلة الثانية: تطبيق الرصيد المعلّق على الرصيد المتاح.
عندما يتم "إيداع" الرموز من الرصيد العام أو عندما يتم نقل الرموز بشكل سري من token account إلى token account آخر، تُضاف الرموز في البداية إلى الرصيد السري المعلّق. قبل أن تتمكن الرموز من الاستخدام في التحويلات السرية، يجب "تطبيق" الرصيد المعلّق على الرصيد المتاح.
يوضح المخطط التالي الخطوات المتبعة في تطبيق الرصيد المعلّق على الرصيد المتاح:
التعليمة المطلوبة
لتحويل رصيد معلّق إلى رصيد متاح، قم باستدعاء تعليمة ConfidentialTransferInstruction::ApplyPendingBalance.
توفر حزمة spl_token_client الطريقة
confidential_transfer_apply_pending_balance التي تبني وترسل معاملة تحتوي على
تعليمة ApplyPendingBalance، كما هو موضح في المثال أدناه.
مثال على الكود
يوضح المثال التالي كيفية تطبيق الرصيد السري المعلّق على الرصيد السري المتاح.
تعتمد التحويلات السرية على برنامج ZK ElGamal Proof، المُفعَّل على الشبكة
الرئيسية وشبكة devnet. لا يُفعِّل solana-test-validator الاعتيادي هذه الميزة،
لكن يفعل ذلك validator محلي يعمل بنسخ مطابقة للشبكة الرئيسية مثل
Surfpool. شغّل المثال مقابل أحد هذه الخيارات (يستخدم
الكود شبكة devnet) مع حساب ممول، واستبدل عنواني العملة والحساب الوهميَّين
بعنوانيك الخاصَّين.
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?