DEXUniswapAPIEthereum

DEX Data APIs: Get Uniswap Trades, Prices and OHLC in One Query

How to pull Uniswap v2, v3 and v4 trades, token prices and OHLC candles from one GraphQL schema that also covers 300+ other DEXs on nine chains, and how to stream the same rows.

Cover Image for DEX Data APIs: Get Uniswap Trades, Prices and OHLC in One Query

This post first appeared in 2020, when Uniswap had just overtaken Coinbase in daily volume and the way to get its trades was a set of REST endpoints on Bloxy. Those endpoints have been retired. What replaced them is one GraphQL schema that returns Uniswap v2, v3 and v4 swaps with USD prices attached, works unchanged on every chain Uniswap runs on, and streams the same rows over WebSocket or Kafka. Every query below was run against the current API before publishing.

What a DEX data API has to do now

In 2020 the problem was access. Today the raw swap events are easy to get from any node; the work is in what happens after:

  • Decoding three protocol versions. v2 pools are simple constant-product contracts. v3 adds concentrated liquidity and tick state. v4 runs every pool inside a single PoolManager contract, so a pool is a PoolId rather than an address, and hooks can change how swaps are observed.
  • Pricing long-tail tokens. A swap of a new token against WETH has no USD leg. Someone has to derive a reference price, and a naive derivation produces zeros or nonsense for exactly the tokens people ask about.
  • Counting trades once. Routed swaps and aggregator legs appear as several pool swaps inside one transaction. Summing them as-is inflates volume.
  • Filtering noise. Sandwich bots, dust swaps and bad prints are all real on-chain trades. A feed meant for a chart has to drop them.
  • Covering more than Ethereum. Uniswap runs on Base, Arbitrum, Optimism, Polygon and BNB Chain too, and a per-chain integration for each is a lot of code to maintain.

The DEX API handles those steps before the data reaches you. The rest of this post shows the queries.

Two layers, one schema

Bitquery exposes DEX trades through two layers, and the right one depends on how far back you need to look. The trading data overview covers the choice in detail.

Trading cube (Trading.Trades, Pairs, Tokens)Chain-level cubes (EVM.DEXTrades, EVM.DEXTradeByTokens)
Time windowReal time plus roughly the last 30 daysFull archive since genesis
USD price on every rowYes, via the Bitquery price indexOnly where a USD leg exists
Market cap and supply on the rowYesNo
MEV and outlier filteringYesNo, every swap is returned
Chains in one queryNine, including Ethereum, Base, Arbitrum, Solana and BNB ChainOne chain per query root
OHLCPre-aggregated, one second to one hourBuilt in-query at any interval
Call, event and log contextNoYes

The short version: use the Trading cube for anything live or recent, and drop to the chain-level cubes for history, custom candle intervals, or when you need the originating call or event. The two are built from the same swaps, so the numbers reconcile.

Latest Uniswap trades on Ethereum

One query returns the newest swaps across Uniswap v2, v3 and v4 in the last hour, each with the trader, both token legs, a USD price and the token's market cap at the time of the trade. The time bound keeps the query fast; without it the sort over the whole rolling window can run past the API's time limit. Run it in the Bitquery IDE after generating an API token.

query LatestUniswapTrades {
  Trading {
    Trades(
      where: {
        Pair: {
          Market: {
            NetworkBid: { is: "bid:eth" }
            Protocol: { in: ["uniswap_v2", "uniswap_v3", "uniswap_v4"] }
          }
        }
        Block: { Time: { since_relative: { hours_ago: 1 } } }
      }
      orderBy: { descending: Block_Time }
      limit: { count: 50 }
    ) {
      Block {
        Time
      }
      Side
      Trader {
        Address
      }
      Pair {
        Token {
          Symbol
          Address
        }
        QuoteToken {
          Symbol
        }
        Market {
          Protocol
        }
        Pool {
          Address
          Id
        }
      }
      Amounts {
        Base
        Quote
      }
      AmountsInUsd {
        Quote
      }
      PriceInUsd
      Supply {
        MarketCap
      }
      TransactionHeader {
        Hash
      }
    }
  }
}

A few things worth knowing about the rows that come back:

  • Side is the trader's side, so Buy means the wallet in Trader.Address bought Token with QuoteToken.
  • Pool.Id is populated for v4, where pools have no contract address of their own. Pool.Address is the pool contract on v2 and v3.
  • For USD volume, sum AmountsInUsd.Quote. The base leg is the base amount multiplied by a smoothed reference price, and on fast-moving tokens the two legs can differ.
  • Swap query for subscription and the same selection becomes a live stream.

The Crypto Trades API page documents every field, including the ones that behave differently on Solana.

Every trade of one token

The 2020 version of this post filtered Uniswap trades down to YFI by pasting its contract address into a REST parameter. The equivalent today is a filter on the token id, which is the chain prefix plus the lowercase contract address.

query TokenTradesOnUniswap {
  Trading {
    Trades(
      where: {
        Pair: {
          Token: { Id: { is: "bid:eth:0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e" } }
          Market: { ProtocolFamily: { is: "Uniswap" } }
        }
      }
      orderBy: { descending: Block_Time }
      limit: { count: 50 }
    ) {
      Block {
        Time
      }
      Side
      Trader {
        Address
      }
      Pair {
        Token {
          Symbol
        }
        QuoteToken {
          Symbol
        }
        Market {
          Protocol
        }
      }
      AmountsInUsd {
        Quote
      }
      PriceInUsd
      TransactionHeader {
        Hash
      }
    }
  }
}

That query covers the rolling window of the Trading cube. For the token's full history, the same question goes to the chain-level DEXTradeByTokens cube with the combined dataset, which reaches back to the first block Uniswap traded it:

query TokenTradeHistory {
  EVM(network: eth, dataset: combined) {
    DEXTradeByTokens(
      where: {
        Trade: {
          Currency: { SmartContract: { is: "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e" } }
          Dex: { ProtocolFamily: { is: "Uniswap" } }
        }
      }
      orderBy: { descending: Block_Time }
      limit: { count: 50 }
    ) {
      Block {
        Time
      }
      Transaction {
        Hash
      }
      Trade {
        Dex {
          ProtocolName
        }
        Buyer
        Seller
        Price
        PriceInUSD
        Amount
        AmountInUSD
        Side {
          Type
          Currency {
            Symbol
          }
          Amount
          AmountInUSD
        }
      }
    }
  }
}

On DEXTradeByTokens, Side.Type is the trader's action on the counter-leg: a sell of WETH on the side means the trader bought the token, and a buy means they sold it. The DEXTrades cube describes the same swap from the pool's point of view, and the DEXTrades cube guide walks through that convention with a worked example. Read both before building buy and sell counts from these rows.

One pair across all Uniswap versions

Pair-level questions work the same way. This query returns the latest WETH/USDC trades from Uniswap v1 through v4 on Ethereum, filtering on ProtocolName rather than the family so you can include or exclude a version. There is a saved copy in the IDE.

query LatestPairTrades {
  EVM(network: eth) {
    DEXTradeByTokens(
      orderBy: { descending: Block_Time }
      limit: { count: 50 }
      where: {
        Trade: {
          Currency: { SmartContract: { is: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" } }
          Side: {
            Amount: { gt: "0" }
            Currency: { SmartContract: { is: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" } }
          }
          Dex: {
            ProtocolName: { in: ["uniswap_v4", "uniswap_v3", "uniswap_v2", "uniswap_v1"] }
          }
        }
      }
    ) {
      Block {
        Time
      }
      Transaction {
        Hash
      }
      Trade {
        Dex {
          ProtocolName
        }
        Currency {
          Symbol
        }
        Price
        Amount
        AmountInUSD
        Side {
          Type
          Currency {
            Symbol
          }
          Amount
          AmountInUSD
        }
      }
    }
  }
}

The same cube answers "which pairs does this token trade in", "top traders of a token" and "trades of one wallet". The Ethereum DEX API and Uniswap API pages carry those variants with IDE links.

Which pools count as Uniswap

Protocol names describe a pool's interface, so uniswap_v2 also covers v2-compatible pools deployed by other factories, and on Ethereum those clones account for a meaningful share of v2-labelled trades. When you mean pools deployed by Uniswap itself, filter on the factory as well: Pair.Market.Address on the Trading cube, or Trade.Dex.OwnerAddress on the chain-level cubes.

VersionFactory to filter on
v20x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f
v30x1f98431c8ad98523631ae4a59f267346ea31f984
v4Filter Protocol to uniswap_v4; every v4 pool lives in the single PoolManager and is identified by Pool.Id

OHLC candles for a Uniswap pair

Charts need candles, and building them from raw swaps means writing your own aggregation. The Pairs cube serves them pre-aggregated per market, with the interval chosen in the filter. This returns one-minute candles for WETH/USDC on Uniswap, and it is saved in the IDE.

query UniswapPairOHLC {
  Trading {
    Pairs(
      where: {
        Token: { Address: { is: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" } }
        QuoteToken: { Address: { is: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" } }
        Market: { Network: { is: "Ethereum" }, ProtocolFamily: { is: "Uniswap" } }
        Interval: { Time: { Duration: { eq: 60 } } }
      }
      orderBy: { descending: Interval_Time_End }
      limit: { count: 100 }
    ) {
      Interval {
        Time {
          Start
          End
        }
      }
      Price {
        Ohlc {
          Open
          High
          Low
          Close
        }
      }
      Volume {
        Base
        Quote
        Usd
      }
      Market {
        Address
        Protocol
      }
      Pool {
        Address
      }
    }
  }
}

Each pool is its own series, so a pair that trades on several Uniswap pools returns a candle per pool for every interval, with Pool.Address naming the pool and Market.Address its factory. Native intervals run from one second to one hour in fixed steps; daily and weekly bars are rolled up from those, and the OHLC API guide shows how. For a single reference price per token rather than per pool, the Pairs cube ranks a token's markets so you can take the top one.

The same query on Base, Arbitrum, Optimism, Polygon and BNB Chain

Uniswap is deployed on several chains, and the Trading cube treats them as a filter value rather than a separate integration. Change NetworkBid in the first query and nothing else. Each of these was checked against live data while writing this post.

ChainNetworkBid filterDisplay name in Market.Network
Ethereumbid:ethEthereum
Basebid:baseBase
Arbitrumbid:arbitrumArbitrum
Optimismbid:optimismOptimism
Polygonbid:maticMatic
BNB Chainbid:bscBinance Smart Chain

The display names matter when you filter on Network instead of NetworkBid: they are case-sensitive, and a wrong value returns zero rows with no error. The chain-level cubes follow the same pattern with network: base, network: arbitrum and so on under the EVM root, and the supported chains matrix lists which interfaces cover which network.

Streaming the same rows

Every query above becomes a stream by changing the query keyword to subscription; the filters and the selection stay as they are, and the subscription forms of the trades and candle queries were tested while writing this post. Two rules apply to Trading cube streams: the Trading root takes no dataset argument in a subscription, and server-side aggregate filters such as selectWhere are query-only, as the streaming limits page lists. That is the WebSocket path, and it is the right one for dashboards and alerts that need server-side filtering and USD values.

TransportTypical latencyFilteringReplayFit
WebSocket (GraphQL subscription)About one secondFull, server-sideNoDashboards, alerts, bots with moderate volume
KafkaUnder half a secondClient-sideYes, from retained offsetsIndexers, ETL, high-volume trading systems
CoreCast (gRPC, Solana)Under a hundred millisecondsBasic, server-sideNoSolana MEV and high-frequency use

The streaming overview compares the three in depth. Kafka topics include per-chain DEX trades and the price feed, and Kafka access is provisioned through sales.

Beyond Uniswap

Nothing in these queries is Uniswap-specific except the protocol filter. Remove it and the same rows cover every venue indexed on the chain; on Ethereum that includes Curve, Balancer, PancakeSwap, 1inch, KyberNetwork, DODO, Fluid, Bancor and 0x fills, and across all chains the count runs past three hundred. The Ethereum DEX API page has a one-query listing of the protocol families with trade counts per family.

Solana works differently under the hood, since trades live inside program instructions rather than swap events, but it is exposed through the same schema. The Solana DEX trades page covers Raydium, Pump.fun, Orca and Jupiter, and the Solana DEX API product page summarises delivery options for that chain.

Pools, liquidity and traders

Trades are the most requested dataset, but the schema goes further, and each of these has a documented starting point:

  • Liquidity and reserves. The Trading cube carries no pool depth; the Ethereum liquidity API shows how to read reserves and liquidity changes from the chain-level cubes.
  • New pools. Pool and pair creation are contract events, and the Uniswap API page has the v2, v3 and v4 factory subscriptions.
  • Uniswap v4 specifics. Pool ids, hooks and the singleton PoolManager are covered in the Uniswap v4 API guide.
  • Trader analytics. Per-wallet history, leaderboards and PnL-style aggregates live in the Traders API.
  • Screeners. Server-side filters on computed aggregates, so a chain-wide screen runs in one request, are described under selectWhere screeners.

FAQ

Is there still a REST API for DEX data?

The Bloxy REST endpoints from the original version of this post were retired. The current API is GraphQL served over HTTPS, so any HTTP client can call it with a POST request and a bearer token, and the responses are plain JSON. Subscriptions use the same schema over WebSocket.

Does the API cover Uniswap v4?

Yes. v4 swaps arrive in the same rows as v2 and v3, with Pool.Id identifying the pool inside the PoolManager. Coverage of pools that use unusual hooks is worth testing against your own pool list during a trial.

How far back does the data go?

The Trading cube holds roughly the last month and is built for live products. The chain-level DEXTrades and DEXTradeByTokens cubes hold the full archive back to genesis, and the combined dataset spans both in one query.

Why is the USD price zero for some trades on the chain-level cubes?

Those cubes derive USD from the trade itself, so a swap between two tokens with no USD leg has no price. The Trading cube attaches a price from the Bitquery price index instead, which is why it is the default for anything user-facing.

Which cube should I use for candles?

For charts within the recent window, Trading.Pairs or Trading.Tokens, which are pre-aggregated. For candles older than that, or at intervals the fixed grid does not offer, aggregate DEXTradeByTokens in-query.

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.