Storing data in the database

Working with Entities

The Open Ethereum Indexer leverages TypeORM (opens in a new tab) for database operations, simplifying the process of defining, storing, and querying blockchain data. It is essential to execute ALL SQL queries using TypeORM. This approach ensures that blocks are handled as a single database transaction, ensuring that if any part of the transaction fails, the entire transaction fails, thereby maintaining data integrity. Additionally, it enables the indexer to track changes that need to be reversed in the event of a blockchain reorganization.

Creating Entities

Entities are TypeScript classes that map to database tables. Here's how to create an entity for storing ERC-20 transfers:

// entities/Transfer.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, Index } from 'typeorm';
 
@Entity()
export class Transfer {
  @PrimaryGeneratedColumn()
  id: number;
 
  @Column()
  from: string;
 
  @Column()
  to: string;
 
  @Column('varchar')
  value: string;
 
  @Column('int')
  blockNumber: number;
 
  @Column()
  transactionHash: string;
 
  @Column('int')
  logIndex: number;
 
  @Column('int')
  timestamp: number;
}

Important TypeORM Decorators

  • @Entity() - Marks a class as a database entity
  • @Column() - Defines a column in the table
  • @PrimaryGeneratedColumn() - Creates an auto-incrementing primary key
  • @Index() - Creates a database index for faster queries

You can also define relations between entities.

Full list in TypeORM Decorator Reference (opens in a new tab)

Entity Migrations

When you add or change your entity structure, you'll need to create migrations to update the database schema. You can do this automatically using typeorm by running the migration:generate command documented here (opens in a new tab).

Using Entities in Handlers

Here's how to use your entities in event handlers:

import { onEvent } from '@open-ethereum/indexer';
import { Transfer } from './entities/Transfer.entity';
 
onEvent('*:Transfer', {
  onIndex: async (payload: LogEvent, context: LogContext) => {
    const { log, parsedEvent, block } = payload;
    const { from, to, value } = parsedEvent.args;
 
    const { moduleRef, entityManager } = context;
 
    const transferRepo = entityManager.getRepository(Transfer);
 
    const transfer = new Transfer();
    transfer.contractAddress = log.address;
    transfer.from = from;
    transfer.to = to;
    transfer.value = value.toString();
    transfer.blockNumber = block.number;
    transfer.transactionHash = log.transactionHash;
    transfer.logIndex = log.logIndex;
    transfer.timestamp = block.timestamp;
 
    await transferRepo.save(transfer);
  },
});

Querying Entities

You can query your entities in your handlers using TypeORM's repository pattern:

const transferRepo = entityManager.getRepository(Transfer);
 
const transfers = await transferRepo.find({
  where: [{ from: address }, { to: address }],
  order: {
    blockNumber: 'DESC',
    logIndex: 'DESC',
  },
  take: limit,
});

If you are indexing raw data as is from the chain, you can should use the generic indexing functionality provided by the indexer. You can read more about that on the generic indexing page.