On-Chain-Programme verschieben Token, indem sie eine
Cross Program Invocation (CPI) in das Token Program senden.
Ihr Programm erstellt eine Token Program- Anweisung, stellt die benötigten
Konten bereit und ruft invoke auf (oder invoke_signed, wenn ein
Program Derived Address signiert). Das Token Program führt diese Anweisung dann
mit den Berechtigungen aus, die Ihr Programm ihm überträgt.
Diese Seite behandelt den tokenspezifischen Teil dieses Ablaufs. Für den
CPI-Mechanismus selbst – wie invoke und invoke_signed funktionieren,
wie Signer- und Schreibberechtigungen weitergegeben werden, und das
CPI-Kostenmodell – siehe
Cross Program Invocation.
Häufige Muster
Die meisten Token-CPIs folgen demselben Schema: Anweisung erstellen, die token
accounts und eine Autorität übergeben und aufrufen. Das folgende Beispiel
überträgt Token mit TransferChecked, das die Mint und die Dezimalstellen
im Rahmen der Übertragung überprüft.
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>,}
Das Prägen, Verbrennen und Schließen von Konten folgt demselben Muster mit einer
anderen Anweisung – MintTo, Burn und CloseAccount im Anchor-
anchor_spl::token-Modul
oder den entsprechenden Buildern in
pinocchio-token. Für vollständige,
ausführbare Programme, die in Anchor und nativem Rust per CPI in das Token
Program aufrufen, siehe die
Token Program-Beispiele – insbesondere das
Beispiel "Transfer Tokens".
Mit einer PDA-Autorität signieren
Wenn die Autorität eines token accounts ein
Program Derived Address ist, das Ihrem Programm gehört,
signiert das Programm die CPI selbst mit invoke_signed und übergibt die
PDA-Seeds als Signer- Seeds. In Anchor verwenden Sie
CpiContext::new_with_signer mit den Seeds anstelle von
CpiContext::new. Die Anweisung ist ansonsten identisch mit den obigen
Beispielen. Weitere Informationen zur vollständigen Funktionsweise finden Sie
unter CPIs with PDA Signers.
Batching
Die Anweisung Batch führt mehrere Token Program- Anweisungen innerhalb
eines einzelnen Programmaufrufs aus. Da jeder CPI feste Rechenkosten verursacht,
verbraucht das Ausführen mehrerer Token-Operationen in einem Batch-CPI weniger
Compute-Einheiten als das Ausgeben eines
separaten CPI pro Operation — siehe das
CPI-Kostenmodell dafür, wie sich CPI-Kosten
summieren.
Ein geringerer Rechenaufwand reduziert die priority fees, die eine Transaktion pro Compute-Einheit zahlt, und verbessert ihre Chancen auf Ausführung — was besonders für Programme relevant ist, die viele Token-Operationen in einer einzigen Anweisung durchführen. Typische Anwendungsfälle sind das Verteilen einer Überweisung an viele Empfänger oder das Ausführen eines mehrstufigen Token-Ablaufs (z. B. Synchronisieren, Übertragen und Schließen) in einem einzigen CPI.
Batch akzeptiert nur Token Program- Anweisungen als untergeordnete
Elemente, und ein Batch kann keinen weiteren Batch enthalten. Für Operationen,
die Token bewegen (wie Übertragungen, Prägungen und Burns), überprüft das
Programm, ob die betroffenen Konten dem Token Program gehören, bevor es sie
ausführt.
Quellenreferenz
| Element | Beschreibung | Quelle |
|---|---|---|
Batch | Die Batch-Anweisung (Diskriminator 255). | Quelle |
process_batch | Batch-Prozessor-Logik. | Quelle |
Batch via CPI aufrufen
Das Crate pinocchio-token stellt
unter pinocchio_token::instructions::Batch einen Batch-Builder
bereit. Sie fügen jede untergeordnete Anweisung in die Puffer des Batches ein
und senden dann einen einzigen CPI mit invoke.
Ein Batch wird durch drei vom Aufrufer bereitgestellte Puffer unterstützt:
einen für die serialisierten instruction data, einen für die Konten-Metadaten
der einzelnen untergeordneten Anweisungen und einen für die an den CPI
übergebenen Konten-Ansichten. Erstellen Sie diese als MaybeUninit-Slices,
übergeben Sie sie an Batch::new, fügen Sie untergeordnete Elemente hinzu
und rufen Sie dann auf:
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 schreibt den Batch-Discriminator in das erste Byte des
Datenpuffers und gibt einen Batch zurück, der verfolgt, wie viel von jedem
Puffer bereits verwendet wurde. Jeder Token Program-Anweisungs-Builder (wie z.
B. Transfer, TransferChecked, MintTo und Burn)
implementiert das Trait IntoBatch, sodass into_batch die instruction
data, die Konten-Metadaten und die Konten-Views dieser Anweisung zum Batch
hinzufügt. Der Aufruf von invoke führt den assemblierten Batch als
einzelnen CPI in das Token Program aus.
Die Puffer begrenzen die Kapazität des Batches. Dimensioniere den Datenpuffer
auf 1 + Σ(2 + child_data_len) Bytes (den Discriminator plus einen
2-Byte-Header und die serialisierten Daten für jedes Kind) und jeden
Kontenpuffer auf die Gesamtanzahl der Konten aller Kinder. Passt ein Kind nicht
hinein, gibt into_batch ProgramError::InvalidArgument zurück. Zur
Orientierung: Batch::MAX_DATA_LEN beträgt 10 KiB und
Batch::MAX_ACCOUNTS_LEN entspricht der maximalen CPI-Kontenanzahl der
Laufzeitumgebung.
Für Batches, deren Größe erst zur Laufzeit bekannt ist, aktiviere das Feature
alloc des Crates und verwende BatchState::new(accounts_len, data_len),
um die Puffer auf dem Heap zu allokieren, und rufe dann as_batch auf, um
einen Batch zu erhalten.
Wenn die Autorität einer Kind-Anweisung eine PDA ist, die deinem Programm
gehört, signiere den CPI mit invoke_signed anstatt mit invoke und
übergib die PDA-Seeds als Signer-Einträge.
instruction data-Layout
Ein Batch kodiert seine Kinder nacheinander hinter dem 255-Discriminator. Alle
Konten werden flach, in der richtigen Reihenfolge, übergeben und für jedes Kind
durch num_accounts aufgeteilt. Der Builder erzeugt genau dieses 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)
Die Kapazität eines Batches wird durch die an Batch::new übergebenen
Puffer begrenzt: Der Datenpuffer muss den Discriminator sowie den Header und die
Daten jedes Kindes aufnehmen, und die Kontenpuffer müssen alle Konten der Kinder
enthalten. Dimensioniere sie für den größten Batch, den du erstellst, oder
verwende BatchState (mit dem Feature alloc), um sie zur Laufzeit zu
bemessen. Der kombinierte CPI unterliegt weiterhin den Konto- und Größenlimits
der Transaktion – für sehr große Fan-outs solltest du daher Address Lookup
Tables verwenden, um mehr Konten unterzubringen.
Is this page helpful?