Basic Factory
Index swaps from Uniswap V3 pools dynamically discovered via factory.
Use case : Track events from contracts created by a factory.
This example requires generated ABI files. Generate them using: npx @subsquid/evm-typegen src/abi abi/UniswapV3Factory.json
npx @subsquid/evm-typegen src/abi abi/UniswapV3Pool.json
See the Custom ABI section for details.
import {
evmPortalSource ,
evmDecoder ,
factory ,
factorySqliteDatabase ,
} 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" ;
async function main () {
const source = evmPortalSource ({
portal: "https://portal.sqd.dev/datasets/ethereum-mainnet" ,
});
const decoder = evmDecoder ({
range: { from: 12369621 },
contracts: factory ({
address: "0x1f98431c8ad98523631ae4a59f267346ea31f984" ,
event: factoryAbi . events . PoolCreated ,
parameter: "pool" ,
database: factorySqliteDatabase ({
path: "./uniswap-v3-pools.sqlite" ,
}),
}),
events: {
swap: poolAbi . events . Swap ,
},
});
const target = createTarget ({
write : async ({ logger , read }) => {
for await ( const { data } of read ()) {
logger . info ( `Parsed ${ data . swap . length } swaps` );
}
},
});
await source . pipe ( decoder ). pipeTo ( target );
}
void main ();
Filter Factory Events
Track only specific factory-created contracts by filtering creation events.
Use case : Reduce data transfer and processing by filtering factory events at the source.
This example requires generated ABI files. Generate them using: npx @subsquid/evm-typegen src/abi abi/UniswapV3Factory.json
npx @subsquid/evm-typegen src/abi abi/UniswapV3Pool.json
See the Custom ABI section for details.
import {
evmPortalSource ,
evmDecoder ,
factory ,
factorySqliteDatabase ,
} 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" ;
async function main () {
const WETH = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" ;
const source = evmPortalSource ({
portal: "https://portal.sqd.dev/datasets/ethereum-mainnet" ,
});
const decoder = evmDecoder ({
range: { from: 12369621 },
contracts: factory ({
address: "0x1f98431c8ad98523631ae4a59f267346ea31f984" ,
event: {
event: factoryAbi . events . PoolCreated ,
params: {
token0: WETH , // Only pools with WETH as token0
},
},
parameter: "pool" ,
database: factorySqliteDatabase ({
path: "./uniswap-v3-weth-pools.sqlite" ,
}),
}),
events: {
swap: poolAbi . events . Swap ,
},
});
const target = createTarget ({
write : async ({ logger , read }) => {
for await ( const { data } of read ()) {
logger . info ( `Parsed ${ data . swap . length } swaps from WETH pools` );
}
},
});
await source . pipe ( decoder ). pipeTo ( target );
}
void main ();
How It Works
Portal Filtering : Topic filters are sent to the Portal API, reducing data transfer at the source.
Database Filtering : The SQLite adapter stores only matching factory events.
Event Decoding : Only child contracts from filtered factory events are decoded.
Case Insensitive : Parameter matching is case-insensitive for addresses.
Filter Syntax
Single Parameter
Filter by one indexed parameter:
factory ({
address: "0x1f98431c8ad98523631ae4a59f267346ea31f984" ,
event: {
event: factoryAbi . events . PoolCreated ,
params: {
token0: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" , // WETH
},
},
parameter: "pool" ,
database: factorySqliteDatabase ({
path: "./uniswap-v3-weth-pools.sqlite" ,
}),
})
Multiple Parameters
Filter by multiple indexed parameters (AND logic):
factory ({
address: "0x1f98431c8ad98523631ae4a59f267346ea31f984" ,
event: {
event: factoryAbi . events . PoolCreated ,
params: {
token0: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" , // WETH
token1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" , // USDC
fee: 3000 , // 0.3% fee tier
},
},
parameter: "pool" ,
database: factorySqliteDatabase ({
path: "./uniswap-v3-weth-usdc-pools.sqlite" ,
}),
})
Array Values
Use arrays to match multiple values (OR logic):
const WETH = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" ;
const USDC = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" ;
const USDT = "0xdac17f958d2ee523a2206206994597c13d831ec7" ;
factory ({
address: "0x1f98431c8ad98523631ae4a59f267346ea31f984" ,
event: {
event: factoryAbi . events . PoolCreated ,
params: {
token0: [ WETH , USDC , USDT ], // Match any of these as token0
},
},
parameter: "pool" ,
database: factorySqliteDatabase ({
path: "./uniswap-v3-major-tokens.sqlite" ,
}),
})
Use arrays to match multiple values. For example, filter pools for both WETH and USDC as token0.
Only indexed parameters can be used for filtering. Check your contract ABI to verify which parameters are indexed. Non-indexed parameters are ignored.
Common Use Cases
WETH Trading Pairs - Focus on pools involving wrapped ETH for liquidity analysis
Stablecoin Pairs - Filter for USDC, USDT, or DAI to track stablecoin markets
Multi-Token Monitoring - Use arrays to track pools for a portfolio of tokens
Fee Tier Analysis - Filter by specific fee tiers (500, 3000, 10000) to analyze different pool types
Access factory event data in decoded events.
Use case : Include factory context with each decoded event.
import {
evmPortalSource ,
evmDecoder ,
factory ,
DecodedEvent ,
factorySqliteDatabase ,
} 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" ;
// Transform to include factory event
function addFactoryMetadata < T , F >( event : DecodedEvent < T , F >) {
return {
... event . event ,
blockNumber: event . block . number ,
factoryEvent: event . factory ?. event ,
};
}
async function main () {
const source = evmPortalSource ({
portal: "https://portal.sqd.dev/datasets/ethereum-mainnet" ,
});
const decoder = evmDecoder ({
range: { from: 12369621 },
contracts: factory ({
address: "0x1f98431c8ad98523631ae4a59f267346ea31f984" ,
event: factoryAbi . events . PoolCreated ,
parameter: "pool" ,
database: factorySqliteDatabase ({
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 ),
}));
const target = 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 (),
});
}
}
},
});
await source . pipe ( decoder ). pipeTo ( target );
}
void main ();
See all 64 lines
Pre-indexing Factory Experimental
Pre-populate factory database before main pipeline.
Use case : Discover all existing contracts before starting main indexing.
This is an experimental feature and may change in future versions. The pre-indexing request is limited and this approach won’t work for thousands of addresses. Use this feature only for small to medium-sized factory contract sets.
import {
evmPortalSource ,
evmDecoder ,
factory ,
factorySqliteDatabase ,
} 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" ;
// Create factory database
const factoryDb = factorySqliteDatabase ({
path: "./uniswap-v3-pools.sqlite" ,
});
// Step 1: Pre-index all historical pool creations
console . log ( "Pre-indexing pools..." );
const preIndexSource = evmPortalSource ({
portal: "https://portal.sqd.dev/datasets/ethereum-mainnet" ,
});
const preIndexDecoder = evmDecoder ({
range: { from: 12369621 , to: 20000000 },
contracts: [ "0x1f98431c8ad98523631ae4a59f267346ea31f984" ],
events: {
poolCreated: factoryAbi . events . PoolCreated ,
},
});
async function main () {
const preIndexTarget = createTarget ({
write : async ({ logger , read }) => {
for await ( const { data } of read ()) {
for ( const event of data . poolCreated ) {
await factoryDb . add ( event . event . pool );
logger . info ( `Added pool: ${ event . event . pool } ` );
}
}
},
});
await preIndexSource . pipe ( preIndexDecoder ). pipeTo ( preIndexTarget );
console . log ( "Pre-indexing complete. Starting main pipeline..." );
// Step 2: Run main pipeline with populated factory database
const mainSource = evmPortalSource ({
portal: "https://portal.sqd.dev/datasets/ethereum-mainnet" ,
});
const mainDecoder = evmDecoder ({
range: { from: 20000000 },
contracts: factory ({
address: "0x1f98431c8ad98523631ae4a59f267346ea31f984" ,
event: factoryAbi . events . PoolCreated ,
parameter: "pool" ,
database: factoryDb ,
}),
events: {
swap: poolAbi . events . Swap ,
},
});
const mainTarget = createTarget ({
write : async ({ logger , read }) => {
for await ( const { data } of read ()) {
logger . info ( `Processed ${ data . swap . length } swaps` );
}
},
});
await mainSource . pipe ( mainDecoder ). pipeTo ( mainTarget );
}
void main ();
See all 76 lines
Multiple Factories
Track contracts from multiple factory contracts.
Use case : Index events from contracts created by different factories.
import {
evmPortalSource ,
evmDecoder ,
factory ,
factorySqliteDatabase ,
} from "@subsquid/pipes/evm" ;
import * as uniswapV3FactoryAbi from "./abi/uniswap-v3-factory" ;
import * as uniswapV2FactoryAbi from "./abi/uniswap-v2-factory" ;
import * as poolAbi from "./abi/pool" ;
const source = evmPortalSource ({
portal: "https://portal.sqd.dev/datasets/ethereum-mainnet" ,
});
const pipeline = source . pipeComposite ({
v3: evmDecoder ({
range: { from: 12369621 },
contracts: factory ({
address: "0x1f98431c8ad98523631ae4a59f267346ea31f984" ,
event: uniswapV3FactoryAbi . events . PoolCreated ,
parameter: "pool" ,
database: factorySqliteDatabase ({ path: "./v3-pools.sqlite" }),
}),
events: { swap: poolAbi . events . Swap },
}),
v2: evmDecoder ({
range: { from: 10000835 },
contracts: factory ({
address: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f" ,
event: uniswapV2FactoryAbi . events . PairCreated ,
parameter: "pair" ,
database: factorySqliteDatabase ({ path: "./v2-pairs.sqlite" }),
}),
events: { swap: poolAbi . events . Swap },
}),
});
async function main () {
const target = createTarget ({
write : async ({ logger , read }) => {
for await ( const { data } of read ()) {
logger . info ({
v3Swaps: data . v3 . swap . length ,
v2Swaps: data . v2 . swap . length ,
});
}
},
});
await pipeline . pipeTo ( target );
}
void main ();
See all 53 lines