Skip to main content

使用事件

Sui 网络在链上存储了无数对象,在这些对象上,Move 代码可以执行各种操作。通常希望跟踪这些活动,例如发现模块铸造 NFT 的次数或计算智能合约生成的交易中的 SUI 数量。

为了支持活动监控,Move 在 Sui 网络上提供了一个发出事件的结构。当你与 Sui 网络建立连接时,实际上是在你的客户端和 Sui 网络节点之间创建了一个双向的交互式通信会话。有了这个打开的会话,你可以订阅 Sui 网络添加到事件流中的特定事件,以实现对事件的实时监控。

Move 事件结构

Sui 中的事件对象包括以下属性:

  • id: 包含交易摘要 ID 和事件序列的 JSON 对象。
  • packageId: 发出事件的包的对象 ID。
  • transactionModule: 执行交易的模块。
  • sender: 触发事件的 Sui 网络地址。
  • type: 正在发出的事件类型。
  • parsedJson: 描述事件的 JSON 对象。
  • bcs: 二进制规范化序列化值。
  • timestampMs: 以毫秒为单位的 Unix 纪元时间戳。

发现事件

如果要订阅链上的事件,首先需要知道有哪些事件可用。通常你可能已经知道或者可以发现你自己的代码发出的事件,但当你需要订阅你不拥有的包的链上事件时,情况就变得不那么直观了。Sui RPC 提供了一个 queryEvents 方法,用于查询链上的包并返回可以订阅的可用事件。

过滤事件

你可以过滤你的代码用于查询或订阅的事件。这两种过滤选项相似,但也有一些差异。

在 Move 中发出事件

要在 Move 模块中创建事件,请添加 sui::event 依赖。

use sui::event;

在添加了依赖之后,你可以使用 emit 函数在你想要监视的动作发生时触发事件。例如,以下代码是一个使用数字甜甜圈的示例应用程序的一部分。collect_profits 函数负责收集 SUI,并在每次调用该函数时触发一个事件。要对该事件采取行动,你需要订阅它。

/// Take coin from `DonutShop` and transfer it to tx sender.
/// Requires authorization with `ShopOwnerCap`.
public fun collect_profits( _: &ShopOwnerCap, shop: &mut DonutShop, ctx: &mut TxContext ) {
let amount = balance::value(&shop.balance);
let profits = coin::take(&mut shop.balance, amount, ctx);
// simply create new type instance and emit it event::emit(ProfitsCollected { amount }); transfer::public_transfer(profits, tx_context::sender(ctx)) }

在 Move 中订阅事件

在真空中触发事件并不是非常有用。你还需要有能力监听这些事件,以便可以对其进行操作。在 Sui 中,你订阅这些事件并提供在事件触发时触发的逻辑。

Sui 完整节点支持使用通过 WebSocket API 传输的 JSON-RPC 通知进行订阅功能。你可以直接与 RPC 进行交互 suix_subscribeEvent, suix_subscribeTransaction,或者你可以使用像 Sui TypeScript SDK 这样的 SDK。以下是来自其中一个示例的摘录,该示例使用 TypeScript SDK 对在筛选器中标识的内容创建了一个异步订阅。

let unsubscribe = await provider.subscribeEvent({
filter: { <PACKAGE_ID> },
onMessage: (event) => {
console.log("subscribeEvent", JSON.stringify(event, null, 2))
}
});

Move 智能合约可以调用其他发出事件的智能合约。例如,Deepbook_utils 可以调用 dee9 智能合约并发出此事件。请注意,使用包、事务模块来查询 dee9/clob_v2 会漏掉以下事件,即使这实际上是 dee9 包发出的事件。当前对这个问题的解决方法是了解你关心的所有 packageId,并在 queryEvent 调用中搜索这些内容。

{
"id": {
"txDigest": "bZnc1E7k1fJYLxWihfre5xCw1tX1CyAN6579zypJeiU",
"eventSeq": "0"
},
"packageId": "0x158f2027f60c89bb91526d9bf08831d27f5a0fcb0f74e6698b9f0e1fb2be5d05",
"transactionModule": "deepbook_utils",
"sender": "0x4419ae182ac112bb065bda2146136ed02524ee2611478bfe8ca5d3835bee4af6",
"type": "0xdee9::clob_v2::OrderPlaced<0x2::sui::SUI, 0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>",
"parsedJson": {
"base_asset_quantity_placed": "1000000000",
"client_order_id": "20082022",
"expire_timestamp": "1697121171540",
"is_bid": false,
"order_id": "9223372036854945121",
"original_quantity": "1000000000",
"owner": "0x8c23e5e23c6eb654d69f8ae7de3be23584f435cad81fa4b9cb024b6c989b7818",
"pool_id": "0x7f526b1263c4b91b43c9e646419b5696f424de28dda3c1e6658cc0a54558baa7",
"price": "500000"
},
"bcs": "2pWctGGQ9KULfmnzNtGuPpggLQrj1ZiUQaxva4neM6QWAtUAkuPAzU2eGrdZaGHti3bsUefDioUwwYoVR3bYBkG7Gxf5JVVSxxqTqzxdg5os5ESwFaP69ZcrNsya4G9rHK4KBac9i3m1MseN38xDwMvAMx3"
}

https://suiexplorer.com/txblock/bZnc1E7k1fJYLxWihfre5xCw1tX1CyAN6579zypJeiU

以下是一个使用原始 dee9 包的交易示例。 https://suiexplorer.com/txblock/896CKHod5GQ4kzhF7EwTAGyhQBdaTb9rQS41dcL76gj8

{
"id": {
"txDigest": "896CKHod5GQ4kzhF7EwTAGyhQBdaTb9rQS41dcL76gj8",
"eventSeq": "0"
},
"packageId": "0x000000000000000000000000000000000000000000000000000000000000dee9",
"transactionModule": "clob_v2",
"sender": "0xf821d3483fc7725ebafaa5a3d12373d49901bdfce1484f219daa7066a30df77d",
"type": "0xdee9::clob_v2::OrderPlaced<0xbc3a676894871284b3ccfb2eec66f428612000e2a6e6d23f592ce8833c27c973::coin::COIN, 0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>",
"parsedJson": {
"base_asset_quantity_placed": "5000000",
"client_order_id": "1696545636947311087",
"expire_timestamp": "1696549236947",
"is_bid": true,
"order_id": "562414",
"original_quantity": "5000000",
"owner": "0xf995d6df20e18421928ff0648bd583ccdf384ab05791d8be21d32977a37dacfc",
"pool_id": "0xf0f663cf87f1eb124da2fc9be813e0ce262146f3df60bc2052d738eb41a25899",
"price": "274518000000"
},
"bcs": "4SgemkCzrqEsTHLFgMcbUtttZCf2CrEH2njjFL1rizCHzvAoYsToGrbFLffQPtGxsSt96Xr4j2SLNeLcBGKeYXDrVYWqivhf3551Mqj71DZBxq5D1Qwfgh1TKeF43Jz4b4XH1nEpkya2Pr8515vzJbHUkpP"
}

示例

订阅事件

这个例子利用了 Sui TypeScript SDK 来订阅具有 ID <PACKAGE_ID> 的包发出的事件。每次事件触发时,代码都会将响应显示在控制台上。

TypeScript

要创建事件订阅,你可以使用一个基本的 Node.js 应用程序。你需要 Sui TypeScript SDK,因此在项目的根目录中使用 npm install @mysten/sui.js 安装模块。在你的 TypeScript 代码中,从库中导入 JsonRpcProvider 和一个连接。

import { JsonRpcProvider, testnetConnection } from '@mysten/sui.js';

// Package is on Testnet.
const provider = new JsonRpcProvider(testnetConnection);
const Package = '<PACKAGE_ID>';

const MoveEventType = '<PACKAGE_ID>::<MODULE_NAME>::<METHOD_NAME>';

console.log(
await provider.getObject({
id: Package,
options: { showPreviousTransaction: true },
}),
);

let unsubscribe = await provider.subscribeEvent({
filter: { Package },
onMessage: (event) => {
console.log('subscribeEvent', JSON.stringify(event, null, 2));
},
});

process.on('SIGINT', async () => {
console.log('Interrupted...');
if (unsubscribe) {
await unsubscribe();
unsubscribe = undefined;
}
});

响应

当订阅的事件触发时,示例将显示以下事件的 JSON 表示形式。

subscribeEvent {
"id": {
"txDigest": "HkCBeBLQbpKBYXmuQeTM98zprUqaACRkjKmmtvC6MiP1",
"eventSeq": "0"
},
"packageId": "0x2d6733a32e957430324196dc5d786d7c839f3c7bbfd92b83c469448b988413b1",
"transactionModule": "coin_flip",
"sender": "0x46f184f2d68007e4344fffe603c4ccacd22f4f28c47f321826e83619dede558e",
"type": "0x2d6733a32e957430324196dc5d786d7c839f3c7bbfd92b83c469448b988413b1::coin_flip::Outcome",
"parsedJson": {
"bet_amount": "4000000000",
"game_id": "0xa7e1fb3c18a88d048b75532de219645410705fa48bfb8b13e8dbdbb7f4b9bbce",
"guess": 0,
"player_won": true
},
"bcs": "3oWWjWKRVu115bnnZphyDcJ8EyF9X4pgVguwhEtcsVpBf74B6RywQupm2X",
"timestampMs": "1687912116638"
}

Rust SDK

use futures::StreamExt;
use sui_sdk::rpc_types::SuiEventFilter;
use sui_sdk::{SuiClient, SuiClientBuilder};

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build(
"https://fullnode.devnet.sui.io:443",
).await.unwrap();
let mut subscribe_all = sui.event_api().subscribe_event(SuiEventFilter::All(vec![])).await?;
loop {
println!("{:?}", subscribe_all.next().await);
}
}

过滤事件查询

要过滤从查询返回的事件,请使用以下数据结构。目前,无法组合过滤器。

QueryDescriptionJSON-RPC Parameter Example
AllAll events{"All"}
TransactionEvents emitted from the specified transaction{"Transaction":"DGUe2TXiJdN3FI6MH1FwghYbiHw+NKu8Nh579zdFtUk="}
MoveModuleEvents emitted from the specified Move module{"MoveModule":{"package":"", "module":"nft"}}
MoveEventMove struct name of the event{"MoveEvent":"::nft::MintNFTEvent"}
EventTypeType of event described in Events section{"EventType": "NewObject"}
SenderQuery by sender address{"Sender":"0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"}
RecipientQuery by recipient{"Recipient":{"AddressOwner":"0xa3c00467938b392a12355397bdd3d319cea5c9b8f4fc9c51b46b8e15a807f030"}}
ObjectReturn events associated with the given object{"Object":"0x727b37454ab13d5c1dbb22e8741bff72b145d1e660f71b275c01f24e7860e5e5"}
TimeRangeReturn events emitted in [start_time, end_time] interval{"TimeRange":{"startTime":1669039504014, "endTime":1669039604014}}

用于订阅的事件过滤

要创建订阅,你可以设置一个过滤器,仅返回你有兴趣监听的事件集。与过滤事件查询不同,你可以组合订阅过滤器。

FilterDescriptionJSON-RPC Parameter Example
PackageMove package ID{"Package":"<PACKAGE-ID>"}
MoveModuleMove module where the event was emitted{"MoveModule": {"package": "<PACKAGE-ID>", "module": "nft"}}
MoveEventTypeMove event type defined in the move code{"MoveEventType":"<PACKAGE-ID>::nft::MintNFTEvent"}
MoveEventModuleMove event module defined in the move code{"MoveEventModule": {"package": "<PACKAGE-ID>", "module": "nft", "event": "MintNFTEvent"}}
MoveEventFieldFilter using the data fields in the move event object{"MoveEventField":{ "path":"/name", "value":"NFT"}}
SenderAddressAddress that started the transaction{"SenderAddress": "0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"}
SenderSender address{"Sender":"0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"}
TransactionTransaction hash{"Transaction":"ENmjG42TE4GyqYb1fGNwJe7oxBbbXWCdNfRiQhCNLBJQ"}
TimeRangeTime range in millisecond{"TimeRange": {"start_time": "1685959791871", "end_time": "1685959791871"}}