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上编辑