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:
public fun lock<T: key + store>(
obj: T,
ctx: &mut TxContext,
): (Locked<T>, Key) {
let key = Key { id: object::new(ctx) };
let mut lock = Locked {
id: object::new(ctx),
key: object::id(&key),
};
event::emit(LockCreated {
lock_id: object::id(&lock),
key_id: object::id(&key),
creator: ctx.sender(),
item_id: object::id(&obj)
});
// Adds the `object` as a DOF for the `lock` object
dof::add(&mut lock.id, LockedObjectKey {}, obj);
(lock, key)
}
Querying Events
- IOTA RPC
- Rust
- GraphQL
The IOTA RPC provides a queryEvents
method to query on-chain packages and return available events. As an example, the following curl
command queries the Deepbook package on Mainnet for a specific type of event:
curl -X POST https://api.testnet.iota.cafe:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "iotax_queryEvents",
"params": [
{
"MoveModule": {
"package": "0x0000000000000000000000000000000000000000000000000000000000000002",
"module": "display",
"type": "0x0000…0002::display::Display<0xba68…286b::testnet_nft::TestnetNFT>"
}
},
null,
3,
false
]
}'
The TypeScript SDK provides a wrapper for the iotax_queryEvents
method:
client.queryEvents
.
You can use the following as an example on how to query for events using the query_events
function. You should update
the PACKAGE_ID_CONST
with a package of your choice.
use iota_sdk::{rpc_types::EventFilter, types::Identifier, IotaClientBuilder};
const PACKAGE_ID_CONST: &str = "";
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let iota_testnet = IotaClientBuilder::default()
.build("https://api.testnet.iota.cafe:443")
.await?;
let events = iota_testnet
.event_api()
.query_events(
EventFilter::MoveModule {
package: PACKAGE_ID_CONST.parse()?,
module: Identifier::new("dev_trophy")?,
},
None,
None,
false,
)
.await?;
for event in events.data {
println!("Event: {:?}", event.parsed_json);
}
Ok(())
}
You can use GraphQL to query events instead of JSON RPC. The following example queries are in the
iota-graphql-rpc
crate in the IOTA repo.
Event Connection
{
events(
filter: {
eventType: "0x3164fcf73eb6b41ff3d2129346141bd68469964c2d95a5b1533e8d16e6ea6e13::Market::ChangePriceEvent<0x2::iota::IOTA>"
}
) {
nodes {
sendingModule {
name
package { digest }
}
type {
repr
}
sender {
address
}
timestamp
json
bcs
}
}
}
Filter Events By Sender
query ByTxSender {
events(
first: 1
filter: {
sender: "0xdff57c401e125a7e0e06606380560b459a179aacd08ed396d0162d57dbbdadfb"
}
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
sendingModule {
name
}
type {
repr
}
sender {
address
}
timestamp
json
bcs
}
}
}
Filter Events By Emitting Package and Type
query ByTxSender {
events(
first: 1
filter: {
sender: "0xdff57c401e125a7e0e06606380560b459a179aacd08ed396d0162d57dbbdadfb"
}
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
sendingModule {
name
}
type {
repr
}
sender {
address
}
timestamp
json
bcs
}
}
}
The TypeScript SDK provides functionality to interact with the IOTA GraphQL service.
Subscribing to Events
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
- TypeScript
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);
}
}
To create the event subscription, you can use a basic Node.js app. You need the IOTA TypeScript SDK, so install the module using npm install @iota/iota-sdk
at the root of your project. In your TypeScript code, import JsonRpcProvider
and a connection from the library.
import { getFullnodeUrl, IotaClient } from '@iota/sdk/client';
// Package is on Testnet.
const client = new IotaClient({
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"
}
Monitoring Events
Firing events is not very useful in a vacuum. You also need the ability to respond to those events. There are two methods from which to choose when you need to monitor on-chain events:
- Incorporate a custom indexer to take advantage of IOTA's micro-data ingestion framework.
- Poll the IOTA network on a schedule to query events.
Using a custom indexer provides a near-real time monitoring of events, so is most useful when your project requires immediate reaction to the firing of events. Polling the network is most useful when the events you're monitoring don't fire often or the need to act on those events are not immediate. The following section provides a polling example.
Filtering Events
You can filter events when querying or subscribing to receive only the events you are interested in.
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:
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
When subscribing to events, you can combine filters for more precise results:
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"}} |