DEXUniswapAPIEthereum

Uniswap Pools API: New Pools, Reserves, Liquidity and Pool Prices

List newly created Uniswap v2, v3 and v4 pools, read their reserves in USD, see the first deposits, rank a token's pools by liquidity and stream pool changes. Every query runs on the current API.

Cover Image for Uniswap Pools API: New Pools, Reserves, Liquidity and Pool Prices

The 2020 version of this post listed newly created Uniswap v2 pairs with the first Bitquery GraphQL schema and read a pool's reserves from its token balances. That schema has been retired. This rewrite does the same four things on the current API, across Uniswap v2, v3 and v4: find new pools, learn about their tokens, read the first deposits, and read the reserves right now. It then goes further than the original could, with liquidity in USD, price impact per pool and a live stream of pool changes. Every query below was run against the live API before publishing.

What a Uniswap pool is, by version

Anyone can create a Uniswap pool for two tokens, and the first deposit sets the opening price. How the pool exists on chain changed with each version, and that decides which query finds it.

v2v3v4
What gets deployedOne pair contract per token pair, itself an ERC-20 (the LP token)One pool contract per token pair per fee tierNothing. Every pool is a state entry inside one PoolManager contract
IdentityPair addressPool addressPoolId, a hash of the tokens, fee, tick spacing and hooks
Creation eventPairCreated on the v2 factoryPoolCreated on the v3 factoryInitialize on the PoolManager
Where reserves liveIn the pair contractIn the pool contractIn the PoolManager, keyed by PoolId

The addresses used below are Ethereum mainnet; the same queries work on Base, Arbitrum, Optimism, Polygon and BNB Chain with that chain's factory addresses and network value.

Newly created pools

A v2 pool is announced by the factory's PairCreated event, which carries both token addresses and the new pair address. The Events query below returns the most recent ones, decoded, with the transaction that created them. Generate a token in the Bitquery IDE to run it.

query NewUniswapV2Pairs {
  EVM(network: eth) {
    Events(
      orderBy: { descending: Block_Number }
      limit: { count: 10 }
      where: {
        Log: {
          SmartContract: { is: "0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f" }
          Signature: { Name: { is: "PairCreated" } }
        }
      }
    ) {
      Block {
        Time
        Number
      }
      Transaction {
        Hash
      }
      Arguments {
        Name
        Value {
          ... on EVM_ABI_Address_Value_Arg {
            address
          }
          ... on EVM_ABI_BigInt_Value_Arg {
            bigInteger
          }
        }
      }
    }
  }
}

Each row's Arguments hold token0, token1 and pair, plus the running count of pairs the factory has created. v3 and v4 announce pools the same way with different events and arguments, so one query can watch all three factories at once:

query NewUniswapPoolsAllVersions {
  EVM(network: eth) {
    Events(
      orderBy: { descending: Block_Number }
      limit: { count: 20 }
      where: {
        Log: {
          SmartContract: {
            in: [
              "0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f"
              "0x1f98431c8ad98523631ae4a59f267346ea31f984"
              "0x000000000004444c5dc75cb358380d2e3de08a90"
            ]
          }
          Signature: { Name: { in: ["PairCreated", "PoolCreated", "Initialize"] } }
        }
      }
    ) {
      Block {
        Time
      }
      Log {
        SmartContract
        Signature {
          Name
        }
      }
      Transaction {
        Hash
      }
      Arguments {
        Name
        Value {
          ... on EVM_ABI_Address_Value_Arg {
            address
          }
          ... on EVM_ABI_Bytes_Value_Arg {
            hex
          }
          ... on EVM_ABI_BigInt_Value_Arg {
            bigInteger
          }
        }
      }
    }
  }
}

What each version's arguments give you:

  • v2 PairCreated: token0, token1, pair.
  • v3 PoolCreated: token0, token1, tickSpacing, pool.
  • v4 Initialize: id (the PoolId), currency0, currency1, fee, tickSpacing, hooks, and the opening sqrtPriceX96 and tick. A zero currency0 means native ETH.

Swap query for subscription and the same filter becomes a live feed of pool creation; there is a saved stream in the IDE. For the full history, add dataset: combined and page through the results: the v2 factory alone has emitted several hundred thousand of these events since it went live, and the archive holds every one.

Learning about the tokens

The 2020 post looked each token up by address to get its symbol and decimals. The Tokens cube answers the same lookup with the fields you need next: current price, market cap, supply and volume, blended across every pool the token trades in.

query TokenSnapshot {
  Trading {
    Tokens(
      where: {
        Token: {
          Address: { is: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" }
          Network: { is: "Ethereum" }
        }
        Interval: { Time: { Duration: { eq: 60 } } }
      }
      orderBy: { descending: Block_Time }
      limit: { count: 1 }
    ) {
      Token {
        Symbol
        Name
        Address
      }
      Price {
        IsQuotedInUsd
        Ohlc {
          Close
        }
      }
      Supply {
        MarketCap
        CirculatingSupply
      }
      Volume {
        Usd
      }
    }
  }
}

A token that has not traded yet has no row here, which is the usual case in the minutes after a pool is created. The creation event already carries the addresses, and the first trades appear in the DEX trades queries as soon as they land.

The first deposits into a pool

Whoever creates a pool seeds it with both tokens, and those first transfers set the opening price. Transfers into the pool address, oldest first, show exactly what was deposited. The example uses the original v2 USDC/WETH pair, so it reaches back to the pair's first block through the combined dataset.

query FirstDepositsIntoPool {
  EVM(network: eth, dataset: combined) {
    Transfers(
      where: {
        Transfer: {
          Receiver: { is: "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc" }
          Amount: { gt: "0" }
        }
      }
      orderBy: { ascending: Block_Time }
      limit: { count: 2 }
    ) {
      Block {
        Time
        Number
      }
      Transaction {
        Hash
      }
      Transfer {
        Sender
        Amount
        Currency {
          Symbol
          SmartContract
        }
      }
    }
  }
}

For a pool created in the last few days, the DEXPoolEvents cube gives the same answer with more context: order it by ascending block time for the pool and the first row is the initial mint, with the reserves after it and their USD value.

Reserves right now

The 2020 post read a pool's reserves as the token balances of the pair address, and that still works, on the current Balances cube. Filter to the pool address and its two tokens; without the currency filter you also get the stray tokens people airdrop to well-known pool addresses.

query PoolReservesFromBalances {
  EVM(network: eth, dataset: combined) {
    Balances(
      where: {
        Balance: { Address: { is: "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc" } }
        Currency: {
          SmartContract: {
            in: [
              "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
              "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
            ]
          }
        }
      }
    ) {
      Currency {
        Symbol
        SmartContract
      }
      Balance {
        Amount
        AmountInUSD
        LastChangeTime
      }
    }
  }
}

The pool-native view is DEXPoolEvents, which records reserves and spot prices in both directions after every swap, mint and burn. Its latest row for a pool is the current state, and the liquidity API guide has the pool-specific and protocol-wide variants.

query PoolReservesAndSpotPrice {
  EVM(network: eth) {
    DEXPoolEvents(
      limit: { count: 1 }
      orderBy: { descending: Block_Time }
      where: {
        PoolEvent: {
          Pool: { SmartContract: { is: "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640" } }
        }
      }
    ) {
      Block {
        Time
      }
      PoolEvent {
        AtoBPrice
        BtoAPrice
        Dex {
          ProtocolName
        }
        Liquidity {
          AmountCurrencyA
          AmountCurrencyAInUSD
          AmountCurrencyB
          AmountCurrencyBInUSD
        }
        Pool {
          SmartContract
          PoolId
          CurrencyA {
            Symbol
          }
          CurrencyB {
            Symbol
          }
        }
      }
    }
  }
}

Which to use:

  • Balances is current state for any address, however old the pool, and it is the right call for v2 and v3 pools that hold their own funds. It does not stream.
  • DEXPoolEvents carries USD values, spot prices and a change history, and it streams. It keeps a short rolling window, days rather than weeks, so it is for live and recent state. For v4 pools it is the only option, because their funds sit in the PoolManager and the row is keyed by PoolId.

A token's pools, ranked by liquidity

The same cube lists every pool a token trades in and ranks them by what they hold. This returns the deepest pools where the token is the first currency; the liquidity guide shows the matching half for the second currency, and the exclusion of the PoolManager keeps v4 rows from collapsing onto one address.

query TopPoolsForToken {
  EVM(network: eth) {
    DEXPoolEvents(
      limit: { count: 10 }
      orderBy: { descendingByField: "PoolEvent_Liquidity_AmountCurrencyA_maximum" }
      where: {
        PoolEvent: {
          Pool: {
            CurrencyA: { SmartContract: { is: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce" } }
            SmartContract: { notIn: ["0x000000000004444c5dc75cb358380d2e3de08a90"] }
          }
        }
      }
    ) {
      PoolEvent {
        Dex {
          ProtocolName
        }
        Liquidity {
          AmountCurrencyA(maximum: Block_Time)
          AmountCurrencyAInUSD(maximum: Block_Time)
          AmountCurrencyB(maximum: Block_Time)
        }
        Pool {
          SmartContract
          CurrencyA {
            Symbol
          }
          CurrencyB {
            Symbol
          }
        }
      }
    }
  }
}

The maximum: Block_Time selector takes each pool's latest reserves rather than summing rows, which is what a ranking needs.

Price impact per pool

Reserves say how much a pool holds; the slippage cube says how much you can trade against it. For each pool and slippage tolerance it returns the largest input that stays within that tolerance, the guaranteed minimum output and the average execution price, in both directions. Tolerances run from a tenth of a percent to ten percent, and each snapshot also carries a zero-tolerance row with the spot price, so seven rows describe one moment for one pool.

query PoolPriceImpact {
  EVM(network: eth) {
    DEXPoolSlippages(
      where: {
        Price: {
          Pool: { SmartContract: { is: "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640" } }
        }
      }
      orderBy: { descending: Block_Time }
      limit: { count: 7 }
    ) {
      Block {
        Time
      }
      Price {
        SlippageBasisPoints
        AtoB {
          Price
          MaxAmountIn
          MinAmountOut
        }
        BtoA {
          Price
          MaxAmountIn
          MinAmountOut
        }
        Pool {
          CurrencyA {
            Symbol
          }
          CurrencyB {
            Symbol
          }
        }
      }
    }
  }
}

The numbers come from simulating swaps against the pool's live tick state, which is why v3 and v4 concentrated liquidity is handled correctly rather than approximated from reserves. Uniswap v4 slippage and price impact explains why the v3 formula breaks on v4, and real-time liquidity for DEX routers shows how bots use this table.

Positions and v4 pool ids

Two things this post cannot cover in one section each have their own guide:

  • v3 positions are NFTs minted by the position manager, and the Uniswap v3 position API tracks mints, burns and liquidity changes per position.
  • v4 pools share one contract, so token-pair filters alone are ambiguous when two pools differ only by fee or hooks. The Uniswap v4 liquidity guide shows how to read one concrete pool by PoolId.

Streaming pool changes

Every DEXPoolEvents query above becomes a stream by changing query to subscription. This one emits a row each time a Uniswap pool's reserves change, with the reserves after the change:

subscription UniswapPoolChanges {
  EVM(network: eth) {
    DEXPoolEvents(
      where: {
        PoolEvent: {
          Dex: { ProtocolName: { in: ["uniswap_v2", "uniswap_v3", "uniswap_v4"] } }
        }
      }
    ) {
      Block {
        Time
      }
      PoolEvent {
        Dex {
          ProtocolName
        }
        Liquidity {
          AmountCurrencyA
          AmountCurrencyB
        }
        Pool {
          SmartContract
          PoolId
          CurrencyA {
            Symbol
          }
          CurrencyB {
            Symbol
          }
        }
      }
      Transaction {
        Hash
      }
    }
  }
}

The same rows are published on the eth.dexpools.proto topic of the Kafka streams, which add replay from retained offsets and lower latency; Kafka credentials are separate from IDE tokens and come through sales.

Charts for a pool

Candles per pool come from the Pairs cube rather than from pool events, pre-aggregated from one second to one hour. The DEX data APIs post has the query, and the Pairs cube reference covers ranking a token's pools to get its most reliable price.

FAQ

How do I get every Uniswap pool that exists?

Sweep the creation events with dataset: combined, paging by block number: PairCreated on the v2 factory, PoolCreated on the v3 factory and Initialize on the PoolManager. For only the pools that are active right now, query DEXPoolEvents with limitBy on the pool address instead.

How far back does pool data go?

Creation events and transfers are in the archive back to each factory's first block. Balances is current state with no history. DEXPoolEvents and DEXPoolSlippages keep a rolling window of a few days; for historical reserves or depth, use a cloud export.

A v4 pool has no address. How do I query it?

By PoolId, which the Initialize event returns as id and which DEXPoolEvents carries on every row. Two v4 pools for the same tokens with different fees or hooks are different ids.

Does this work on other chains?

Yes. Change network and use that chain's factory addresses. The Uniswap API page links the Base, BNB Chain and Polygon variants, and the DEX API covers the full list of venues.

Related reading

Subscribe to our newsletter

Subscribe and never miss any updates related to our APIs, new developments & latest news etc. Our newsletter is sent once a week on Monday.