Anchor-events zijn getypeerde, met Borsh geserialiseerde records die zijn opgenomen in de IDL van een programma. Hiermee kunnen clients de activiteit van een programma interpreteren zonder voor mensen leesbare logberichten te hoeven parsen.
Events zijn nuttig voor indexering en notificaties, maar ze zijn niet de status van het programma. Sla elke waarde die het programma opnieuw moet lezen op in een account en beschouw events als een record dat is afgeleid van een geslaagde transactie.
Een event definiëren en verzenden
Voeg #[event] toe aan een struct en roep emit! aan nadat de statuswijziging is geslaagd.
use anchor_lang::prelude::*;#[program]pub mod payments {use super::*;pub fn record_payment(ctx: Context<RecordPayment>, amount: u64) -> Result<()> {emit!(PaymentRecorded {authority: ctx.accounts.authority.key(),amount,});Ok(())}}#[derive(Accounts)]pub struct RecordPayment<'info> {pub authority: Signer<'info>,}#[event]pub struct PaymentRecorded {pub authority: Pubkey,pub amount: u64,}
emit! schrijft de event-bytes met de sol_log_data-syscall van Solana. Transactielogs
tonen het resultaat als Program data: <base64>. De gecodeerde waarde begint met
de event-discriminator, gevolgd door de met Borsh geserialiseerde velden.
Houd events beknopt. Grote strings en vectoren verbruiken compute, vergroten de transactiemetadata en worden eerder getroffen door het afkappen van logs.
Kiezen tussen log- en CPI-events
Anchor ondersteunt twee verzendmethoden:
| Methode | Opslaglocatie | Afweging |
|---|---|---|
emit! | Programmadatalogs | Eenvoudiger en goedkoper, maar logs kunnen worden afgekapt |
emit_cpi! | Inner instruction data van een CPI | Duurzamere metadata, met extra accounts en compute |
Schakel de event-cpi-feature van Anchor in om CPI-events te gebruiken:
[dependencies]anchor-lang = { version = "1.1.2", features = ["event-cpi"] }
Annoteer vervolgens de accounts-struct van de instructie met #[event_cpi] en verzend
vanuit een handler waarvan de contextvariabele ctx heet:
pub fn record_payment(ctx: Context<RecordPayment>, amount: u64) -> Result<()> {emit_cpi!(PaymentRecorded {authority: ctx.accounts.authority.key(),amount,});Ok(())}#[event_cpi]#[derive(Accounts)]pub struct RecordPayment<'info> {pub authority: Signer<'info>,}
#[event_cpi] voegt de event authority- en huidige programma-accounts toe die vereist zijn voor
de ondertekende self-CPI. CPI-events kunnen niet worden verwerkt via het log-event-abonnement
van Anchor; haal in plaats daarvan de transactie op en decodeer de bijbehorende inner
instruction.
Gebruik het grootboek als bron
RPC-providers kunnen programmalogs afkappen. Als het missen van een event gevolgen zou hebben voor saldo's, rechten of een andere workflow waarin correctheid cruciaal is, leid het resultaat dan af uit programma-accounts of instructiedata van de transactie. Gebruik events om indexering efficiënt te maken, niet als enige vastlegging van de status.
Abonneren vanuit TypeScript
De huidige Anchor TypeScript-client is @anchor-lang/core. Een Program-instantie
die is gemaakt op basis van de IDL van het programma kan overeenkomende log-events decoderen en
de via het RPC-logabonnement aanleveren:
const listenerId = program.addEventListener("PaymentRecorded",(event, slot, signature) => {console.log({ event, slot, signature });},"confirmed");// Remove the websocket subscription when it is no longer needed.await program.removeEventListener(listenerId);
De callback ontvangt de gedecodeerde eventdata, de slot en de handtekening van de transactie. Abonnementen dekken alleen logs die worden geleverd terwijl de verbinding actief is. Sla verwerkte handtekeningen op en vul gemiste transacties aan na een verbroken verbinding als de consument de volledige geschiedenis nodig heeft.
Logs van opgehaalde transacties decoderen
Haal voor backfills en eenmalige inspectie een transactie op en geef elke
Program data:-payload door aan de event coder:
const transaction = await connection.getTransaction(signature, {commitment: "confirmed",maxSupportedTransactionVersion: 0});if (!transaction?.meta || transaction.meta.err) {throw new Error("Transaction was not found or did not succeed");}for (const log of transaction.meta.logMessages ?? []) {const prefix = "Program data: ";if (!log.startsWith(prefix)) continue;const decoded = program.coder.events.decode(log.slice(prefix.length));if (decoded) console.log(decoded.name, decoded.data);}
Een transactie kan meerdere programma's aanroepen. Houd voor een productieparser de
logregels Program <address> invoke en success als grenzen bij, of gebruik de eventparser van Anchor,
zodat een Program data:-regel van een ander programma niet als de jouwe wordt gedecodeerd.
Mislukte transacties kunnen logs en inner instructions behouden van werk dat vóór
de fout is voltooid, dus wijs transactiemetadata met een err die niet null is altijd af
voordat je een van beide eventvormen indexeert. Het logabonnement van Anchor voert deze controle uit
voordat een event-callback wordt aangeroepen.
Zoek voor een emit_cpi!-event de self-CPI op in meta.innerInstructions,
controleer of de programma-ID die van jouw programma is en decodeer de instruction
data met base58. Controleer voordat je bytes verwijdert eerst de
EVENT_IX_TAG_LE van Anchor:
import bs58 from "bs58";const EVENT_IX_TAG_LE = Buffer.from("e445a52e51cb9a1d", "hex");const instructionData = Buffer.from(bs58.decode(innerInstruction.data));if (!instructionData.subarray(0, 8).equals(EVENT_IX_TAG_LE)) {// This is an ordinary self-CPI, not an emit_cpi! event.continue;}const eventData = instructionData.subarray(8).toString("base64");const decoded = program.coder.events.decode(eventData);
Door zowel de programma-ID als de tag te controleren, voorkom je dat een gewone self-CPI voor een event wordt aangezien.
Eventschema's versioneren
Standaard leidt Anchor een event-discriminator van 8 bytes af uit de eventnaam.
Anchor ondersteunt ook een expliciete discriminator via
#[event(discriminator = ...)]. Discriminators moeten uniek blijven binnen het
programma en stabiel zijn voor elke client die het event decodeert.
Het wijzigen van de velden van een verzonden struct wijzigt de Borsh-indeling. Definieer bij een ingrijpende
schemawijziging een nieuw event, zoals PaymentRecordedV2, in plaats van
PaymentRecorded stilzwijgend te wijzigen. Blijf het oude event verzenden tijdens een
migratieperiode wanneer bestaande indexers er nog van afhankelijk zijn.
Geef voor evolueerbare data de voorkeur aan velden met vaste breedte en identifiers waarmee een indexer uitgebreidere accountdata afzonderlijk kan ophalen. Publiceer de bijgewerkte IDL vóór of tegelijk met de programma-upgrade, zodat consumenten het nieuwe event direct kunnen decoderen.
Events testen
Test het gedecodeerde contract, niet de base64-string. Registreer de listener voordat je de instructie verstuurt, controleer de gedecodeerde velden en de handtekening van de transactie, en verwijder de listener altijd:
let listenerId = 0;const received = new Promise<{event: { authority: PublicKey; amount: BN };signature: string;}>((resolve) => {listenerId = program.addEventListener("PaymentRecorded",(event, _slot, signature) => resolve({ event, signature }),"confirmed");});try {const signature = await program.methods.recordPayment(new BN(500)).rpc();const result = await received;assert.equal(result.signature, signature);assert.equal(result.event.amount.toString(), "500");} finally {await program.removeEventListener(listenerId);}
Test ook dat de indexer event-bytes negeert die in de metadata van mislukte transacties zijn behouden, en voeg een fixture toe voor elke ondersteunde eventversie. Als je
emit_cpi! gebruikt, test dan het decoderen vanuit innerInstructions, omdat loglisteners
deze events niet rechtstreeks ontvangen.
Verder lezen
Is this page helpful?