Track Liquidity Pool Composition Changes Over Time
Liquidity pool composition tracking is the method of monitoring how the relative amounts of tokens inside an AMM pool (like Uniswap or Curve) change as trades execute and liquidity is added or removed. These shifts reveal important on-chain dynamics—impending impermanent loss, arbitrage activity, or changing market sentiment. By querying the underlying blockchain data stored on Dune Analytics, you can slice pool ratios by hour, day, or block and build charts that tell a continuous story. This guide teaches you the exact SQL patterns to use for Uniswap V2, V3, and Curve pools, plus how to interpret the resulting visualizations.
Whether you're a LP manager, a strategy researcher, or a risk analyst, understanding the composition trajectory of a pool helps you time entries, evaluate yield sources, and spot anomalies. We'll walk from raw data tables to polished charts, naming each tool and technique along the way.
- Pool composition tracking reveals impermanent loss, arbitrage activity, and liquidity distribution shifts.
- Dune Analytics provides the necessary raw event tables for Uniswap V2, V3, and Curve—each requires a slightly different SQL approach.
- For Uniswap V3, use the pool_stats view or derive the ratio from sqrtPriceX96 to get virtual reserve composition.
- Visualize the ratio as a line or the share as a stacked area to quickly spot trend changes.
- Automate alerts based on ratio thresholds to stay informed without manual chart checking.
- Balancer and other multi-token pools need a running balance query; start from a known snapshot and apply swap deltas.
Why Monitor Pool Composition Changes?
The token ratio in a liquidity pool rarely stays constant. In a simple Uniswap V2 pair like USDC/ETH, every swap alters the ratio according to the constant product formula. Over a week, the ETH amount might drop 10% relative to USDC if net buying pressure favors ETH. That shift is a leading indicator of where liquidity is going and how LPs are being exposed.
By conducting liquidity pool composition tracking, you can:
- Detect early signs of impermanent loss before it becomes severe.
- See when arbitrageurs rebalance a pool after a price change on another exchange.
- Assess whether a Curve pool’s balance is leaning toward one stablecoin, indicating a peg deviation.
- Validate that a concentrated liquidity position in Uniswap V3 is still within the active range.
Example: If you notice the ETH reserve in a Uniswap V2 ETH/USDC pool has dropped 20% in 24 hours while the USDC reserve rose, that suggests aggressive ETH buying. An LP providing both sides would now hold more USDC—a classic IL scenario if they originally deposited at a different ratio.
Setting Up Your Dune Analytics Workspace
Dune Analytics (dune.com) is the go-to platform for on-chain querying. If you don’t have an account, register (free tier works). Once logged in:
- Click 'New Query' and select the V2 (SparkSQL) engine for most examples, though Dune now defaults to DuneSQL. We'll use DuneSQL syntax.
- Familiarize yourself with the
dex.schema—specificallyuniswap_v2_ethereum.Pair_evt_Syncandcurve_ethereum.pool_view_v6. These event tables contain reserves after each event. - Bookmark a few core address tables:
tokens.erc20for symbol/decimals, andprices.usdif you want USD-converted values.
Dune’s query editor also has a visualization tab that plots time series directly. You’ll craft SQL that outputs a timestamp and a ratio column, then choose 'Area' or 'Line' chart to see the composition evolve.
Querying Uniswap V2 Pool Compositions
Uniswap V2 stores reserve data in the Pair_evt_Sync event, which logs reserve0 and reserve1 after each swap. To get the ratio of token0 to token1, you need the token addresses and decimals. Here's a practical query for the USDC/WETH pool (0xB4e...):
WITH reserves AS (
SELECT
evt_block_time AS dt,
reserve0 / POW(10, decimals0) AS reserve0_human,
reserve1 / POW(10, decimals1) AS reserve1_human
FROM uniswap_v2_ethereum.Pair_evt_Sync
WHERE contract_address = 0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc
AND evt_block_time > NOW() - INTERVAL '30' day
)
SELECT
dt,
reserve0_human,
reserve1_human,
reserve0_human / NULLIF(reserve1_human,0) AS ratio_t0_t1
FROM reserves
ORDER BY dtAdjust decimals by joining tokens.erc20. For USDC (6 decimals) and WETH (18), the ratio shows how many USDC per ETH the pool currently holds. Run this query and plot dt vs ratio_t0_t1. You’ll see the composition evolving trade by trade.
For a more aggregated view (hourly averages), use a date_trunc('hour', dt) and take the last reserve of each hour via last_value window function.
Adapting Queries for Uniswap V3 (Concentrated Liquidity)
Uniswap V3 doesn’t expose a single pair of reserves; instead each position covers a specific tick range and the pool's liquidity is spread across ticks. To get the overall composition of the entire pool (virtual reserves), you need to reconstruct the current price and then calculate what the reserves would be if the pool were V2-like at that price.
The key tables are uniswap_v3_ethereum.Pool_evt_Swap, which gives sqrtPriceX96 after each swap. Using the formula price = (sqrtPriceX96 / 2^96)^2 scaled by token decimal ratio. Then you can infer virtual reserves: L = sqrt(k) where k is constant, but V3’s k depends on the active tick range. For simplicity, we can define a 'composition ratio' as the amount of token1 that would be in the pool if all liquidity were concentrated in a single bin at the current price. A more accurate approach uses the tick and liquidity from the Pool_evt_Mint and Pool_evt_Burn events, but for end users, Dune provides a pre-computed view: uniswap_v3_ethereum.pool_stats that includes token0_reserve and token1_reserve (virtual).
| Parameter | V2 Source | V3 Source |
|---|---|---|
| Reserve0 | Pair_evt_Sync.reserve0 | pool_stats.token0_reserve |
| Reserve1 | Pair_evt_Sync.reserve1 | pool_stats.token1_reserve |
| Decimals | From tokens.erc20 join | Same join |
| Ratio calculation | reserve0/reserve1 | token0_reserve/token1_reserve |
Using pool_stats is simpler but updated only after swaps. For real-time, you can still derive from Pool_evt_Swap and track the price directly. But the ratio of virtual reserves tells the same story: how many token0 the pool holds relative to token1.
Tracking Curve StableSwap Pools
Curve pools use a different invariant (StableSwap) and often hold three or four stablecoins. Each pool has a balances array updated after trades and deposits. For example, the 3pool (DAI/USDC/USDT) uses the contract 0xbEbc... and emits a TokenExchange event that includes balances. Querying that:
WITH balance_updates AS (
SELECT
evt_block_time AS dt,
balances[1] / 1e18 AS dai_balance,
balances[2] / 1e6 AS usdc_balance,
balances[3] / 1e6 AS usdt_balance
FROM curve_ethereum.pool_evt_TokenExchange
WHERE contract_address = 0xbEbc44782C7dB0b1A60Cb6fe97e0F3A1c1A8b290
)
SELECT
dt,
dai_balance,
usdc_balance,
usdt_balance,
dai_balance / (dai_balance + usdc_balance + usdt_balance) AS dai_share
FROM balance_updates
ORDER BY dtThis gives you the absolute balances and the share of DAI. Plotting the share over time reveals if the pool has become heavily skewed toward one stablecoin (e.g., 90% USDT), which often signals a depeg or arbitrage opportunity. For MetaPools (e.g., FRAX/3pool), you’ll need to adjust the base pool balances accordingly—Dune has curated curve_ethereum.pool_view_v6 that aggregates these into a single row per block.
Visualizing Token Ratio Shifts with Charts
Dune’s built-in charting lets you turn your ratio query into an area chart showing the proportion of each token. For a two-token pool, you can use a stacked area with reserve0_human and reserve1_human—but the absolute values may dwarf each other if one token has a different price. Better to plot the ratio (e.g., USDC per WETH) as a line.
- Line chart: Plot ratio against time. Add a horizontal line at the initial deposit ratio to see divergence (impermanent loss).
- Area chart: After normalizing to a total of 1, each token’s share fills the chart. This is intuitive for multi-token pools like Curve 3pool.
- Bar chart: If you group by day and average the ratio, a bar chart shows daily volatility.
To export, Dune allows CSV download or embed. For real-time dashboards, use Dune’s scheduler (Pro plan) or connect via API to a custom frontend. The key is to set the time granularity appropriately: too fine (every block) and you get noise; too coarse (weekly) and you miss arbitrage runs.
Interpreting Composition Changes: What a Shift Means
A sudden change in pool composition is rarely random. Consider these scenarios:
Arbitrage rebalancing: If the price of ETH on Binance drops faster than on-chain, arbitrageurs will buy ETH from the Uniswap pool (lowering ETH reserves) until the pool price matches. The composition shift reflects that arbitrage flow. In a Curve pool, a large deviation in the share of USDT (e.g., from 33% to 10%) usually means USDT is trading below $1 elsewhere and traders are swapping into it—or out of it if it’s above.
Impermanent loss: Compare the current composition to the composition at the time of deposit. If a V2 LP deposited 50/50 value and the pool now holds 70% of tokenA and 30% of tokenB (by token count), the LP will suffer IL if tokenA appreciated. The ratio divergence is the driver.
Liquidity mining hoarding: If a farm offers incentives for depositing one side of a pair, users may add only that token (via single-sided staking), skewing the composition. Track the ratio to see if the pool is becoming ‘imbalanced’ due to farming rather than trading.
Advanced: Weighted Pools (Balancer) and Multi-Token Compositions
Uniswap V2 gives equal weight (50/50) to both tokens, but Balancer allows arbitrary weights and multiple tokens. Tracking composition in a Balancer pool requires reading the ‘pools’ state and the ‘swap’ events that update balances. Balancer V2 uses a Vault contract; the relevant table is balancer_v2_ethereum.Vault_evt_Swap which logs amountIn and amountOut for each token. To reconstruct the pool’s balances, you need to start from a known snapshot (e.g., the PoolCreated event) and iterate every swap, or use Dune’s balancer_v2_ethereum.pool_balances materialized view that gives balances at block level.
For a 80/20 ETH/USDC pool, the composition target is not 50/50 by value. The ratio of token counts will follow the formula balance0 ^ 0.8 * balance1 ^ 0.2 = constant. Track the actual weight deviation by comparing reserve amounts to what the invariant expects. A query example with Balancer is longer, but the principle is the same: you join the swap events and maintain a running balance per token. Then plot individual token shares.
Tip: Use Dune’s blockchain_ethereum.logs as a fallback if a curated table is missing. Filter by topic0 for the Swap event signature.
Automating Alerts with Dune's API or Bots
Once you have a query that gives the current composition, you can set up automated monitoring. Dune’s API (REST) lets you fetch the latest result programmatically. You could write a Python script that runs every hour, retrieves the ratio, and sends a Discord message if the ratio deviates beyond a threshold (e.g., USDC/WETH ratio falls below 1000).
Alternatively, use Dune’s built-in alerts (available in Pro/Team plans) that watch a query result and email you when it changes. For free-tier users, run a cron job hitting the Dune API with your API key. Example: if the Curve 3pool DAI share exceeds 40%, that might indicate a depeg of DAI; trigger a notification.
This turns your liquidity pool composition tracking from a manual analysis into an ongoing vigilance system.
Step-by-step
- Create a Dune Analytics account and open a new query using DuneSQL engine.
- Identify the pool address and its token decimals via etherscan or Dune's token registry.
- Write a query that captures post-swap reserves: for Uniswap V2 use Pair_evt_Sync; for V3 use pool_stats; for Curve use pool_evt_TokenExchange with balances.
- Adjust reserves by token decimals to get human-readable amounts.
- Calculate the ratio of token0 to token1 (or share for multi-token) as your composition metric.
- Select a time range (e.g., last 30 days) and order by block time.
- Run the query and switch to Dune's Chart view; choose line or stacked area to visualize.
- Set up a parameterized dashboard to easily change pool address and time window.
- Optionally, export the query result to a scheduled alert or API endpoint for ongoing tracking.
Common mistakes to avoid
- Using raw reserve values without dividing by decimals—this gives astronomically large numbers that hide the actual ratio.
- Assuming Uniswap V3 stores total reserves in the same way as V2; V3 requires deriving virtual reserves from sqrtPriceX96.
- Forgetting to handle the NULL case when one reserve is zero (e.g., after a pool migration) causing division errors.
- Selecting too fine a time granularity (per block) and getting chart noise that obscures the trend; use hourly buckets instead.
- Ignoring the fact that Curve pools with multiple coins have balances stored in a fixed order that may differ per pool (e.g., [DAI, USDC, USDT] vs [USDC, USDT, DAI]).
- Relying solely on the last swap event of a day—if the pool is inactive for hours, the daily snapshot may be misleading; use a pivot approach or take the last event per hour.
Frequently asked questions
What is the best way to track liquidity pool composition if I'm not comfortable writing SQL?
Use Dune’s Explore section to find pre-built dashboards for any top Uniswap or Curve pool. Fork the query and adjust the pool address and time range. Alternatively, tools like Zerion or DeBank show current composition but not historical changes; Dune is the only free option for custom time-series analysis.
Can I track composition changes for concentrated liquidity positions like Uniswap V3 ticks?
Yes, but only for the whole pool’s virtual reserves, not for an individual LP’s position. To track a specific position’s tokens, you need to reconstruct its range and the amount of liquidity it provided using the Mint and Burn events. That is more advanced and involves tracking the NFT token ID.
How frequently should I capture composition data to get meaningful insights?
For most pools, hourly snapshots strike a balance between detail and noise. High-frequency arbitrage flashes happen within minutes, but the composition effect may be tiny. Daily snapshots are fine for long-term IL assessment. In Dune, use date_trunc('hour', dt) with a window function to sample each hour's last value.
Related reading
Track the entities behind the concepts
DeFi Intel maps 11,000+ protocols, tokens and companies to a typed knowledge graph — with live data, incidents and regulation.