For developers / Real-world assets
How a price is made
Two aggregation methods, one publisher. The arithmetic below is what makes a price robust to a venue misbehaving; it is not what makes it trustworthy. That is the last section, and it is the one to read before you liquidate anything on these numbers.
Where a price comes from
Thirteen feeds are aggregated from five public spot venues, each subscribed with that venue's own product id. The other twenty have no venue leg and come from Pyth Network's Hermes service alone.
| Source | What |
|---|---|
| Spot venues | Coinbase, Kraken, Bitstamp, Gemini, Bitfinexeach subscribed with that venue's own product id, taken from the catalog |
| Second source | Pyth Network, over its Hermes servicethe only source for the twenty feeds with no venue leg, and a cross-check for the other thirteen |
The two methods fail differently, and a consumer needs both behaviours. An exchange-backed feed survives a venue going dark, because the median is taken across five and each carries its own freshness. A single-source feed cannot survive its source being unavailable, and says so rather than guessing.
When a venue counts
Each venue is evaluated on its own before anything is combined. The rules are applied in this order, and the first that fails takes the venue out of the median for that evaluation:
| Rule | Threshold |
|---|---|
| Connection state | must be onlinea venue whose socket is down is never fresh, whatever its last quote said |
| Quote age | 10 smeasured on a MONOTONIC receive clock, never on the exchange's timestamp or the host's wall clock |
| Delivery lag | 5 s either waya timestamp trailing its receipt means the socket is replaying a backlog; one leading it means the venue dates quotes in the future. The whole venue sits out rather than falling through to its last trade |
| Spread | 50 bpsthe mid is used when the book is uncrossed and tighter than this |
| Last trade | 60 sthe fallback when no usable mid exists, inside its own longer window |
Not on the exchange's timestamp, and not on the host's wall clock. A venue with a fast or slow clock cannot make its own quote look fresh, and a host whose clock steps cannot invalidate every venue at once. The exchange timestamp is used for one thing only: the delivery-lag check, which compares it against the moment of receipt in both directions.
The best bid and ask are preferred when the book is recent, uncrossed and tighter than the spread limit. Otherwise the last trade stands in, inside its own longer window. A venue whose socket is replaying a backlog is taken out entirely rather than falling through to its last trade, because that trade is late by the same amount.
The median
- Evaluate every venue against the rules above and keep the fresh ones.
- If fewer than minSources are fresh, publish nothing and report the reason - no_sources when none is fresh, too_few_sources otherwise. Nothing thin is ever published.
- Take the median of what survives.
- If the spread between the included venues exceeds 200 bps and dropping one would still leave minSources, drop the venue furthest from the median - on a tie the older observation - and take the median again. At most one venue is dropped.
- Round half-even to the feed's decimals. The result is an integer, not a float.
- Date the aggregate with the NEWEST included quote, capped at the current wall clock so no venue with a fast clock can date a median into the future.
degraded is set when the median rested on exactly minSources venues: still published, one drop-out from silence.
A thin feed publishes nothing rather than something
minSources is per feed, and it is 3 on every exchange-backed feed. Below it there is no answer at all: no last-known value is republished on chain, the round does not advance and updatedAt stops moving. The service still reports the last good answer as lastKnown for diagnosis, and that field is explicitly not a price to trade on.
The aggregate is dated with the newest included quote, capped at the current wall clock. Both halves matter: a venue whose sixty-second-old last trade stood in for a stale book must not date a median whose other inputs are sub-second, and no venue with a fast clock may push an observation time into the future - where the contract would skip it as reason 4.
Cross-check and stand-in
An exchange-backed feed that also has a Pyth leg is cross-checked against it on every evaluation. Within 100 bps the two are recorded as agreeing; beyond it the feed is marked diverged and the dispersion flag is raised.
When the venues go stale, Pyth may stand in for them - but only if the two agreed recently and are not diverging now. A feed publishing from the stand-in is marked degraded and its source reads pyth rather than exchanges-median, so a consumer can tell. If they are diverged and Pyth is fresh, the feed publishes nothing and reports cross_check_diverged.
Two fields expose this. crossCheckBps is the current distance between the two methods, and source says which one produced the round you are holding - 1 for the venue median, 2 for the second source. A feed whose source has changed is still a valid price, produced a different way; if that matters to your product, watch the field rather than inferring it.
The second source
Every update from the second source passes these checks before it is eligible to be a price. A rejected update increments a counter by reason, visible per feed on the service's source and audit routes:
| Check | Limit |
|---|---|
| Exponent | pinned on the first updateany later drift is rejected, so a silent rescaling cannot pass |
| Slot and publish time | must advancean update that does not move them forward is dropped |
| Future tolerance | 5 sa publish time further ahead than this is rejected |
| Confidence | 50 bps crypto and index, 30 equity, 10 commodity and fxthe reported confidence interval as a fraction of the price; wider is rejected |
| EMA band | 10 % crypto and index, 5 % elsewherea jump away from the exponential moving average needs a SECOND sample within 100 bps of the first before it is used. A lone spike is dropped |
| Freshness | 10 s since receipt, 30 s publish lagboth measured at receipt on the monotonic clock |
Without the credential, twenty of the thirty-three feeds are silent
The upstream needs a credential. Without one it answers unauthorised and every feed with no venue leg stays unpublished - twenty of the thirty-three, which is the whole real-world set plus XMR/USD.
The thirteen exchange-backed feeds continue: they are computed from public venues and need no credential. What they lose is the cross-check and the stand-in, since neither can be established without a Pyth price to compare against. Readiness reports this as a warning rather than a failure, so the service looks healthy while two thirds of the catalog is silent - check the per-feed status, not the service's.
The exponent check is the quiet one and the most important. The scale of an update is pinned from the first authenticated one and any later drift is refused outright, because a silent rescaling is a price wrong by a power of ten that looks entirely well formed.
How it reaches the chain
- The aggregating service holds no signing key at all. It computes prices and serves them on an internal port.
- The node's publisher polls that port, authenticates the body with a shared HMAC of the exact bytes, and re-checks every item against on-chain state before signing - mirroring the contract's own skip rules so a bad item costs a counter rather than gas.
- The batch is written as ONE system transaction per EVM block, signed by the publisher key, which lives in the node's process and is used by nothing else in it.
- The service then reads its own result back off the chain and reports it, which is what the onchain field of every FeedView is.
At most 64 feeds go into one batch by default, and the contract's own maxBatch caps it again. A batch older than 2 s is not published.
The division of labour is the point. Everything slow or fallible - the poll, the authentication, the catalog read, the selection, the encoding, the signing and the write-ahead-log append - happens off the sealing path, which receives a finished transaction and applies it at the cost of any other transaction. And because the publisher re-checks every item against on-chain state before signing, mirroring the contract's own skip rules, an aggregator sending something bad costs a counter rather than a round.
The trust model
A price is as trustworthy as the operator that produces blocks, and no more. The publisher is the block producer.
- One party operates the node, holds the publisher key and orders transactions. A price inherits exactly the assumption that already governs ordering.
- Nothing on chain arbitrates a price. There is no second publisher, no dispute window and no slashing: the contract checks that an item is well formed, positive, not from the future and not older than the feed's last observation, and then writes it.
- The owner role can add a feed, change a feed's policy, rotate the publisher and adjust the batch and tolerance parameters. Ownership transfer is two-step. Those are the only write paths that are not the publisher's.
- Splitting the key from the computation limits one failure, not the other: compromising the aggregating service lets an attacker propose prices, and every one is still re-checked and signed by the node. Compromising the node is the end of the argument.
- If you liquidate on these prices, that is the assumption you are taking, and it is the one to state to your own users.
Nothing above changes that. The venue rules, the median, the outlier rejection and the cross-check make a price robust against a source misbehaving, which is a real and common failure. They do nothing against the party that signs. Read them as quality controls, not as a consensus.
The practical consequence for an integrator: these prices are suitable for a product whose users are told who produces them, and they carry the same assumption as the ordering of your own transactions - so if you already accept that assumption to use the chain at all, consuming the feeds adds no new one. If your design needs a price no single party can forge, this layer does not provide it and no parameter makes it do so.