Event Anchor adalah catatan bertipe yang diserialisasi dengan Borsh dan disertakan dalam IDL program. Event memungkinkan klien menafsirkan aktivitas program tanpa perlu mengurai pesan log yang dapat dibaca manusia.
Event berguna untuk pengindeksan dan notifikasi, tetapi bukan state program. Simpan setiap nilai yang harus dibaca kembali oleh program di dalam akun, dan perlakukan event sebagai catatan turunan dari transaksi yang berhasil.
Mendefinisikan dan memancarkan event
Tambahkan #[event] pada sebuah struct dan panggil emit! setelah perubahan state berhasil.
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! menulis byte event menggunakan syscall sol_log_data milik Solana. Log transaksi
menampilkan hasilnya sebagai Program data: <base64>. Nilai yang dienkode diawali dengan
discriminator event, diikuti field-nya yang diserialisasi dengan Borsh.
Buat event tetap fokus. String dan vektor yang besar menghabiskan compute, memperbesar metadata transaksi, dan lebih mungkin terkena pemotongan log.
Memilih antara event log dan event CPI
Anchor mendukung dua jalur emisi:
| Jalur | Lokasi penyimpanan | Pertimbangan |
|---|---|---|
emit! | Log data program | Lebih sederhana dan murah, tetapi log dapat terpotong |
emit_cpi! | Data instruksi inner dari sebuah CPI | Metadata lebih tahan lama, dengan akun dan compute tambahan |
Untuk menggunakan event CPI, aktifkan fitur event-cpi milik Anchor:
[dependencies]anchor-lang = { version = "1.1.2", features = ["event-cpi"] }
Kemudian anotasi struct akun instruksi dengan #[event_cpi] dan pancarkan
dari handler yang variabel kontekstnya bernama ctx:
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] menambahkan akun event authority dan akun program saat ini yang diperlukan oleh
CPI mandiri yang ditandatangani. Event CPI tidak dapat dikonsumsi melalui langganan event log
Anchor; sebagai gantinya, ambil transaksinya dan dekode inner instruction yang sesuai.
Gunakan ledger sebagai sumber
Penyedia RPC dapat memotong log program. Jika kehilangan sebuah event akan memengaruhi saldo, izin, atau alur kerja lain yang kritis terhadap kebenaran, turunkan hasilnya dari akun program atau data instruksi transaksi. Gunakan event untuk membuat pengindeksan efisien, bukan sebagai satu-satunya catatan state.
Berlangganan dari TypeScript
Klien TypeScript Anchor saat ini adalah @anchor-lang/core. Instance Program
yang dibuat dari IDL program dapat mendekode event log yang cocok dan mengirimkannya
melalui langganan log RPC:
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);
Callback menerima data event yang sudah didekode, slot, dan signature transaksi. Langganan hanya mencakup log yang dikirim selama koneksi aktif. Simpan signature yang sudah diproses dan isi ulang transaksi yang terlewat setelah koneksi terputus jika konsumen membutuhkan riwayat yang lengkap.
Mendekode log transaksi yang diambil
Untuk backfill dan inspeksi satu kali, ambil transaksi dan teruskan setiap
payload Program data: ke 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);}
Satu transaksi dapat memanggil beberapa program. Untuk parser produksi, lacak
batas log Program <address> invoke dan success, atau gunakan event parser Anchor
agar entri Program data: dari program lain tidak didekode sebagai milik Anda.
Transaksi yang gagal dapat menyimpan log dan inner instruction dari pekerjaan yang selesai
sebelum error, jadi selalu tolak metadata transaksi dengan err non-null
sebelum mengindeks kedua bentuk event. Langganan log Anchor melakukan pemeriksaan ini
sebelum mengirimkan callback event.
Untuk event emit_cpi!, cari self-CPI di meta.innerInstructions,
pastikan program ID-nya adalah program Anda, dan dekode base58 data
instruksinya. Sebelum membuang byte apa pun, verifikasi
EVENT_IX_TAG_LE milik 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);
Memeriksa program ID sekaligus tag mencegah self-CPI biasa agar tidak keliru dianggap sebagai event.
Membuat versi skema event
Secara default, Anchor menurunkan discriminator event 8 byte dari nama event.
Anchor juga mendukung discriminator eksplisit melalui
#[event(discriminator = ...)]. Discriminator harus tetap unik di dalam
program dan stabil bagi setiap klien yang mendekode event tersebut.
Mengubah field pada struct yang dipancarkan akan mengubah layout Borsh-nya. Untuk perubahan
skema yang bersifat breaking, definisikan event baru seperti PaymentRecordedV2 alih-alih
diam-diam mengubah PaymentRecorded. Tetap pancarkan event lama selama masa
migrasi jika indexer yang ada masih bergantung padanya.
Untuk data yang dapat berevolusi, pilih field berlebar tetap dan identifier yang memungkinkan indexer mengambil data akun yang lebih kaya secara terpisah. Publikasikan IDL yang diperbarui sebelum atau bersamaan dengan upgrade program agar konsumen dapat langsung mendekode event baru.
Menguji event
Uji kontrak yang sudah didekode, bukan string base64-nya. Daftarkan listener sebelum mengirim instruksi, periksa field yang sudah didekode dan signature transaksi, lalu selalu hapus listener-nya:
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);}
Uji juga bahwa indexer mengabaikan byte event yang tersimpan di metadata transaksi
yang gagal, dan tambahkan fixture untuk setiap versi event yang didukung. Jika Anda menggunakan
emit_cpi!, uji proses dekode dari innerInstructions karena listener log tidak
menerima event tersebut secara langsung.
Bacaan lanjutan
Is this page helpful?