Crypto Price Oracle Design: Push Feeds, TWAPs, Staleness Checks, and Manipulation Cost
How to source, validate, and fail safe on price data in smart contracts — comparing push oracles, pull oracles, and AMM TWAPs, with the exact validation code and risk parameters that bound your downside.
Summary
An oracle is not a price — it is an assumption about how expensive it is to lie to you. Choosing between push feeds, pull feeds, and TWAPs is really choosing what an attacker must spend to move your reported price far enough, for long enough, to extract more than the attempt costs. Design starts by asking how much value your protocol will expose to one asset, then works backward to a data source whose manipulation cost exceeds it.
Three oracle shapes
| Type | How it updates | Best for | Main risk |
|---|---|---|---|
| Push (Chainlink) | Node network writes on heartbeat or deviation | Blue-chip assets, lending collateral | Staleness between updates; feed deprecation |
| Pull (Pyth, RedStone) | Consumer submits a signed price with the transaction | High-frequency needs, perps, long-tail assets | Caller controls timing within a validity window |
| AMM TWAP | Derived from on-chain pool cumulative prices | Assets with no professional feed | Manipulation cost scales only with pool depth |
The comparison people get wrong is push versus pull. Pull oracles are not less secure — the price is signed by the same kind of publisher network — but they invert who chooses the update moment. Because the caller submits the price, they can select the most favourable observation inside the validity window. Shrink that window, and validate the publish timestamp against block time on-chain.
Validation code you should never skip
function getPrice(address asset) public view returns (uint256) {
AggregatorV3Interface feed = feeds[asset];
(uint80 roundId, int256 answer, , uint256 updatedAt, uint80 answeredInRound)
= feed.latestRoundData();
if (answer <= 0) revert InvalidPrice();
if (updatedAt == 0) revert IncompleteRound();
if (answeredInRound < roundId) revert StaleRound();
if (block.timestamp - updatedAt > heartbeat[asset] + GRACE)
revert StalePrice();
// Normalize every feed to 18 decimals at the boundary, once.
return uint256(answer) * 10 ** (18 - feed.decimals());
}On an L2, add one more check before any of this: read the sequencer uptime feed and reject prices if the sequencer has been down, plus a grace period after it returns. During a sequencer outage, users cannot submit transactions to defend positions, and liquidating them the instant the sequencer restarts — against prices that moved while they were locked out — is both unfair and, in practice, a source of mass liquidation events.
Sizing exposure to manipulation cost
For any asset you value with a TWAP, estimate the capital required to move the pool price by the percentage that would make an attack profitable, and hold it there for the full averaging window while arbitrageurs push back. Then set the borrow or exposure cap below that number with a wide margin. If you cannot compute it, you cannot list the asset — that is a complete answer, not a failure.
- Longer TWAP windows raise manipulation cost but increase lag, which is its own risk during real crashes.
- Multi-source medians help against a single feed failing, but correlated sources (two oracles both reading the same thin pool) give false confidence.
- A deviation circuit breaker that pauses new borrows on a large single-block move costs you almost nothing and cuts the tail off your loss distribution.
- Always keep a governance-controlled fallback path to switch feeds without a full upgrade. Feeds do get deprecated, and doing that migration under pressure is how mistakes happen.
Frequently asked questions
- Can I just use the Uniswap spot price?
- No — for any purpose where the price determines who receives value. A spot price can be moved arbitrarily within a single transaction using a flash loan, and this is the single most exploited pattern in DeFi history. Spot is acceptable only for display, or for slippage checks where the user bears their own outcome.
- How often should a price feed update?
- Match the heartbeat to the asset's volatility and to how much value you expose to it. Major assets typically use a deviation threshold of 0.5% or a heartbeat of one hour, whichever fires first. Your staleness check should allow the heartbeat plus a modest grace period, and no more.
- What happens if the oracle goes down entirely?
- Your protocol must fail closed on new risk and open on risk reduction: block new borrows and new leverage, but keep repayment and collateral top-ups working. Freezing everything traps users; continuing to liquidate against a stale price destroys them.
Building something like this?
I'm Harsh Mittal — I build production systems across Web3, AI, and financial infrastructure: smart contracts and DeFi protocols, RAG pipelines and LLM agents, market data infrastructure, and the interfaces on top of them. If this is the kind of problem you're working on, I can help you ship it.