Terapkan saldo tertunda

Cara menerapkan saldo tertunda ke saldo tersedia

Sebelum token dapat ditransfer secara rahasia, saldo token publik harus dikonversi menjadi saldo rahasia. Konversi ini terjadi dalam dua tahap:

  1. Saldo Tertunda Rahasia: Awalnya, token "disetor" dari saldo publik ke saldo rahasia "tertunda".
  2. Saldo Tersedia Rahasia: Saldo tertunda kemudian "diterapkan" ke saldo tersedia, membuat token tersedia untuk transfer rahasia.

Bagian ini menjelaskan tahap kedua: menerapkan saldo tertunda ke saldo tersedia.

Ketika token "disetor" dari saldo publik atau ketika token ditransfer secara rahasia dari satu token account ke token account lain, token tersebut awalnya ditambahkan ke saldo tertunda rahasia. Sebelum token dapat digunakan untuk transfer rahasia, saldo tertunda harus "diterapkan" ke saldo tersedia.

Apply Pending Balance

Diagram berikut menunjukkan langkah-langkah yang terlibat dalam menerapkan saldo tertunda ke saldo tersedia:

Apply Pending Balance

Instruksi yang Diperlukan

Untuk mengonversi saldo tertunda menjadi saldo tersedia, panggil instruksi ConfidentialTransferInstruction::ApplyPendingBalance.

Crate spl_token_client menyediakan metode confidential_transfer_apply_pending_balance yang membangun dan mengirim transaksi dengan instruksi ApplyPendingBalance, seperti yang ditunjukkan dalam contoh di bawah ini.

Contoh Kode

Contoh berikut menunjukkan cara menerapkan saldo tertunda rahasia ke saldo tersedia rahasia.

Transfer rahasia bergantung pada program ZK ElGamal Proof, yang diaktifkan di mainnet dan devnet. solana-test-validator standar tidak mengaktifkannya, tetapi validator lokal yang mem-fork mainnet seperti Surfpool mengaktifkannya. Jalankan contoh terhadap salah satunya (kode menggunakan devnet) dengan payer yang telah didanai, dan ganti alamat mint dan akun placeholder dengan milik Anda sendiri.

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?

Daftar Isi

Edit Halaman
© 2026 Yayasan Solana. Semua hak dilindungi.