Skip to main content

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 packageIds 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.

info

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": _.

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":"<PACKAGE-ID>", "module":"nft"}}
MoveEventModuleEvents emitted, defined on the specified Move module.{"MoveEventModule": {"package": "<DEFINING-PACKAGE-ID>", "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}}

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.

info

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": _.

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"}}