Sui

Sui is a Layer 1 blockchain also utilizing the Move language, but with a distinct object-centric model.

Core Concepts

Sui integration utilizes Move-based concepts similar to Aptos but adapted for Sui's unique architecture, especially its object model.

Processors

Sui 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/sui/0xYourPackage.dex'

dex.bind({
  network: SuiNetwork.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 Sui's core system modules, providing ready-to-use bindings for system-level monitoring:

// SUI coin module
import { sui } from '@sentio/sdk/sui/builtin/0x2'

// Staking system
import { staking_pool } from '@sentio/sdk/sui/builtin/0x3'

// Validator system
import { validator_set } from '@sentio/sdk/sui/builtin/0x3'

These processors are generated by Sentio SDK from Sui's system packages and provide fully-typed interfaces for monitoring token transfers, staking operations, validator activities, and other system functions.

SuiObjectProcessor

Monitors a specific object by ID and its dynamic fields.

  • Use Case: Track state of a specific pool, NFT, or singleton object
  • Binding: SuiObjectProcessor.bind({ objectId: '0x...', network: SuiNetwork.MAIN_NET, startCheckpoint: 1000000n })

SuiObjectTypeProcessor

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: SuiObjectTypeProcessor.bind({ objectType: '0x...::nft::Token', network: SuiNetwork.MAIN_NET })
  • Supports generic type T with @typemove/sui for type-safe decoding

SuiAddressProcessor

Monitors all objects owned by an address and transactions sent to it.

  • Use Case: Portfolio tracking, wallet monitoring, treasury management
  • Binding: SuiAddressProcessor.bind({ address: '0x...', network: SuiNetwork.MAIN_NET, startCheckpoint: 1000000n })

SuiModulesProcessor

Binds to a package address for manual event/call processing.

  • Note: Consider using code generation for type-safe processors instead
  • Binding: SuiModulesProcessor.bind({ address: '0x...', network: SuiNetwork.MAIN_NET })
  • Advanced Use: Rarely needed as user-defined processors handle most use cases better

SuiGlobalProcessor

Processes all transactions network-wide with optional filtering.

  • Warning: Can be resource-intensive; use specific processors when possible
  • Binding: SuiGlobalProcessor.bind({ network: SuiNetwork.MAIN_NET })
  • Advanced Use: Only for chain-wide analytics requiring global transaction monitoring

Handlers

Handlers vary depending on the processor type:

  • SuiModulesProcessor/SuiGlobalProcessor:

    • onMoveEvent(handler(event, ctx), filter): Triggered when a specific Move event matching the filter (type string) is emitted.
    • onEntryFunctionCall(handler(call, ctx), filter): Triggered when an entry function matching the filter (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) (SuiGlobalProcessor only): Triggered when objects matching the typeFilter are changed (created, mutated, deleted) within any transaction block.
  • SuiAddressProcessor/SuiObjectProcessor / SuiObjectTypeProcessor:

    • onTimeInterval(handler(objects | self, dynamicFields, ctx), intervalMinutes?, backfillIntervalMinutes?, type?, fetchConfig?): Periodically fetches and processes objects based on time intervals.
      • SuiAddressProcessor: handler(objects: SuiMoveObject[], ctx: SuiAddressContext)
      • SuiObjectProcessor: handler(self: SuiMoveObject, dynamicFields: SuiMoveObject[], ctx: SuiObjectContext)
      • SuiObjectTypeProcessor: handler(self: TypedSuiMoveObject<T>, dynamicFields: SuiMoveObject[], ctx: SuiObjectContext)
    • onCheckpointInterval(handler(...), interval?, backfillInterval?, type?, fetchConfig?): Similar to onTimeInterval but based on checkpoint intervals.
    • onTransactionBlock(handler(tx, ctx), filter?) (SuiAddressProcessor only): Handles transaction blocks sent to the bound address.
    • onObjectChange(handler(changes, ctx)) (SuiObjectTypeProcessor only): Processes changes specific to the bound object type.

Context (ctx)

Handlers receive a context object specific to the processor and handler type (SuiContext, SuiAddressContext, SuiObjectContext, SuiObjectChangeContext) providing:

  • Chain information: network, checkpoint.
  • Source details: address (package or account), moduleName, objectId.
  • Transaction/Event details: transaction, eventIndex, timestamp.
  • Helper methods: Sui client (ctx.client) for interacting with the RPC, coder for decoding Move data.
  • Standard SDK outputs: ctx.meter.Counter('...'), ctx.eventLogger.emit('...'), ctx.exporter.sui_Object(...).

Fetch Configuration (fetchConfig)

  • Transaction-based handlers (onMoveEvent, onEntryFunctionCall, onTransactionBlock) support MoveFetchConfig (similar to Aptos) to include resourceChanges, allEvents, or inputs.
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) support MoveAccountFetchConfig (mainly owned: true/false).
interface MoveAccountFetchConfig {
    owned: boolean;
}

Getting Started Example (Processing Events)

First, create a Sui processor project: yarn sentio create -c sui <project-name>. You can refer to the CLI Reference to learn details.

import { SuiModulesProcessor, SuiContext, SuiNetwork } from "@sentio/sdk/sui";
import { SuiEvent } from '@mysten/sui/client';

const PACKAGE_ADDRESS = "0xdee9...package_id"; // Replace with your package ID

// Define the expected structure of the event based on your Move contract
interface MyCustomEvent {
  field1: string;
  amount: bigint;
  user: string;
}

SuiModulesProcessor.bind({
  address: PACKAGE_ADDRESS,
  network: SuiNetwork.MAIN_NET,
  startCheckpoint: 25000000n
})
.onMoveEvent(async (event: SuiEvent, ctx: SuiContext) => {
  // Check if the event type matches what we expect
  if (event.type === `${PACKAGE_ADDRESS}::my_module::MyCustomEvent`) {
    // Type cast the parsedJson for easier access, assuming it matches MyCustomEvent
    const decodedEvent = event.parsedJson as MyCustomEvent;

    ctx.meter.Counter("my_custom_event_cnt").add(1);
    ctx.meter.Counter("my_custom_event_amount_total").add(decodedEvent.amount);

    ctx.eventLogger.emit("MyCustomEventFired", {
      distinctId: decodedEvent.user,
      field1Value: decodedEvent.field1,
      eventAmount: decodedEvent.amount.toString(), // Convert bigint to string for logging
      suiTxDigest: ctx.transaction?.digest
    });
  }
}, { type: `${PACKAGE_ADDRESS}::my_module::MyCustomEvent` }); // Filter specifically for this event type
import { SuiObjectTypeProcessor, SuiNetwork, TypedSuiMoveObject } from "@sentio/sdk/sui";
import { TypeDescriptor } from "@typemove/move";

// Define the expected structure of your Move object based on your contract
interface MyCustomObject {
  id: { id: string };
  field1: string;
  amount: bigint;
  owner: string;
  metadata?: {
    name: string;
    description: string;
  };
}

const OBJECT_TYPE = "0x1234...::my_module::MyCustomObject"; // Replace with your object type

SuiObjectTypeProcessor.bind({
  objectType: new TypeDescriptor(OBJECT_TYPE),
  network: SuiNetwork.MAIN_NET,
  startCheckpoint: 25000000n
})
.onTimeInterval(async (self: TypedSuiMoveObject<MyCustomObject>, dynamicFields, ctx) => {
  // Access decoded object fields here
  const fields = self.data_decoded;

  // Access generic type arguments if your object has them
  const typeArg0 = self.type_arguments[0];
  const typeArg1 = self.type_arguments[1];

  ctx.meter.Counter("my_custom_object_cnt").add(1);
  ctx.meter.Counter("my_custom_object_amount_total").add(fields.amount);

  ctx.eventLogger.emit("MyCustomObjectSnapshot", {
    distinctId: "system", // Use system for timed intervals
    objectId: fields.id?.id || "unknown",
    field1Value: fields.field1,
    objectAmount: fields.amount.toString(), // Convert bigint to string for logging
    owner: fields.owner,
    metadata: JSON.stringify(fields.metadata || {}),
    typeArgument0: typeArg0 ? `0x${typeArg0}` : "none",
    typeArgument1: typeArg1 ? `0x${typeArg1}` : "none",
    checkpoint: ctx.checkpoint.toString()
  });
}, undefined, 60000); // Run every 60 seconds