For AI agents: the documentation index is at /llms.txt. Markdown versions of pages are available by appending .md to the URL.
Skip to main content

How to Index Uniswap v4 Swaps

Uniswap v4 keeps every pool inside one contract. There is no pool contract per pair and no factory to follow, so indexing v4 means indexing a single address, the PoolManager, and reading the pool id carried on each event.

This page has both routes. Build a minimal indexer yourself, which is the walkthrough below, or start from the official Uniswap V4 indexer, the production implementation that powers v4.xyz, which is described further down.

In a hurry, skip to the agent prompt.

Prerequisites​

  • Node.js 22 or higher
  • Docker, which envio dev uses for Postgres and Hasura
  • An API token

PoolManager addresses​

Two of the deployments, from Uniswap's own deployment list:

ChainChain IDPoolManager
Ethereum10x000000000004444c5dc75cB358380D2e3dE08A90
Base84530x498581fF718922c3f8e6A244956aF099B2652b2b

Set up​

mkdir uniswap-v4-swaps && cd uniswap-v4-swaps
pnpm init
pnpm pkg set type=module
pnpm add envio

config.yaml

# yaml-language-server: $schema=./node_modules/envio/evm.schema.json
name: uniswap-v4-swaps
contracts:
- name: PoolManager
handler: src/EventHandlers.ts
events:
- event: "Initialize(bytes32 indexed id, address indexed currency0, address indexed currency1, uint24 fee, int24 tickSpacing, address hooks, uint160 sqrtPriceX96, int24 tick)"
- event: "Swap(bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee)"
chains:
- id: 8453 # Base
start_block: 25350000
contracts:
- name: PoolManager
address: "0x498581fF718922c3f8e6A244956aF099B2652b2b"

The signatures come from IPoolManager.sol. Solidity declares id as PoolId, the currencies as Currency and the hook as IHooks, which are bytes32, address and address underneath, so that is what the config uses. The Swap signature above hashes to 0x40e9cecb9f5f1f1c5b9c97dec2917b7ee92e57ba5563708daca94dd84ad7112f, which is the topic the PoolManager emits.

schema.graphql

type Pool {
id: ID!
currency0: String!
currency1: String!
fee: Int!
tickSpacing: Int!
hooks: String!
swapCount: Int!
}

type Swap {
id: ID!
pool_id: String!
sender: String!
amount0: BigInt!
amount1: BigInt!
tick: Int!
blockNumber: Int!
blockTimestamp: Int!
}

src/EventHandlers.ts

import { indexer } from "envio";

indexer.onEvent(
{ contract: "PoolManager", event: "Initialize" },
async ({ event, context }) => {
context.Pool.set({
id: event.params.id,
currency0: event.params.currency0,
currency1: event.params.currency1,
fee: Number(event.params.fee),
tickSpacing: Number(event.params.tickSpacing),
hooks: event.params.hooks,
swapCount: 0,
});
},
);

indexer.onEvent(
{ contract: "PoolManager", event: "Swap" },
async ({ event, context }) => {
context.Swap.set({
id: `${event.chainId}_${event.block.number}_${event.logIndex}`,
pool_id: event.params.id,
sender: event.params.sender,
amount0: event.params.amount0,
amount1: event.params.amount1,
tick: Number(event.params.tick),
blockNumber: event.block.number,
blockTimestamp: event.block.timestamp,
});

const pool = await context.Pool.get(event.params.id);
if (pool) {
context.Pool.set({ ...pool, swapCount: pool.swapCount + 1 });
}
},
);

Every numeric event parameter arrives as a BigInt, including the small ones like fee, tickSpacing and tick. Writing one of those straight into an Int! field fails with cannot cast type bigint to integer[], so wrap them in Number(), and keep BigInt! for the amounts.

Put your API token in a .env file in the project root, which is where the indexer reads it from:

ENVIO_API_TOKEN=your_token_here

Then start it:

pnpm envio dev

Querying the result​

Busiest pools, from the counter the handler keeps:

query BusiestPools {
Pool(limit: 3, order_by: { swapCount: desc }) {
id
currency0
currency1
fee
swapCount
}
}

Indexing Base from block 25,350,000 to 26,418,699 gave 4,344 pools and 750,230 swaps, and the busiest pool came back like this:

{
"id": "0x7af84d60777413f90cc511a83cf702b128bc885f84d6ca8be4b60063328d907a",
"currency0": "0x0000000000000000000000000000000000000000",
"currency1": "0x000000000D564D5be76f7f0d28fE52605afC7Cf8",
"fee": 0,
"swapCount": 159420
}

Recent swaps for one pool:

query PoolSwaps {
Swap(
where: { pool_id: { _eq: "0x96d4b53a38337a5733179751781178a2613306063c511b78cd02684739288c0a" } }
order_by: { blockNumber: desc }
limit: 3
) {
sender
amount0
amount1
blockNumber
}
}
{
"sender": "0x6fF5693b99212Da76ad316178A184AB56D299b43",
"amount0": "995365987060141",
"amount1": "-2701188",
"blockNumber": 26418690
}

Amounts are signed and written from the pool's side, so one is negative and the other positive on every swap.

Reading v4 data​

  • Native ETH is the zero address. v4 handles ETH directly rather than through WETH. Currencies are sorted numerically in the pool key, and the zero address sorts first, so a pool that trades ETH always carries it as currency0 and never as currency1. In the run above, 723 of the 4,344 pools had native ETH as currency0
  • Most pools carry hooks. hooks is the address of the contract attached to the pool, and it is the zero address when there is none. In the same run, 3,107 of the 4,344 pools had one
  • fee is in millionths, capped at 1,000,000, so 3000 is 0.30% and 500 is 0.05%. A fee of exactly 0x800000 means the pool sets its fee dynamically, which PoolKey.sol describes as the highest bit being set. A fee of 0 is a pool whose LP fee is zero, which is common where a hook charges instead
  • Swap carries its own fee, which is the fee taken on that swap rather than the pool's LP fee from Initialize. The handler above ignores it, so do not confuse the two if you add it to the schema
  • The pool id is a hash, not an address. Initialize is the only event that maps it to the currencies, fee, tick spacing and hook, which is why this indexer stores pools as well as swaps

Adding more chains​

Add another entry under chains with that chain's id, start block and PoolManager address. The contract and handler are already shared, so nothing else changes. See multichain indexing.

Hand this to your coding agent​

Set up an Envio HyperIndex indexer for Uniswap v4 swaps on Base.

- pnpm init, pnpm pkg set type=module, pnpm add envio
- config.yaml: contract PoolManager at 0x498581fF718922c3f8e6A244956aF099B2652b2b on
chain 8453, with the Initialize and Swap event signatures from IPoolManager.sol.
PoolId is bytes32, Currency is address and IHooks is address in the signature
- schema.graphql: a Pool entity (currencies, fee, tickSpacing, hooks, swapCount) and a
Swap entity (pool_id, sender, amount0, amount1, tick, block number and timestamp)
- src/EventHandlers.ts: import { indexer } from "envio" and register both events with
indexer.onEvent. Wrap fee, tickSpacing and tick in Number(), since every numeric
param arrives as a BigInt, and keep the amounts as BigInt
- Put ENVIO_API_TOKEN in a .env file in the project root
- Run it with pnpm envio dev, which needs Docker

Docs: https://docs.envio.dev/docs/HyperIndex/example-uniswap-v4-multi-chain-indexer

The production indexer​

The official Uniswap V4 indexer goes much further than the walkthrough above. It is the same indexer that powers the v4.xyz website, providing real-time data for the Uniswap V4 interface.

Key Features​

  • Multichain Support: Indexes Uniswap V4 across the 18 chains listed in its config.yaml, in real time
  • Complete Pool Metrics: Tracks pool statistics including volume, TVL, fees, and other critical metrics
  • Swap Analysis: Monitors swap events and liquidity changes with high precision
  • Hook Stats: Tracks every hook with the pools and swaps it touches and its TVL, volume and fees
  • Production Ready: Powers the official v4.xyz interface with production-grade reliability
  • Fast Syncing: HyperIndex reading HyperSync leads most scenarios in our benchmarks, which run in CI and check each tool's output against ground truth

V4 gif

Technical Overview​

This indexer is built using TypeScript and provides a unified GraphQL API for accessing Uniswap V4 data across all supported chains. The architecture is designed to handle high throughput and maintain consistency across different chains.

Performance Advantages​

  • Reads from HyperSync rather than RPC, which is where the speed comes from. The benchmarks page has the measured figures per scenario
  • Real-time Updates: Maintains low latency for new blocks while efficiently managing historical data

Use Cases​

  • Power analytics dashboards and trading interfaces
  • Monitor DeFi positions and protocol health
  • Track historical performance of Uniswap V4 pools
  • Build custom notifications and alerts
  • Analyze hook interactions and their impact

Getting Started​

To use this indexer, you can:

  1. Clone the repository
  2. Follow the installation instructions in the README
  3. Run the indexer locally or deploy it to a production environment
  4. Access indexed data through the GraphQL API

Contribution​

The Uniswap V4 indexer is actively maintained and welcomes contributions from the community. If you'd like to contribute or report issues, please visit the GitHub repository.

note

This is an official reference implementation that powers the v4.xyz website. While extensively tested in production, remember to validate the data for your specific use case. The indexer is continuously updated to support the latest Uniswap V4 features and optimizations.