IOTA
IOTA is a decentralized blockchain infrastructure featuring a Move-based Layer 1 protocol and an Ethereum Virtual Machine (EVM) Layer 2, designed for real-world asset tokenization, digital identity, and enterprise use cases with exceptional scalability and energy efficiency.
Core Concepts
IOTA integration leverages Move-based concepts similar to Sui, optimized for IOTA's dual-layer architecture supporting both Move smart contracts on L1 and EVM compatibility on L2. This enables diverse use cases including real-world asset tokenization, supply chain tracking, and DeFi applications.
Processors
IOTA integration offers several processor types, listed in order of common usage:
User-Defined Processors
Generate type-safe processors from your smart contract ABIs using Sentio's code generation tools. These provide the best developer experience with full type safety and auto-completion.
// Example: Generated from your custom DEX contract
import { dex } from './types/iota/0xYourPackage.dex'
dex.bind({
network: IotaNetwork.MAIN_NET,
startCheckpoint: 1000000n
}).onEventSwapEvent(async (evt, ctx) => {
// Fully typed event data
const { amount_in, amount_out, user } = evt.data_decoded
// Process swap...
})Built-in System Processors
Sentio SDK pre-generates type-safe processors from IOTA's core system modules, providing ready-to-use bindings for system-level monitoring:
// Validator staking system
import { validator, validator_set } from '@sentio/sdk/iota/builtin/0x3'
// IOTA system module
import { iota_system } from '@sentio/sdk/iota/builtin/0x3'These processors are generated by Sentio SDK from IOTA's system packages and provide fully-typed interfaces for monitoring staking, validator operations, epoch changes, and other system activities.
IotaObjectProcessor
Monitors a specific object by ID and its dynamic fields.
- Use Case: Track state of a specific pool, NFT, or singleton object
- Binding:
IotaObjectProcessor.bind({ objectId: '0x...', network: IotaNetwork.MAIN_NET, startCheckpoint: 1000000n })
IotaObjectTypeProcessor
Monitors ALL objects of a specific Move type across the network.
- Use Case: Track all NFTs in a collection, all pools of a DEX
- Binding:
IotaObjectTypeProcessor.bind({ objectType: '0x...::nft::Token', network: IotaNetwork.MAIN_NET }) - Supports generic type
Twith@typemove/iotafor type-safe decoding
IotaAddressProcessor
Monitors all objects owned by an address and transactions sent to it.
- Use Case: Portfolio tracking, wallet monitoring, treasury management
- Binding:
IotaAddressProcessor.bind({ address: '0x...', network: IotaNetwork.MAIN_NET, startCheckpoint: 1000000n })
IotaModulesProcessor
Binds to a package address for manual event/call processing.
- Note: Consider using code generation for type-safe processors instead
- Binding:
IotaModulesProcessor.bind({ address: '0x...', network: IotaNetwork.MAIN_NET }) - Advanced Use: Rarely needed as user-defined processors handle most use cases better
IotaGlobalProcessor
Processes all transactions network-wide with optional filtering.
- Warning: Can be resource-intensive; use specific processors when possible
- Binding:
IotaGlobalProcessor.bind({ network: IotaNetwork.MAIN_NET }) - Advanced Use: Only for chain-wide analytics requiring global transaction monitoring
Handlers
Handlers vary depending on the processor type:
-
IotaModulesProcessor/IotaGlobalProcessor:onMoveEvent(handler(event, ctx), filter): Triggered when a specific Move event matching thefilter(type string) is emitted.onEntryFunctionCall(handler(call, ctx), filter): Triggered when an entry function matching thefilter(e.g.,0x...::module::function_name) is called.onTransactionBlock(handler(tx, ctx), filter?): Triggered for transaction blocks matching the filter (e.g., involving specific addresses).onObjectChange(handler(changes, ctx), typeFilter)(IotaGlobalProcessoronly): Triggered when objects matching thetypeFilterare changed (created, mutated, deleted) within any transaction block.
-
IotaAddressProcessor/IotaObjectProcessor/IotaObjectTypeProcessor:onTimeInterval(handler(objects | self, dynamicFields, ctx), intervalMinutes?, backfillIntervalMinutes?, type?, fetchConfig?): Periodically fetches and processes objects based on time intervals.IotaAddressProcessor:handler(objects: IotaMoveObject[], ctx: IotaAddressContext)IotaObjectProcessor:handler(self: IotaMoveObject, dynamicFields: IotaMoveObject[], ctx: IotaObjectContext)IotaObjectTypeProcessor:handler(self: TypedIotaMoveObject<T>, dynamicFields: IotaMoveObject[], ctx: IotaObjectContext)
onCheckpointInterval(handler(...), interval?, backfillInterval?, type?, fetchConfig?): Similar toonTimeIntervalbut based on checkpoint intervals.onTransactionBlock(handler(tx, ctx), filter?)(IotaAddressProcessoronly): Handles transaction blocks sent to the bound address.onObjectChange(handler(changes, ctx))(IotaObjectTypeProcessoronly): Processes changes specific to the bound object type.
Context (ctx)
ctx)Handlers receive a context object specific to the processor and handler type (IotaContext, IotaAddressContext, IotaObjectContext, IotaObjectChangeContext) providing:
- Chain information:
network,checkpoint. - Source details:
address(package or account),moduleName,objectId. - Transaction/Event details:
transaction,eventIndex,timestamp. - Helper methods: IOTA
client(ctx.client) for interacting with the RPC,coderfor decoding Move data. - Standard SDK outputs:
ctx.meter.Counter('...'),ctx.eventLogger.emit('...'),ctx.exporter.iota_Object(...).
Fetch Configuration (fetchConfig)
fetchConfig)- Transaction-based handlers (
onMoveEvent,onEntryFunctionCall,onTransactionBlock) supportMoveFetchConfigto includeresourceChanges,allEvents, orinputs.
interface MoveFetchConfig {
allEvents: boolean; // Fetch all events for the transaction
includeFailedTransaction?: boolean; // Include failed transactions
inputs: boolean; // Fetch transaction input arguments
resourceChanges: boolean; // Fetch resource changes
resourceConfig?: ResourceConfig; // Specific configuration for resource fetching
supportMultisigFunc?: boolean; // Support for multisig functions
}- Interval-based handlers (
onTimeInterval,onCheckpointInterval) supportMoveAccountFetchConfig(mainlyowned: true/false).
interface MoveAccountFetchConfig {
owned: boolean;
}Getting Started Example (Monitoring Validator Metrics)
First, create an IOTA processor project: yarn sentio create -c iota <project-name>. You can refer to the CLI Reference to learn details.
import { validator, validator_set } from '@sentio/sdk/iota/builtin/0x3'
import { IotaNetwork } from '@sentio/sdk/iota'
/**
* IOTA Validator Metrics Processor
*
* This processor tracks validator staking activities, rewards, and performance metrics.
* It captures:
* - Staking/unstaking events from delegators
* - Validator epoch rewards and performance scores
* - Real-time exchange rates for calculating delegator returns
*/
// Track when delegators stake tokens with a validator
validator.bind({ network: IotaNetwork.MAIN_NET }).onEventStakingRequestEvent(
async (evt, ctx) => {
// Extract staking details
const validator_address = evt.data_decoded.validator_address
const delegator_address = evt.data_decoded.staker_address
const amount = evt.data_decoded.amount.scaleDown(9) // Convert from 9 decimals to human-readable
// Log staking event for tracking delegator positions
ctx.eventLogger.emit('stake_action', {
action: 'stake',
amount, // Positive amount for staking
pool: evt.data_decoded.pool_id,
validator: validator_address,
delegator: delegator_address
})
},
{ allEvents: true }
)
// Track when delegators unstake tokens from a validator
validator.bind({ network: IotaNetwork.MAIN_NET }).onEventUnstakingRequestEvent(
async (evt, ctx) => {
// Extract unstaking details
const validator_address = evt.data_decoded.validator_address
const delegator_address = evt.data_decoded.staker_address
const principal = evt.data_decoded.principal_amount.scaleDown(9) // Original staked amount
const reward = evt.data_decoded.reward_amount.scaleDown(9) // Rewards earned
const total_amount = principal.plus(reward) // Total withdrawal
// Log unstaking event with negative principal to calculate net positions
ctx.eventLogger.emit('stake_action', {
action: 'unstake',
amount: -principal, // Negative principal for net position calculation
reward: reward, // Rewards earned (always positive)
total_withdrawn: total_amount, // Total amount returned to delegator,
pool: evt.data_decoded.pool_id,
validator: validator_address,
delegator: delegator_address,
stake_activation_epoch: evt.data_decoded.stake_activation_epoch.toString(), // When stake was activated
unstaking_epoch: evt.data_decoded.unstaking_epoch.toString() // When unstaking was requested
})
},
{ allEvents: true }
)
/**
* Track validator performance metrics at each epoch
* This event fires at the end of each epoch (approximately every 24 hours)
* and provides comprehensive validator statistics including:
* - Total stake and voting power
* - Rewards earned during the epoch
* - Pool token exchange rate (for calculating delegator returns)
* - Performance score (tallying rule global score)
*/
validator_set.bind({ network: IotaNetwork.MAIN_NET }).onEventValidatorEpochInfoEventV1(
async (evt, ctx) => {
try {
// Extract validator performance metrics
const validator_address = evt.data_decoded.validator_address
const epoch = evt.data_decoded.epoch
const stake = evt.data_decoded.stake.scaleDown(9) // Total stake with validator
const voting_power = evt.data_decoded.voting_power // Consensus voting weight
const commission_rate = evt.data_decoded.commission_rate // Fee charged to delegators (basis points)
const pool_staking_reward = evt.data_decoded.pool_staking_reward.scaleDown(9) // Rewards earned this epoch
const pool_token_exchange_rate = evt.data_decoded.pool_token_exchange_rate // Exchange rate for pool tokens
const reference_gas_survey_quote = evt.data_decoded.reference_gas_survey_quote // Gas price reference
const tallying_rule_global_score = evt.data_decoded.tallying_rule_global_score // Validator performance score
// Calculate exchange rate for pool tokens to IOTA
// This rate increases over time as rewards accumulate
const exchange_rate = Number((pool_token_exchange_rate as any).numerator || 1) / Number((pool_token_exchange_rate as any).denominator || 1)
// Record metrics for dashboard visualization
ctx.meter.Gauge('validator_stake').record(stake, { validator: validator_address }) // Total staked amount
ctx.meter.Gauge('validator_voting_power').record(voting_power, { validator: validator_address }) // Network influence
ctx.meter.Gauge('validator_epoch_rewards').record(pool_staking_reward, { validator: validator_address }) // Epoch rewards
ctx.meter.Gauge('validator_commission').record(commission_rate, { validator: validator_address }) // Commission rate
ctx.meter.Gauge('validator_exchange_rate').record(exchange_rate, { validator: validator_address }) // Pool token value
ctx.meter.Gauge('validator_performance_score').record(tallying_rule_global_score, { validator: validator_address }) // Performance metric
// Log comprehensive epoch snapshot for historical analysis
ctx.eventLogger.emit('validator_epoch_info', {
validator: validator_address,
epoch: epoch.toString(),
stake,
voting_power: voting_power.toString(),
commission_rate,
pool_staking_reward,
exchange_rate,
reference_gas_survey_quote: reference_gas_survey_quote.toString(),
tallying_rule_global_score: tallying_rule_global_score.toString()
})
} catch (error) {
console.error('Error processing validator epoch info:', error)
}
},
{ allEvents: true }
)Best Practices
-
Start Checkpoint: Always specify a
startCheckpointto avoid processing the entire chain history unnecessarily. -
Scaling Decimals: IOTA uses 9 decimal places. Use
.scaleDown(9)to convert from smallest units to human-readable values. -
Error Handling: Wrap handler logic in try-catch blocks to prevent processor crashes from unexpected data.
-
Efficient Filtering: Use specific filters in handlers to reduce processing overhead:
.onMoveEvent(handler, '0x3::validator::StakingRequestEvent') -
Resource Management: Use
fetchConfigjudiciously - only fetch what you need (events, inputs, resource changes).
Resources
Updated 15 days ago