The relayer
It turns the registry into prices. Nothing settles without it.
relayer/ is what makes a registry entry into a number on chain. Nothing
settles without it, and a pairable with no price cannot settle anything.
cd relayer && npm install
npm test # 296 passing on 2026-08-25, offline
npm run probe # read every provider, touch no cluster
npm run dry # one cycle, sign nothing
npm run once # one real cycle
npm start # keep going, every intervalSecs (60 by default)
The gates are the part that has to be right
Everything in relayer/src/gates.ts is pure and offline, separated from the
fetching specifically so it can be tested without a network.
relayer/test/gates.test.ts carries 26 of the 296 offline tests, counted on
2026-08-25.
The governing rule: a gate failure means hold the last on-chain value. Never
push a zero, never push a guess. quote() returns a hold with a reason rather
than throwing, so a single bad provider degrades one pairable instead of taking
down the cycle.
The order the gates run in is load-bearing and covered in The oracle and the breaker: sanity band, staleness, cross-check split, MAD filter, spread gate, then the cross-check veto.
Two details that are easy to get subtly wrong:
Providers come in four tiers, and the log says which
Works with no configuration at all: Pyth, Jupiter routed quotes, NOAA, Steam, Wikimedia, DefiLlama, and a Solana RPC read.
Pyth feed ids are resolved from Pyth's own registry at runtime, so a wrong symbol fails loudly with a list of near misses instead of silently pricing against the wrong asset.
A pairable with no binding is reported UNBOUND on every cycle rather than
skipped quietly, because a registry entry that never gets a price is
indistinguishable from a working one in the UI until somebody tries to trade
against it.
The full set of per-pairable statuses: PUSH, HOLD, SKIP, FROZEN,
UNBOUND, UNAVAIL, MISSING, BREAKER, ERROR.
Declared feeds are config, not code
A FRED series id in seed.json is a new pairable.
A FRED series id in bindings.ts is a code change, a review, and a redeploy.
declaredProviders resolves a reference to a provider and applies one composed
unit conversion, and it deliberately does not try to become a scripting
language. Anything needing real parsing already has httpSources.
crossCheckOnly rides on the observation rather than on the provider, so it is
stamped on the way out rather than threaded through every provider.
Manual attestations expire
export const MANUAL_SHELF_LIFE_DAYS = 30;
This was a real bug for as long as the comment above it claimed otherwise.
gates.maxSourceAgeSecs defaults to 0 and 0 means "no gate", so 18 of the 20
attestations that existed at the time were pushing forever. PEAR-EA, the
flagship, had been publishing a number somebody typed on 17 August for eight
days, indistinguishable on chain from a live feed. Fifteen attestations remain
as of 2026-08-25.
A gate you have to remember to set is not a safety property.
An expired attestation reports unavailable rather than throwing, so it reads as "this pairable has no provider", which is exactly what it is, instead of an error somebody is tempted to retry past. The shelf life is overridable per entry, because a Big Mac price and a private-company valuation do not go stale at the same rate.
pinned is a different thing and is never stale by construction: the federal
minimum wage does not need an oracle, it needs an act of Congress.
Hard pairables get two independent sources
The oracle and a routed Jupiter quote off the real pool, so the spread gate does real work.
Their spread band is deliberately loose, because xStocks trade around the clock while the equity they track does not, and an overnight divergence between the token and the last equity print is real rather than a fault. The tight check on those is the liquidity floor.
WTI is resolved to its front month each cycle
Pyth publishes futures per contract month, and a hardcoded one works right up
until it expires and goes quiet. Copper is not sourced from Pyth at all:
Metal.XCU/USD is listed but dead with publish_time 0, and a
listed-but-unpublished feed is worse than no feed.
See Expiry and the roll for the blend and the calendar.
The price series the app reads
relayer/src/history.ts appends a JSONL line per accepted push, and the app
reads those files directly. There is no indexer and no backend: pairables,
markets and positions are filtered getProgramAccounts calls, so what the app
draws is chain state at a block rather than a cache somebody else populated.
{"t":1787662955,"v":4627646288,"n":2,"s":15}
That is XAU-OZT at $4,627.65 per troy ounce, two sources agreeing, 15bps of
spread between them.
peard