Mark Relayer
One page for everything the mark-pushing role touches: the relay key’s authority, where prices come from, the on-chain k-of-n quorum that is the consensus, the roster ceremony, the per-lane guard chain, the operator runbook for btr-keeper oracle, and the store and wire reference the role depends on.
| Section | Covers |
|---|---|
| 1. The role | What the relay key can and cannot do; where marks come from |
| 2. Threat model | Oracle-manipulation classes and what resists them |
| 3. Quorum | The on-chain signature check, the digest, the push wire, access control, scheme rationale |
| 4. The roster ceremony | Roster commitment, rotation, reference disjointness |
| 5. Push guards, staleness and bands | The guard chain, per-push vs cumulative bounds, TTL, deviation bounds, LVR and OEV |
| 6. Keeper operations | Triggers, startup gates, reverts, liveness guard, what to watch, escalation |
| 7. Store reference | Where the marks live, lane classes, FeedData, consumer reads |
| 8. Oracle generations | What changed generation to generation, in one table |
| 9. Transparency: verify it yourself | Re-verifying the whole chain client-side |
| 10. Related | Neighbouring pages |
1. The role
For the operator running btr-keeper oracle, the process relaying signed marks into the chain’s mark store (MarkStoreP8, run by the Pool implementation). Metrics: Observability.
1.1. What the key is, and what it is not
The push path, publishMarks() on the Pool implementation, accepts a segment only from a relayer listed in that tier’s roster commitment. Price authority is the k-of-n signature set recovered against the committed signer roster; msg.sender pays gas. There is no session path: every segment is fully quorum-signed. The relay key authors nothing; it only chooses when to submit an already-signed segment. It must not be the deployer, the owner, a guardian, or an attester.
The contract does not enforce that separation. The relayer list decides which addresses may submit, not what else they hold. Role disjointness of the relay address is verified at the arming ceremony, against the live owner, guardian and roster.
| The relay key can | The relay key cannot |
|---|---|
| Land a validly co-signed segment, paying gas | Author a mark. It holds no attester key and the store verifies signatures, not the sender beyond the list |
| Choose when to push and which lanes ride along | Land a lane outside the deviation band, or a source second a lane has already accepted. Both fail per lane, not the whole segment |
| Stop pushing, which halts nothing but ages every lane toward its TTL | Halt, reanchor, change a class or un-halt anything. Guardian, steward and owner levers (Guardian) |
Losing the relay key is a liveness incident only. The other relayers on the list keep pushing; replacing the key is a roster rotation (setAuth, GOVERNANCE). The cutoff for a compromised attester key is the same rotation, and until it lands the guardian halts the affected lanes. A compromised attester quorum is bounded by the reference tier and the guardian, not by the relay.
1.2. Where prices come from
BTR does not acquire or aggregate prices. It consumes attested marks from high-frequency off-chain mark providers and re-verifies them on chain. A provider must supply a mark, a volatility estimate, a confidence interval and a source timestamp, land inside the lane’s TTL and survive the per-push deviation band. Which providers serve which lane is a deployment fact: deployed instances.
The signing quorum lives off chain. Each signer holds its own key on its own node, independent of the others. The replica serving a quote builds the segment from its own aggregated view, signs it, and proposes it to its peers; a peer countersigns only after re-validating every record against its own independent market view (price tolerance, timestamp skew and age, volatility/confidence understatement). Below quorum, no quote is served. A signed segment reaching the chain therefore means k independent processes, holding k independent keys, each independently agreed this price was correct at this time.
The aggregation method, the signing service, the quote endpoint and key management are off-chain provider internals, not reproduced here. What follows is on-chain BTR only.
2. Threat model
2.1. Oracle-manipulation classes
Failure modes that have repeatedly drained oracle-priced venues:
| Vector | Mechanism |
|---|---|
| Single-key compromise | One stolen producer key pushes an arbitrary mark, the pool prices off it, an attacker swaps the drained side. |
| Gradual walk | No single push looks unreasonable; a compromised producer walks the mark in many small steps until collateral is gone. A per-push sanity band leaves the sum unbounded. |
| Future-dated report | A far-future timestamp clears the replay guard once, then freezes the lane permanently at a stale price to be picked off. |
| Volatility understatement | A producer signs to collapse the pricing spread, then round-trips against a spread-free mark. |
| Source manipulation | No key stolen, no producer misbehaving: an attacker moves a majority of the venues the composite is built from, and the quorum faithfully signs an aggregate of manipulated inputs. Nothing downstream can tell it from a real move. The guards below bound how fast that mark walks the pool, never whether it is right; the defenses are off-chain and economic (Security Overview). |
Design goal: track a real crash faithfully, never brick, and never let one or few compromised keys one-shot or slow-walk the mark.
2.2. Manipulation resistance
- External-mark quoting: the quote source is a mark aggregated off-venue, not pool-internal reserve state, so a single-block, flash-loan or sandwich move of the reserves cannot move the quote.
- Volatility-adaptive per-lane band: a segment moves a lane at most , so a single compromised segment cannot one-shot the mark; a breach darkens the lane, it does not revert (§5.2).
- Roster governance: every segment needs signatures from the tier’s committed roster (, ). The roster, and the relayer list change together, only through
setAuthat theGOVERNANCEtier. - Guardian fast-freeze: guardian, risk steward or owner can
halt(lane, tierMask)on either tier, instantly, for every pool at once; only the owner canunhalt. A halted lane reads markless and fails closed inFeedMathLib.gate; release semantics and thereanchorpast the ceiling: §5.2.
3. Quorum
3.1. The on-chain quorum is the consensus
There is no separate consensus protocol between BTR and the mark provider. The consensus is the on-chain signature check. The store holds one 128-bit roster commitment per tier: the high half of keccak256(nSigners | signers | k | nRelayers | relayers), signers strictly ascending, addresses packed. The push carries the roster in calldata; the store hashes it and compares it to the commitment, so a roster costs one storage word however many signers it holds.
Authorization, per segment (MarkStoreBase._quorum):
- The roster prefix of the tail must hash to the tier’s commitment, and
msg.sendermust be one of its relayers. - The tail carries 65-byte signatures over one EIP-712 digest. Each is recovered against the same digest, and every recovered address must be strictly greater than the previous one and present in the roster. Strict increase is the deduplication check: a repeated signature fails on its second appearance, an unsorted set at the first inversion. accepted signatures prove distinct roster keys. A malformed tail or reverts
InvalidBlobHeader; anything else revertsNotAuthorized. - The stride is a fixed 65 bytes with no EIP-2098 compact form. The signature count is the quorum claim, so its encoding must be unambiguous.
- A segment’s lanes are written only when its quorum passes: a failed quorum reverts the whole push.
A signature binds the price, not the order segments land in: an open push would let anyone holding two signed segments push the extreme one, trade, push the other and trade back. Hence the relayer list. One stolen signer key can push nothing on its own.
3.2. What the digest binds
The digest is EIP-712 BatchQuoteV4(bytes32 blobHash) over the segment’s own calldata (header and lane entries, not the quorum tail). The domain is name "BTR ExternalOracleV4", version "2" (the P8 layout’s; a segment of another wire never verifies here), chainId, and a per-tier verifyingContract: the PoolFactory for the primary tier, address(keccak256(factory, 2)) for the reference tier (tierVerifier). The name is a frozen signing label (MarkStoreBase); the V4 oracle contract it names is retired.
| Field | What it prevents |
|---|---|
chainId | Cross-chain and post-fork replay. |
verifyingContract | Cross-tier replay. A segment signed for the primary tier cannot verify on the reference tier, or the reverse, even when the two share signers. Without it the reference band (§5.2) would fall to message reuse. |
version | Cross-layout replay. |
3.3. The push path: wire, quorum, and per-lane guards
publishMarks() reads its calldata as segments back to back; each is one tier at one source second.
Wire. Header 13 bytes: t:u8 | srcSecs:u32 | laneMask:u64. Then, per set lane in ascending order, markF:u32 | code:u8, and a σ8:u8 when code & 0x80 is set; code & 0x7F is the confidence code (0..61). Then the quorum tail. markF is a 25-bit mantissa with a self-describing exponent; there is no per-feed bias and no epoch.
Whole-push reverts:
| Error | Cause |
|---|---|
InvalidBlobHeader() | t not 1 or 2, empty lane mask, truncated segment, a confidence code above 61, or a malformed tail |
FutureTimestamp() | srcSecs leads block.timestamp by more than 5 s |
StaleData(age, 120) | srcSecs trails block.timestamp by more than 120 s |
NotAuthorized() | Roster does not match the commitment, sender not a listed relayer, or a signature not from a strictly ascending roster member |
Per-lane, fail-soft (the rest of the segment lands):
| Outcome | Cause |
|---|---|
| Skipped | Lane unregistered, or halted on this tier |
| Skipped | srcSecs not past the lane’s own observation second (replay guard) |
| Darkened | Deviation band breach (§5.4): the lane keeps its mark as anchor and reads markless |
| Accepted | Within the band, or the lane had no mark (first push, or after reanchor) |
The push emits no event. Signed segments are the transaction’s own calldata and every lane is a storage read, so cadence comes from the calldata and the receipt, and accepted, skipped and darkened lanes from the lane words at that block (§9). Each group of four lanes is one storage word per tier, written once per segment: a 9-lane primary push costs 73,972 gas full-transaction.
3.4. Access control
| Role | Capabilities |
|---|---|
| Owner | setAuth(t, signers, k, relayers) at GOVERNANCE; register(lane) at LISTING; reanchor(lane, tierMask) at TUNING; unhalt(lane, tierMask), instant |
| Guardian, risk steward or owner | halt(lane, tierMask), instant |
| Guardian or owner | cancel(op) on any queued store op |
PoolFactory | migrateFrom(oldImpl) during a fleet upgrade, once, into an empty store: P8 source only, copied raw. Queued store ops do not carry over; re-queue a pending setAuth after the upgrade |
| Signer | One of the -of- roster keys; authorizes a segment by signature. Not a caller role |
Timelocked ops queue on first call and run on a repeat of the same calldata inside the 7-day grace window. Before the AccessControl quorum policy is armed (the ceremony), they run at once. Every store op checks the store is the one pools read (_live: the factory’s implementation is this one), so nothing lands on, or queues against, a retired or pending implementation.
There is no instant signer revoke. A leaked key is answered by halting the lanes it could move, then rotating the roster at GOVERNANCE; with , one leaked key moves nothing by itself.
3.5. Signature scheme rationale (k ECDSA, not aggregation)
The quorum is concatenated 65-byte ECDSA signatures over one shared digest, not a BLS or Schnorr aggregate:
- Aggregation is a gas regression on EVM chains. BLS verification needs a BN254 pairing; Schnorr-style schemes need per-signer public keys on chain plus an off-chain nonce round. At a roster of at most 16, plain
ecrecovercalls are cheaper and simpler. - Membership changes stay cheap. A rotation is one commitment word. A fixed-committee aggregate would turn it into a key re-share.
- No interactive nonce round. Each replica signs the shared digest; the relayer sorts and concatenates.
- The quorum amortizes per segment. All signatures cover one digest, so a segment of lanes pays verification once.
4. The roster ceremony
Loosening price authority (new signers, lower , new relayers) and hardening it run the same path: setAuth at GOVERNANCE, guardian-or-owner cancellable. The instant lever is the lane halt, not a roster edit.
4.1. Genesis: no 1-of-1 bootstrap
setAuth refuses fewer than 3 signers, or , an unsorted or zero signer, and an empty or oversized (> 16) relayer list. A deployment never passes through a single-key state. The ceremony script (Protocol.s.sol) commits both tiers’ rosters, reads them back, and commits the same relayer set to both tiers. Each keeper tier config lists exactly the manifest’s relayers for that tier, and a keeper test fails if the two drift, so the addresses that push are the addresses committed.
4.2. Rotation
setAuth(t, signers, k, relayers) replaces a tier’s whole commitment. Off-chain, a new signer is a standalone key on its own node, not derived from an existing signer. The mark provider’s signer catalog and the committed roster must change together: a roster the provider does not sign for stops that tier.
4.3. Ceremony parameters
| Constant | Value | Meaning |
|---|---|---|
MAX_ROSTER | 16 | Cap on signers and on relayers per tier |
| min signers / min | 3 / 2 | No 1-of-1 or 2-of-2 roster |
| Roster delay | GOVERNANCE tier of AccessControl.GOV_DELAYS() (schedule) | Delay on every setAuth |
| Grace | 7 days (ConstantsLib.GRACE_PERIOD_SECS) | Execution window after maturity |
4.4. Independent reference: signer disjointness
The cumulative bound (§5.2) works only if the reference tier is operationally independent of the primary. Both tiers live in one store under one AccessControl owner; what can differ is the roster.
Signer disjointness is not enforced on chain. Nothing compares the two tiers’ signers. Required configuration: disjoint rosters, signed by separate provider deployments. Per-deployment status: compare the two tiers’ AuthUpdated logs on the store.
Operating obligation. Every armed spoke band fails closed once its reference lane goes stale, so the reference tier is relayed on the same /heartbeat discipline as the primary, from its own signed segments (the per-tier domain means one tier’s segment never verifies on the other). Operator detail: §6.2.
Consequence. Where the rosters overlap by keys, one compromise signs both tiers, the reference band walks in lockstep and never trips. Disjointness makes the second tier require a second, independent compromise.
5. Push guards, staleness and bands
5.1. How a push is guarded (defense in depth)
A verified quorum is necessary, not sufficient. Every lane passes a fail-closed guard chain before it writes a mark.
| Guard | Bounds which vector | Rule |
|---|---|---|
| k-of-n roster quorum (§3.1) | Single-key compromise | distinct roster signatures per segment, or the push reverts |
Monotonic srcSecs per lane | Replay / reorder | A lane’s own last-accepted source second must strictly advance; a non-advancing lane is skipped silently |
| Future-dated bound (5 s) | Future-dated freeze | Reject a srcSecs leading the chain by more than 5 s |
| Absolute freshness (120 s) | Withheld stale segment | Reject a segment whose srcSecs is more than 120 s old; the read side gates on the class ttl |
| Volatility-adaptive band | Per-push spike | At most , normally a Brownian step over the source-time gap. A breach darkens the lane (§5.4) |
| σ and confidence floor | Understatement | Every accepted mark floors the lane’s σ and confidence at the realized |
Class σ floor (minSigma) | Understatement (baseline) | Every σ read is clamped to the class floor, including a σ stored before the floor was raised |
Reference band (refBandBps, lane R tier) | Gradual walk (cumulative) | Halt swaps once the primary diverges from the reference tier by the band (Flow Guards) |
| USD depeg band | Numeraire depeg | Halt the hub when the base’s USD mark leaves parity by its band (Flow Guards) |
| Guardian fast-freeze | Any suspected incident | halt(lane, tierMask), instant (Guardian) |
5.2. Per-push band vs cumulative band
The signatures authorize authenticity, not magnitude. Two magnitude bounds run in series.
Per-segment (sanity). A segment moves a lane at most a Brownian step over the source-time gap, never more than . The band binds per segment, not per transaction: segments stepping srcSecs by one second can chain about 125 advancing moves in one call. The quorum is trusted for magnitude; the cumulative bound is the reference tier.
A dark lane self-clears, up to the ceiling. The source-time gap grows while a lane is dark, so the band widens with it and a lane that fell behind during an outage clears once the true move fits. A lane whose age field saturates (4,095 s) reads stale under every class and bands at the cap. Past it does not clear.
Recovery past the ceiling is reanchor. reanchor(lane, tierMask), owner at TUNING, drops a dark lane’s anchor on the chosen tiers; its next push lands unbanded, as a first push does. Verify that push against the source before trading resumes. A class’s maxDev, ttl and minSigma are immutable in the store; retuning them is a new Pool implementation with a new class table, moved in by the fleet upgrade (migrateFrom keeps every registered lane and clamps stored σ to the new floor).
Cumulative bound. A per-segment band does not bound the total excursion. The reference tier closes that: every spoke leg arms refBandBps against its lane’s R mark, and swaps halt once the primary diverges past it. It is magnitude-based and push-rate-independent. Given disjoint rosters, draining a pool by walking the mark takes two simultaneously compromised quorums, and even then only within the reference band.
5.3. Staleness protection
- One fail-closed gate:
FeedMathLib.gatereverts, in order, on HALTED, STALE, DEAD (mark 0) and UNCERTAIN, for the quote mark, the base mark and the reference mark alike. - Freshness is measured from the lane’s own source second: ,
StaleData. is the lane class’s ttl (at most 4,089 s, so a saturated age always reads stale). - Confidence halt:
confidence > MAX_CONFIDENCE_HALT_BPS = 1,000bps revertsThresholdViolation. Exactly 1,000 bps still quotes. Below the line, confidence widens the spread (Spread & Fees §3.5). - A mark landed this block is not gated. It has cleared the quorum, the source clock and the band. Rejecting it would hand every relayer a pool-wide outage switch. One pool call loads each leg’s mark word once at its start, so every leg of the call reads one mark.
- Staleness surcharge: the spread ramps with , grace (Spread & Fees §3.4).
- Depeg halt: the base reverts
BaseDepeggedwhen its USD mark leaves 1.0 by more than its band (Flow Guards).
5.4. Deviation bounds
Canonical statement of the per-lane band. Write for the mark, for the class maxDev in bps, for the lane’s σ (never below the class floor) in PBPS, for the source-second gap since the lane’s own last accepted observation. A segment darkens the lane when :
The band compares against the live mark, or on a dark lane against its anchor, so re-entry is banded over the whole gap.
| Term | Value | Rationale |
|---|---|---|
| 6 | sanity cap on a Brownian step | |
| 1,800 s | The provider’s 30-minute σ window | |
| 9 | Caps the band at | |
| class field, 1 to 2,000 bps | Microstructure floor, not the primary bound | |
| source seconds since the lane’s last accept | Source time, not block time |
σ and confidence are floored at the realized move on every accepted mark, whether or not the segment carried a σ byte. A σ byte on a lane that did not advance only raises a dark, anchored lane’s σ, sizing its own heal band without a price write.
Lane classes. Each lane has one of seven classes fixed in the store constructor: ttl 1 to 4,089 s, maxDev 1 to 2,000 bps, minSigma 65 to 4,063,232 PBPS. The ceremony builds the table from the chain manifest and bounds each field before packing.
- σ ceiling: 4,063,232 PBPS, the σ8 code’s range.
- Confidence: halts past 1,000 bps in
FeedMathLib.gate.
5.5. Known risks: LVR and OEV
AIMM’s external-mark design changes which oracle risks matter. It does not make LP capital immune to adverse selection.
LVR (Loss Versus Rebalancing): push-latency form
Definition: LP loss vs a continuously rebalanced strategy at the true external price. Classical CFMM LVR comes from a reserve-implied mid that only moves when someone trades. AIMM quotes the lane’s pushed mark, so that channel is closed, but only that channel: the residual below is real and unrebated.
Residual risk: between the moment the true price crosses the keeper’s (or a heartbeat / CI-spike fires) and the correcting push confirming on-chain, informed flow can still trade against a frozen mark. Call this push-latency LVR. Pushes fire on three triggers:
- A crossing.
- Heartbeat expiry.
- A CI spike: the feed’s confidence interval widening past a keeper-side threshold, which covers depeg and dispersion onset between crossings.
Primary controls:
| Control | Role |
|---|---|
Keeper theta_bps () | Bounds intended stale gap before a push |
heartbeat_s | Liveness ceiling; ops hard-fail if violated |
Pool minFeePbps | Trader pays ; size so one-way cost covers typical stale gap |
Provider freshness + poll_interval_ms | Detection latency before the keeper even builds a tx |
Secondary / defense-in-depth:
| Control | Role | Caveat |
|---|---|---|
vegaBps → | Widens spread when is already high | Weak on the first jump of a new regime |
confidence → | Taxes uncertain marks | Halt past MAX_CONFIDENCE_HALT_BPS |
STALE_Z → | past the grace | Zero while : does not price healthy intra-θ drift |
| Coverage skew (fixed protocol law) | Taxes one-way inventory moves | Zero at coverage |
kappaCovBps coverage wall | Convex toll on coverage-declining output | Required on every listed asset including the hub; a spoke never above its hub |
minDispersionPbps / curve | Depth near mid | Tighter / more center-bumped = better UX, more size extractable per bp of gap |
refBandBps / TTL halt | Circuit breakers | Availability vs bleed |
OEV (Oracle Extractable Value): update MEV at the push
Definition: MEV from ordering around the oracle update itself, not from slow between-push drift. Glossary: OEV.
On a chain with a public mempool, a keeper push is otherwise a publicly visible transaction before it lands; searchers can frontrun, backrun, or pre-position on a predictable heartbeat schedule. Private submission is a config requirement on the current wire: every push is routed directly to builder inboxes rather than the public mempool, and the keeper refuses to arm without that route configured. Which builders and payment addresses are wired is a deployment fact.
Primary controls:
| Control | Role |
|---|---|
minFeePbps | The pool param that taxes both frontrun and backrun of a legitimate push |
| Private submission | Required to arm; keeps every push off the public mempool |
| Atomic push + solver auction returning bid to LPs | Stronger capture (not shipped) |
Important trade-offs:
- Lower → smaller jumps per event, but more events → OEV frequency can rise even as LVR per gap falls.
heartbeat_sis a primary LVR control and a net OEV aggravant (pure wall-clock predictability).- The deviation band and the signer quorum defend compromised-key abuse, not third-party reordering around a legitimate push.
STALE_Zdoes not protect the immediate post-push backrun ( resets to 0).
Keeper and heartbeat are set per asset class, tighter and longer heartbeat for pegged assets, wider and shorter heartbeat for volatiles. They are off-chain keeper configuration, not on-chain parameters: the lane’s class exposes maxDev and , readable by anyone. Align pool minFee with the mean- or hard-gate policy chosen for each class.
6. Keeper operations
6.1. When the keeper pushes
Three triggers, evaluated per feed against the last landed on-chain push:
| Trigger | Rule |
|---|---|
| Deviation | the live mark has drifted from the last landed mark past a share of the edge the pool is currently quoting for that leg |
| Heartbeat | the per-feed maximum time between on-chain pushes has elapsed |
| CI spike | the feed’s confidence interval has widened materially since the last push |
The deviation trigger is edge-relative, not a static θ. The keeper mirrors the pool’s own spread computation per leg and pushes once the drift reaches a fixed share of it. A static threshold per feed would not track fee floors and risk parameters, which move independently of it. Confidence is excluded from the trigger basis, though the pool’s quote includes it: a freshness proxy in the push boundary would make a stale feed push less when it should push more.
CI spike covers the pinned mark: a depeg onset that holds the price flat while dispersion explodes trips neither the deviation trigger nor the heartbeat. A feed carrying no trigger of its own can ride along on an already-due blob when it is close enough to its own push boundary, so a batch lands one coherent view.
Heartbeat is a staleness bound the keeper enforces on itself, not a liveness watchdog. The ops rule coupling it to the contract is ttl ≈ 2·heartbeat, and the keeper hard-fails at startup on any feed whose heartbeat exceeds half its on-chain TTL.
There is no hard push-rate cap. Per-feed refractory gaps bound spam and per-feed hourly forecasts drive gas budgeting and alerting, but neither refuses a push: the binding ceiling is a daily gas-spend breaker, which degrades gracefully; a hard rate cap would mis-price the hour the mark moves most.
6.2. Both tiers, or neither
Every spoke bands against its lane’s reference tier and fail-closes when the reference goes stale, so refreshing only the primary bricks every spoke swap once the reference passes its TTL. The two tiers cannot share a segment: the per-tier EIP-712 domain means each needs its own signed segment. The keeper relays both from one store address and one relayer set; the feed-liveness guard in §6.5 watches both tiers. Each tier bands on its own σ: the reference keeper keeps the σ its segment carried rather than mirroring the primary’s.
The relayers of a tier run as one leader and standbys. A standby watches every lane of the segment it would relay, not one representative lane, and stands down once the leader’s push lands; a band-darkened lane therefore cannot hide the leader’s landing and trigger a duplicate relay. The keeper forwards exactly k signatures, since the store rejects a surplus, and ages the upstream quote on its floored source second, the same second the store checks.
6.3. Startup gates
The keeper refuses to arm rather than push into a misconfiguration: it aborts at startup on any config, chain, feed-roster or quorum mismatch, on an upstream catalog that does not cover every subscribed feed under the configured oracle address and chain id, and on a missing private-submission route (§5.5). Quorum is re-read at a bounded cadence through the run, not only at boot, so a roster rotation is enforced live. The anti-pick-off gate is evaluated over every supervised pool, checking each asset’s deployed minFeePbps against the push threshold actually in force, and the same invariant is independently enforced by the risk keeper (Risk Steward).
Live broadcasting requires two independent arming flags; neither is set by default, so the keeper’s normal state is to run the full loop and broadcast nothing, which is also how a config change is validated. The exact gate names and thresholds are operational and not published.
6.4. Revert taxonomy
Only header, source-time and quorum failures revert the whole push; everything price-shaped fails soft, per lane. There is no event and no accepted mask: read the lane words. A band-darkened lane pages from the keeper (a markless, unhalted lane after a successful receipt). The silent per-lane monotonic skip is covered by watching each lane’s observation age directly.
6.5. The feed-liveness guard
btr-keeper guards is a separate role with a separate key: it sweeps every lane on both tiers at the factory, pages a human, then halts what stays dead.
Its heartbeat is read from the pusher’s own configuration, so the heartbeat a guard checks is the heartbeat the pusher promises. Four gates bound a halt, each reporting distinctly: a rolling automated-action budget consumed only by a landed broadcast, the two arming flags, a guardian-bit read of AccessControl.perms(signer) at startup, and the broadcast itself.
Automated action is rate-limited; alerting is never bounded by that limit.
Load-time rules refuse a config that could go dark: at least one alert stage; the earliest alert fires strictly before the earliest halt; an armed load rejects zero addresses.
The halt leg signs as a guardian and inherits Guardian, including the owner-only unhalt: a guard-driven halt always ends with a human.
6.6. After a change
- Liveness: the keeper marks a heartbeat after every successful tick; its probe restarts the process when that heartbeat goes stale.
- Latency: p50/p99 per leg from the health surface (quote fetch, pre-submission, submit).
- Triggers: one line per lane per relay decision, the effective θ and heartbeat, gas spend and the low-balance gate.
- On chain:
PoolFactory.feedOf(feedId, t).updatedAtSecsadvancing on every lane, on both tiers. - Downstream:
feeds.tradable_ratioandfeeds.worst_age_ratio(Observability).
A newly registered lane is dark and markless: its first push lands unbanded. Get that push in under supervision and verify it against the source before the leg is listed. The same holds after a reanchor.
6.7. Escalate
| Symptom | Escalate to |
|---|---|
| An attester key believed leaked | Guardian: halt the lanes at risk. Owner: setAuth rotation at GOVERNANCE |
| A lane pushing authentic marks you believe are wrong | Guardian: halt(lane, tierMask) |
Lane dark past 10·maxDev after an outage | Owner: reanchor(lane, tierMask) at TUNING, then verify the first push. Plan an hour |
A class’s ttl, maxDev or minSigma structurally wrong | New Pool implementation with a retuned class table, through the fleet upgrade at GOVERNANCE |
| Both tiers stale simultaneously | Incident: every spoke is fail-closed. security@btr.markets |
Nothing on this list is reachable from the relay key.
7. Store reference
To only read a BTR mark from your own protocol, go to Consuming Price Feeds. Data-flow view: Internal oracle.
7.1. Where the marks live
One mark store per chain, in the Pool implementation’s own storage. Pools read it by a 1-3 byte STATICCALL into Pool.fallback (one storage word per four lanes per tier); pushes and store governance DELEGATECALL the store logic MarkStoreP8. Each pool leg names a lane (OracleConfig.lane, below 64) and reads both tiers of it:
| Mode | Value | Quote source | When to use |
|---|---|---|---|
EXTERNAL | 0 | the lane’s primary (P) mark | Every priced leg, and always the base |
INTERNAL | 1 | 1.0 peg; the P mark gates depeg only | Cash-collateralized 1:1 units only |
Every spoke leg arms a reference band against the lane’s R mark; the base carries the USD depeg band instead (Flow Guards).
7.2. FeedData: the read shape
PoolFactory.getFeed(feedId) (primary) and feedOf(feedId, t) serve the IOracle.FeedData view of a lane, the feed id mapped by setFeedLane:
| Field | Role |
|---|---|
mark1e18 | Mark (1e18 WAD); 0 on a dark or halted lane |
sigmaPbps | σ (PBPS), never below the class floor |
updatedAtSecs | The lane’s accepted observation second |
ttlSecs | Class ttl |
confidenceBps | Mark CI (bps), decoded from the confidence code |
flags | bit0 = halted |
maxDevBps | Class maxDev |
sourceTsMs | updatedAtSecs in ms |
classes() on the store returns the lane-to-class map and the packed class table.
7.3. Consumer reads
| Need | Read | Notes |
|---|---|---|
| Spot / quote | mark1e18 | The only quote source (§5.5) |
| Freshness | age vs | From the lane’s own observation second (§5.3) |
| Uncertainty | confidenceBps | Widens the spread; halts past 1,000 bps |
| Realized vol | sigmaPbps | Vol band and staleness surcharge |
8. Oracle generations
| Generation | Layout | Lanes / word | Wire | Clock | Status |
|---|---|---|---|---|---|
| V1 | 64-bit, one feed per slot | 1 | v1 | shared per-push source time | Retired |
| V2 | 28-bit packed | 8 | v2 | per-slot deciseconds | Retired |
| V3 | 22-bit packed, session grants | 10 | v4 diff wire | deciseconds since an epoch | Retired |
| V4 | 29-bit packed, cyclic clock | 8 | v5 | deciseconds since midnight UTC | Retired |
V5 (ExternalOracleV5) | 25-bit mantissa + 7-bit exponent, beacon proxy | 4 | v6 | per-lane source second | Retired before mainnet |
P8 (MarkStoreP8) | 56-bit lanes, both tiers, class table | 4 per tier | 8 | per-word clock, 12-bit lane age | Current |
Every generation keeps the FeedData read shape and the k-of-n trust anchor. In the current generation the store lives in the Pool implementation: there is no oracle proxy, no beacon and no separate oracle AccessControl. The store changes only with the Pool implementation, through PoolFactory.executeReferenceUpgrade, which calls migrateFrom on the new store before any pool moves.
Which generation a chain runs, and its live addresses: deployed instances.
9. Transparency: verify it yourself
No part of the trust chain depends on trusting a BTR server. The Oracle Transparency page in the BTR front-end re-verifies the model client-side:
- Roster. Rebuilt from the store’s
AuthUpdatedlogs per tier and checked against the on-chain commitment. A queuedsetAuth(OpQueued) is shown with its ETA, so a rotation is visible before it lands. - Lane table. Mark, σ, confidence, age,
maxDev, halted status, per tier, from the lane words. - Push log. A push emits no event, so BTR keeps a record of every push the keepers land, one row per lane: trigger (deviation, heartbeat, confidence spike), source-to-chain latency by stage, distance from the reference price. “Verify” on a row fetches the signed payload, checks it against the transaction input from the chain’s own RPC, re-recovers the signatures in the browser under the tier’s domain (§3.2) and compares the roster they carry with the store’s on-chain commitment. The “k verified” badge is computed locally. The page’s own roster is read the same way: off the newest signed push, once the commitment matches.
- Relayers. Listed as informational: “relayer, not price authority”.
- Per-price redirect into the mark provider. Each decoded record links to the provider’s public verifier for the same
(symbol, sourceTs)attestation.
10. Related
| Page | Content |
|---|---|
| Internal oracle | Oracle modes and data-flow view |
| Spread & Fees | How , confidence and staleness affect fees |
| Parametrization | Feed configuration reference |
| Flow Guards | Depeg and reference-band circuit breakers |
| Security Overview | Defense in depth, threat model, trust assumptions |
| Access control | Roles matrix, timelock tiers, halt authority |
| Guardian | The levers this role escalates to |
| Risk Steward | The other bounded key, and the shared 2θ invariant |
| Observability | Every metric named above, with healthy ranges |
| Consuming Price Feeds | Read-side integration |
| Contract Addresses | Live store and rosters per chain |