# Uniswap V3 Subgraph This project implements a comprehensive subgraph for indexing and querying Uniswap V3 protocol data on Ethereum. The subgraph tracks all pool creation events, swaps, liquidity provision operations (mints/burns), and generates time-series analytics including hourly and daily aggregates for tokens, pools, and the entire Uniswap V3 ecosystem. Built with The Graph protocol, it processes blockchain events in real-time and maintains an indexed database optimized for efficient GraphQL queries. The subgraph monitors the Uniswap V3 Factory contract deployed at `0x1F98431c8aD98523631AE4a59f267346ea31F984` starting from block 12369621 on Ethereum mainnet. It dynamically instantiates pool data sources as new pools are created, tracking all swap activity, liquidity changes, price movements, fees, and total value locked (TVL). The system calculates USD-denominated values by deriving ETH prices from stablecoin pools and computing token prices based on their liquidity in whitelisted token pools, enabling accurate analytics across the entire DeFi ecosystem. ## Core Data Schema ### Factory Entity The Factory entity tracks protocol-wide aggregate statistics across all Uniswap V3 pools. ```graphql { factory(id: "0x1F98431c8aD98523631AE4a59f267346ea31F984") { poolCount txCount totalVolumeUSD totalVolumeETH totalFeesUSD totalFeesETH totalValueLockedUSD totalValueLockedETH owner } } ``` ### Pool Entity Pool entities represent individual Uniswap V3 liquidity pools with specific token pairs and fee tiers. ```graphql { pool(id: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8") { token0 { symbol name decimals } token1 { symbol name decimals } feeTier liquidity sqrtPrice tick token0Price token1Price volumeUSD volumeToken0 volumeToken1 txCount totalValueLockedUSD totalValueLockedToken0 totalValueLockedToken1 feesUSD createdAtTimestamp createdAtBlockNumber } } ``` ### Token Entity Token entities store comprehensive data for each ERC20 token traded on Uniswap V3. ```graphql { token(id: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") { symbol name decimals totalSupply volume volumeUSD untrackedVolumeUSD feesUSD txCount poolCount totalValueLocked totalValueLockedUSD derivedETH whitelistPools } } ``` ### Swap Entity Swap entities capture individual token exchange transactions with full details. ```graphql { swaps( first: 10 orderBy: timestamp orderDirection: desc where: { pool: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" } ) { id transaction { id blockNumber timestamp gasUsed gasPrice } timestamp pool { id token0 { symbol } token1 { symbol } } token0 { symbol } token1 { symbol } sender recipient origin amount0 amount1 amountUSD sqrtPriceX96 tick logIndex } } ``` ### Mint Entity Mint entities track liquidity provision events where users add liquidity to pools. ```graphql { mints( first: 10 orderBy: timestamp orderDirection: desc where: { pool: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" } ) { id transaction { id timestamp } timestamp pool { id } token0 { symbol } token1 { symbol } owner sender origin amount amount0 amount1 amountUSD tickLower tickUpper logIndex } } ``` ### Burn Entity Burn entities represent liquidity withdrawal events from pools. ```graphql { burns( first: 10 orderBy: timestamp orderDirection: desc where: { pool: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" } ) { id transaction { id timestamp } timestamp pool { id } token0 { symbol } token1 { symbol } owner origin amount amount0 amount1 amountUSD tickLower tickUpper logIndex } } ``` ### Tick Entity Tick entities store liquidity distribution data at specific price points in pools. ```graphql { ticks( first: 100 where: { pool: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" } orderBy: tickIdx ) { id tickIdx pool { id } liquidityGross liquidityNet price0 price1 createdAtTimestamp createdAtBlockNumber } } ``` ## Time-Series Analytics ### UniswapDayData Entity Protocol-wide daily aggregated statistics for tracking overall Uniswap V3 performance. ```graphql { uniswapDayDatas( first: 30 orderBy: date orderDirection: desc ) { id date volumeETH volumeUSD volumeUSDUntracked feesUSD txCount tvlUSD } } ``` ### PoolDayData Entity Daily statistics for individual pools including OHLC price data. ```graphql { poolDayDatas( first: 30 orderBy: date orderDirection: desc where: { pool: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" } ) { id date pool { token0 { symbol } token1 { symbol } } liquidity sqrtPrice token0Price token1Price tick tvlUSD volumeToken0 volumeToken1 volumeUSD feesUSD txCount open high low close } } ``` ### PoolHourData Entity Hourly pool statistics for high-resolution time-series analysis. ```graphql { poolHourDatas( first: 24 orderBy: periodStartUnix orderDirection: desc where: { pool: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" } ) { id periodStartUnix pool { id } liquidity sqrtPrice token0Price token1Price tick tvlUSD volumeToken0 volumeToken1 volumeUSD feesUSD txCount open high low close } } ``` ### TokenDayData Entity Daily token-level metrics aggregated across all pools containing the token. ```graphql { tokenDayDatas( first: 30 orderBy: date orderDirection: desc where: { token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" } ) { id date token { symbol name } volume volumeUSD untrackedVolumeUSD totalValueLocked totalValueLockedUSD priceUSD feesUSD open high low close } } ``` ### TokenHourData Entity Hourly token statistics for granular price and volume tracking. ```graphql { tokenHourDatas( first: 24 orderBy: periodStartUnix orderDirection: desc where: { token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" } ) { id periodStartUnix token { symbol } volume volumeUSD untrackedVolumeUSD totalValueLocked totalValueLockedUSD priceUSD feesUSD open high low close } } ``` ## Event Handlers ### handlePoolCreated Function Processes PoolCreated events from the Uniswap V3 Factory contract to initialize new pool entities. ```typescript import { PoolCreated } from '../types/Factory/Factory' import { Pool, Token, Factory } from '../types/schema' import { Pool as PoolTemplate } from '../types/templates' import { fetchTokenSymbol, fetchTokenName, fetchTokenDecimals } from '../utils/token' import { ZERO_BI, ZERO_BD } from '../utils/constants' export function handlePoolCreated(event: PoolCreated): void { let factory = Factory.load(FACTORY_ADDRESS) if (factory === null) { factory = new Factory(FACTORY_ADDRESS) factory.poolCount = ZERO_BI factory.totalVolumeETH = ZERO_BD factory.totalVolumeUSD = ZERO_BD factory.txCount = ZERO_BI factory.totalValueLockedUSD = ZERO_BD } factory.poolCount = factory.poolCount.plus(ONE_BI) let pool = new Pool(event.params.pool.toHexString()) let token0 = Token.load(event.params.token0.toHexString()) if (token0 === null) { token0 = new Token(event.params.token0.toHexString()) token0.symbol = fetchTokenSymbol(event.params.token0) token0.name = fetchTokenName(event.params.token0) token0.decimals = fetchTokenDecimals(event.params.token0) token0.derivedETH = ZERO_BD token0.volumeUSD = ZERO_BD } pool.token0 = token0.id pool.token1 = token1.id pool.feeTier = BigInt.fromI32(event.params.fee) pool.createdAtTimestamp = event.block.timestamp pool.liquidity = ZERO_BI pool.volumeUSD = ZERO_BD pool.save() PoolTemplate.create(event.params.pool) token0.save() factory.save() } ``` ### handleSwap Function Processes Swap events to track token exchanges, update prices, volumes, and fees. ```typescript import { Swap as SwapEvent } from '../types/templates/Pool/Pool' import { Pool, Token, Factory, Swap, Bundle } from '../types/schema' import { convertTokenToDecimal, loadTransaction } from '../utils' import { getTrackedAmountUSD, getEthPriceInUSD, findEthPerToken } from '../utils/pricing' import { updatePoolDayData, updateTokenDayData } from '../utils/intervalUpdates' export function handleSwap(event: SwapEvent): void { let bundle = Bundle.load('1')! let factory = Factory.load(FACTORY_ADDRESS)! let pool = Pool.load(event.address.toHexString())! let token0 = Token.load(pool.token0)! let token1 = Token.load(pool.token1)! let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals) let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals) let amount0Abs = amount0.lt(ZERO_BD) ? amount0.times(BigDecimal.fromString('-1')) : amount0 let amount1Abs = amount1.lt(ZERO_BD) ? amount1.times(BigDecimal.fromString('-1')) : amount1 let amountTotalUSDTracked = getTrackedAmountUSD( amount0Abs, token0, amount1Abs, token1 ).div(BigDecimal.fromString('2')) let feesUSD = amountTotalUSDTracked .times(pool.feeTier.toBigDecimal()) .div(BigDecimal.fromString('1000000')) factory.totalVolumeUSD = factory.totalVolumeUSD.plus(amountTotalUSDTracked) factory.totalFeesUSD = factory.totalFeesUSD.plus(feesUSD) pool.volumeUSD = pool.volumeUSD.plus(amountTotalUSDTracked) pool.feesUSD = pool.feesUSD.plus(feesUSD) pool.liquidity = event.params.liquidity pool.tick = BigInt.fromI32(event.params.tick) pool.sqrtPrice = event.params.sqrtPriceX96 token0.volumeUSD = token0.volumeUSD.plus(amountTotalUSDTracked) token1.volumeUSD = token1.volumeUSD.plus(amountTotalUSDTracked) let transaction = loadTransaction(event) let swap = new Swap(transaction.id + '#' + pool.txCount.toString()) swap.transaction = transaction.id swap.pool = pool.id swap.amount0 = amount0 swap.amount1 = amount1 swap.amountUSD = amountTotalUSDTracked swap.tick = BigInt.fromI32(event.params.tick) updatePoolDayData(event) updateTokenDayData(token0, event) updateTokenDayData(token1, event) swap.save() pool.save() factory.save() } ``` ### handleMint Function Processes Mint events when liquidity is added to pools, updating tick data and TVL. ```typescript import { Mint as MintEvent } from '../types/templates/Pool/Pool' import { Pool, Token, Mint, Tick } from '../types/schema' import { convertTokenToDecimal, loadTransaction } from '../utils' import { createTick } from '../utils/tick' export function handleMint(event: MintEvent): void { let pool = Pool.load(event.address.toHexString())! let token0 = Token.load(pool.token0)! let token1 = Token.load(pool.token1)! let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals) let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals) let amountUSD = amount0.times(token0.derivedETH.times(bundle.ethPriceUSD)) .plus(amount1.times(token1.derivedETH.times(bundle.ethPriceUSD))) token0.totalValueLocked = token0.totalValueLocked.plus(amount0) token1.totalValueLocked = token1.totalValueLocked.plus(amount1) if ( pool.tick !== null && BigInt.fromI32(event.params.tickLower).le(pool.tick) && BigInt.fromI32(event.params.tickUpper).gt(pool.tick) ) { pool.liquidity = pool.liquidity.plus(event.params.amount) } let lowerTickId = event.address.toHexString() + '#' + event.params.tickLower.toString() let lowerTick = Tick.load(lowerTickId) if (lowerTick === null) { lowerTick = createTick(lowerTickId, event.params.tickLower, pool.id, event) } lowerTick.liquidityGross = lowerTick.liquidityGross.plus(event.params.amount) lowerTick.liquidityNet = lowerTick.liquidityNet.plus(event.params.amount) let transaction = loadTransaction(event) let mint = new Mint(transaction.id + '#' + pool.txCount.toString()) mint.transaction = transaction.id mint.pool = pool.id mint.owner = event.params.owner mint.amount = event.params.amount mint.amount0 = amount0 mint.amount1 = amount1 mint.amountUSD = amountUSD mint.tickLower = BigInt.fromI32(event.params.tickLower) mint.tickUpper = BigInt.fromI32(event.params.tickUpper) mint.save() lowerTick.save() pool.save() } ``` ### handleBurn Function Processes Burn events when liquidity is removed from pools. ```typescript import { Burn as BurnEvent } from '../types/templates/Pool/Pool' import { Pool, Token, Burn, Tick } from '../types/schema' import { convertTokenToDecimal, loadTransaction } from '../utils' export function handleBurn(event: BurnEvent): void { let pool = Pool.load(event.address.toHexString())! let token0 = Token.load(pool.token0)! let token1 = Token.load(pool.token1)! let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals) let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals) token0.totalValueLocked = token0.totalValueLocked.minus(amount0) token1.totalValueLocked = token1.totalValueLocked.minus(amount1) if ( pool.tick !== null && BigInt.fromI32(event.params.tickLower).le(pool.tick) && BigInt.fromI32(event.params.tickUpper).gt(pool.tick) ) { pool.liquidity = pool.liquidity.minus(event.params.amount) } let lowerTickId = event.address.toHexString() + '#' + event.params.tickLower.toString() let lowerTick = Tick.load(lowerTickId) if (lowerTick) { lowerTick.liquidityGross = lowerTick.liquidityGross.minus(event.params.amount) lowerTick.liquidityNet = lowerTick.liquidityNet.minus(event.params.amount) lowerTick.save() } let transaction = loadTransaction(event) let burn = new Burn(transaction.id + '#' + pool.txCount.toString()) burn.transaction = transaction.id burn.pool = pool.id burn.owner = event.params.owner burn.amount = event.params.amount burn.amount0 = amount0 burn.amount1 = amount1 burn.tickLower = BigInt.fromI32(event.params.tickLower) burn.tickUpper = BigInt.fromI32(event.params.tickUpper) burn.save() pool.save() } ``` ## Pricing Functions ### getEthPriceInUSD Function Derives the ETH price in USD from the USDC/WETH pool with 0.3% fee tier. ```typescript import { Pool } from '../types/schema' import { BigDecimal } from '@graphprotocol/graph-ts' const USDC_WETH_03_POOL = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8' export function getEthPriceInUSD(): BigDecimal { let usdcPool = Pool.load(USDC_WETH_03_POOL) if (usdcPool !== null) { return usdcPool.token0Price } return ZERO_BD } ``` ### findEthPerToken Function Calculates the ETH-denominated price for any token based on liquidity in whitelisted pools. ```typescript import { Token, Pool, Bundle } from '../types/schema' import { BigDecimal } from '@graphprotocol/graph-ts' const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' const MINIMUM_ETH_LOCKED = BigDecimal.fromString('60') export function findEthPerToken(token: Token): BigDecimal { if (token.id == WETH_ADDRESS) { return ONE_BD } let whiteList = token.whitelistPools let largestLiquidityETH = ZERO_BD let priceSoFar = ZERO_BD for (let i = 0; i < whiteList.length; i++) { let pool = Pool.load(whiteList[i]) if (pool && pool.liquidity.gt(ZERO_BI)) { if (pool.token0 == token.id) { let token1 = Token.load(pool.token1)! let ethLocked = pool.totalValueLockedToken1.times(token1.derivedETH) if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(MINIMUM_ETH_LOCKED)) { largestLiquidityETH = ethLocked priceSoFar = pool.token1Price.times(token1.derivedETH) } } if (pool.token1 == token.id) { let token0 = Token.load(pool.token0)! let ethLocked = pool.totalValueLockedToken0.times(token0.derivedETH) if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(MINIMUM_ETH_LOCKED)) { largestLiquidityETH = ethLocked priceSoFar = pool.token0Price.times(token0.derivedETH) } } } } return priceSoFar } ``` ### sqrtPriceX96ToTokenPrices Function Converts the Q64.96 fixed-point square root price to human-readable token prices. ```typescript import { BigInt, BigDecimal } from '@graphprotocol/graph-ts' import { Token } from '../types/schema' import { exponentToBigDecimal, safeDiv } from '../utils' const Q192 = BigInt.fromI32(2).pow(192) export function sqrtPriceX96ToTokenPrices( sqrtPriceX96: BigInt, token0: Token, token1: Token ): BigDecimal[] { let num = sqrtPriceX96.times(sqrtPriceX96).toBigDecimal() let denom = BigDecimal.fromString(Q192.toString()) let price1 = num .div(denom) .times(exponentToBigDecimal(token0.decimals)) .div(exponentToBigDecimal(token1.decimals)) let price0 = safeDiv(BigDecimal.fromString('1'), price1) return [price0, price1] } ``` ### getTrackedAmountUSD Function Calculates tracked USD volume for swaps based on token whitelist status. ```typescript import { Token, Bundle } from '../types/schema' import { BigDecimal } from '@graphprotocol/graph-ts' export const WHITELIST_TOKENS: string[] = [ '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599' // WBTC ] export function getTrackedAmountUSD( tokenAmount0: BigDecimal, token0: Token, tokenAmount1: BigDecimal, token1: Token ): BigDecimal { let bundle = Bundle.load('1')! let price0USD = token0.derivedETH.times(bundle.ethPriceUSD) let price1USD = token1.derivedETH.times(bundle.ethPriceUSD) if (WHITELIST_TOKENS.includes(token0.id) && WHITELIST_TOKENS.includes(token1.id)) { return tokenAmount0.times(price0USD).plus(tokenAmount1.times(price1USD)) } if (WHITELIST_TOKENS.includes(token0.id) && !WHITELIST_TOKENS.includes(token1.id)) { return tokenAmount0.times(price0USD).times(BigDecimal.fromString('2')) } if (!WHITELIST_TOKENS.includes(token0.id) && WHITELIST_TOKENS.includes(token1.id)) { return tokenAmount1.times(price1USD).times(BigDecimal.fromString('2')) } return ZERO_BD } ``` ## Token Utility Functions ### fetchTokenSymbol Function Retrieves token symbol from ERC20 contracts with fallback support for non-standard implementations. ```typescript import { ERC20 } from '../types/Factory/ERC20' import { ERC20SymbolBytes } from '../types/Factory/ERC20SymbolBytes' import { Address } from '@graphprotocol/graph-ts' export function fetchTokenSymbol(tokenAddress: Address): string { let contract = ERC20.bind(tokenAddress) let symbolResult = contract.try_symbol() if (!symbolResult.reverted) { return symbolResult.value } let contractSymbolBytes = ERC20SymbolBytes.bind(tokenAddress) let symbolResultBytes = contractSymbolBytes.try_symbol() if (!symbolResultBytes.reverted) { return symbolResultBytes.value.toString() } return 'unknown' } ``` ### fetchTokenDecimals Function Fetches token decimals with validation and static definition fallback. ```typescript import { ERC20 } from '../types/Factory/ERC20' import { BigInt, Address } from '@graphprotocol/graph-ts' import { StaticTokenDefinition } from './staticTokenDefinition' export function fetchTokenDecimals(tokenAddress: Address): BigInt | null { let contract = ERC20.bind(tokenAddress) let decimalResult = contract.try_decimals() if (!decimalResult.reverted) { if (decimalResult.value.lt(BigInt.fromI32(255))) { return decimalResult.value } } let staticDefinition = StaticTokenDefinition.fromAddress(tokenAddress) if (staticDefinition) { return staticDefinition.decimals } return null } ``` ## Deployment and Usage ### Building the Subgraph Compile TypeScript mappings and generate types from the GraphQL schema and ABIs. ```bash npm install npm run codegen npm run build ``` ### Local Deployment Deploy to a local Graph Node for development and testing. ```bash npm run create-local npm run deploy-local ``` ### Production Deployment Deploy to The Graph's hosted service or decentralized network. ```bash graph auth --product hosted-service npm run deploy ``` ### Querying the Subgraph Access the GraphQL endpoint via HTTP POST requests or GraphQL clients. ```bash curl -X POST \ -H "Content-Type: application/json" \ -d '{ "query": "{ pools(first: 5, orderBy: totalValueLockedUSD, orderDirection: desc) { id token0 { symbol } token1 { symbol } feeTier totalValueLockedUSD volumeUSD } }" }' \ https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-subgraph ``` ## Summary This Uniswap V3 subgraph provides comprehensive blockchain data indexing for DeFi applications, analytics platforms, and trading interfaces. It processes millions of on-chain events to maintain real-time state of all Uniswap V3 pools, tokens, and liquidity positions with accurate USD valuations derived from oracle-grade price feeds. The subgraph's event handlers track every swap, mint, and burn operation while maintaining tick-level liquidity distribution data crucial for understanding concentrated liquidity positions. The time-series data generated by the subgraph enables powerful analytics including historical price charts, volume trends, fee generation tracking, and TVL monitoring across hourly and daily intervals. Applications can query this indexed data with sub-second latency via GraphQL, making it ideal for building trading dashboards, portfolio trackers, liquidity mining calculators, and automated trading strategies. The whitelist-based pricing system ensures accurate USD valuations even for long-tail assets by deriving prices through liquid pools containing established tokens like WETH, USDC, and DAI.