Oracles
Why a perpetual exchange has to import a price it does not set, the grades the protocol assigns to an incoming price, and what a trader sees while a feed is degraded.
A perpetual has no expiry and no delivery, so nothing about the contract itself forces its price to track the asset it names. The two things that do are funding, sized off the gap between the market's mark price and an outside reference, and margin, which values every position against that reference. Both need a price the exchange does not produce.
Why not just use the exchange's own price
The exchange's own last traded price is the one number a trader can move. An account that pushes the mark price up by trading against itself marks its own long into profit, borrows against that profit, and liquidates the shorts on the other side, without the underlying asset having moved a cent.
So every market carries an oracle: an account, written by somebody other than the trader, holding a price, a confidence interval, and a timestamp. The source is set per market.
What breaks when the oracle is wrong
Margin, liquidation, funding, and the AMM's quote are all measured against the oracle, so a bad print liquidates solvent accounts and pays out on positions that were never in profit. An oracle fails in four ways: a wrong price, a stale price, a confidence band so wide that treating its midpoint as exact understates the error in every margin number, and a data fault such as a zero or a price five times the running average.
The protocol does not decide which is happening. It grades each incoming sample, and each action decides which grades it will accept.
The six grades
Every read is classified into one of six failure grades, or Valid. The default thresholds:
| Grade | What it means | Default threshold |
|---|---|---|
| Non-positive | Any price field at or below zero | Fixed, not configurable |
| Too volatile | The price and the running oracle TWAP differ by a large factor | 5x up, or down to one fifth |
| Too uncertain | The reported confidence is too wide relative to price | 2% of price, times the market's tier multiplier |
| Stale for margin | The sample is too old to value a position against | 48 seconds |
| Insufficient data points | Too few publishers were quoting | Pyth push only |
| Stale for AMM | The sample is too old for the AMM to quote against | 4 seconds |
Stablecoin feeds get three times the margin staleness window, 144 seconds rather than 48. The 2% confidence threshold is scaled by the market's contract tier, from 1x on tier A up to 50x on Highly Speculative and Isolated, so the tail tiers tolerate a band up to 100% of price before a sample is too uncertain.
Grades do not block everything equally
Each action asks separately whether the current grade is good enough for what it is about to do, and one that can lose money on a stale price is stricter than one that cannot.
| Action | Accepts |
|---|---|
| Auction-skipping AMM fill | Valid only |
| Low-risk AMM fill | Valid, or stale for the AMM within the low-risk threshold |
| Margin calculation, orderbook fill | Anything except non-positive, too volatile, too uncertain, stale for margin |
| Liquidation, trigger order | Anything except non-positive and too volatile |
| TWAP update, AMM curve update | Anything except non-positive |
| Funding update, P&L settlement | Valid, stale for the AMM, insufficient data points, stale for margin |
So a feed that goes 10 seconds without an update stops the AMM quoting while orderbook matching, margin, and liquidation carry on. At 50 seconds margin calculations and orderbook fills stop too, but liquidation and trigger orders still run: a position that is genuinely underwater should not become unliquidatable because the price is old.
What a trader actually sees
Fills get worse before they stop. The AMM is the first thing to withdraw. On a market where it was supplying most of the depth, a degraded feed shows up as an order filling only against resting orders, or not filling at all. Nothing errors; the order waits.
Margin numbers can freeze. Once a feed is stale for margin, anything requiring a margin check, including opening a position and withdrawing collateral, reverts rather than proceeding on a price the protocol will not stand behind. This is why a withdrawal can fail during an outage on a market the account is not even trading.
Liquidation still runs. A stale feed does not protect an underwater account. The reverse is less obvious: during a genuine oracle dislocation, wide bands can leave a position that neither its owner can close nor anyone else can liquidate until the price comes back. See Guard rails for the two divergence bands that produce that state.
Funding is damped, not skipped. While a feed is invalid its TWAP stops updating while the mark TWAP keeps moving. On recovery the price is interpolated toward the mark TWAP, weighted by how long the outage lasted, so a market does not settle a large funding payment because its oracle was absent.
Sources
Two sources are supported: Pyth Lazer and Pyth push. The Switchboard and Pyth pull variants are deprecated and rejected onchain. The source is set per market, so read the market account for the one a given market decodes. A Pyth Lazer price is not written by Pyth's own crank: a keeper relays a signed message onchain and the program verifies the publisher's signature before it is used.
A market that has not listed on either feed can use a prelaunch oracle, which is self-referential: the price is the market's own mark TWAP, clamped to an admin-configured maximum. Such a market has no outside anchor, which is why prelaunch markets sit in the tiers with no insurance coverage and the widest confidence tolerance.