Solana documentatieProgramma's ontwikkelen

Anchor-events

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.

lib.rs
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:

MethodeOpslaglocatieAfweging
emit!ProgrammadatalogsEenvoudiger en goedkoper, maar logs kunnen worden afgekapt
emit_cpi!Inner instruction data van een CPIDuurzamere metadata, met extra accounts en compute

Schakel de event-cpi-feature van Anchor in om CPI-events te gebruiken:

Cargo.toml
[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:

lib.rs
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?

Inhoudsopgave

Pagina Bewerken
© 2026 Solana Foundation. Alle rechten voorbehouden.