Come prelevare token dal saldo disponibile riservato
Per prelevare token dal saldo disponibile riservato al saldo pubblico:
-
Crea due prove lato client:
Prova di uguaglianza (CiphertextCommitmentEqualityProofData): Verifica che il testo cifrato del saldo disponibile rimanente dopo il prelievo corrisponda al relativo impegno di Pedersen, garantendo che il nuovo saldo disponibile dell'account sia calcolato correttamente come
remaining_balance = current_balance - withdraw_amount.Prova di intervallo (BatchedRangeProofU64Data): Verifica che il saldo disponibile rimanente dopo il prelievo sia non negativo e rientri in un intervallo specificato.
-
Per ogni prova:
- Invoca il programma ZK ElGamal proof per verificare i dati della prova.
- Memorizza i metadati specifici della prova in un account "context state" della prova da utilizzare in altre istruzioni.
-
Invoca l'istruzione ConfidentialTransferInstruction::Withdraw fornendo i due account delle prove.
-
Chiudi gli account delle prove per recuperare i SOL utilizzati per crearli.
Il seguente diagramma mostra i passaggi coinvolti nel prelievo di token dal saldo disponibile riservato al saldo pubblico:
Istruzioni richieste
Per prelevare token dal saldo disponibile riservato al saldo pubblico, è necessario:
- Generare una prova di uguaglianza e una prova di intervallo lato client
- Invocare il programma Zk ElGamal proof per verificare le prove e inizializzare gli account "context state"
- Invocare l'istruzione ConfidentialTransferInstruction::Withdraw fornendo i due account delle prove.
- Chiudere i due account delle prove per recuperare il rent.
L'esempio Rust di seguito genera le prove con il crate
spl-token-confidential-transfer-proof-generation, le verifica ciascuna in un
account context state tramite il programma ZK ElGamal Proof, fa riferimento a
entrambi gli account nell'istruzione di prelievo e li chiude successivamente.
L'esempio TypeScript utilizza l'helper getConfidentialWithdrawInstructionPlan
da @solana-program/token-2022/confidential, che assembla gli stessi account
delle prove, il prelievo e la chiusura come piano di istruzioni
multi-transazione.
Codice di Esempio
Il seguente esempio preleva i token dal saldo disponibile riservato riportandoli al saldo pubblico. L'account deve essere già configurato per i trasferimenti riservati e disporre di un saldo riservato disponibile.
I trasferimenti riservati dipendono dal programma ZK ElGamal Proof, abilitato su
mainnet e devnet. Un solana-test-validator standard non lo abilita, ma un
validator locale con fork della mainnet come Surfpool
sì. Esegui l'esempio su uno di questi ambienti (il codice utilizza devnet) con
un payer finanziato, e sostituisci i segnaposto con il tuo mint e token account.
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(),);// Owner = fee payer = token account owner. The account must already be// configured for confidential transfers with an available confidential// balance to withdraw from.let owner = load_keypair()?;let amount: u64 = 100;let decimals: u8 = 2;// Setup: create an available confidential balance to withdraw.let (mint, token_account) = setup_withdrawable_account(&rpc_client, &owner, amount, decimals)?;// Derive the owner's keys and read the current confidential available balance.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 ElGamal available-balance ciphertext is required to build the proof.let available_balance: ElGamalCiphertext = ct_extension.available_balance.try_into().map_err(|e| anyhow::anyhow!("decode available balance: {e:?}"))?;// Read the plaintext 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!("decode decryptable balance: {e:?}"))?;let current_available = decryptable_balance.decrypt(&aes_key).context("decrypt available balance")?;// Generate the equality and range proofs for the withdrawal.let proof_data = withdraw_proof_data(&available_balance, current_available, amount, &elgamal_keypair).map_err(|e| anyhow::anyhow!("withdraw_proof_data: {e}"))?;let new_available = current_available.checked_sub(amount).context("insufficient confidential balance")?;let new_decryptable: PodAeCiphertext = aes_key.encrypt(new_available).into();// The owner is the context-state authority for both proof accounts.let authority: Address = owner.pubkey().to_bytes().into();// Equality proof context state account.let equality_account = Keypair::new();let equality_size = size_of::<ProofContextState<CiphertextCommitmentEqualityProofContext>>();let equality_create_ix = system_instruction::create_account(&owner.pubkey(),&equality_account.pubkey(),rpc_client.get_minimum_balance_for_rent_exemption(equality_size)?,equality_size as u64,&ZK_PROOF_PROGRAM_ID,);let equality_verify_ix = ProofInstruction::VerifyCiphertextCommitmentEquality.encode_verify_proof(Some(ContextStateInfo {context_state_account: &Address::from(equality_account.pubkey().to_bytes()),context_state_authority: &authority,}),&proof_data.equality_proof_data,);send_tx(&rpc_client, &[equality_create_ix], &[&owner, &equality_account])?;send_tx(&rpc_client, &[equality_verify_ix], &[&owner])?;// Range proof context state account.let range_account = Keypair::new();let range_size = size_of::<ProofContextState<BatchedRangeProofContext>>();let range_create_ix = system_instruction::create_account(&owner.pubkey(),&range_account.pubkey(),rpc_client.get_minimum_balance_for_rent_exemption(range_size)?,range_size as u64,&ZK_PROOF_PROGRAM_ID,);let range_verify_ix = ProofInstruction::VerifyBatchedRangeProofU64.encode_verify_proof(Some(ContextStateInfo {context_state_account: &Address::from(range_account.pubkey().to_bytes()),context_state_authority: &authority,}),&proof_data.range_proof_data,);send_tx(&rpc_client, &[range_create_ix], &[&owner, &range_account])?;send_tx(&rpc_client, &[range_verify_ix], &[&owner])?;// Withdraw, referencing both pre-verified proof accounts.let withdraw_ixs = withdraw(&spl_token_2022::id(),&token_account,&mint,amount,decimals,&new_decryptable,&owner.pubkey(),&[&owner.pubkey()],ProofLocation::ContextStateAccount(&equality_account.pubkey()),ProofLocation::ContextStateAccount(&range_account.pubkey()),)?;let blockhash = rpc_client.get_latest_blockhash()?;let transaction =Transaction::new_signed_with_payer(&withdraw_ixs, Some(&owner.pubkey()), &[&owner], blockhash);let signature = rpc_client.send_and_confirm_transaction(&transaction)?;// Close both proof accounts to reclaim their rent.for account in [&equality_account, &range_account] {let close_ix = close_context_state(ContextStateInfo {context_state_account: &Address::from(account.pubkey().to_bytes()),context_state_authority: &authority,},&authority,);send_tx(&rpc_client, &[close_ix], &[&owner])?;}println!("Withdrew {amount} tokens to the public balance: {signature}");Ok(())}
Typescript
const client = await createClient().use(signerFromFile(join(homedir(), ".config/solana/id.json"))).use(solanaRpc({rpcUrl: "https://api.devnet.solana.com",maxConcurrency: 1}));// The Solana CLI default keypair, used as fee payer, mint authority, and// token account owner.const owner = client.payer;const depositAmount = 100n;const amount = 25n;const decimals = 2;// Setup: create a confidential account with an available confidential balance.const mint = await createConfidentialMint(client, owner, decimals);const token = await createConfidentialTokenAccount(client, owner, mint);await mintPublicTokens(client, owner, mint, token, depositAmount);await depositTokens(client, owner, mint, token, depositAmount, decimals);await applyPendingBalance(client, owner, mint, token);// Derive the owner's recoverable ElGamal and AES keys, bound to (owner, mint).const { elgamalKeypair, aesKey } = await deriveConfidentialKeys(owner, mint);const tokenAccount = (await fetchToken(client.rpc, token)).data;// Builds the equality + range proof accounts, the withdraw, and the closes as a// multi-transaction instruction plan.const plan = await getConfidentialWithdrawInstructionPlan({rpc: client.rpc,payer: owner,authority: owner,token,mint,tokenAccount,amount,decimals,elgamalKeypair,aesKey});const result = await client.sendTransactions(plan);const summary = summarizeTransactionPlanResult(result);const signature =summary.successfulTransactions[summary.successfulTransactions.length - 1].context.signature;console.log(`Withdrew ${amount} tokens to the public balance: ${signature}`);
Is this page helpful?