123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- ---
- title: Emit Events
- description:
- Learn how to emit events in Anchor programs using emit! and emit_cpi! macros.
- ---
- ## Examples
- Anchor provides two macros for emitting events in your programs:
- 1. `emit!()` - Emits events directly to program logs. This is the simpler,
- though program logs may be truncated by data providers in some cases
- 2. `emit_cpi!()` - Emits events through a Cross Program Invocation (CPI) by
- including the event data in the instruction data.
- <Callout type="info">
- The `emit_cpi()` approach was introduced an alternative to program logs, which
- can sometimes be truncated by data providers. While CPI instruction data is less
- likely to be truncated, this approach does incur additional compute costs from
- the Cross Program Invocation.
- </Callout>
- <Callout type="info">
- For more robust solutions for events, consider geyser gRPC services by
- [Triton](https://docs.triton.one/project-yellowstone/dragons-mouth-grpc-subscriptions)
- or [Helius](https://docs.helius.dev/data-streaming/geyser-yellowstone).
- </Callout>
- ### `emit`
- The
- [`emit!()`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/event/src/lib.rs#L101-L109)
- macro provides a way to emit events through program logs. When called, it:
- 1. Uses the
- [`sol_log_data()`](https://github.com/anza-xyz/agave/blob/c2b350023ba849d1b33142592264aaa51fcb7f1e/sdk/program/src/log.rs#L115-L124)
- syscall to write the data to program logs
- 2. Encodes the event data as a
- [base64 string](https://github.com/anza-xyz/agave/blob/c2b350023ba849d1b33142592264aaa51fcb7f1e/program-runtime/src/stable_log.rs#L46-L61)
- prefixed with `Program Data:`
- To receive emitted events in your client application, use the
- [`addEventListener()`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/ts/packages/anchor/src/program/event.ts#L74-L123)
- method. This method automatically
- [parses and decodes](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/ts/packages/anchor/src/program/event.ts#L232-L253)
- event data from the program logs.
- Example usage:
- <Tabs items={["Program", "Client"]}>
- <Tab value="Program">
- ```rust title="lib.rs"
- use anchor_lang::prelude::*;
- declare_id!("8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy");
- #[program]
- pub mod event {
- use super::*;
- pub fn emit_event(_ctx: Context<EmitEvent>, input: String) -> Result<()> {
- // [!code word:emit!]
- // [!code highlight]
- emit!(CustomEvent { message: input });
- Ok(())
- }
- }
- #[derive(Accounts)]
- pub struct EmitEvent {}
- // [!code highlight:4]
- #[event]
- pub struct CustomEvent {
- pub message: String,
- }
- ```
- </Tab>
- <Tab value="Client">
- ```ts title="test.ts"
- import * as anchor from "@coral-xyz/anchor";
- import { Program } from "@coral-xyz/anchor";
- import { Event } from "../target/types/event";
- describe("event", () => {
- // Configure the client to use the local cluster.
- anchor.setProvider(anchor.AnchorProvider.env());
- const program = anchor.workspace.Event as Program<Event>;
- it("Emits custom event", async () => {
- // Set up listener before sending transaction
- // [!code word:addEventListener]
- // [!code highlight:4]
- const listenerId = program.addEventListener("customEvent", event => {
- // Do something with the event data
- console.log("Event Data:", event);
- });
- // Message to be emitted in the event
- const message = "Hello, Solana!";
- // Send transaction
- await program.methods.emitEvent(message).rpc();
- // Remove listener
- await program.removeEventListener(listenerId);
- });
- });
- ```
- </Tab>
- </Tabs>
- The following is the output of the program logs. The event data is base64
- encoded as `Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE=`.
- ```shell title="Program Logs"
- Log Messages:
- Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy invoke [1]
- Program log: Instruction: EmitEvent
- Program data: Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE=
- Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy consumed 1012 of 200000 compute units
- Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy success
- ```
- <Callout type="info">
- Ensure the RPC provider you use does not truncate the program logs from the
- transaction data.
- </Callout>
- ### `emit_cpi`
- The
- [`emit_cpi!()`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/event/src/lib.rs#L155-L195)
- macro emits events through Cross Program Invocations (CPIs) to the program
- itself. The event data is encoded and included in the CPI's instruction data
- (instead of program logs).
- To emit events through CPIs, you need to enable the `event-cpi` feature in your
- program's `Cargo.toml`:
- ```toml title="Cargo.toml"
- [dependencies]
- anchor-lang = { version = "0.31.1", features = ["event-cpi"] }
- ```
- Example usage:
- <Tabs items={["Program", "Client"]}>
- <Tab value="Program">
- ```rust title="lib.rs"
- use anchor_lang::prelude::*;
- declare_id!("2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1");
- #[program]
- pub mod event_cpi {
- use super::*;
- pub fn emit_event(ctx: Context<EmitEvent>, input: String) -> Result<()> {
- // [!code word:emit_cpi!]
- // [!code highlight]
- emit_cpi!(CustomEvent { message: input });
- Ok(())
- }
- }
- // [!code highlight]
- #[event_cpi]
- #[derive(Accounts)]
- pub struct EmitEvent {}
- // [!code highlight:4]
- #[event]
- pub struct CustomEvent {
- pub message: String,
- }
- ```
- </Tab>
- <Tab value="Client">
- ```ts title="test.ts"
- import * as anchor from "@coral-xyz/anchor";
- import { Program } from "@coral-xyz/anchor";
- import { EventCpi } from "../target/types/event_cpi";
- describe("event-cpi", () => {
- // Configure the client to use the local cluster.
- anchor.setProvider(anchor.AnchorProvider.env());
- const program = anchor.workspace.EventCpi as Program<EventCpi>;
- it("Emits custom event", async () => {
- const message = "Hello, Solana!";
- const transactionSignature = await program.methods.emitEvent(message).rpc();
- // Wait for the transaction to be confirmed
- await program.provider.connection.confirmTransaction(
- transactionSignature,
- "confirmed",
- );
- // Fetch the transaction data
- // [!code highlight:4]
- const transactionData = await program.provider.connection.getTransaction(
- transactionSignature,
- { commitment: "confirmed" },
- );
- // Decode the event data from the CPI instruction data
- // [!code highlight:4]
- const eventIx = transactionData.meta.innerInstructions[0].instructions[0];
- const rawData = anchor.utils.bytes.bs58.decode(eventIx.data);
- const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8));
- const event = program.coder.events.decode(base64Data);
- console.log(event);
- });
- });
- ```
- </Tab>
- </Tabs>
- The
- [`event_cpi`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/event/src/lib.rs#L228-L237)
- attribute must be added to the `#[derive(Accounts)]` struct for the instruction
- instruction that emits events using the `emit_cpi!()` macro. This attribute
- [automatically includes additional accounts](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/syn/src/parser/accounts/event_cpi.rs#L28-L70)
- that are required for the self CPI.
- ```rust title="lib.rs"
- // [!code highlight]
- #[event_cpi]
- #[derive(Accounts)]
- pub struct RequiredAccounts {
- // --snip--
- }
- ```
- To get the emitted event data in your client application, you need to fetch the
- transaction using the transaction signature and parse the event data from the
- CPI instruction data.
- ```ts title="test.ts"
- // 1. Fetch the full transaction data using the transaction signature
- const transactionData = await program.provider.connection.getTransaction(
- transactionSignature,
- { commitment: "confirmed" },
- );
- // 2. Extract the CPI (inner instruction) that contains the event data
- const eventIx = transactionData.meta.innerInstructions[0].instructions[0];
- // 3. Decode the event data
- const rawData = anchor.utils.bytes.bs58.decode(eventIx.data);
- const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8));
- const event = program.coder.events.decode(base64Data);
- console.log(event);
- ```
- Below is an example transaction showing how event data appears in the
- transaction details. When using `emit_cpi!()`, the event data is encoded and
- included in the `data` field of an inner instruction (CPI).
- In the example transaction below, the encoded event data is
- `"data": "6AJcBqZP8afBKheoif1oA6UAiLAcqYr2RaR33pFnEY1taQp"` in the
- `innerInstructions` array.
- ```shell title="Transaction Data"
- {
- "blockTime": 1735854530,
- "meta": {
- "computeUnitsConsumed": 13018,
- "err": null,
- "fee": 5000,
- "innerInstructions": [
- {
- "index": 0,
- "instructions": [
- {
- "accounts": [
- 1
- ],
- "data": "6AJcBqZP8afBKheoif1oA6UAiLAcqYr2RaR33pFnEY1taQp",
- "programIdIndex": 2,
- "stackHeight": 2
- }
- ]
- }
- ],
- "loadedAddresses": {
- "readonly": [],
- "writable": []
- },
- "logMessages": [
- "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 invoke [1]",
- "Program log: Instruction: EmitEvent",
- "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 invoke [2]",
- "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 consumed 5000 of 192103 compute units",
- "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 success",
- "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 consumed 13018 of 200000 compute units",
- "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 success"
- ],
- "postBalances": [
- 499999999999995000,
- 0,
- 1141440
- ],
- "postTokenBalances": [],
- "preBalances": [
- 500000000000000000,
- 0,
- 1141440
- ],
- "preTokenBalances": [],
- "rewards": [],
- "status": {
- "Ok": null
- }
- },
- "slot": 3,
- "transaction": {
- "message": {
- "header": {
- "numReadonlySignedAccounts": 0,
- "numReadonlyUnsignedAccounts": 2,
- "numRequiredSignatures": 1
- },
- "accountKeys": [
- "4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1",
- "2brZf9PQqEvv17xtbj5WNhZJULgVZuLZT6FgH1Cqpro2",
- "2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1"
- ],
- "recentBlockhash": "2QtnU35RXTo7uuQEVARYJgWYRYtbqUxWQkK8WywUnVdY",
- "instructions": [
- {
- "accounts": [
- 1,
- 2
- ],
- "data": "3XZZ984toC4WXCLkxBsLimpEGgH75TKXRJnk",
- "programIdIndex": 2,
- "stackHeight": null
- }
- ],
- "indexToProgramIds": {}
- },
- "signatures": [
- "3gFbKahSSbitRSos4MH3cqeVv2FiTNaLCuWaLPo6R98FEbHnTshoYxopGcx74nFLqt1pbZK9i8dnr4NFXayrMndZ"
- ]
- }
- }
- ```
- <Callout type="info">
- Currently, event data emitted through CPIs cannot be directly subscribed to.
- To access this data, you must fetch the complete transaction data and manually
- decode the event information from the instruction data of the CPI.
- </Callout>
|