Πώς να αποσύρετε tokens από το εμπιστευτικό διαθέσιμο υπόλοιπο
Για να αποσύρετε tokens από το εμπιστευτικό διαθέσιμο υπόλοιπο στο δημόσιο υπόλοιπο:
-
Δημιουργήστε δύο αποδείξεις από την πλευρά του client:
Απόδειξη Ισότητας (CiphertextCommitmentEqualityProofData): Επαληθεύει ότι το εναπομείναν κρυπτογραφημένο κείμενο διαθέσιμου υπολοίπου μετά την ανάληψη αντιστοιχεί στην δέσμευση Pedersen που του αντιστοιχεί, εξασφαλίζοντας ότι το νέο διαθέσιμο υπόλοιπο του λογαριασμού υπολογίζεται σωστά ως
remaining_balance = current_balance - withdraw_amount.Απόδειξη Εύρους (BatchedRangeProofU64Data): Επαληθεύει ότι το εναπομείναν διαθέσιμο υπόλοιπο μετά την ανάληψη είναι μη αρνητικό και εντός καθορισμένου εύρους.
-
Για κάθε απόδειξη:
- Καλέστε το πρόγραμμα απόδειξης ZK ElGamal για επαλήθευση των δεδομένων της απόδειξης.
- Αποθηκεύστε τα μεταδεδομένα που αφορούν την απόδειξη σε έναν λογαριασμό "context state" για χρήση σε άλλες εντολές.
-
Καλέστε την εντολή ConfidentialTransferInstruction::Withdraw παρέχοντας τους δύο λογαριασμούς απόδειξης.
-
Κλείστε τους λογαριασμούς απόδειξης για να ανακτήσετε το SOL που χρησιμοποιήθηκε για τη δημιουργία τους.
Το παρακάτω διάγραμμα δείχνει τα βήματα που εμπλέκονται στην ανάληψη tokens από το εμπιστευτικό διαθέσιμο υπόλοιπο στο δημόσιο υπόλοιπο:
Απαιτούμενες Εντολές
Για να αποσύρετε tokens από το εμπιστευτικό διαθέσιμο υπόλοιπο στο δημόσιο υπόλοιπο, πρέπει:
- Δημιουργήστε μια απόδειξη ισότητας και μια απόδειξη εύρους από την πλευρά του client
- Καλέστε το πρόγραμμα απόδειξης Zk ElGamal για επαλήθευση των αποδείξεων και αρχικοποίηση των λογαριασμών "context state"
- Καλέστε την εντολή ConfidentialTransferInstruction::Withdraw παρέχοντας τους δύο λογαριασμούς απόδειξης.
- Κλείστε τους δύο λογαριασμούς απόδειξης για να ανακτήσετε το rent.
Το παρακάτω παράδειγμα Rust δημιουργεί τις αποδείξεις με το crate
spl-token-confidential-transfer-proof-generation, επαληθεύει κάθε μία σε έναν
λογαριασμό context state μέσω του προγράμματος ZK ElGamal Proof, αναφέρεται και
στους δύο λογαριασμούς στην εντολή ανάληψης και τους κλείνει στη συνέχεια. Το
παράδειγμα TypeScript χρησιμοποιεί τον βοηθό
getConfidentialWithdrawInstructionPlan από το
@solana-program/token-2022/confidential, ο οποίος συναρμολογεί τους ίδιους
λογαριασμούς απόδειξης, την ανάληψη και το κλείσιμο ως πολυ-συναλλακτικό σχέδιο
εντολών.
Παράδειγμα Κώδικα
Το παρακάτω παράδειγμα αναλαμβάνει tokens από το διαθέσιμο εμπιστευτικό υπόλοιπο πίσω στο δημόσιο υπόλοιπο. Ο λογαριασμός πρέπει να έχει ήδη ρυθμιστεί για εμπιστευτικές μεταφορές και να διατηρεί ένα διαθέσιμο εμπιστευτικό υπόλοιπο.
Οι εμπιστευτικές μεταφορές εξαρτώνται από το πρόγραμμα ZK ElGamal Proof, το
οποίο είναι ενεργοποιημένο στο mainnet και στο devnet. Ένα τυπικό
solana-test-validator δεν το ενεργοποιεί, αλλά ένας τοπικός validator με fork
του mainnet όπως το Surfpool το κάνει. Εκτελέστε το
παράδειγμα ενάντια σε ένα από αυτά (ο κώδικας χρησιμοποιεί devnet) με έναν
χρηματοδοτημένο payer, και αντικαταστήστε τα placeholders με το mint και το
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?