If you ever made any research or play around the source code for Uniswap v3 or Raydium CLMM, you most likely would stumble upon the observation array. To the uninitiated, it looks like a series of random, massive integers.
But in an amazing way these numbers are the feats of financial engineering. they allow a liquidity pool to act as it's very own On-Chain Oracle without requiring an external price such as Chainlink or Pyth Network.
In this article, we would skim through the math that makes Concentrated Liquidity Market Maker (CLMM) observation states work.
The Core Problem: Liquidity Manipulation
In V2 AMMs, the current price is very dangerous, flash loan attack can be used to buy massive amount of Token A, driving price up using that inflated price as collateral to borrow against a lending protocol, and then dumping the token—all in one block.
To fix this, CLMMs make use of Time-Weighted Average Price TWAP to efficiently calculate TWAP on-chain by using the Observation State.
Transforming Price into Ticks
In a CLMM, we don't track the price directly, We track the Ticks. The price(P) and tick (i) are related by:
By taking the logarithm, we get:
Why do we do this? Because math with exponents is hard for computers to average, but math with integers (ticks) is easy. When we move into "Tick Space," we are essentially working in a logarithmic scale where multiplication turns into addition.
The Accumulator Pattern
The Observation State uses an Accumulator. Instead of storing every price change, the contract stores a "running total" of the price-time product.
Whenever a swap happens, the contract updates the tickCumulative:
- a (subset of t): The new cumulative value.
- i (subset of current): The current active tick.
- Delta t: The number of seconds since the last update
Example:
- At 12:00:00, the tick is 10 and the cumulative total is 1,000,000.
- At 12:00:10, a swap happens. 10 seconds have passed.
- New Cumulative: $1,000,000 + (10 \times 10) = 1,000,100$.
Reconstructing the Average
To find the average price between two timestamps t(subset 1) and t(subset 2), a developer just needs to fetch two observations from the state array.The math for the Average Tick ($\bar{i}$) is:
Once you have the average tick, you convert it back to the human-readable
This result is a Geometric Mean. It is mathematically superior for finance because it represents the "true" center of a percentage-based move, making it much harder for a single whale to skew the average with a brief spike.
Liquidity Tracking
The Observation State also tracks secondsPerLiquidityCumulative. This allows protocols to see how much liquidity was active at specific price ranges.
The formula follows the same accumulator logic:
This allows external contracts to verify not just the price, but the depth of the market during a specific window.
Development Tip: Cardinality
By default, most CLMM pools only store one observation (the most recent one). If you are building a lending protocol or a dApp that needs 30-minute price history, you must call the increaseObservationCardinalityNext function.
This "slots" more space in the array, allowing the pool to store more history (e.g., 100 observations instead of 1).
In Summary
The Observation State is a "compressed" history of the pool. By storing the integral of the tick over time, CLMMs provide:
Gas Efficiency: Only one update per block.
Security: High resistance to Flash Loan attacks.
Independence: No need for off-chain oracles.






Top comments (0)