Event Processors

Learn about structure of a custom event processor script

As defined in manifest.yml each custom event processor has a handler.ts and an abi.json. When a custom processor is deployed it listens for all the "event" topics defined in the abi.json, and executes handler.js for every single event.

As mentioned in Getting Started guide, you can see the basic structure of a processor in the starter-boilerplate repo.

Example of an empty processor

import { EventHandlerInput, database } from "flair-sdk";

export async function (event: EventHandlerInput) {
  console.debug(
    'Processing my custom event: ',
    { event, myVar: process.env.SOME_VARIABLE }
  );
};

handler.ts

  • handler.js must export a function named processEvent

  • processEvent function will receive 1 argument:

    • An object that gives you event that gives you access to the received transaction log and parsed event.

  • processEvent function must return either:

    • true which means event was processed

    • false which means event was skipped for any reason

    • undefined which is considered a success

  • processEvent has a timeout of 60 seconds (a soft-limit that can be increased if you needed).

  • The boolean value returned by processEvent is only used for statistics reasons, so it won't impact your processor logic if anything (or nothing) is returned.

export type EventHandlerInput<ArgsType = Record<string, any>> = {
  chainId: number
  blockHash: string
  blockNumber: number
  blockTimestamp: number
  txHash: string
  txIndex: number
  
  // Incoming EVM event log object with additional information.
  // See below.
  log: IngestedLog
  
  // An object used for chronological ordering and handling re-orgs,
  // When using database integration you just pass this object as-is.
  horizon: Horizon;
  
  // If this log was processed correctly (based on abi.json), then
  // this object contains human-readable "name" and named "args" of the event.
  parsed?: {
    name: string
    args: ArgsType
  }
  
  // Exact event object found in your abi.json for this log topic
  abi: {
    type: 'event';
    name: string;
    inputs: Record<string, any>[];
    // ... any other user-defined field (.e.g "entityName")
  }[];
}

export type IngestedLog = {
  // Normalized Data from RPC nodes
  blockNumber: `0x${string}`
  blockHash: string
  address: string
  data: string
  logIndex: number
  topics: string[]
  transactionHash: string
  transactionIndex: number

  // Augmented data from RPC nodes
  blockTimestamp: number

  //////
  // Additional Flair-specific data:
  //////
  chainId: number
  
  // If there was a re-org for this block blockForkIndex
  // will be increased incrementally. So you can depend on this value
  // if you need to know which log is the latest.
  // Our database integration automatically takes care of upserting
  // the correct entity because this value is part of "horizon" desribed
  // above.
  blockForkIndex: number
  
  // Index of this log within the current transaction,
  // which calculated by Flair before sending the log to your processor.
  localIndex: number
  
  // The time when this log was delivered to Flair by the RPC node.
  ingestionReceivedAt: number
  
  // Indicates how this event log was ingested.
  //  - "realtime" means it was captured via RPC websocket or http-based listener
  //  - "redrive" means RPC node failed to emit but Flair redriver caught the event after a delay
  //  - "backfill" means this log is received via a user-initiated backfill request
  delivery: 'realtime' | 'redrive' | 'backfill'
}

export type Horizon = {
  blockNumber: number
  forkIndex: number
  transactionIndex: number
  
  // Exact logIndex reported by RPC,
  // which is index of this event in the whole block.
  logIndex: number
  
  // Index of this log within the current transaction,
  // which calculated by Flair before sending the log to your processor.
  localIndex: number
}

Example usage:

import { EventHandlerInput, database } from "flair-sdk";

export async function (event: EventHandlerInput) {
  console.debug(
    `My custom event block number: ${event.blockNumber.toString()}`,
    `My custom event tx hash: ${event.txHash}`,
    `My custom event name: ${event.parsed.name}`,
    `My custom event topic hash: ${event.log.topics[0]}`,
    `My custom event args object: ${event.parsed.args}`,
  );
};

Need help how to work with certain data?

Reach out to our engineers 🙂

abi.json

This file is used to know which event topics to listen to and parse incoming transaction logs, before they are handed over to your processor handler function above.

Example of an abi.json:

[
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  },
  // and more events...
]

Importing others files or libraries

You can import any .ts, .js, and .json file relative your handler.ts processor as long as they are under the same src/ directory. If you need any help ping our engineers 🙂

Last updated