Getting Started

Getting Started

This guide will help you set up and run your first Open Ethereum Indexer project. It is designed to cover the basics of getting started with the indexer. You should read the full docs for more specific details as this tries to cover each concept quickly and succinctly. It is based on the example projects.

Prerequisites

Before you begin, ensure you have the following installed:

Installation

Creating a New Project

The easiest way to get started is to use the NestJS CLI (opens in a new tab) to create a new project:

  1. Install the NestJS CLI:
pnpm install -g @nestjs/cli
  1. Create a new project:
nest new my-indexer
  1. Delete the files in the src directory so you have a clean project.

  2. Install the Open Ethereum Indexer package and typeorm

pnpm install @open-ethereum/indexer typeorm
  1. Follow the sections below which will explain each file and what it should contain.

Setting up the Environment

  1. Create a .env file with your configuration:
# .env
RPC_URL=https://eth-mainnet.g.alchemy.com/v2/your-api-key
CHAIN_ID=1
 
SQL_DB=my_indexer
SQL_USERNAME=postgres_username
SQL_PASSWORD=postgres_password
SQL_HOST=localhost
SQL_PORT=5432
SQL_SCHEMA=my_indexer
 
PORT=3050
 
SLEEP_INTERVAL=100
MAX_BLOCKS_PER_QUERY=10
 
LOG_LEVEL=debug

Replace the values with your specific configuration.

Basic Project Structure

The basic structure of an Open Ethereum Indexer project looks like this:

my-indexer/
├── src/
│   ├── app.module.ts         # Main application module
│   ├── indexer.config.ts     # Indexer configuration
│   ├── main.ts               # Application entry point
│   ├── subscriptions.ts      # Event and block subscriptions
│   └── entities/             # Entity definitions (optional)
├── package.json
└── .env

Let's look at each of these files.

Configuration

The configuration file (src/indexer.config.ts) defines your blockchain connection, database settings, and other options:

// src/indexer.config.ts
import { IndexerConfig } from '@open-ethereum/indexer';
import ERC20_ABI from './abis/ERC20.json'; // Optional: import ABIs for your contracts
 
export const indexerConfig: IndexerConfig = {
  indexer: {
    network: {
      rpcUrl: process.env.NODE_RPC_URL,
      chainId: parseInt(process.env.NODE_CHAIN_ID),
    },
    contracts: {
      // Optional: define the contracts you want to index
      USDT: {
        abi: USDTAbi,
        address: '0xdac17f958d2ee523a2206206994597c13d831ec7',
        // Optional: Exclude some events you don't care about - we'll keep just Transfer events
        excludeEvents: [
          'Issue',
          'Redeem',
          'Deprecate',
          'Params',
          'DestroyedBlackFunds',
          'AddedBlackList',
          'RemovedBlackList',
          'Approval',
          'Pause',
          'Unpause',
        ],
        // Optional: specify a range of blocks to index
        startBlock: 22215331,
      },
    },
  },
  database: {
    // Optional: specify migration scripts for database
    migrations: [InitialSchema1742552800422],
  },
};

You can get the USDT abi from etherscan (opens in a new tab) or here USDT.json (opens in a new tab).

Subscriptions

The subscriptions file (src/subscriptions.ts) defines handlers for blockchain events and blocks:

// src/subscriptions.ts
import { onEvent, onBlock } from '@open-ethereum/indexer';
 
// Listen for USDT Transfer events
onEvent('USDT:Transfer', {
  onIndex: async (payload) => {
    const { from, to, value } = payload.parsedEvent.args;
    console.log(`USDT Transfer: ${from} -> ${to}: ${value.toString()}`);
    // Here you would typically store this data in the database
  },
});
 
// Listen for new blocks
onBlock({
  onIndex: async (payload) => {
    const { number, hash, timestamp } = payload;
    console.log(`New block: ${number}`);
    // Here you would typically update your latest block information
  },
});

Application Module

The application module (src/app.module.ts) integrates the indexer into your NestJS application:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { IndexerModule } from '@open-ethereum/indexer';
import { indexerConfig } from './indexer.config';
import './subscriptions';
 
@Module({
  imports: [IndexerModule.forRoot(indexerConfig)],
})
export class AppModule {}

In this Module you can also add any custom modules you want to your application. You can look at the Block Notifier example to see how a custom telegram module can be added to the indexer.

Main Application

The main file (src/main.ts) bootstraps your NestJS application:

// src/main.ts
import 'dotenv/config';
import { NestFactory } from '@nestjs/core';
import { setupSwagger } from '@open-ethereum/indexer';
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import { Logger } from '@nestjs/common';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    bufferLogs: true,
  });
 
  // Create a separate app for metrics
  const metricsApp = await NestFactory.create(
    PrometheusModule.register({ path: '/metrics' }),
  );
 
  // Get the logger
  const logger = app.get(Logger);
  app.useLogger(logger);
 
  // Setup Swagger API documentation
  setupSwagger(app);
 
  // Start the main application
  const port = process.env.PORT ?? 3050;
  await app.listen(port);
 
  // Start the metrics server
  const metricsPort = process.env.METRICS_PORT ?? 3051;
  await metricsApp.listen(metricsPort);
 
  logger.log(`Application started on port ${port}`);
  logger.log(
    `Swagger documentation available at http://localhost:${port}/api-docs`,
  );
  logger.log(`Metrics available at http://localhost:${metricsPort}/metrics`);
}
bootstrap();

Running Your Indexer

To start your indexer:

# Development mode
pnpm run start:dev
 
# Production mode
pnpm run build
pnpm run start:prod

Creating Entities

To store blockchain data, you'll want to create TypeORM entities. Here's a contrived example entity:

// src/entities/ExampleEntity.ts
import { Column, Entity, PrimaryColumn } from 'typeorm';
 
@Entity({ name: 'example_entity' })
export class ExampleEntity {
  @PrimaryColumn({
    name: 'id',
    type: 'uuid',
    update: false,
  })
  public id: string;
 
  @Column({
    name: 'example_column_one',
    nullable: false,
    type: 'varchar',
    update: false,
  })
  public exampleColumnOne: string;
 
  @Column({
    name: 'example_column_two',
    nullable: false,
    type: 'numeric',
    update: false,
  })
  public exampleColumnTwo: string;
}

You'll need to register the entity with the indexer. Create a file called src/entity-registration.ts and add the following:

// src/entity-registration.ts
import { entityRegistry } from '@open-ethereum/indexer';
import { ExampleEntity } from './output/entities/ExampleEntity';
 
export const entities = [ExampleEntity];
 
// Register all entities from the entities object
Object.keys(entities).forEach((key) => {
  const entity = entities[key];
  if (entity && typeof entity === 'function') {
    entityRegistry.register(entity);
  } else {
    console.warn(`Skipping invalid entity export: ${key}`);
  }
});

This MUST be imported into your src/main.ts file.

And update your subscription to store the data. Here, we just hardcode the values for the sake of example but the event data is available in the payload object.

// Update subscriptions.ts
import { getRepository } from 'typeorm';
import { Transfer } from './entities/Transfer.entity';
import { LogContext, LogEvent } from '@open-ethereum/indexer';
 
onEvent('USDC:Transfer', {
  onIndex: async (payload: LogEvent, context: LogContext) => {
    const { moduleRef, entityManager } = context;
 
    const exampleRepository = entityManager.getRepository(ExampleEntity);
 
    const exampleEntity = new ExampleEntity();
    exampleEntity.id = uuidv4();
    exampleEntity.exampleColumnOne = 'something';
    exampleEntity.exampleColumnTwo = '123';
    await exampleRepository.save(exampleEntity);
  },
});

Accessing the API

Once your indexer is running, you can access:

  • The REST API at http://localhost:3050/api
  • The Swagger documentation at http://localhost:3050/api-docs
  • The metrics at http://localhost:3051/metrics

For example, to get recent transfers:

GET http://localhost:3050/api/transfers?limit=10&orderBy=blockNumber&orderDirection=DESC

Next Steps

Now that you have a basic indexer running, you might want to: