DubheSuiIndexer

Dubhe Rust Indexer

dubhe-indexer is a high-performance Rust binary that listens to Sui blockchain events emitted by your Dubhe contract and writes them into a PostgreSQL database. It exposes a built-in GraphQL endpoint and a gRPC endpoint for querying indexed data in real time.


How it works

The indexer is event-driven — it processes SetRecord, SetField, and DeleteRecord events emitted by the Dubhe framework. Only events whose original_package_id matches your config and whose table name is listed in your dubhe.config.json are indexed.

Important: The indexer can only process events it has seen. If start_checkpoint in dubhe.config.json is set later than your deployment checkpoint, events before that point are permanently missed and will not appear in the database.


Installation

Install dubhe-indexer using the Dubhe CLI:

pnpm dubhe doctor

The doctor command checks your environment and installs required binaries, including dubhe-indexer. You can also download the binary directly from GitHub releases.


Prerequisites

  1. PostgreSQL — the indexer stores all indexed data in PostgreSQL.
  2. dubhe.config.json — generated from dubhe.config.ts via dubhe convert-json.
  3. A running Sui node or access to a remote checkpoint store (testnet/mainnet).

Configuration file

Generate dubhe.config.json from your TypeScript config before starting the indexer:

dubhe convert-json --config-path dubhe.config.ts --output-path dubhe.config.json

The JSON file must include start_checkpoint — the checkpoint from which the indexer begins reading. Set it to the checkpoint at or just before your contract was deployed:

{
  "name": "my_game",
  "description": "My on-chain game",
  "packageId": "0x123...",
  "originalPackageId": "0x123...",
  "start_checkpoint": "1000000",
  "resources": {
    "level": "u32",
    "stats": {
      "fields": { "attack": "u32", "hp": "u32" }
    }
  }
}

start_checkpoint is read from the config file. The --first-checkpoint CLI argument is present in --help output but is not used by the current binary — always set it in dubhe.config.json.


CLI reference

dubhe-indexer [OPTIONS]

Options:
  -c, --config-json <PATH>       Path to dubhe.config.json         [default: dubhe.config.json]
      --database-url <URL>       PostgreSQL connection string       [default: postgres://postgres@localhost:5432/postgres]
      --checkpoint-url <URL>     Checkpoint source: local directory (e.g. .chk) or
                                 remote http URL (e.g. https://checkpoints.testnet.sui.io)
                                                                    [default: .chk]
      --use-rpc-ingestion        Use gRPC to fetch checkpoints from --rpc-url instead
                                 of local path / remote store       [default: false]
      --rpc-url <URL>            gRPC endpoint (used with --use-rpc-ingestion)
                                                                    [default: http://localhost:9000]
      --port <PORT>              Proxy server port (GraphQL + gRPC via proxy, health, playground)
                                                                    [default: 8080]
      --grpc-port <PORT>         Direct gRPC backend port           [default: 8085]
      --graphql-port <PORT>      Independent GraphQL backend port   [default: 8089]
      --force                    Clear the database before starting [default: false]

Service ports

When the indexer starts, it launches three services:

ServiceDefault portDescription
Proxy server8080Serves /graphql, /playground, /health, /metadata, /welcome; forwards gRPC requests
gRPC backend8085Direct gRPC endpoint for DubheGrpcClient
GraphQL backend8089Independent PostGraphile GraphQL service

For most use cases, connect your client to the proxy at port 8080:

  • GraphQL queries: http://localhost:8080/graphql
  • GraphQL playground: http://localhost:8080/playground
  • gRPC (via proxy): http://localhost:8080 (prefix /dubhe_grpc.*)
  • Direct gRPC: http://localhost:8085

--grpc-port and --graphql-port must be different values. The binary exits immediately if they match.


Checkpoint sources

The --checkpoint-url argument controls where the indexer reads checkpoints from:

ValueSourceWhen to use
.chk (default)Local directoryLocalnet — the directory where dubhe node writes checkpoint files
https://checkpoints.testnet.sui.ioRemote storeTestnet
https://checkpoints.mainnet.sui.ioRemote storeMainnet
--use-rpc-ingestion + --rpc-urlgRPC from a full nodeWhen you have a full node with gRPC checkpoint API

Localnet

Use pnpm dubhe node to start the local Sui node. It writes checkpoint files to .chk by default, and the indexer reads from .chk by default — no extra flags needed:

# Terminal 1: start local node (writes checkpoints to .chk)
pnpm dubhe node
 
# Terminal 2: start indexer (reads from .chk by default)
dubhe-indexer \
  --config-json dubhe.config.json \
  --database-url postgres://postgres:postgres@localhost:5432/postgres

To clear the database and start fresh (useful after redeploying):

dubhe-indexer \
  --config-json dubhe.config.json \
  --database-url postgres://postgres:postgres@localhost:5432/postgres \
  --force

Testnet / Mainnet

Pass the remote checkpoint store URL via --checkpoint-url:

# Testnet
dubhe-indexer \
  --config-json dubhe.config.json \
  --checkpoint-url https://checkpoints.testnet.sui.io \
  --database-url postgres://user:password@localhost:5432/dubhe_prod
 
# Mainnet
dubhe-indexer \
  --config-json dubhe.config.json \
  --checkpoint-url https://checkpoints.mainnet.sui.io \
  --database-url postgres://user:password@localhost:5432/dubhe_prod

Querying indexed data

GraphQL client

import { createDubheGraphqlClient } from '@0xobelisk/graphql-client';
 
const client = createDubheGraphqlClient({
  endpoint: 'http://localhost:8080/graphql',
  subscriptionEndpoint: 'ws://localhost:8080/graphql'
});
 
const players = await client.getAllTables('player', {
  filter: { hp: { greaterThan: '50' } },
  orderBy: [{ field: 'hp', direction: 'DESC' }],
  first: 10
});

gRPC client

import { DubheGrpcClient } from '@0xobelisk/grpc-client';
 
const grpc = new DubheGrpcClient({ baseUrl: 'http://localhost:8085' });
 
const result = await grpc.dubheGrpcClient.queryTable({
  tableId: 'player',
  filters: [],
  pagination: { page: 0, pageSize: 20 }
});

Via createClient

If you use @0xobelisk/client, both clients are created automatically:

import { createClient } from '@0xobelisk/client/sui';
 
const { graphqlClient, grpcClient } = createClient({
  network: 'localnet',
  packageId: PackageId,
  metadata,
  endpoints: {
    graphql: 'http://localhost:8080/graphql',
    grpc: 'http://localhost:8085'
  }
});

Monitoring and troubleshooting

Logging

# Info level (recommended)
RUST_LOG=info dubhe-indexer --config-json dubhe.config.json --database-url ...
 
# Debug level (verbose)
RUST_LOG=debug dubhe-indexer --config-json dubhe.config.json --database-url ...

Common issues

No data indexed, no errors

  • Check that start_checkpoint in dubhe.config.json is at or before the checkpoint of your contract deployment.
  • Check that originalPackageId matches the package ID from your first deployment (not an upgraded package ID).
  • Check that resource names in dubhe.config.json match the table names emitted by the contract.

Database connection refused

  • Verify PostgreSQL is running: pg_isready -h localhost.
  • Pass the connection string via --database-url, not via an environment variable — the indexer binary reads only the CLI argument.

Checkpoints not arriving (localnet)

  • Confirm pnpm dubhe node is running and writing to the .chk directory.
  • If you restarted the node, run with --force to clear stale data.

Production deployment

Docker

FROM ubuntu:22.04
 
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
 
COPY dubhe-indexer /usr/local/bin/dubhe-indexer
RUN chmod +x /usr/local/bin/dubhe-indexer
 
COPY dubhe.config.json /app/dubhe.config.json
WORKDIR /app
 
CMD ["dubhe-indexer", \
     "--config-json", "dubhe.config.json", \
     "--checkpoint-url", "https://checkpoints.mainnet.sui.io", \
     "--database-url", "postgres://user:password@db-host:5432/dubhe_prod", \
     "--port", "8080"]

Logging in production

RUST_LOG=info dubhe-indexer \
  --config-json dubhe.config.json \
  --checkpoint-url https://checkpoints.mainnet.sui.io \
  --database-url postgres://user:password@db-host:5432/dubhe_prod