Solana-DokumentationProgramme entwickeln

Anchor-Events

Anchor-Events sind typisierte, mit Borsh serialisierte Datensätze, die in der IDL eines Programms enthalten sind. So können Clients die Programmaktivität interpretieren, ohne menschenlesbare Lognachrichten parsen zu müssen.

Events eignen sich für Indexierung und Benachrichtigungen, sind aber kein Programmzustand. Speichere jeden Wert, den das Programm erneut lesen muss, in Konten, und betrachte Events als einen Datensatz, der aus einer erfolgreichen Transaktion abgeleitet wird.

Ein Event definieren und ausgeben

Füge #[event] zu einer Struct hinzu und rufe emit! auf, nachdem die Zustandsänderung erfolgreich war.

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! schreibt die Event-Bytes mit dem Syscall sol_log_data von Solana. Die Transaktionslogs zeigen das Ergebnis als Program data: <base64>. Der kodierte Wert beginnt mit der Event-Discriminator, gefolgt von den mit Borsh serialisierten Feldern.

Halte Events schlank. Große Strings und Vektoren verbrauchen Compute, vergrößern die Transaktionsmetadaten und sind eher von Log-Kürzungen betroffen.

Zwischen Log- und CPI-Events wählen

Anchor unterstützt zwei Ausgabewege:

PfadSpeicherortKompromiss
emit!Programmdaten-LogsEinfacher und günstiger, aber Logs können gekürzt werden
emit_cpi!Inner instruction data aus einem CPIDauerhaftere Metadaten, aber mit zusätzlichen Konten und höherem Compute-Verbrauch

Um CPI-Events zu verwenden, aktiviere das Feature event-cpi von Anchor:

Cargo.toml
[dependencies]
anchor-lang = { version = "1.1.2", features = ["event-cpi"] }

Annotiere dann die Konten-Struct der Anweisung mit #[event_cpi] und gib das Event aus einem Handler aus, dessen Kontextvariable ctx heißt:

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] fügt die Event-Authority und die Konten des aktuellen Programms hinzu, die für den signierten Self-CPI erforderlich sind. CPI-Events lassen sich nicht über das Log-Event-Abonnement von Anchor konsumieren; rufe stattdessen die Transaktion ab und dekodiere die passende innere Anweisung.

Das Ledger als Quelle verwenden

RPC-Provider können Programmlogs kürzen. Wenn ein fehlendes Event Guthaben, Berechtigungen oder einen anderen korrektheitskritischen Workflow beeinträchtigen würde, leite das Ergebnis aus den Konten des Programms oder den instruction data der Transaktion ab. Nutze Events, um die Indexierung effizient zu machen, nicht als einzige Aufzeichnung des Zustands.

Aus TypeScript abonnieren

Der aktuelle Anchor-TypeScript-Client ist @anchor-lang/core. Eine aus der Programm-IDL erzeugte Program- Instanz kann passende Log-Events dekodieren und sie über das RPC-Log-Abonnement zustellen:

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);

Der Callback erhält die dekodierten Event-Daten, den slot und die Transaktionssignatur. Abonnements erfassen nur Logs, die zugestellt werden, solange die Verbindung aktiv ist. Speichere verarbeitete Signaturen und hole nach einer Trennung verpasste Transaktionen nach, wenn der Consumer eine vollständige Historie benötigt.

Logs abgerufener Transaktionen dekodieren

Für Backfills und einmalige Prüfungen rufst du eine Transaktion ab und übergibst jede Program data:-Payload an den 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);
}

Eine Transaktion kann mehrere Programme aufrufen. Verfolge in einem Produktions-Parser die Log-Grenzen Program <address> invoke und success oder nutze den Event-Parser von Anchor, damit der Program data:-Eintrag eines anderen Programms nicht als deiner dekodiert wird. Fehlgeschlagene Transaktionen können Logs und innere Anweisungen von Arbeit behalten, die vor dem Fehler abgeschlossen wurde. Weise daher Transaktionsmetadaten mit einem err ungleich null immer ab, bevor du eine der beiden Event-Formen indexierst. Das Log-Abonnement von Anchor führt diese Prüfung durch, bevor es einen Event-Callback auslöst.

Suche bei einem emit_cpi!-Event den Self-CPI in meta.innerInstructions, bestätige, dass seine Programm-ID deinem Programm entspricht, und dekodiere dessen instruction data per base58. Prüfe vor dem Entfernen von Bytes das EVENT_IX_TAG_LE von 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);

Die Prüfung von Programm-ID und Tag verhindert, dass ein gewöhnlicher Self-CPI fälschlich für ein Event gehalten wird.

Event-Schemas versionieren

Standardmäßig leitet Anchor eine 8-Byte-Event-Discriminator aus dem Event-Namen ab. Anchor unterstützt außerdem eine explizite Discriminator über #[event(discriminator = ...)]. Discriminators müssen innerhalb des Programms eindeutig und für jeden Client, der das Event dekodiert, stabil bleiben.

Das Ändern der Felder einer ausgegebenen Struct ändert deren Borsh-Layout. Definiere bei einer inkompatiblen Schemaänderung ein neues Event wie PaymentRecordedV2, anstatt PaymentRecorded stillschweigend zu ändern. Gib das alte Event während eines Migrationsfensters weiterhin aus, wenn bestehende Indexer noch davon abhängen.

Bevorzuge für weiterentwickelbare Daten Felder fester Breite und Bezeichner, mit denen ein Indexer umfangreichere Kontodaten separat abrufen kann. Veröffentliche die aktualisierte IDL vor oder zusammen mit dem Programm-Upgrade, damit Consumer das neue Event sofort dekodieren können.

Events testen

Teste den dekodierten Vertrag, nicht den base64-String. Registriere den Listener, bevor du die Anweisung sendest, prüfe die dekodierten Felder und die Transaktionssignatur und entferne den Listener immer:

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);
}

Teste außerdem, dass der Indexer Event-Bytes ignoriert, die in den Metadaten fehlgeschlagener Transaktionen erhalten bleiben, und füge für jede unterstützte Event-Version eine Fixture hinzu. Wenn du emit_cpi! verwendest, teste die Dekodierung aus innerInstructions, da Log-Listener diese Events nicht direkt empfangen.

Is this page helpful?

Inhaltsverzeichnis

Seite bearbeiten
© 2026 Solana Foundation. Alle Rechte vorbehalten.