LogoAnchor 中文文档

发出事件

学习如何在 Anchor 程序中使用 emit! 和 emit_cpi! 宏发出事件。

示例

Anchor 提供了两个宏来在你的程序中发出事件:

  1. emit!() - 直接向程序日志发出事件。这种方法更简单, 但数据提供商有时可能会截断程序日志
  2. emit_cpi!() - 通过跨程序调用 (CPI) 发出事件, 将事件数据包含在指令数据中。

emit_cpi() 方法是作为程序日志的替代方案引入的,因为程序日志有时会被数据提供商截断。 虽然 CPI 指令数据不太可能被截断,但这种方法确实会因为跨程序调用而产生额外的计算成本。

对于更健壮的事件解决方案,请考虑使用 TritonHelius 提供的 geyser gRPC 服务。

emit

emit!() 宏提供了一种通过程序日志发出事件的方式。当调用时,它会:

  1. 使用 sol_log_data() 系统调用将数据写入程序日志
  2. 将事件数据编码为 base64 字符串 并加上 Program Data: 前缀

要在客户端应用程序中接收发出的事件,请使用 addEventListener() 方法。该方法会自动 解析和解码 程序日志中的事件数据。

使用示例:

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<()> {    emit!(CustomEvent { message: input });  Ok(())  } }   #[derive(Accounts)] pub struct EmitEvent {}    #[event] pub struct CustomEvent {  pub message: String, }

以下是程序日志的输出。事件数据被 base64 编码为 Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE=

程序日志
日志消息:  Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy invoke [1]  Program log: Instruction: EmitEvent  Program data: Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE=  Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy consumed 1012 of 200000 compute units  Program 8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy success

确保你使用的 RPC 提供商不会从交易数据中截断程序日志。

emit_cpi

emit_cpi!() 宏通过跨程序调用 (CPI) 向程序本身发出事件。事件数据被编码并包含在 CPI 的指令数据中(而不是程序日志中)。

要通过 CPI 发出事件,你需要在程序的 Cargo.toml 中启用 event-cpi 功能:

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

使用示例:

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<()> {    emit_cpi!(CustomEvent { message: input });  Ok(())  } }    #[event_cpi] #[derive(Accounts)] pub struct EmitEvent {}    #[event] pub struct CustomEvent {  pub message: String, }

event_cpi 属性必须添加到使用 emit_cpi!() 宏发出事件的指令的 #[derive(Accounts)] 结构体中。该属性 自动包含所需的额外账户 以支持自我 CPI。

lib.rs
 #[event_cpi] #[derive(Accounts)] pub struct RequiredAccounts {  // --snip-- }

要在客户端应用程序中获取发出的事件数据,你需要使用交易签名获取完整交易数据,并从 CPI 指令数据中解析事件数据。

test.ts
// 1. 使用交易签名获取完整交易数据 const transactionData = await program.provider.connection.getTransaction(  transactionSignature,  { commitment: "confirmed" }, );   // 2. 提取包含事件数据的 CPI(内部指令) const eventIx = transactionData.meta.innerInstructions[0].instructions[0];   // 3. 解码事件数据 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);

以下是一个示例交易,展示了事件数据在交易详情中的呈现方式。使用 emit_cpi!() 时,事件数据会被编码并包含在内部指令(CPI)的 data 字段中。

在下面的示例交易中,编码的事件数据为 "data": "6AJcBqZP8afBKheoif1oA6UAiLAcqYr2RaR33pFnEY1taQp",位于 innerInstructions 数组中。

交易数据
{  "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"  ]  } }

目前,通过 CPI 发出的事件数据无法直接订阅。 要访问此数据,你必须获取完整交易数据并手动从 CPI 的指令数据中解码事件信息。

On this page

在GitHub上编辑