대기 중인 잔액을 사용 가능한 잔액에 적용하는 방법
토큰을 기밀로 전송하려면 먼저 공개 토큰 잔액을 기밀 잔액으로 변환해야 합니다. 이 변환은 두 단계로 이루어집니다:
- 기밀 대기 잔액: 처음에는 토큰이 공개 잔액에서 "대기" 기밀 잔액으로 "입금"됩니다.
- 기밀 사용 가능 잔액: 그런 다음 대기 잔액이 사용 가능한 잔액에 "적용"되어 토큰을 기밀 전송에 사용할 수 있게 됩니다.
이 섹션에서는 두 번째 단계인 대기 잔액을 사용 가능한 잔액에 적용하는 과정을 설명합니다.
토큰이 공개 잔액에서 "입금"되거나 한 token account에서 다른 token account로 기밀 전송될 때, 토큰은 처음에 기밀 대기 잔액에 추가됩니다. 토큰을 기밀 전송에 사용하려면 대기 잔액을 사용 가능한 잔액에 "적용"해야 합니다.
다음 다이어그램은 대기 잔액을 사용 가능한 잔액에 적용하는 단계를 보여줍니다:
필수 명령어
대기 잔액을 사용 가능한 잔액으로 변환하려면 ConfidentialTransferInstruction::ApplyPendingBalance 명령어를 호출하세요.
spl_token_client 크레이트는 confidential_transfer_apply_pending_balance
메서드를 제공하며, 이 메서드는 아래 예시에서 보여주듯이 ApplyPendingBalance
명령어가 포함된 트랜잭션을 빌드하고 전송합니다.
예제 코드
다음 예제는 기밀 대기 잔액을 기밀 사용 가능 잔액에 적용하는 방법을 보여줍니다.
기밀 전송은 ZK ElGamal Proof 프로그램에 의존하며, 이 프로그램은 메인넷과
데브넷에서 활성화되어 있습니다. 기본 solana-test-validator에서는 이를
활성화하지 않지만, Surfpool과 같은 메인넷 포킹 로컬
validator는 지원합니다. 자금이 충전된 페이어로 해당 환경 중 하나(코드는 데브넷
사용)에서 예제를 실행하고, 플레이스홀더 민트 및 계정 주소를 본인 것으로
교체하세요.
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?