Event Handlers
Event handlers define how your indexer processes blockchain events. After defining your configuration (config.yaml
) and GraphQL schema (schema.graphql
), generate the boilerplate code for event handlers by running:
pnpm codegen
Registering Event Handlers
Register an asynchronous handler for each blockchain event you want to process. The basic handler registration syntax is shown below:
- TypeScript
- JavaScript
- ReScript
import { <CONTRACT_NAME> } from "generated";
<CONTRACT_NAME>.<EVENT_NAME>.handler(async ({ event, context }) => {
// Your logic here
});
const { <CONTRACT_NAME> } = require("generated");
<CONTRACT_NAME>.<EVENT_NAME>.handler(async ({ event, context }) => {
// Your logic here
});
Handlers.<CONTRACT_NAME>.<EVENT_NAME>.handler(async ({ event, context }) => {
// Your logic here
});
Event Object
Each handler receives an event
object containing details about the emitted event, including parameters and blockchain metadata.
Accessing Event Parameters
Event parameters are accessed via:
event.params.<PARAMETER_NAME>
Example usage:
const sender = event.params.sender;
const amount = event.params.amount;
Additional Event Information
The event object also contains additional metadata:
event.chainId
– Chain ID of the network emitting the event.event.srcAddress
– Contract address emitting the event.event.logIndex
– Index of the log within the block.event.block
– Block information (number, timestamp, etc.).event.transaction
– Transaction details (hash, index, gas used, etc.).
Example event type definition:
type Event<Params, TransactionFields, BlockFields> = {
params: Params;
chainId: number;
srcAddress: string;
logIndex: number;
transaction: TransactionFields;
block: BlockFields;
};
You can configure additional block and transaction fields in your config.yaml
under field_selection
.
Using the Handler context
The handler context
provides methods to interact with entities stored in the database.
Retrieving Entities
Retrieve entities asynchronously using get
:
await context.<ENTITY_NAME>.get(entityId);
Modifying Entities
Use set
to create or update an entity:
context.<ENTITY_NAME>.set(entityObject);
Deleting Entities (Unsafe)
To delete an entity:
context.<ENTITY_NAME>.deleteUnsafe(entityId);
The deleteUnsafe
method is experimental and unsafe. Manually handle all entity references after deletion to maintain database consistency.
Updating Specific Entity Fields
Use the following approach to update specific fields in an existing entity:
- JavaScript
- TypeScript
- ReScript
const pool = await context.Pool.get(poolId);
if (pool) {
context.Pool.set({
...pool,
totalValueLockedETH: pool.totalValueLockedETH.plus(newDeposit),
});
}
const pool = await context.Pool.get(poolId);
if (pool) {
context.Pool.set({
...pool,
totalValueLockedETH: pool.totalValueLockedETH.plus(newDeposit),
});
}
let pool = await context.pool.get(poolId);
pool->Option.forEach(pool => {
context.pool.set({
...pool,
totalValueLockedETH: pool.totalValueLockedETH.plus(newDeposit),
});
});
Handler Example: NewGreeting
Event
Here's a complete example handler for the NewGreeting
event:
- JavaScript
- TypeScript
- ReScript
let { Greeter } = require("generated");
Greeter.NewGreeting.handler(async ({ event, context }) => {
let userId = event.params.user.toString();
let user = await context.User.get(userId);
if (user) {
context.User.set({
id: userId,
latestGreeting: event.params.greeting,
numberOfGreetings: user.numberOfGreetings + 1,
greetings: [...user.greetings, event.params.greeting],
});
} else {
context.User.set({
id: userId,
latestGreeting: event.params.greeting,
numberOfGreetings: 1,
greetings: [event.params.greeting],
});
}
});
import { Greeter, User } from "generated";
Greeter.NewGreeting.handler(async ({ event, context }) => {
const userId = event.params.user.toString();
const currentUser = await context.User.get(userId);
const userObject: User = currentUser
? {
id: userId,
latestGreeting: event.params.greeting,
numberOfGreetings: currentUser.numberOfGreetings + 1,
greetings: [...currentUser.greetings, event.params.greeting],
}
: {
id: userId,
latestGreeting: event.params.greeting,
numberOfGreetings: 1,
greetings: [event.params.greeting],
};
context.User.set(userObject);
});
open Types;
Handlers.Greeter.NewGreeting.handler(async ({ event, context }) => {
let userId = event.params.user->Ethers.ethAddressToString;
let currentUserOpt = context.user.get(userId);
switch currentUserOpt {
| Some(existingUser) =>
context.user.set({
id: userId,
latestGreeting: event.params.greeting,
numberOfGreetings: existingUser.numberOfGreetings + 1,
greetings: existingUser.greetings->Belt.Array.concat([event.params.greeting]),
})
| None =>
context.user.set({
id: userId,
latestGreeting: event.params.greeting,
numberOfGreetings: 1,
greetings: [event.params.greeting],
})
}
});
Accessing config.yaml
Data in Handlers
You can access your indexer configuration within handlers using getConfigByChainId
:
- JavaScript
- TypeScript
- ReScript
const { getConfigByChainId } = require("../generated/src/ConfigYAML.bs.js");
Greeter.NewGreeting.handler(async ({ event, context }) => {
const config = getConfigByChainId(event.chainId);
});
import { getConfigByChainId } from "../generated/src/ConfigYAML.gen";
Greeter.NewGreeting.handler(async ({ event, context }) => {
const config = getConfigByChainId(event.chainId);
});
open Types;
Handlers.Greeter.NewGreeting.handler(async ({ event, context }) => {
let config = ConfigYAML.getConfigByChainId(event.chainId);
});
This exposes configuration data such as:
syncSource
,startBlock
,confirmedBlockThreshold
- Contract-specific data (
abi
,addresses
,events
)
Performance Considerations
For performance optimization and best practices, refer to:
These guides offer detailed recommendations on optimizing entity loading and indexing performance.