Liquidity Lu saysA Time-Weighted Average Price oracle stores cumulative price-time and divides over a window — flattening flash-loan price spikes that one-block oracles fall for.
A spot-price oracle is trivially manipulable: a flash loan can crash the price in one block, the oracle reads it, and a leveraged position liquidates. A TWAP records a cumulative sum Σ price · dt and divides by the elapsed window — so a one-block spike contributes negligibly to a 30-minute average.
Uniswap v2's oracle is a built-in TWAP; v3 stores per-tick snapshots for finer granularity. The demo computes a TWAP over four time-weighted points and shows a flash-crash spike barely moving the average.
Power-ups you unlock
TWAP = Σ price · dt / Σ dt over a time window
Single-block manipulation contributes only one dt slot
Longer windows = more manipulation-resistant, slower to react
Uniswap v2/v3 oracles are built-in TWAPs
TWAP cost per attack scales with window length
The Sandwich Bot attacks — common mistakes
Using a window so short that flash loans still move the average
Reading spot price instead of TWAP for lending oracle inputs
Forgetting TWAPs lag during real price discovery moves
Composing two TWAPs without checking total lag
Boss battleCompute the TWAP across 4 sample points, then re-compute after a flash crash and show the average barely moves.
Example code
<!doctype html><html><head><meta charset="utf-8"></head>
<body style="background:#06040d;color:#e6e0ff;font-family:monospace;padding:20px"><pre id="o"></pre>
<script>
const pts = [{t:0,p:100},{t:300,p:102},{t:600,p:101},{t:900,p:103},{t:1200,p:99}];
function twap(samples){
let cum = 0;
for(let i = 1; i < samples.length; i++) cum += samples[i-1].p * (samples[i].t - samples[i-1].t);
return cum / samples[samples.length-1].t;
}
const honest = twap(pts);
// flash crash: 1-second spike to $50 inside the window
const attacked = [...pts.slice(0, 4), {t:901, p:50}, {t:1200, p:99}];
const attackedTwap = twap(attacked);
document.getElementById('o').textContent = [
'samples (t, price): ' + pts.map(s => '(' + s.t + ',' + s.p + ')').join(' '),
'honest TWAP over 1200s: ' + honest.toFixed(4),
'',
'flash-crash injection: 1-second spike to $50',
'TWAP after attack: ' + attackedTwap.toFixed(4),
'manipulation impact: ' + Math.abs(honest - attackedTwap).toFixed(4) + ' (negligible)'
].join('\n');
</script></body></html>