使用事件
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);
}
}
过滤事件查询
要过滤从查询返回的事件,请使用以下数据结构。目前,无法组合过滤器。
Query | Description | JSON-RPC Parameter Example |
---|---|---|
All | All events | {"All"} |
Transaction | Events emitted from the specified transaction | {"Transaction":"DGUe2TXiJdN3FI6MH1FwghYbiHw+NKu8Nh579zdFtUk="} |
MoveModule | Events emitted from the specified Move module | {"MoveModule":{"package":"", "module":"nft"}} |
MoveEvent | Move struct name of the event | {"MoveEvent":"::nft::MintNFTEvent"} |
EventType | Type of event described in Events section | {"EventType": "NewObject"} |
Sender | Query by sender address | {"Sender":"0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"} |
Recipient | Query by recipient | {"Recipient":{"AddressOwner":"0xa3c00467938b392a12355397bdd3d319cea5c9b8f4fc9c51b46b8e15a807f030"}} |
Object | Return events associated with the given object | {"Object":"0x727b37454ab13d5c1dbb22e8741bff72b145d1e660f71b275c01f24e7860e5e5"} |
TimeRange | Return events emitted in [start_time, end_time] interval | {"TimeRange":{"startTime":1669039504014, "endTime":1669039604014}} |
用于订阅的事件过滤
要创建订阅,你可以设置一个过滤器,仅返回你有兴趣监听的事件集。与过滤事件查询不同,你可以组合订阅过滤器。
Filter | Description | JSON-RPC Parameter Example |
---|---|---|
Package | Move package ID | {"Package":"<PACKAGE-ID>"} |
MoveModule | Move module where the event was emitted | {"MoveModule": {"package": "<PACKAGE-ID>", "module": "nft"}} |
MoveEventType | Move event type defined in the move code | {"MoveEventType":"<PACKAGE-ID>::nft::MintNFTEvent"} |
MoveEventModule | Move event module defined in the move code | {"MoveEventModule": {"package": "<PACKAGE-ID>", "module": "nft", "event": "MintNFTEvent"}} |
MoveEventField | Filter using the data fields in the move event object | {"MoveEventField":{ "path":"/name", "value":"NFT"}} |
SenderAddress | Address that started the transaction | {"SenderAddress": "0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"} |
Sender | Sender address | {"Sender":"0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"} |
Transaction | Transaction hash | {"Transaction":"ENmjG42TE4GyqYb1fGNwJe7oxBbbXWCdNfRiQhCNLBJQ"} |
TimeRange | Time range in millisecond | {"TimeRange": {"start_time": "1685959791871", "end_time": "1685959791871"}} |