Skip to main content

Subscribing to On-Chain Events in IOTA

Monitoring on-chain activity is essential for understanding and reacting to actions performed by smart contracts on the IOTA network. By subscribing to events emitted by Move packages, you can track activities such as NFT minting or IOTA transactions in real-time. This guide will show you how to emit events in Move and subscribe to them using the IOTA network.

Understanding Events in IOTA

Events in IOTA provide a structured way to capture and broadcast on-chain activities. Each event contains specific attributes that offer detailed information about what occurred.

Event Structure

An event object in IOTA 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 IOTA 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.

Exploring Available Events

To subscribe to on-chain events, you first need to identify which events are available. While you can easily track events emitted by your own code, discovering events from external packages can be more challenging. The IOTA RPC provides the queryEvents method, which allows you to query on-chain packages and obtain a list of events you can subscribe to.

Applying Event Filters

When targeting specific events for querying or subscribing, you can use filters to refine your results. Although the filtering options for querying and subscribing are similar, there are notable differences to be aware of.

Emitting Events in Move

To emit events from your Move modules, you need to use the iota::event module. Emitting events allows external applications to subscribe and respond to specific on-chain activities.

First, import the event module in your Move code:

use iota::event;

Then, within your function, you can emit an event using the emit function. For example:

/// 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));
}

Subscribing to Events

To react to events emitted by Move modules, you need to subscribe to them. IOTA full nodes support event subscriptions via JSON-RPC notifications over WebSocket. You can interact with the [RPC directly]iotax_subscribeEvent, iotax_subscribeTransaction or use an SDK like the IOTA 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, 0x107a::nft calls the 0x2::display::new_with_fields smart contract and emits a 0x2::display::DisplayCreated event. Note that using package and transaction module to query for 0x2::display misses the following event even though it is actually an event the 0x2 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": "DrZmtQDDCUKooLzFCi29VhUB4w6AT8phCsT9d62BAf8g",
"eventSeq": "0"
},
"packageId": "0x000000000000000000000000000000000000000000000000000000000000107a",
"transactionModule": "nft",
"sender": "0x0000000000000000000000000000000000000000000000000000000000000000",
"type": "0x2::display::DisplayCreated<0x107a::nft::Nft>",
"parsedJson": {
"id": "0xa12d72c159d57d4c7f540b2b9e985327628d856b20c1d6cdfd3028a2a605abfe"
},
"bcs": "CFbAeqXAwwkyTxUD36FtzTGEcMGrVj4zgcTR1G7AaRjb",
"timestampMs": "1521456213521"
}

Examples

Subscribe to Event

This example leverages the IOTA 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.

Rust

See Rust SDK.

use futures::StreamExt;
use iota_sdk::rpc_types::EventFilter;
use iota_sdk::IOTAClientBuilder;
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
let iota = IOTAClientBuilder::default()
.ws_url("wss://api.testnet.iota.cafe:443")
.build("https://api.testnet.iota.cafe:443")
.await.unwrap();
let mut subscribe_all = iota.event_api().subscribe_event(EventFilter::All(vec![])).await?;
loop {
println!("{:?}", subscribe_all.next().await);
}
}

Filtering Events

You can filter events when querying or subscribing to receive only the events you are interested in.

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

Filtering Event Queries

When querying events, use the following filters:

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

When subscribing to events, you can combine filters for more precise results:

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

Question 1/2

What is the main purpose of emitting events in IOTA's Move framework?