multiversx-indexer
v0.1.18
Published
MultiversX event indexer with Kepler integration, Drizzle schema sync, and real-time backfill
Maintainers
Readme
multiversx-indexer
MultiversX event indexer with Kepler integration, Drizzle schema sync, and real-time backfill.
Installation
npm install multiversx-indexerFeatures
- Kepler integration — Index events from MultiversX via Kepler's Elasticsearch and WebSocket APIs
- Drizzle ORM — Define your schema with Drizzle, auto-sync migrations
- Backfill + realtime — Historical backfill from ES, then live events via WebSocket
- PGLite or Postgres — Use embedded PGLite for local dev, or PostgreSQL for production
- Type-safe handlers — Event handlers with full TypeScript support
Quick start
Create a new project
npx multiversx-indexer init
# or in a subdirectory
npx multiversx-indexer init ./my-indexerThis scaffolds:
indexer.config.ts— Configurationschema.ts— Drizzle schema for your processed tablesdrizzle.config.ts— Drizzle Kit confighandlers/— Event handlersdocker-compose.yml— Optional Postgres for local dev.env.example— Environment template
Configure
- Copy
.env.exampleto.envand setKEPLER_API_KEY(get one at projectx.mx) - Edit
indexer.config.tswith your contract address and event identifiers - Customize
schema.tsand handlers for your use case
Run
# Optional: start Postgres
docker compose up -d
# Run the indexer (dev mode with auto-restart)
bun run devCommands
| Command | Description |
|---------|-------------|
| init [dir] | Scaffold a new indexer project |
| start [config] | Run indexer: migrate → backfill → realtime |
| dev [config] | Same as start, with auto-restart on file changes |
| studio [config] | Open Drizzle Studio for the database |
Configuration
import { defineConfig } from "multiverse-indexer";
import * as schema from "./schema";
import { handleMyEvent } from "./handlers/my-event";
export default defineConfig({
database: process.env.DATABASE_URL
? { url: process.env.DATABASE_URL }
: { dataDir: "./indexer-data" },
schemaName: schema.PROCESSED_SCHEMA,
sources: [
{
id: "kepler_mainnet",
type: "kepler",
apiKey: process.env.KEPLER_API_KEY ?? "",
batchSize: 10_000,
multiversxApiUrl: "https://api.multiversx.com",
},
],
contracts: [
{
sourceId: "kepler_mainnet",
address: "erd1qqqq...",
eventIdentifiers: ["my_event"],
},
],
schema,
handlers: {
"erd1qqqq...:my_event": handleMyEvent,
},
healthPort: 42069,
});Endpoints
When healthPort is set in your config, the indexer starts an HTTP server (default: port 42069) with the following endpoints:
| Endpoint | Method | Description |
|----------|--------|-------------|
| /health | GET | Liveness probe — Returns { healthy, phase }. Responds with 200 when the indexer is running, 503 otherwise. Use for Kubernetes liveness checks. |
| /ready | GET | Readiness probe — Returns { ready, phase, eventsProcessed, uptimeMs }. Responds with 200 when the indexer has finished backfill and is processing live events, 503 during startup. Use for Kubernetes readiness checks. |
| /graphql | GET/POST | GraphQL API — Auto-generated from your Drizzle schema. Exposes queries for each table (e.g. rewardEvents, rewardEvent). Includes a GraphiQL playground when opened in a browser. Only available when schema and schemaName are configured. |
Example responses:
/health→{ "healthy": true, "phase": "realtime" }/ready→{ "ready": true, "phase": "realtime", "eventsProcessed": 1234, "uptimeMs": 45000 }
Event handlers
Handlers receive events and a context with db, schema, and client (chain reader):
import type { EventHandler } from "multiverse-indexer";
import { base64ToUtf8 } from "multiverse-indexer";
import type * as schema from "../schema";
export const handleMyEvent: EventHandler = async (event, context) => {
const data = event.topics[0] ? base64ToUtf8(event.topics[0]) : "";
const table = (context.schema as typeof schema).myTable;
await context.db
.insert(table)
.values({
id: event.id,
txHash: event.txHash,
timestamp: event.timestamp,
data,
})
.onConflictDoNothing();
};Requirements
- Node.js 18+ or Bun
- Kepler API key — Get one here
License
MIT
