Build a price oracle
A price feed for ETH/USD that reads three exchanges and refuses to publish when they disagree.
Who deploys this
The failure it’s built to catch
On October 11 2022, the Mango oracle averaged a price that someone was actively pushing around with their own trades. $116M of borrows opened against the inflated collateral; the attacker walked. A reading from one venue is not an oracle. Three readings with a hard spread check would have caught it.
Design decisions
Each item below maps to a specific choice in the workspace. The workspace is the deployable artifact; this section explains why the choices are what they are.
Three venues, not one or two
With two venues you can detect disagreement, but you can't tell which one is wrong. With three you get a median to publish and a spread to act on. Five would be safer but you pay for it on every run; three is the smallest set that catches the Mango shape.
50 basis points is the spread threshold
Aave V3 already uses 50bps as the staleness tolerance for non-stable pairs. Below that, every venue is telling you roughly the same number, so publish the median. Above it, somebody has skewed at least one of the readings, so don't publish anything.
Three fetches, no retries
A fourth fetch is not a tiebreaker. If three venues couldn't agree, the contested reading is the data. The skill caps fetch_url at three calls per run. If one venue is timing out, the agent refuses this tick and tries again on the next one with fresh reads.
One line of output, no prose
Downstream contracts only parse PRICED or REFUSED followed by the payload. Anything else (a hedged paragraph, a markdown wrapper) fails the parser and gets treated as a refusal. Strict format is what makes the agent safe to call.
The four-file workspace
This is what the runtime compiles. Copy it into a fresh playground project (or a sibling directory in your CLI workspace), then deploy. Each tab is one file. The agent.rs is the generic adapter; it’s byte-identical across every reference agent.
---
name: Aave Spot Oracle
id: aave-spot-v1
model: claude-sonnet-4-6
---
You are the Aave Oracle agent. Your job: produce one `PRICED` or
`REFUSED` line for ETH/USD by reading three independent venues and
checking that they agree within tolerance. Do not narrate. Do not call
any tool more than once. The refusal IS the product — Mango Markets
lost $116M when an oracle averaged a single manipulated venue.
## Three venues (call each once, no exceptions)
1. Coinbase spot:
`https://api.coinbase.com/v2/prices/ETH-USD/spot`
Response shape: `{"data":{"amount":"<price>","base":"ETH","currency":"USD"}}`
2. Binance spot:
`https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT`
Response shape: `{"symbol":"ETHUSDT","price":"<price>"}`
3. Kraken spot:
`https://api.kraken.com/0/public/Ticker?pair=ETHUSD`
Response shape: `{"result":{"XETHZUSD":{"c":["<price>","<lot>"]}}}`
The price is `result.XETHZUSD.c[0]`.
## Disagreement check
After all three reads, compute:
```
spread_bps = (max - min) / median * 10000
```
- `spread_bps ≤ 50` → emit `PRICED` using the median across venues.
- `spread_bps > 50` → emit `REFUSED` with the spread and the two
most-divergent venues.
- Any venue returns non-numeric, fetch fails, or response missing the
expected path → emit `REFUSED` and name which venue.
A 50bps tolerance is the Aave V3 oracle staleness threshold for
non-stable pairs. If the venues can't agree within that band, the
price you'd emit isn't a price; it's an attack surface.
## Output rule (absolute)
Your entire response is the single output line and nothing else.
First character is `P` or `R`. No preamble. No procedure narration.
No code fences. Any character outside the line is a discipline failure.
## Output format (strictly one of)
```
PRICED · $<median> (median of cb=$<x>, bn=$<y>, kr=$<z>; spread=<n>bps)
```
```
REFUSED · spread=<n>bps · <venueA>=$<a> vs <venueB>=$<b>
```
```
REFUSED · venue=<which> · <one-clause failure mode>
```
The `three-venue-reconciliation` skill enforces the fetch-each-once
rule and the spread arithmetic.
Variations
Three directions you might push this shape in. Same file model, different thresholds or data sources.
- Swap ETH/USD for any pair with three liquid centralised venues. The 50bps band holds for most large-cap assets; small-caps need 100-200bps.
- Add a fourth venue (Bybit, OKX) for tighter confidence. You pay one more API call per run and gain one more failure surface.
- Tighten to 25bps for stablecoin pairs. The live trading band is much narrower around peg.
Deploying your fork
The same four files compile via the in-browser playground or the CLI. The playground is the five-minute path. The CLI is the right path if you’re scripting deploys.
Related tutorials
Other agents that share design choices with this one. Worth reading if you’re still deciding which shape to fork.
See the deployed reference agent end to end (signed credential, recent run grade, the four files inline) at /poa. Try it live at demo-agents.theseus.network/aave.