Token Program'ı CPI ile Çağırma

Zincir üstü programlar, Token Program'a bir Cross Program Invocation (CPI) göndererek token taşır. Programınız bir Token Program talimatı oluşturur, gerekli hesapları sağlar ve invoke (ya da bir Program Derived Address imzaladığında invoke_signed) işlevini çağırır. Token Program, ardından bu talimatı programınızın kendisine devrettiği ayrıcalıklarla çalıştırır.

Bu sayfa, söz konusu akışın token'a özgü tarafını ele almaktadır. CPI mekanizmasının kendisi için — invoke ve invoke_signed nasıl çalışır, imzalayan ve yazılabilir ayrıcalıklar nasıl yayılır ve CPI başına maliyet modeli — bkz. Cross Program Invocation.

Yaygın desenler

Çoğu token CPI aynı yapıyı izler: talimatı oluştur, token hesaplarını ve bir yetkiyi ilet ve çağır. Aşağıdaki örnek, transfer işleminin bir parçası olarak mint ve ondalık basamakları doğrulayan TransferChecked ile token transfer etmektedir.

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

Mint etme, yakma ve hesap kapatma işlemleri, farklı bir talimatla aynı deseni izler — Anchor'ın anchor_spl::token modülünde MintTo, Burn ve CloseAccount ya da pinocchio-token içindeki eşleşen oluşturucular. Token Program'a hem Anchor hem de yerel Rust ile CPI yapan eksiksiz ve çalıştırılabilir programlar için token program örneklerine bakın — özellikle "Transfer Tokens" örneği.

PDA yetkisiyle imzalama

Bir token account üzerindeki yetki, programınıza ait bir Program Derived Address olduğunda, program CPI'yi invoke_signed ile PDA seed'lerini imzalayan seed'ler olarak geçirerek bizzat imzalar. Anchor'da, CpiContext::new yerine seed'lerle birlikte CpiContext::new_with_signer kullanın. Talimat, bunun dışında yukarıdaki örneklerle aynıdır. Tam mekanik için PDA İmzalayıcılarla CPI sayfasına bakın.

Toplu İşlem

Batch komutu, tek bir program çağrısı içinde birden fazla Token Program talimatı çalıştırır. Her CPI sabit bir hesaplama maliyeti taşıdığından, tek bir toplu CPI içinde birden fazla token işlemi çalıştırmak, her işlem için ayrı bir CPI göndermekten daha az hesaplama birimi kullanır — CPI başına maliyetlerin nasıl biriktiğini görmek için CPI maliyet modeline bakın.

Daha düşük hesaplama kullanımı, bir işlemin hesaplama birimi başına ödediği öncelik ücretlerini azaltır ve işlemin onaylanma olasılığını artırır — bu, tek bir talimatta çok sayıda token işlemi gerçekleştiren programlar için özellikle önemlidir. Yaygın kullanım senaryoları arasında bir transferi çok sayıda alıcıya dağıtmak veya çok adımlı bir token akışını (örneğin senkronizasyon, transfer ve kapatma) tek bir CPI içinde çalıştırmak yer alır.

Batch yalnızca Token Program talimatlarını alt eleman olarak kabul eder ve bir toplu işlem başka bir toplu işlem içeremez. Token taşıyan işlemler (transferler, mint'ler ve burn'ler gibi) için program, bu işlemleri çalıştırmadan önce etkilenen hesapların Token Program'a ait olduğunu doğrular.

Kaynak referansı

ÖğeAçıklamaKaynak
BatchToplu İşlem talimatı (ayrıştırıcı 255).Kaynak
process_batchToplu işlem işleyici mantığı.Kaynak

CPI Aracılığıyla Toplu İşlem Çağırma

pinocchio-token crate'i, pinocchio_token::instructions::Batch altında bir Batch oluşturucu sunar. Her alt talimatı toplu işlemin arabelleklerine ekler ve ardından invoke ile tek bir CPI gönderirsiniz.

Bir Batch, çağıran tarafından sağlanan üç arabelleğe dayanır: biri seri hale getirilmiş instruction data için, biri alt talimatlara ait hesap meta verileri için, biri de CPI'ya iletilen hesap görünümleri için. Bunları MaybeUninit dilimleri olarak oluşturun, Batch::new'e aktarın, alt elemanları ekleyin ve ardından çağırın:

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, toplu iş ayırt edici değerini veri tamponunun ilk baytına yazar ve her tamponun ne kadarının kullanıldığını izleyen bir Batch döndürür. Her Token Program instruction builder (örneğin Transfer, TransferChecked, MintTo ve Burn), IntoBatch özelliğini uygular; böylece into_batch, ilgili talimatın verilerini, hesap meta bilgilerini ve hesap görünümlerini toplu işe ekler. invoke çağrıldığında, hazırlanan toplu iş Token Program'a tek bir CPI olarak iletilir.

Tamponlar, toplu işin kapasitesini sınırlandırır. Veri tamponunu 1 + Σ(2 + child_data_len) bayt (ayırt edici, artı 2 baytlık başlık ve her alt öğenin serileştirilmiş verisi) olarak boyutlandırın; her hesap tamponunu ise tüm alt öğeler genelindeki toplam hesap sayısı kadar ayarlayın. Bir alt öğe sığmazsa into_batch, ProgramError::InvalidArgument döndürür. Referans olarak, Batch::MAX_DATA_LEN 10 KiB'dir ve Batch::MAX_ACCOUNTS_LEN çalışma zamanının izin verdiği maksimum CPI hesap sayısına eşittir.

Boyutu yalnızca çalışma zamanında bilinen toplu işler için, paketin alloc özelliğini etkinleştirin ve tamponları heap üzerinde tahsis etmek için BatchState::new(accounts_len, data_len) kullanın; ardından bir Batch elde etmek için as_batch çağırın.

Bir alt talimatın yetkisi, programınıza ait bir PDA ise, CPI'yı invoke yerine invoke_signed ile imzalayın ve PDA seed'lerini Signer girdileri olarak iletin.

instruction data düzeni

Bir toplu iş, alt öğelerini 255 ayırt edicisinin ardına art arda kodlar. Tüm hesaplar düz biçimde, sırayla iletilir ve her alt öğe için num_accounts tarafından dilimlenir. Builder tam olarak şu aktarım formatını üretir:

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)

Bir toplu işin kapasitesi, Batch::new'e iletilen tamponlarla sınırlıdır: veri tamponu, ayırt ediciyi ve her alt öğenin başlık ve verilerini barındırmalı; hesap tamponları ise her alt öğenin hesaplarını tutabilmelidir. Bunları oluşturduğunuz en büyük toplu iş için boyutlandırın ya da çalışma zamanında boyutlandırmak amacıyla (alloc özelliğiyle) BatchState kullanın. Birleşik CPI, işlemin hesap ve boyut sınırlarına tabi olmaya devam eder; bu nedenle çok büyük fan-out'lar için daha fazla hesap sığdırmak amacıyla Address Lookup Table'ları kullanın.

Is this page helpful?

İçindekiler

Sayfayı Düzenle
© 2026 Solana Vakfı. Tüm hakları saklıdır.