Token Programin kutsuminen CPI:n kautta

On-chain-ohjelmat siirtävät tokeneita lähettämällä Cross Program Invocation (CPI) Token Programiin. Ohjelmasi rakentaa Token Program -ohjeen, toimittaa tarvittavat tilit ja kutsuu invoke (tai invoke_signed, kun Program Derived Address allekirjoittaa). Token Program suorittaa ohjeen niillä käyttöoikeuksilla, jotka ohjelmasi on myöntänyt sille.

Tämä sivu käsittelee kyseisen prosessin token-kohtaisen puolen. CPI-mekanismista itsestään — miten invoke ja invoke_signed toimivat, miten allekirjoittajan ja kirjoitettavien tilien käyttöoikeudet välittyvät, sekä CPI-kustannusmallista — katso Cross Program Invocation.

Yleiset mallit

Useimmat token CPI:t noudattavat samaa kaavaa: rakenna ohje, välitä token account -tilit ja auktoriteetti, ja kutsu. Alla oleva esimerkki siirtää tokeneita TransferChecked-funktiolla, joka tarkistaa mintin ja desimaalit osana siirtoa.

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>,
}

Tokeneiden luominen, polttaminen ja tilien sulkeminen noudattavat samaa kaavaa eri ohjeella — MintTo, Burn ja CloseAccount Anchorin anchor_spl::token -moduulissa tai vastaavilla rakentajilla pinocchio-token-kirjastossa. Täydellisiä, ajettavia ohjelmia, jotka käyttävät CPI:tä Token Programiin sekä Anchorilla että natiivilla Rustilla, löydät token program -esimerkeistä — erityisesti "Transfer Tokens" -esimerkistä.

Allekirjoittaminen PDA-auktoriteetilla

Kun token account -tilin auktoriteetti on Program Derived Address, jonka omistaa ohjelmasi, ohjelma allekirjoittaa CPI:n itse invoke_signed-funktiolla välittäen PDA:n seedit allekirjoittajan seedeinä. Anchorissa käytä CpiContext::new_with_signer seedien kanssa CpiContext::new:n sijaan. Ohje on muuten identtinen yllä olevien esimerkkien kanssa. Katso CPIs with PDA Signers täydellisen mekanismin selitystä varten.

Eräajo

Batch-käsky suorittaa useita Token Program -käskyjä yhden ohjelmakutsun sisällä. Koska jokaisella CPI:llä on kiinteä laskentakustannus, useampien tokenoperaatioiden suorittaminen yhdessä eräajo-CPI:ssä kuluttaa vähemmän laskentayksiköitä kuin erillisen CPI:n käyttäminen kutakin operaatiota kohti — katso CPI-kustannusmalli saadaksesi lisätietoa CPI-kohtaisten kustannusten kertymisestä.

Pienempi laskentakäyttö vähentää prioriteettimaksuja, joita transaktio maksaa laskentayksikköä kohti, ja parantaa sen onnistumismahdollisuuksia — mikä on tärkeintä ohjelmille, jotka suorittavat monia tokenoperaatioita yhdessä käskyssä. Yleisiä käyttötapauksia ovat siirron jakaminen useille vastaanottajille tai monivaiheisen tokenvirran suorittaminen (esimerkiksi synkronointi, siirto ja sulkeminen) yhdessä CPI:ssä.

Batch hyväksyy ainoastaan Token Program -käskyjä alikäskyiksi, eikä erä voi sisältää toista erää. Tokeneja siirtävien operaatioiden (kuten siirtojen, luomisten ja polttamisten) osalta ohjelma tarkistaa, että kyseessä olevat tilit ovat Token Program -omistuksessa ennen niiden suorittamista.

Lähdeviite

KohdeKuvausLähde
BatchBatch-käsky (erottelija 255).Lähde
process_batchEräajon käsittelylogiikka.Lähde

Batch-kutsuminen CPI:n kautta

pinocchio-token-paketti tarjoaa Batch-rakentajan kohteen pinocchio_token::instructions::Batch alla. Vaiheista jokainen alikäsky erän puskureihin ja lähetä sitten yksi CPI käyttäen invoke.

Batch:n taustalla on kolme kutsujan toimittamaa puskuria: yksi serialisoitua instruction data varten, yksi alikäskykohtaisia tiliviittauksia varten ja yksi CPI:lle välitettäviä tiliäkymiä varten. Luo ne MaybeUninit-siivuina, välitä ne Batch::new:lle, lisää alikäskyt ja kutsu sitten:

Batch two transfers in one CPI
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 program
pub 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 kirjoittaa erän erottelijan databufferin ensimmäiseen tavuun ja palauttaa Batch:n, joka seuraa kuinka paljon kutakin bufferia on käytetty. Jokainen Token Program -käskynrakentaja (kuten Transfer, TransferChecked, MintTo ja Burn) toteuttaa IntoBatch-traitin, joten into_batch lisää kyseisen käskyn datan, tiliviittaukset ja tilinäkymät erään. Kutsumalla invoke lähetetään koottu erä yhtenä CPI-kutsuna Token Programiin.

Bufferit rajoittavat erän kapasiteettia. Mitoita databufferi 1 + Σ(2 + child_data_len) tavuun (erotelija sekä 2 tavun otsake ja kunkin lapsen sarjallistettu data) ja kukin tilibufferi kaikkien lasten tilien kokonaismäärään. Jos lapsi ei mahdu, into_batch palauttaa ProgramError::InvalidArgument:n. Viitteenä: Batch::MAX_DATA_LEN on 10 KiB ja Batch::MAX_ACCOUNTS_LEN vastaa ajoympäristön enimmäis-CPI-tilimäärää.

Eriä varten, joiden koko on tiedossa vasta ajon aikana, ota käyttöön craten alloc-ominaisuus ja käytä BatchState::new(accounts_len, data_len):a allokoidaksesi bufferit keolle, sitten kutsu as_batch saadaksesi Batch:n.

Kun lapsi-käskyn auktoriteetti on ohjelmasi omistama PDA, allekirjoita CPI käyttämällä invoke_signedinvoke:n sijaan ja anna PDA-siemenet Signer-merkintöinä.

instruction data -asettelu

Erä koodaa lapsensa peräkkäin 255-erottelijan jälkeen. Kaikki tilit välitetään tasaisessa järjestyksessä ja kunkin lapsen osalta ositetaan num_accounts:lla. Rakentaja tuottaa täsmälleen tämän siirtoformaatin:

Batch Wire Format
[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)

Erän kapasiteetti on rajoitettu Batch::new:lle välitettyjen bufferien mukaan: databufferin on pidettävä erotelija sekä jokaisen lapsen otsake ja data, ja tilibufferien on pidettävä jokaisen lapsen tilit. Mitoita ne suurimman rakentamasi erän mukaan tai käytä BatchState:ä (alloc-ominaisuuden kanssa) mitoittaaksesi ne ajon aikana. Yhdistetty CPI on edelleen transaktion tili- ja kokorajoitusten alainen, joten hyvin suurissa hajautuksissa käytä Address Lookup Tableseja mahduttaaksesi enemmän tilejä.

Is this page helpful?

Sisällysluettelo

Muokkaa sivua
© 2026 Solana Foundation. Kaikki oikeudet pidätetään.