Factory contracts

Factory contracts

Factory contracts are a common smart contract design pattern where one contract (a "factory") creates many instances of another kind of contract (the "child" contract).

Add a child contract

Set the factory field in ponder.config.ts

Add the child contract to the contracts list in ponder.config.ts. Be sure to use the child contract name and ABI. Then, rather than setting an explicit address as you would for a normal contract, use the factory option.

ponder.config.ts
import type { Config } from "@ponder/core";
import { parseAbiItem } from "viem";
 
export const config: Config = {
  networks: [
    /* ... */
  ],
  contracts: [
    {
      name: "SudoswapPool",
      abi: "./abis/SudoswapPool.json",
      network: "mainnet",
      factory: {
        // The address of the factory contract that creates instances of this child contract.
        address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4",
        // The event emitted by the factory that announces a new instance of this child contract.
        event: parseAbiItem("event NewPair(address poolAddress)"),
        // The name of the parameter that contains the address of the new child contract.
        parameter: "poolAddress",
      },
      startBlock: 14645816,
    },
  ],
};

Register indexing functions

When you register an indexing function for a child contract event, that function will process events for every instance of that child contract. The event.log.address field contains the address of the specific child contract that emitted the event.

This indexing function will run for every SudoswapPool contract created by the factory.

src/index.ts
import { ponder } from "@/generated";
 
ponder.on("SudoswapPool:Transfer", async ({ event }) => {
  // Here, `event.log.address` will vary depending on which
  // Sudoswap pool contract emitted this Transfer event.
  console.log(event.log.address);
});

Requirements and limitations

Scaling

Ponder does not currently support factory contracts with more than 10,000 children. This includes Uniswap V2, V3, and many other permisionless pool-based DeFi protocols.

Why not? The Ethereum JSON-RPC API does not scale well for factory contracts. There's only so much that Ponder's sync engine can do about this while remaining compatible with the JSON-RPC API.

⚠️

A note from the developers: We're actively working to address scaling challenges via features like remote sync, shared caching, concurrent indexing, and other fun tricks. Thanks for your patience.

Factory event signature

Ponder only supports factory contracts where the following requirements are met:

  1. The factory contract emits an event log announcing the creation of each new child contract.
  2. The event log contains the new child contract address as a named parameter of type "address". The parameter can be either indexed or non-indexed.

Here are a few factory event signatures with their eligibility explained:

// ✅ Eligible. The parameter "child" has type "address" and is non-indexed.
event ChildContractCreated(address child);
 
// ✅ Eligible. The parameter "pool" has type "address" and is indexed.
event PoolCreated(address indexed deployer, address indexed pool, uint256 fee);
 
// ❌ Ineligible. The parameter "contracts" is an array type, which is not supported.
// Always emit a separate event for each child contract, even if they are created in a batch.
event ContractsCreated(address[] contracts);
 
// ❌ Ineligible. The parameter "child" is a struct/tuple, which is not supported.
struct ChildContract {
  address addr;
}
event ChildCreated(ChildContract child);

Nested factory patterns

Ponder doesn't support factory patterns that are nested beyond a single layer.