events.mdx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. ---
  2. title: Emit Events
  3. description:
  4. Learn how to emit events in Anchor programs using emit! and emit_cpi! macros.
  5. ---
  6. ## Examples
  7. Anchor provides two macros for emitting events in your programs:
  8. 1. `emit!()` - Emits events directly to program logs. This is the simpler,
  9. though program logs may be truncated by data providers in some cases
  10. 2. `emit_cpi!()` - Emits events through a Cross Program Invocation (CPI) by
  11. including the event data in the instruction data.
  12. <Callout type="info">
  13. The `emit_cpi()` approach was introduced an alternative to program logs, which
  14. can sometimes be truncated by data providers. While CPI instruction data is less
  15. likely to be truncated, this approach does incur additional compute costs from
  16. the Cross Program Invocation.
  17. </Callout>
  18. <Callout type="info">
  19. For more robust solutions for events, consider geyser gRPC services by
  20. [Triton](https://docs.triton.one/project-yellowstone/dragons-mouth-grpc-subscriptions)
  21. or [Helius](https://docs.helius.dev/data-streaming/geyser-yellowstone).
  22. </Callout>
  23. ### `emit`
  24. The
  25. [`emit!()`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/event/src/lib.rs#L101-L109)
  26. macro provides a way to emit events through program logs. When called, it:
  27. 1. Uses the
  28. [`sol_log_data()`](https://github.com/anza-xyz/agave/blob/c2b350023ba849d1b33142592264aaa51fcb7f1e/sdk/program/src/log.rs#L115-L124)
  29. syscall to write the data to program logs
  30. 2. Encodes the event data as a
  31. [base64 string](https://github.com/anza-xyz/agave/blob/c2b350023ba849d1b33142592264aaa51fcb7f1e/program-runtime/src/stable_log.rs#L46-L61)
  32. prefixed with `Program Data:`
  33. To receive emitted events in your client application, use the
  34. [`addEventListener()`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/ts/packages/anchor/src/program/event.ts#L74-L123)
  35. method. This method automatically
  36. [parses and decodes](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/ts/packages/anchor/src/program/event.ts#L232-L253)
  37. event data from the program logs.
  38. Example usage:
  39. <Tabs items={["Program", "Client"]}>
  40. <Tab value="Program">
  41. ```rust title="lib.rs"
  42. use anchor_lang::prelude::*;
  43. declare_id!("8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy");
  44. #[program]
  45. pub mod event {
  46. use super::*;
  47. pub fn emit_event(_ctx: Context<EmitEvent>, input: String) -> Result<()> {
  48. // [!code word:emit!]
  49. // [!code highlight]
  50. emit!(CustomEvent { message: input });
  51. Ok(())
  52. }
  53. }
  54. #[derive(Accounts)]
  55. pub struct EmitEvent {}
  56. // [!code highlight:4]
  57. #[event]
  58. pub struct CustomEvent {
  59. pub message: String,
  60. }
  61. ```
  62. </Tab>
  63. <Tab value="Client">
  64. ```ts title="test.ts"
  65. import * as anchor from "@coral-xyz/anchor";
  66. import { Program } from "@coral-xyz/anchor";
  67. import { Event } from "../target/types/event";
  68. describe("event", () => {
  69. // Configure the client to use the local cluster.
  70. anchor.setProvider(anchor.AnchorProvider.env());
  71. const program = anchor.workspace.Event as Program<Event>;
  72. it("Emits custom event", async () => {
  73. // Set up listener before sending transaction
  74. // [!code word:addEventListener]
  75. // [!code highlight:4]
  76. const listenerId = program.addEventListener("customEvent", event => {
  77. // Do something with the event data
  78. console.log("Event Data:", event);
  79. });
  80. // Message to be emitted in the event
  81. const message = "Hello, Solana!";
  82. // Send transaction
  83. await program.methods.emitEvent(message).rpc();
  84. // Remove listener
  85. await program.removeEventListener(listenerId);
  86. });
  87. });
  88. ```
  89. </Tab>
  90. </Tabs>
  91. The following is the output of the program logs. The event data is base64
  92. encoded as `Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE=`.
  93. ```shell title="Program Logs"
  94. Log Messages:
  95. Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy invoke [1]
  96. Program log: Instruction: EmitEvent
  97. Program data: Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE=
  98. Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy consumed 1012 of 200000 compute units
  99. Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy success
  100. ```
  101. <Callout type="info">
  102. Ensure the RPC provider you use does not truncate the program logs from the
  103. transaction data.
  104. </Callout>
  105. ### `emit_cpi`
  106. The
  107. [`emit_cpi!()`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/event/src/lib.rs#L155-L195)
  108. macro emits events through Cross Program Invocations (CPIs) to the program
  109. itself. The event data is encoded and included in the CPI's instruction data
  110. (instead of program logs).
  111. To emit events through CPIs, you need to enable the `event-cpi` feature in your
  112. program's `Cargo.toml`:
  113. ```toml title="Cargo.toml"
  114. [dependencies]
  115. anchor-lang = { version = "0.31.1", features = ["event-cpi"] }
  116. ```
  117. Example usage:
  118. <Tabs items={["Program", "Client"]}>
  119. <Tab value="Program">
  120. ```rust title="lib.rs"
  121. use anchor_lang::prelude::*;
  122. declare_id!("2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1");
  123. #[program]
  124. pub mod event_cpi {
  125. use super::*;
  126. pub fn emit_event(ctx: Context<EmitEvent>, input: String) -> Result<()> {
  127. // [!code word:emit_cpi!]
  128. // [!code highlight]
  129. emit_cpi!(CustomEvent { message: input });
  130. Ok(())
  131. }
  132. }
  133. // [!code highlight]
  134. #[event_cpi]
  135. #[derive(Accounts)]
  136. pub struct EmitEvent {}
  137. // [!code highlight:4]
  138. #[event]
  139. pub struct CustomEvent {
  140. pub message: String,
  141. }
  142. ```
  143. </Tab>
  144. <Tab value="Client">
  145. ```ts title="test.ts"
  146. import * as anchor from "@coral-xyz/anchor";
  147. import { Program } from "@coral-xyz/anchor";
  148. import { EventCpi } from "../target/types/event_cpi";
  149. describe("event-cpi", () => {
  150. // Configure the client to use the local cluster.
  151. anchor.setProvider(anchor.AnchorProvider.env());
  152. const program = anchor.workspace.EventCpi as Program<EventCpi>;
  153. it("Emits custom event", async () => {
  154. const message = "Hello, Solana!";
  155. const transactionSignature = await program.methods.emitEvent(message).rpc();
  156. // Wait for the transaction to be confirmed
  157. await program.provider.connection.confirmTransaction(
  158. transactionSignature,
  159. "confirmed",
  160. );
  161. // Fetch the transaction data
  162. // [!code highlight:4]
  163. const transactionData = await program.provider.connection.getTransaction(
  164. transactionSignature,
  165. { commitment: "confirmed" },
  166. );
  167. // Decode the event data from the CPI instruction data
  168. // [!code highlight:4]
  169. const eventIx = transactionData.meta.innerInstructions[0].instructions[0];
  170. const rawData = anchor.utils.bytes.bs58.decode(eventIx.data);
  171. const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8));
  172. const event = program.coder.events.decode(base64Data);
  173. console.log(event);
  174. });
  175. });
  176. ```
  177. </Tab>
  178. </Tabs>
  179. The
  180. [`event_cpi`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/event/src/lib.rs#L228-L237)
  181. attribute must be added to the `#[derive(Accounts)]` struct for the instruction
  182. instruction that emits events using the `emit_cpi!()` macro. This attribute
  183. [automatically includes additional accounts](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/syn/src/parser/accounts/event_cpi.rs#L28-L70)
  184. that are required for the self CPI.
  185. ```rust title="lib.rs"
  186. // [!code highlight]
  187. #[event_cpi]
  188. #[derive(Accounts)]
  189. pub struct RequiredAccounts {
  190. // --snip--
  191. }
  192. ```
  193. To get the emitted event data in your client application, you need to fetch the
  194. transaction using the transaction signature and parse the event data from the
  195. CPI instruction data.
  196. ```ts title="test.ts"
  197. // 1. Fetch the full transaction data using the transaction signature
  198. const transactionData = await program.provider.connection.getTransaction(
  199. transactionSignature,
  200. { commitment: "confirmed" },
  201. );
  202. // 2. Extract the CPI (inner instruction) that contains the event data
  203. const eventIx = transactionData.meta.innerInstructions[0].instructions[0];
  204. // 3. Decode the event data
  205. const rawData = anchor.utils.bytes.bs58.decode(eventIx.data);
  206. const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8));
  207. const event = program.coder.events.decode(base64Data);
  208. console.log(event);
  209. ```
  210. Below is an example transaction showing how event data appears in the
  211. transaction details. When using `emit_cpi!()`, the event data is encoded and
  212. included in the `data` field of an inner instruction (CPI).
  213. In the example transaction below, the encoded event data is
  214. `"data": "6AJcBqZP8afBKheoif1oA6UAiLAcqYr2RaR33pFnEY1taQp"` in the
  215. `innerInstructions` array.
  216. ```shell title="Transaction Data"
  217. {
  218. "blockTime": 1735854530,
  219. "meta": {
  220. "computeUnitsConsumed": 13018,
  221. "err": null,
  222. "fee": 5000,
  223. "innerInstructions": [
  224. {
  225. "index": 0,
  226. "instructions": [
  227. {
  228. "accounts": [
  229. 1
  230. ],
  231. "data": "6AJcBqZP8afBKheoif1oA6UAiLAcqYr2RaR33pFnEY1taQp",
  232. "programIdIndex": 2,
  233. "stackHeight": 2
  234. }
  235. ]
  236. }
  237. ],
  238. "loadedAddresses": {
  239. "readonly": [],
  240. "writable": []
  241. },
  242. "logMessages": [
  243. "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 invoke [1]",
  244. "Program log: Instruction: EmitEvent",
  245. "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 invoke [2]",
  246. "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 consumed 5000 of 192103 compute units",
  247. "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 success",
  248. "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 consumed 13018 of 200000 compute units",
  249. "Program 2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1 success"
  250. ],
  251. "postBalances": [
  252. 499999999999995000,
  253. 0,
  254. 1141440
  255. ],
  256. "postTokenBalances": [],
  257. "preBalances": [
  258. 500000000000000000,
  259. 0,
  260. 1141440
  261. ],
  262. "preTokenBalances": [],
  263. "rewards": [],
  264. "status": {
  265. "Ok": null
  266. }
  267. },
  268. "slot": 3,
  269. "transaction": {
  270. "message": {
  271. "header": {
  272. "numReadonlySignedAccounts": 0,
  273. "numReadonlyUnsignedAccounts": 2,
  274. "numRequiredSignatures": 1
  275. },
  276. "accountKeys": [
  277. "4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1",
  278. "2brZf9PQqEvv17xtbj5WNhZJULgVZuLZT6FgH1Cqpro2",
  279. "2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1"
  280. ],
  281. "recentBlockhash": "2QtnU35RXTo7uuQEVARYJgWYRYtbqUxWQkK8WywUnVdY",
  282. "instructions": [
  283. {
  284. "accounts": [
  285. 1,
  286. 2
  287. ],
  288. "data": "3XZZ984toC4WXCLkxBsLimpEGgH75TKXRJnk",
  289. "programIdIndex": 2,
  290. "stackHeight": null
  291. }
  292. ],
  293. "indexToProgramIds": {}
  294. },
  295. "signatures": [
  296. "3gFbKahSSbitRSos4MH3cqeVv2FiTNaLCuWaLPo6R98FEbHnTshoYxopGcx74nFLqt1pbZK9i8dnr4NFXayrMndZ"
  297. ]
  298. }
  299. }
  300. ```
  301. <Callout type="info">
  302. Currently, event data emitted through CPIs cannot be directly subscribed to.
  303. To access this data, you must fetch the complete transaction data and manually
  304. decode the event information from the instruction data of the CPI.
  305. </Callout>