> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Factory transformers

> Index dynamic contracts with the factory pattern

Use the factory pattern when you need to index events from contracts that are deployed dynamically by a known factory contract — for example, Uniswap V3 pools created by the `UniswapV3Factory`.

<Note>
  The examples below use typegen-generated ABI modules. See [Specifying events](../basic-development/handling-events#specifying-events) for how to generate them from a JSON ABI.
</Note>

## Basic factory

Track events from contracts created by a factory. The `contractFactory()` helper discovers child contracts from the factory's creation events and maintains the address list in a local SQLite database.

```ts theme={"system"}
import { evmPortalStream, evmEventDecoder, contractFactory, contractFactorySqliteStore } from "@subsquid/pipes/evm";
import { createTarget } from "@subsquid/pipes";
import * as factoryAbi from "./abi/uniswap-v3-factory";
import * as poolAbi from "./abi/uniswap-v3-pool";

await evmPortalStream({
  id: "uniswap-v3-swaps",
  portal: "https://portal.sqd.dev/datasets/ethereum-mainnet",
  outputs: evmEventDecoder({
    range: { from: 12369621 },
    contracts: contractFactory({
      address: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
      event: factoryAbi.events.PoolCreated,
      childAddressField: "pool",
      database: contractFactorySqliteStore({ path: "./uniswap-v3-pools.sqlite" }),
    }),
    events: { swap: poolAbi.events.Swap },
  }),
}).pipeTo(createTarget({
  write: async ({ logger, read }) => {
    for await (const { data } of read()) {
      logger.info(`Parsed ${data.swap.length} swaps`);
    }
  },
}));
```

## Filtering factory events

To narrow which child contracts are tracked, pass an `event` object with a `params` field. Only creation events matching the specified parameter values are stored — unmatched contracts are ignored at both the portal and the local database level.

```ts theme={"system"}
contracts: contractFactory({
  address: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
  event: {
    event: factoryAbi.events.PoolCreated,
    params: {
      token0: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
    },
  },
  childAddressField: "pool",
  database: contractFactorySqliteStore({ path: "./uniswap-v3-weth-pools.sqlite" }),
})
```

**Filter rules:**

* Only **indexed parameters** can be used for filtering.
* Multiple parameters are combined with AND logic.
* Passing an **array** of values for a parameter matches any of them (OR logic).
* Address matching is case-insensitive.

<Expandable title="Full example: filter by token0 with WETH pools">
  ```ts theme={"system"}
  import { evmPortalStream, evmEventDecoder, contractFactory, contractFactorySqliteStore } from "@subsquid/pipes/evm";
  import { createTarget } from "@subsquid/pipes";
  import * as factoryAbi from "./abi/uniswap-v3-factory";
  import * as poolAbi from "./abi/uniswap-v3-pool";

  const WETH = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";

  await evmPortalStream({
    id: "uniswap-v3-weth-swaps",
    portal: "https://portal.sqd.dev/datasets/ethereum-mainnet",
    outputs: evmEventDecoder({
      range: { from: 12369621 },
      contracts: contractFactory({
        address: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
        event: {
          event: factoryAbi.events.PoolCreated,
          params: { token0: WETH },
        },
        childAddressField: "pool",
        database: contractFactorySqliteStore({ path: "./uniswap-v3-weth-pools.sqlite" }),
      }),
      events: { swap: poolAbi.events.Swap },
    }),
  }).pipeTo(createTarget({
    write: async ({ logger, read }) => {
      for await (const { data } of read()) {
        logger.info(`Parsed ${data.swap.length} swaps from WETH pools`);
      }
    },
  }));
  ```
</Expandable>

## Including factory event data

`DecodedEvent<T, F>` carries a `.factory` field with the creation event. Use it when you need to include factory context (e.g. pool token addresses) alongside each decoded event.

<Expandable title="Full example: access factory event metadata">
  ```ts theme={"system"}
  import {
    evmPortalStream, evmEventDecoder, contractFactory, DecodedEvent, contractFactorySqliteStore,
  } from "@subsquid/pipes/evm";
  import { createTarget } from "@subsquid/pipes";
  import * as factoryAbi from "./abi/uniswap-v3-factory";
  import * as poolAbi from "./abi/uniswap-v3-pool";

  function addFactoryMetadata<T, F>(event: DecodedEvent<T, F>) {
    return {
      ...event.event,
      blockNumber: event.block.number,
      factoryEvent: event.factory?.event,
    };
  }

  const decoder = evmEventDecoder({
    range: { from: 12369621 },
    contracts: contractFactory({
      address: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
      event: factoryAbi.events.PoolCreated,
      childAddressField: "pool",
      database: contractFactorySqliteStore({ path: "./uniswap-v3-pools.sqlite" }),
    }),
    events: { swap: poolAbi.events.Swap, mint: poolAbi.events.Mint },
  }).pipe(({ swap, mint }) => ({
    swap: swap.map(addFactoryMetadata),
    mint: mint.map(addFactoryMetadata),
  }));

  await evmPortalStream({
    id: "uniswap-v3-factory-metadata",
    portal: "https://portal.sqd.dev/datasets/ethereum-mainnet",
    outputs: decoder,
  }).pipeTo(createTarget({
    write: async ({ logger, read }) => {
      for await (const { data } of read()) {
        for (const s of data.swap) {
          logger.info({
            pool: s.factoryEvent?.pool,
            token0: s.factoryEvent?.token0,
            token1: s.factoryEvent?.token1,
            amount0: s.amount0.toString(),
            amount1: s.amount1.toString(),
          });
        }
      }
    },
  }));
  ```
</Expandable>

## Multiple factories

Pass separate `evmEventDecoder` outputs to track contracts from different factory addresses in a single pipeline.

<Expandable title="Full example: Uniswap V2 and V3 in one pipeline">
  ```ts theme={"system"}
  import {
    evmPortalStream, evmEventDecoder, contractFactory, contractFactorySqliteStore,
  } from "@subsquid/pipes/evm";
  import { createTarget } from "@subsquid/pipes";
  import * as uniswapV3FactoryAbi from "./abi/uniswap-v3-factory";
  import * as uniswapV3PoolAbi from "./abi/uniswap-v3-pool";
  import * as uniswapV2FactoryAbi from "./abi/uniswap-v2-factory";
  import * as uniswapV2PairAbi from "./abi/uniswap-v2-pair";

  await evmPortalStream({
    id: "uniswap-v2-v3-swaps",
    portal: "https://portal.sqd.dev/datasets/ethereum-mainnet",
    outputs: {
      v3: evmEventDecoder({
        range: { from: 12369621 },
        contracts: contractFactory({
          address: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
          event: uniswapV3FactoryAbi.events.PoolCreated,
          childAddressField: "pool",
          database: contractFactorySqliteStore({ path: "./v3-pools.sqlite" }),
        }),
        events: { swap: uniswapV3PoolAbi.events.Swap },
      }),
      v2: evmEventDecoder({
        range: { from: 10000835 },
        contracts: contractFactory({
          address: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
          event: uniswapV2FactoryAbi.events.PairCreated,
          childAddressField: "pair",
          database: contractFactorySqliteStore({ path: "./v2-pairs.sqlite" }),
        }),
        events: { swap: uniswapV2PairAbi.events.Swap },
      }),
    },
  }).pipeTo(createTarget({
    write: async ({ logger, read }) => {
      for await (const { data } of read()) {
        logger.info({ v3Swaps: data.v3.swap.length, v2Swaps: data.v2.swap.length });
      }
    },
  }));
  ```
</Expandable>


## Related topics

- [Quickstart](/en/sdk/pipes-sdk/evm/quickstart.md)
- [Transformer](/en/sdk/pipes-sdk/evm/reference/basic-components/transformer.md)
- [Factory Contracts](/en/sdk/squid-sdk/evm/guides/advanced/factory-contracts.md)
- [evmEventDecoder](/en/sdk/pipes-sdk/evm/reference/utility-components/evm-decoder.md)
- [Migrate to 1.0](/en/sdk/pipes-sdk/evm/migration.md)
