Using Events
The Sui network stores countless objects on chain where Move code can perform actions using those objects. Tracking this activity is often desired, for example, to discover how many times a module mints an NFT or to tally the amount of SUI in transactions that a smart contract generates.
To support activity monitoring, Move provides a structure to emit events on the Sui network. When you establish a connection with the Sui network, you create a two-way interactive communication session between your client and a Sui network node. With an open session, you can subscribe to specific events that the Sui network adds to the stream to create real-time monitoring of events.
Move event structure
An event object in Sui consists of the following attributes:
id
: JSON object containing the transaction digest ID and event sequence.packageId
: The object ID of the package that emits the event.transactionModule
: The module that performs the transaction.sender
: The Sui network address that triggered the event.type
: The type of event being emitted.parsedJson
: JSON object describing the event.bcs
: Binary canonical serialization value.timestampMs
: Unix epoch timestamp in milliseconds.
Discovering events
If you want to subscribe to events on chain, you first need to know what events are available. You typically know or can discover the events your own code emits, but it's not as straightforward when you need to subscribe to on-chain events from packages you don't own. The Sui RPC provides a queryEvents method to query on-chain packages and return available events that you can subscribe to.
Filter events
You can filter the events your code targets for either querying or subscribing. Both filter options are similar but have some differences.
Emit events in Move
To create an event in your Move modules, add the sui::event
dependency.
use sui::event;
With the dependency added, you can use the emit
function to fire an event whenever the action you want to monitor fires. For example, the following code is part of an example application using digital donuts. The collect_profits
function handles the collection of SUI and emits an event whenever the function is called. To act on that event, you need to subscribe to it.
/// 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));
}
Subscribe to events in Move
Firing events is not very useful in a vacuum. You also need the ability to listen for those events so that you can act on them. In Sui, you subscribe to those events and provide logic that triggers when the event fires.
Sui Full nodes support subscribe functionality using JSON-RPC notifications transmitted through the WebSocket API. You can interact with the RPC directly (suix_subscribeEvent, suix_subscribeTransaction) or you can use an SDK like the Sui TypeScript SDK. The following excerpt from one of the examples uses the TypeScript SDK to create an asynchronous subscription to the filter identified in the filter.
let unsubscribe = await provider.subscribeEvent({
filter: { <PACKAGE_ID> },
onMessage: (event) => {
console.log("subscribeEvent", JSON.stringify(event, null, 2))
}
});
Move smart contracts can call other smart contracts that emit events. For example, Deepbook_utils
can call the dee9
smart contract and emit this event. Note that using package, transaction module to query for dee9/clob_v2
misses the following event even though it is actually an event the dee9
package emits. The current workaround for this issue is to know all the packageId
s you care about and search those in the queryEvent
call.
{
"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"
}
{
"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"
}
Examples
Subscribe to event
This example leverages the Sui TypeScript SDK to subscribe to events the package with ID <PACKAGE_ID>
emits. Each time the event fires, the code displays the response to the console.
TypeScript
To create the event subscription, you can use a basic Node.js app. You need the Sui TypeScript SDK, so install the module using npm install @mysten/sui
at the root of your project. In your TypeScript code, import JsonRpcProvider
and a connection from the library.
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
// Package is on Testnet.
const client = new SuiClient({
url: getFullnodeUrl('testnet'),
});
const Package = '<PACKAGE_ID>';
const MoveEventType = '<PACKAGE_ID>::<MODULE_NAME>::<METHOD_NAME>';
console.log(
await client.getObject({
id: Package,
options: { showPreviousTransaction: true },
}),
);
let unsubscribe = await client.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;
}
});
Response
When the subscribed to event fires, the example displays the following JSON representation of the event.
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::EventFilter;
use sui_sdk::SuiClientBuilder;
use anyhow::Result;
#[tokio::main]
async fn main() -> Result<()> {
let sui = SuiClientBuilder::default()
.ws_url("wss://fullnode.mainnet.sui.io:443")
.build("https://fullnode.mainnet.sui.io:443")
.await.unwrap();
let mut subscribe_all = sui.event_api().subscribe_event(EventFilter::All(vec![])).await?;
loop {
println!("{:?}", subscribe_all.next().await);
}
}
Filtering event queries
To filter the events returned from your queries, use the following data structures.
This set of filters applies only to event querying APIs. It differs from the filters offered for the subscriptions API (see following section). In particular, it does not support combinations like "All": [...]
, "Any": [...]
, "And": [_, _]
, "Or": [_, _]
, and "Not": _
.
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":"<PACKAGE-ID>", "module":"nft"}} |
MoveEventModule | Events emitted, defined on the specified Move module. | {"MoveEventModule": {"package": "<DEFINING-PACKAGE-ID>", "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}} |
Filtering events for subscription
To create a subscription, you can set a filter to return only the set of events you're interested in listening for.
This set of filters applies only to event subscription APIs. It differs from the filters offered for the query API (see previous section). In particular, it supports combinations like "All": [...]
, "Any": [...]
, "And": [_, _]
, "Or": [_, _]
, and "Not": _
.
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"}} |