Indexing Factory Contracts

Overview

Factory contracts dynamically create child contracts, requiring special handling for indexing. This guide explains how to effectively track and index both factory and child contracts using Blockflow.

To index factory contracts effectively, you’ll need to:

  1. Track the child contract creation events from the factory contract.

  2. Use Blockflow’s sdk library to initialise and index the child contract.

  3. Define event handlers to process and store event data.

  4. Define schemas to specify the structure of the indexed data.

Prerequisites

  • Blockflow CLI

  • Docker

Key Concepts

Factory contracts have two key characteristics when indexing:

  1. The factory contract has a fixed address

  2. Child contracts have dynamic addresses that are created at runtime

Step-by-Step Implementation

1. Configure Resources in YAML

Define both the factory contract and its child contract template in your studio.yaml:

yamlCopyResources:
  - name: factory
    type: contract/event
    abi: src/abis/factory.json
    address: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"
    triggers:
      - event: PairCreated(address indexed,address indexed,address,uint256)
        handler: src/handlers/PairCreated.PairCreatedHandler
        
  - name: pair
    type: contract/event
    abi: src/abis/pair.json
    triggers:
      - event: Swap(address indexed,uint256,uint256,uint256,uint256,address indexed)
        handler: src/handlers/Swap.SwapHandler

Note: The child contract (pair) doesn't have an address field since its address will be set dynamically.

2. Define Database Schema

Create a schema file to structure your indexed data:

// studio.schema.ts
const Pair = {
  name: "Pair",
  db: "postgres",
  type: "managed",
  reorg: true,
  properties: {
    token0: "string?",
    token1: "string?",
    pair: "string?",
    timestamp: "string?"
  }
};

const Swap = {
  name: "Swap",
  db: "postgres",
  type: "managed",
  reorg: true,
  properties: {
    sender: "string?",
    amount0In: "string?",
    amount1In: "string?",
    amount0Out: "string?",
    amount1Out: "string?",
    to: "string?",
    pair: "string?"
  }
};

module.exports = { Pair, Swap };

3. Implement Event Handlers

To ensure your handlers use the correct types for event data and database entries, run Blockflow’s code generation tool:

blockflow codegen

This command will generate TypeScript types for your handlers based on the schema definitions.

Factory Contract Handler

This handler processes new child contract creation:

// src/handlers/PairCreated.ts

import { Factory, Instance } from "@blockflow-labs/sdk";
import { IEventContext, ISecrets } from "@blockflow-labs/utils";
import { IPair, Pair } from "../types/generated";

/**
 * @dev Event::PairCreated(address token0, address token1, address pair, uint256)
 * @param context Trigger object containing event {token0, token1, pair}, transaction, block, log
 * @param bind Initialization function for database wrapper methods
 */
export const PairCreatedHandler = async (
  context: IEventContext,
  bind: any,
  secrets: ISecrets
) => {
  const { event, transaction, block, log } = context;
  const { token0, token1, pair } = event;

  // Initialize child contract for indexing
  const factory = new Factory(bind);
  await factory.create("pair", pair?.toString());

  // Initialize PostgreSQL client and save pair data
  const client = Instance.PostgresClient(bind);
  const pairDB = client.db(Pair);

  const entry: IPair = {
    token0: token0?.toString(),
    token1: token1?.toString(),
    pair: pair?.toString(),
    timestamp: block.block_timestamp,
  };

  await pairDB.save(entry);
};

Child Contract Handler

This handler processes events from the child contracts:

// src/handlers/Swap.ts

import { IEventContext, IBind, ISecrets } from "@blockflow-labs/utils";
import { Instance } from "@blockflow-labs/sdk";
import { ISwap, Swap } from "../types/generated";

/**
 * @dev Event::Swap(address sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address to)
 * @param context Trigger object containing event details
 */
export const SwapHandler = async (
  context: IEventContext,
  bind: IBind,
  secrets: ISecrets
) => {
  const { event, transaction, block, log } = context;
  const { sender, amount0In, amount1In, amount0Out, amount1Out, to } = event;

  // Initialize PostgreSQL client and save swap data
  const client = Instance.PostgresClient(bind);
  const swapDB = client.db(Swap);

  const entry: ISwap = {
    sender: sender?.toString(),
    amount0In: amount0In?.toString(),
    amount0Out: amount0Out?.toString(),
    amount1In: amount1In?.toString(),
    amount1Out: amount1Out?.toString(),
    to: to?.toString(),
    pair: log.log_address.toString(),
  };

  await swapDB.save(entry);
};

4. Query Data with GraphQL

After completing the code, run the blockflow test command to fill up data. To access the local GraphQL API, add an API resource in the Resources section and set the YAML type to api.

yamlCopyResources:
  - name: graphql-apis
    type: api/graphql
    slug: uniswap
    handler: src/apis/graphql.ts

And again hit blockflow test command. A local apollo server will start at http://localhost:4000/graphql

This guide covers configuring a factory contract to track child creation events, initialising child contracts, and storing event data in a managed database. By following this setup, you can index factory-child relationships for UniswapV2 and view the data through a GraphQL API. Read more at: https://github.com/blockflowlabs/cli-examples/tree/v2-examples/v2/uniswap

Last updated