Tokens opnemen

Hoe tokens op te nemen uit het vertrouwelijk beschikbaar saldo

Om tokens op te nemen uit het vertrouwelijk beschikbaar saldo naar het openbaar saldo:

  1. Maak twee bewijzen aan de clientzijde:

    Gelijkheidsbewijs (CiphertextCommitmentEqualityProofData): Verifieert dat de resterende beschikbare saldo-cijfertekst na de opname overeenkomt met de bijbehorende Pedersen-verbintenis, zodat het nieuwe beschikbare saldo van het account correct wordt berekend als remaining_balance = current_balance - withdraw_amount.

    Bereikbewijs (BatchedRangeProofU64Data): Verifieert dat het resterende beschikbare saldo na de opname niet-negatief is en binnen een bepaald bereik valt.

  2. Voor elk bewijs:

    • Roep het ZK ElGamal-bewijsprogramma aan om de bewijsgegevens te verifiëren.
    • Sla de bewijsspecifieke metadata op in een bewijs-"contextstaataccount" voor gebruik in andere instructies.
  3. Roep de ConfidentialTransferInstruction::Withdraw instructie aan met de twee bewijsaccounts.

  4. Sluit de bewijsaccounts om de SOL die is gebruikt om ze aan te maken terug te vorderen.

Het volgende diagram toont de stappen voor het opnemen van tokens uit het vertrouwelijk beschikbaar saldo naar het openbaar saldo:

Withdraw Tokens

Vereiste instructies

Om tokens op te nemen uit het vertrouwelijk beschikbaar saldo naar het openbaar saldo, moet u:

  • Genereer een gelijkheidsbewijs en bereikbewijs aan de clientzijde
  • Roep het Zk ElGamal-bewijsprogramma aan om de bewijzen te verifiëren en de "contextstaataccounts" te initialiseren
  • Roep de ConfidentialTransferInstruction::Withdraw instructie aan met de twee bewijsaccounts.
  • Sluit de twee bewijsaccounts om rent terug te vorderen.

Het onderstaande Rust-voorbeeld genereert de bewijzen met de spl-token-confidential-transfer-proof-generation crate, verifieert elk bewijs in een contextstaataccount via het ZK ElGamal-bewijsprogramma, verwijst naar beide accounts in de opname-instructie en sluit ze daarna. Het TypeScript-voorbeeld gebruikt de getConfidentialWithdrawInstructionPlan helper uit @solana-program/token-2022/confidential, dat dezelfde bewijsaccounts, opname en afsluiting samenstelt als een instructieplan met meerdere transacties.

Voorbeeldcode

Het volgende voorbeeld haalt tokens op uit het vertrouwelijke beschikbare saldo terug naar het publieke saldo. Het account moet al geconfigureerd zijn voor vertrouwelijke overdrachten en een beschikbaar vertrouwelijk saldo bevatten.

Vertrouwelijke overdrachten zijn afhankelijk van het ZK ElGamal Proof-programma, dat is ingeschakeld op mainnet en devnet. Een standaard solana-test-validator schakelt dit niet in, maar een mainnet-forkende lokale validator zoals Surfpool wel. Voer het voorbeeld uit tegen een van deze (de code gebruikt devnet) met een gefinancierde betaler, en vervang de tijdelijke aanduidingen door uw mint en 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?

Inhoudsopgave

Pagina Bewerken
© 2026 Solana Foundation. Alle rechten voorbehouden.