con un clic
indexer-handlers
// Use when writing or editing event handlers. Handler registration, context API (entity CRUD, getWhere queries, chain, log), spread updates, indexer runtime API, and common pitfalls.
// Use when writing or editing event handlers. Handler registration, context API (entity CRUD, getWhere queries, chain, log), spread updates, indexer runtime API, and common pitfalls.
Use when something is unclear, confusing, or not covered by other skills. Search and read the latest Envio documentation without leaving the terminal.
Use when processing every block (or every Nth block) for time-series data, periodic snapshots, or block-level aggregations. indexer.onBlock API, where filter with block-number range and stride, and block handler context.
Use when writing or editing config.yaml. Chain/contract structure, addresses, start_block, event selection, field_selection, custom event names, env vars, address_format, schema/output paths, YAML validation, and deprecated options.
Use when making RPC calls, fetch requests, or any external I/O from handlers. Effect API with createEffect, S schema validation, context.effect(), preload optimization (handlers run twice), cache and rateLimit options.
Use when indexing contracts deployed by factory contracts at runtime. contractRegister API, dynamic contract config (no address), async registration, and same-block event coverage.
Use when filtering events by indexed parameters to reduce processing volume. The `where` option supports static filters, dynamic per-chain functions, contract address filtering, and conditional enable/disable.
| name | indexer-handlers |
| description | Use when writing or editing event handlers. Handler registration, context API (entity CRUD, getWhere queries, chain, log), spread updates, indexer runtime API, and common pitfalls. |
| metadata | {"managed-by":"envio"} |
This is an ESM project ("type": "module" in package.json). Top-level await is available. Use import/export syntax, not require.
schema.graphql or config.yaml → run pnpm codegenpnpm tsc --noEmitpnpm dev to catch runtime errorsimport { Contract } from "envio";
Contract.Event.handler(async ({ event, context }) => {
// event.params.<name> — decoded event parameters
// event.chainId — chain ID
// event.srcAddress — emitting contract address (checksummed)
// event.logIndex — log index within block
// event.block — { number, timestamp, hash }
// event.transaction — transaction fields (configure via field_selection)
});
Handlers accept an optional 2nd argument — see indexer-wildcard and indexer-filters skills.
// Read
const entity = await context.Entity.get(id); // Entity | undefined
const entity = await context.Entity.getOrThrow(id); // throws if missing
const entity = await context.Entity.getOrCreate({ id, ...defaults });
// Query by indexed fields (@index in schema)
const list = await context.Entity.getWhere({ fieldName: { _eq: value } });
const list = await context.Entity.getWhere({ fieldName: { _gt: value } });
const list = await context.Entity.getWhere({ fieldName: { _lt: value } });
const list = await context.Entity.getWhere({ fieldName: { _gte: value } });
const list = await context.Entity.getWhere({ fieldName: { _lte: value } });
const list = await context.Entity.getWhere({ fieldName: { _in: [value1, value2] } });
// Write
context.Entity.set(entity); // create or update (sync — no await)
context.Entity.deleteUnsafe(id); // delete (sync — no await)
getWhere operators: _eq, _gt, _lt, _gte, _lte, _in. Only @index fields are queryable. See indexer-schema for @index syntax.
context.chain.id // number — current chain ID
context.chain.isRealtime // boolean — true when ALL chains have caught up to head
context.isPreload // boolean — true during preload phase
context.log // { debug, info, warn, error, errorWithExn }
context.effect(fn, input) // external call via Effect API (see indexer-external-calls)
Entities from context.Entity.get() are read-only. Always spread:
const entity = await context.Entity.get(id);
if (entity) {
context.Entity.set({ ...entity, field: newValue });
}
indexer Runtime APIimport { indexer } from "envio";
indexer.name; // "my-indexer"
indexer.chainIds; // [1, 137]
indexer.chains[1].id; // 1
indexer.chains[1].name; // "ethereum"
indexer.chains[1].startBlock; // 0
indexer.chains[1].isRealtime; // false
indexer.chains[1].MyContract.name; // "MyContract"
indexer.chains[1].MyContract.addresses; // ["0x..."]
indexer.chains[1].MyContract.abi; // [...]
Entity IDs — prefer ${chainId}_${blockNumber}_${logIndex} as a unique ID:
const id = `${event.chainId}_${event.block.number}_${event.logIndex}`;
This is globally unique across chains and blocks. Use it as the default unless the entity is a singleton (e.g., a Token or Pool keyed by address).
Entity relationships — schema uses entity references; handlers use the _id suffix that codegen adds:
// Schema: token0: Token! ← entity reference, field name is "token0"
// Handler: { token0_id: token0.id } ← codegen adds _id; NEVER write "token0" here
// Schema: collection: NftCollection!
// Handler: { collection_id: collectionEntity.id }
// WRONG: { token0: token0.id } ← "token0" is not a valid TypeScript field
// WRONG: { collection_id: String! } in schema ← _id belongs in handlers, not schema
// CORRECT: { token0_id: token0.id } in handler
Optionals — string | undefined, not string | null
Decimal normalization — ALWAYS normalize when adding tokens with different decimals.
Schema & config — see indexer-schema and indexer-configuration skills for full reference.
If something is unclear, use the
envio-docsskill to search and read the latest documentation.