Uniswap V3 Adapter
What It Tracks
- swap: Swap events (same as V2)
- lp: NFT LP position holdings
Design Reasoning
Why is V3 more complex than V2?UniV3 uses NFTs for LP positions:
- Each position has a unique
tokenId - Positions have tick ranges (concentrated liquidity)
- Need to track
IncreaseLiquidityandDecreaseLiquidityseparately
V3 can have many pools, so we:
- Listen for
PoolCreatedevents from the factory - Index all pools in Redis
- Filter swap events by indexed pools
The NonFungiblePositionManager (NFPM) is the entry point for:
- Minting new positions (Transfer from 0x0)
- Modifying positions (IncreaseLiquidity/DecreaseLiquidity)
- Transferring positions (Transfer between users)
Manifest
export const manifest = {
name: 'uniswap-v3',
version: '0.0.1',
chainArch: 'evm',
trackables: {
swap: {
kind: 'action',
quantityType: 'token_based',
params: {
factoryAddress: evmAddress('The Uniswap V3 factory address'),
nonFungiblePositionManagerAddress: evmAddress('The NFPM address'),
},
assetSelectors: {
swapLegAddress: evmAddress('The token0 or token1 address to price'),
},
},
lp: {
kind: 'position',
quantityType: 'token_based',
params: {
factoryAddress: evmAddress('The Uniswap V3 factory address'),
nonFungiblePositionManagerAddress: evmAddress('The NFPM address'),
},
// No assetSelectors — NFT tokenId uniquely identifies position
requiredPricer: univ3lpFeed,
},
},
} as const satisfies Manifest;
Event Handling Architecture
buildSqdProcessor: (base) => {
let processor = base;
// 1. Track pool creation (always needed)
processor = processor.addLog({
address: Array.from(factoryAddrs),
topic0: [poolCreatedTopic],
});
// 2. Track LP position events (if lp configured)
if (config.lp.length > 0) {
processor = processor.addLog({
address: Array.from(nfpmAddrs),
topic0: [transferTopic, increaseLiquidityTopic, decreaseLiquidityTopic],
});
}
// 3. Track swap events (if swap configured)
if (config.swap.length > 0) {
processor = processor.addLog({
topic0: [swapTopic], // All pools, filter later
});
}
return processor;
},
onLog: async ({ log, emitFns, redis, sqdRpcCtx }) => {
// Handle PoolCreated — index the pool
if (log.topic0 === poolCreatedTopic) {
const { pool, token0, token1 } = univ3factoryAbi.events.PoolCreated.decode(log);
await redis.sadd(POOL_INDEX_KEY, pool.toLowerCase());
await redis.hset(`pool:${pool.toLowerCase()}:tokens`, {
token0: token0.toLowerCase(),
token1: token1.toLowerCase(),
});
}
// Handle Swap — check if pool is from our factory
if (log.topic0 === swapTopic) {
const isOurPool = await redis.sismember(POOL_INDEX_KEY, log.address.toLowerCase());
if (!isOurPool) return;
// ... handle swap
}
// Handle LP events
if (log.topic0 === increaseLiquidityTopic) { /* ... */ }
if (log.topic0 === decreaseLiquidityTopic) { /* ... */ }
if (log.topic0 === transferTopic) { /* ... */ }
}
Example Config
{
"adapterConfig": {
"adapterId": "uniswap-v3",
"config": {
"swap": [
{
"params": {
"factoryAddress": "0xCdBCd51a5E8728E0AF4895ce5771b7d17fF71959",
"nonFungiblePositionManagerAddress": "0xe43ca1dee3f0fc1e2df73a0745674545f11a59f5"
},
"assetSelectors": {
"swapLegAddress": "0x4200000000000000000000000000000000000006"
},
"pricing": {
"assetType": "erc20",
"priceFeed": { "kind": "pegged", "usdPegValue": 4000 }
}
}
],
"lp": [
{
"params": {
"factoryAddress": "0xCdBCd51a5E8728E0AF4895ce5771b7d17fF71959",
"nonFungiblePositionManagerAddress": "0xe43ca1dee3f0fc1e2df73a0745674545f11a59f5"
},
"pricing": {
"assetType": "erc721",
"priceFeed": {
"kind": "univ3lp",
"token0": { "priceFeed": { "kind": "coingecko", "id": "weth" } },
"token1": { "priceFeed": { "kind": "pegged", "usdPegValue": 1 } }
}
}
}
]
}
}
}
Config Fields
| Trackable | Field | Description |
|---|---|---|
| swap/lp | params.factoryAddress | UniV3 Factory contract |
| swap/lp | params.nonFungiblePositionManagerAddress | NFPM contract |
| swap | assetSelectors.swapLegAddress | Which token to price |
| lp | pricing.assetType | Should be "erc721" for NFTs |
Key Differences from V2
| Aspect | V2 | V3 |
|---|---|---|
| LP Token | ERC20 | ERC721 (NFT) |
| Position ID | Pool address | NFT tokenId |
| Tracking | Transfer events only | Transfer + IncreaseLiquidity + DecreaseLiquidity |
| Pool Discovery | Direct config | Factory indexing |
How to Modify for Your Protocol (e.g., PancakeSwap V3, Ichi)
Same architecture, different addresses?- Update factory and NFPM addresses in config
- No code changes needed
- Update ABI files and regenerate types
- Event handlers stay the same pattern
- Add new topics to
buildSqdProcessor - Add new handlers in
onLog