On-chain programma's verplaatsen tokens door een
Cross Program Invocation (CPI) naar het Token Program te
sturen. Jouw programma bouwt een Token Program-instructie op, levert de
benodigde accounts aan en roept invoke aan (of invoke_signed wanneer
een Program Derived Address ondertekent). Het Token Program voert die instructie
vervolgens uit met de bevoegdheden die jouw programma eraan verleent.
Deze pagina behandelt de token-specifieke kant van die stroom. Voor het
CPI-mechanisme zelf — hoe invoke en invoke_signed werken, hoe
ondertekenaar- en schrijfrechten worden doorgegeven, en het
CPI-kostenmodel — zie
Cross Program Invocation.
Veelvoorkomende patronen
De meeste token-CPI's volgen dezelfde structuur: bouw de instructie op, geef de
token accounts en een autoriteit mee, en roep aan. Het onderstaande voorbeeld
draagt tokens over met TransferChecked, dat de mint en decimalen
verifieert als onderdeel van de overdracht.
use anchor_lang::prelude::*;use anchor_spl::token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked};declare_id!("11111111111111111111111111111111");#[program]pub mod token_cpi {use super::*;pub fn transfer(ctx: Context<TokenTransfer>, amount: u64) -> Result<()> {let cpi_accounts = TransferChecked {from: ctx.accounts.source.to_account_info(),mint: ctx.accounts.mint.to_account_info(),to: ctx.accounts.destination.to_account_info(),authority: ctx.accounts.authority.to_account_info(),};let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(),cpi_accounts,);transfer_checked(cpi_ctx, amount, ctx.accounts.mint.decimals)?;Ok(())}}#[derive(Accounts)]pub struct TokenTransfer<'info> {#[account(mut)]pub source: InterfaceAccount<'info, TokenAccount>,pub mint: InterfaceAccount<'info, Mint>,#[account(mut)]pub destination: InterfaceAccount<'info, TokenAccount>,pub authority: Signer<'info>,pub token_program: Interface<'info, TokenInterface>,}
Minten, verbranden en accounts sluiten volgen hetzelfde patroon met een andere
instructie — MintTo, Burn, en CloseAccount in Anchor's
anchor_spl::token
module, of de bijbehorende builders in
pinocchio-token. Voor volledige,
uitvoerbare programma's die via CPI het Token Program aanroepen in zowel Anchor
als native Rust, zie de
token program-voorbeelden — met name het
"Transfer Tokens"-voorbeeld.
Ondertekenen met een PDA-autoriteit
Wanneer de autoriteit op een token account een
Program Derived Address is dat eigendom is van jouw programma,
ondertekent het programma de CPI zelf met invoke_signed, waarbij de
PDA-seeds worden meegegeven als signer seeds. Gebruik in Anchor
CpiContext::new_with_signer met de seeds in plaats van
CpiContext::new. De instructie is verder identiek aan de bovenstaande
voorbeelden. Zie CPI's met PDA-ondertekenaars
voor de volledige werking.
Batching
De instructie Batch voert meerdere Token Program-instructies uit binnen
één enkele programma-aanroep. Omdat elke CPI een vaste berekeningskost met zich
meebrengt, verbruikt het uitvoeren van meerdere tokenbewerkingen in één
batch-CPI minder compute units dan het
uitsturen van een aparte CPI per bewerking — zie het
CPI-kostenmodel voor hoe kosten per CPI
oplopen.
Lager rekenverbruik verlaagt de prioriteitskosten die een transactie per compute unit betaalt en vergroot de kans dat deze wordt verwerkt — wat het meest van belang is voor programma's die veel tokenbewerkingen uitvoeren in één enkele instructie. Veelvoorkomende toepassingen zijn het uitwaaieren van een overdracht naar meerdere ontvangers, of het uitvoeren van een meerstaps-tokenflow (bijvoorbeeld synchroniseren, overdragen en sluiten) in één enkele CPI.
Batch accepteert alleen Token Program-instructies als onderliggende
instructies, en een batch kan geen andere batch bevatten. Voor bewerkingen
waarbij tokens worden verplaatst (zoals overdrachten, mints en burns) verifieert
het programma of de betrokken accounts eigendom zijn van het Token Program
voordat deze worden uitgevoerd.
Bronverwijzing
| Item | Beschrijving | Bron |
|---|---|---|
Batch | De Batch-instructie (discriminator 255). | Bron |
process_batch | Logica voor batchverwerking. | Bron |
Batch aanroepen via CPI
De crate pinocchio-token biedt een
Batch-builder aan onder pinocchio_token::instructions::Batch. U
plaatst elke onderliggende instructie in de buffers van de batch en verstuurt
vervolgens één enkele CPI met invoke.
Een Batch maakt gebruik van drie door de aanroeper aangeleverde buffers:
één voor de geserialiseerde instruction data, één voor de accountmetadata per
onderliggende instructie, en één voor de accountweergaven die aan de CPI worden
doorgegeven. Maak ze aan als MaybeUninit-slices, geef ze door aan
Batch::new, voeg onderliggende instructies toe en roep vervolgens aan:
use core::mem::MaybeUninit;use pinocchio::{account::AccountView, error::ProgramError, ProgramResult};use pinocchio_token::instructions::{Batch, IntoBatch, Transfer};/// Process SwapBatch.////// Performs two transfers in a single batch CPI instead of two separate/// `invoke()` calls.////// Data layout:/// [0..8] amount_a_to_b (u64 LE)/// [8..16] amount_b_to_a (u64 LE)////// Account layout:/// [0] source_a (writable) — token account A/// [1] source_b (writable) — token account B/// [2] authority_a (signer) — authority for account A/// [3] authority_b (signer) — authority for account B/// [4] token_program — SPL Token programpub fn process(accounts: &[AccountView], data: &[u8]) -> ProgramResult {if data.len() < 16 {return Err(ProgramError::InvalidInstructionData);}if accounts.len() < 5 {return Err(ProgramError::NotEnoughAccountKeys);}let amount_a_to_b = u64::from_le_bytes(data[0..8].try_into().unwrap());let amount_b_to_a = u64::from_le_bytes(data[8..16].try_into().unwrap());let source_a = &accounts[0];let source_b = &accounts[1];let authority_a = &accounts[2];let authority_b = &accounts[3];// Two child transfers, each with 3 accounts and 9 bytes of data// (1-byte discriminator + 8-byte amount).const NUM_CHILDREN: usize = 2;const CHILD_ACCOUNTS: usize = 3;const CHILD_DATA_LEN: usize = 9;// Data buffer: 1-byte batch discriminator + per child (2-byte header + data).const DATA_LEN: usize = 1 + NUM_CHILDREN * (2 + CHILD_DATA_LEN);// Account buffers: total accounts across all children.const ACCOUNTS_LEN: usize = NUM_CHILDREN * CHILD_ACCOUNTS;let mut data_buf = [MaybeUninit::uninit(); DATA_LEN];let mut ix_accounts_buf = [MaybeUninit::uninit(); ACCOUNTS_LEN];let mut accounts_buf = [MaybeUninit::uninit(); ACCOUNTS_LEN];let mut batch = Batch::new(&mut data_buf, &mut ix_accounts_buf, &mut accounts_buf)?;Transfer::new(source_a, source_b, authority_a, amount_a_to_b).into_batch(&mut batch)?;Transfer::new(source_b, source_a, authority_b, amount_b_to_a).into_batch(&mut batch)?;batch.invoke()?;Ok(())}
Batch::new schrijft de batch-discriminator naar het eerste byte van de
databuffer en geeft een Batch terug die bijhoudt hoeveel van elke buffer
is gebruikt. Elke Token Program instructiebouwer (zoals Transfer,
TransferChecked, MintTo en Burn) implementeert de
IntoBatch trait, zodat into_batch de instruction data, accountmeta's
en accountweergaven van die instructie aan de batch toevoegt. Het aanroepen van
invoke verstuurt de samengestelde batch als één CPI naar het Token
Program.
De buffers begrenzen de capaciteit van de batch. Stel de databuffer in op
1 + Σ(2 + child_data_len) bytes (de discriminator, plus een 2-byte-header en
de geserialiseerde data voor elk kind) en elke accountbuffer op het totale
aantal accounts over alle kinderen. Als een kind niet past, geeft
into_batch ProgramError::InvalidArgument terug. Ter referentie:
Batch::MAX_DATA_LEN is 10 KiB en Batch::MAX_ACCOUNTS_LEN is gelijk
aan het maximale CPI-accountaantal van de runtime.
Voor batches waarvan de omvang alleen tijdens runtime bekend is, schakel je de
alloc-feature van de crate in en gebruik je
BatchState::new(accounts_len, data_len) om de buffers op de heap te
alloceren, en roep je vervolgens as_batch aan om een Batch te
verkrijgen.
Wanneer de autoriteit van een kindinstructie een PDA is die eigendom is van jouw
programma, onderteken je de CPI met invoke_signed in plaats van
invoke, waarbij je de PDA-seeds doorgeeft als Signer-vermeldingen.
Indeling van instruction data
Een batch codeert zijn kinderen één voor één achter de 255-discriminator. Alle
accounts worden vlak doorgegeven, op volgorde, en per kind gesegmenteerd door
num_accounts. De bouwer produceert precies dit wire-formaat:
[255] // Batch discriminator// For each child instruction, in order:// [num_accounts: u8] // accounts this instruction consumes// [data_len: u8] // length of this instruction's data// [data: u8; data_len]// the instruction data (begins with its own discriminator)
De capaciteit van een batch wordt begrensd door de buffers die aan
Batch::new worden doorgegeven: de databuffer moet de discriminator plus de
header en data van elk kind bevatten, en de accountbuffers moeten de accounts
van elk kind bevatten. Stel ze in op de grootste batch die je bouwt, of gebruik
BatchState (met de alloc-feature) om ze tijdens runtime in te stellen.
De gecombineerde CPI is nog steeds onderworpen aan de account- en
groottelimieten van de transactie, dus gebruik voor zeer grote fan-outs Address
Lookup Tables om meer accounts te kunnen verwerken.
Is this page helpful?