SUI

Create project

You can create a new project with the following command:

npx @sentio/cli@latest create <your project name> --chain-type sui

Setup contract address

Use sentio add to add contracts and fetch ABIs.

npx @sentio/cli@latest add --chain sui_mainnet [--name <your contract name>]  <your contract address>

Or direct modify the in sentio.yamlfile:

project: your-project-name
contracts:
  - name: <contract name>  # (optional)
    address: <contract address>  # sui contract address
    chain: sui_mainnet # Chain id, sui_mainnet or sui_testnet

Contract name is optional but will make file generated more readable.

Type Generation

Run

yarn sentio gen

Will generate all types bindings for the added contracts.

Navigate to the project directory, where you should find the following files:

β”œβ”€β”€ package.json
β”œβ”€β”€ sentio.yaml
β”œβ”€β”€ abis/sui
β”‚    └──  0x305fdc899f4d5d13a1e03ea784eed9bc5bdcb3e3550a32466ff34518aa4627a3.json [or <contract name>.json]
β”œβ”€β”€ src
β”‚    β”œβ”€β”€ types/sui
β”‚.   β”‚    β”œβ”€β”€ 0x305fdc899f4d5d13a1e03ea784eed9bc5bdcb3e3550a32466ff34518aa4627a3.ts [or <contract name>.ts]
β”‚.   β”‚    └── index.ts
β”‚    β”œβ”€β”€ processor.test.ts
β”‚    └── processor.ts
└── tsconfig.json

Processor

Start your processor by importing the generated code and binding the processor to the contract address.

Event processor

You should find onEventXXX methods to capture events emitted by any transactions.

import { bluemove_launchpad } from "./types/sui/0x305fdc899f4d5d13a1e03ea784eed9bc5bdcb3e3550a32466ff34518aa4627a3.js";

bluemove_launchpad.bind({
  startCheckpoint: 1500000n
})
  .onEventMintNFTEvent(async (event, ctx) => {
    ctx.eventLogger.emit("MintEvent", {
      distinctId: event.data_decoded.creator,
      name: event.data_decoded.name,
      object_id: event.data_decoded.object_id,
      project: "bluemove"
    })
  })

You can access the event content by event, and SUI transaction byctx.transaction. Not all fields in ctx.transaction are fetched by default to increase the performance, to get more fields likeobjectChanges, add fetch config. All options can be found at MoveFetchConfig

  .onEventMintNFTEvent(async (event, ctx) => {
    ...
    },
    { resourceChanges: true }
  })

Object processor

If you want to watch single object content change, you could SuiObjectProcessor , e.g.

SuiObjectProcessor.bind({
  objectId: '0xa14f85860d6ce99154ecbb13570ba5fba1d8dc16b290de13f036b016fd19a29c',
  startCheckpoint: 10000
})
  .onTimeInterval(async (self, objects, ctx) => {
    const fields = await ctx.coder.getDynamicFields(
      objects,
      BUILTIN_TYPES.U64_TYPE,
      single_collateral.PortfolioVault.type()
    )

    ctx.meter.Gauge('fields_count').record(fields.length)
  }, 
  60*24,   // watching time interval  
  60*24*30, // backfill time interval
  undefined,
  { owned: true } // whether to fetch objects owned by the object
  )
  .onCheckpointInterval(...)

It will fetch the object content and optionally all objects belong to it (such as dynamic objects) every certain period of time in history.

To enable fetch owned objects, the last argument of MoveAccountFetchConfig should be used , most used field is owned, represent if objects owned by the object need to be fetched into objects argument of the handler.

If more info for object is needed, use ctx.objectVersion to work with SUI Typescript SDK to do that.

You may also want to carefully tune the two time intervals for better indexing performance

  • Watching time interval: how often the handler is trigger by default
  • Backfill time interval: how often the handler is trigger during processor backfill, you may want to set this a larger number, especially if the object has very long history

Address processor

Similar to object processor, you have use SuiAddressProcessor to fetch all objects belong to an address every certain period of time or checkpoints.

Object type processor

If you want to handle all objects that has the same type instead of single object, use SuiObjectTypeProcessor.onTimeInterval, e.g.

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

SuiObjectTypeProcessor.bind({
  objectType: staking_pool.StakedSui.type()
})
  .onTimeInterval(
    (self, objects, ctx) => {
      ctx.meter.Gauge('voting_power').record(self.data_decoded.principal, { pool: self.data_decoded.pool_id })
    },
    60,
    60 * 24 * 30,
    { owned: false} // optional, default false for all fields
  )

If you want to handle all object changes for certain object type, use SuiObjectTypeProcessor.onObjectChange, e.g.

SuiObjectTypeProcessor.bind({
  objectType: staking_pool.StakedSui.type()
})
  .onObjectChange((changes, ctx) => {
    ctx.meter.Counter('updates').add(changes.length)
  })

Templating object processor

Some times you want to dynamic bind object processor, e.g. you want to register object handler for all pools create by an contracts (we prefer use SuiObjectTypeProcessor for better performance, but some times you only care about a subset of those objets).

In this case, you could create SuiObjectProcessorTemplate and call bind/unbind in other triggers, e.g.

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

const template = new SuiObjectProcessorTemplate().onTimeInterval(() => {
  // logic to record staking pool
})

// begin watch a staking pool
validator_set.bind({ network: SuiNetwork.TEST_NET }).onEventValidatorJoinEvent((evt, ctx) => {
  template.bind({ objectId: evt.data_decoded.staking_pool_id }, ctx)
})

// end watch the staking pool
validator_set.bind({ network: SuiNetwork.TEST_NET }).onEventValidatorLeaveEvent((evt, ctx) => {
  template.unbind({ objectId: evt.data_decoded.staking_pool_id }, ctx)
})