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:
- Node.js (v20+)
- PostgreSQL (using Docker (opens in a new tab) is recommended)
- Access to an Ethereum node via RPC URL (e.g., from Alchemy (opens in a new tab), or your own node)
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:
- Install the NestJS CLI:
pnpm install -g @nestjs/cli
- Create a new project:
nest new my-indexer
-
Delete the files in the
src
directory so you have a clean project. -
Install the Open Ethereum Indexer package and typeorm
pnpm install @open-ethereum/indexer typeorm
- Follow the sections below which will explain each file and what it should contain.
Setting up the Environment
- 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:
- Add more Event Handlers for different contracts
- Customize your Block Handlers for specific analytics
- Create more Entities to store complex data
- Explore the API Reference to understand available endpoints
- Set up Metrics & Monitoring for your indexer
- Check out the Examples for more advanced use cases