Feed oracle

The file and URL for this page still read internal-oracle, a legacy slug. The page is the consumer-side reference for the external feed; INTERNAL mode is the small par helper below.

Every AIMM leg prices off a mark supplied by the IOracle its OracleConfig names. This page is the consumer-side reference for that feed: the read shape getFeed returns, the state each push overwrites, the bounds a push must clear, and the wire format of the signed push API. The one exception is the internal par helper, which returns a fixed mark of 1.0 for cash-collateralized 1:1 tokens, where the peg is the honest price and a live feed would only add a manipulable input. Security properties and the mode reference live in Oracles.


1. Oracle modes

OracleConfig.mode picks one of two modes:

  • EXTERNAL: the mark is whatever the IOracle at primary returns (an NX Rates push feed, a Chainlink adapter, a view-only reader over another pool or a vault NAV, so the book can be fully on-chain).
  • INTERNAL: the par helper above, legal only on cash-collateral 1:1 spokes and never on the base.

The mode reference is Oracles §1; per-pool selection is Curation §3.2.


2. Unified trust schema

NX Rates attests levels (mark, σ, confidence) and signs them; the chain stores them directly and bounds each push by a volatility-adaptive deviation band. No on-chain EMA smoothing (that moved to the source).

FieldNXR attestsChain storesSwap consumes
PriceFresh mark (NXR 30s close, θ + heartbeat)Raw mark; per-push move bounded by the adaptive bandRaw mark (mark1e18, 1e18 WAD)
VolatilityParkinson σ (48×30m NXR bars)Signed σ direct; floored at realized |Δmark|/mark where the σ word is loadedsigmaPbps
UncertaintyMark CI (bps); interim keeper = max(1, 25%·σ) until NXR dispersionCaps onlyRaw confidenceBps

Price authority: k-of-n distinct granted NXR signers over one EIP-712 digest, enforced on-chain on the push path (minimum 2-of-3, MAX_SIGNERS = 16); the submitting keeper is unpermissioned on the signed path. On the live V4 the entry points are pushSignedV4(blob, sigs) and the session variant pushV4(blob), which pins the sender to a quorum-granted relay for at most an hour. Set installation and rotation: Oracles §4.2.


3. FeedData (read shape)

struct FeedData { uint256 mark1e18; // fresh mark, 1e18 WAD. QUOTE SOURCE. (storage encoding is version-specific; see Oracles §2.) uint32 sigmaPbps; // stored σ (PBPS): signed sample floored at |Δmark|/mark. PRICING INPUT. uint32 updatedAtSecs; // observation timestamp (s). On V4 this IS the source time, see below. uint16 ttlSecs; // freshness window (s). uint16 confidenceBps; // mark 1σ CI (bps), decoupled from σ. uint16 flags; // feed flags. bit0 = paused (guardian fast-freeze, fail-closed). uint16 maxDeviationBps; // per-push deviation-band FLOOR (bps), not its max: band ceiling = 10x this. Mandatory non-zero. uint48 sourceTsMs; // NXR-signed source time (ms). Monotonic replay guard + data-age. }

getFeed returns this shape; the mark is a plain 1e18 WAD. The on-chain storage differs by oracle version. V1 packs one feed per 256-bit slot (B64 mark), V2 packs 8 feeds per slot: the two memory layouts are set side by side in Oracles §2; the live V4 packs 8 lanes of 29 bits with one clock per slot (Oracles §11.2). sigmaPbps despite its name is not an on-chain EMA: it is the NXR-signed σ stored directly, floored at the realized \|Δmark\|/mark where the σ word is loaded (a compromised-signer backstop; §4).

Two V4 specifics a consumer must not read past:

  • updatedAtSecs and sourceTsMs are the same instant. V4 stores one reconstructed source time per slot and no relay landing time, so updatedAtSecs == sourceTsMs / 1000. Freshness is source age either way. Outside the 6 h acceptance window the read fails closed: updatedAtSecs comes back as now − 21601, which is stale under every deployed ttl.
  • Age is per slot, not per feed. Eight lanes share one clock, so a lane whose own value did not change still reports the slot’s age. Class-pure slot assignment is what keeps that honest; it is not a per-feed freshness guarantee.

V1 emitted no push event. V4 emits SlotsPushed(seq, sourceTsDs, acceptedMask, blobHash) on both push paths, plus LanesSkipped(slotId, laneMask) when a lane fail-softs.


4. Stored state (no on-chain recurrences)

No price EMA, no σ-EMA fold. Each push overwrites the mark (mark1e18) and sigmaPbps directly, so stored state is exactly what was signed; all smoothing lives at the source (NX Rates), where the full tape is available to do it well.

  • σ floor (compromised-signer backstop): the stored σ is max(signedσ, |Δmark|/mark in PBPS), so a mark move forces a proportional spread. A signer cannot sign σ = 0 to make a mark-then-self-swap round trip spread-free. V4 applies it only where the σ word is already loaded — the deviation-band slow path, or a slot the blob carries σ entries for. σ/conf elision is total, so a sub-band move in a σ-less blob leaves the stored σ untouched.
  • Per-push deviation band: each push must satisfy |Δmark|/mark <= maxDeviationBps + min(6·(σ_pbps/100)·√(dtSource/1800), 9·maxDeviationBps) in bps (volatility-adaptive, using the stored prior σ, stored in PBPS and entering in bps). On V4 a breach skips that lane — it keeps its previous value and its bit appears in LanesSkipped — rather than reverting the push. A lane with no previous mark, i.e. a feed just registered, has nothing to band against and its first push is unbanded. Full derivation: Oracles §8.3.
  • maxDeviationBps is the band floor, not its maximum. The ceiling is exactly 10 × maxDeviationBps, so at the shipped stable floor of 50 bps a single push may move a stable’s mark by up to 500 bps - the whole width of BASE_DEPEG_HALT_BPS = 500. Size the floor against that ceiling.

5. Push API

V1 wire. The live instances run wire v5 on ExternalOracleV4: an 11-byte header (ver:u8(=5) | seq:u32 | sourceTsDs:u24 | nP:u8 | nS:u8 | nC:u8) followed by positional entries — price 5 B (gi:u8 | lane:u32), σ 5 B, confidence 3 B — in strictly ascending gi, with no tickerId on the wire and registration through registerFeed(feedId, globalIndex, expBias, maxDeviationBps, ttlSecs), which seeds no mark and no σ. Byte-exact layout and golden vectors: Oracles §11.5. What follows is the V1 format, kept for readers of the retired instance.

function addFeed(uint64 tickerId, address base, address quote, uint64 price, uint32 sigmaSamplePbps, uint16 confidenceBps, uint16 maxDeviationBps, uint16 ttlSecs) external; // owner-only // tickerId: NXR/MITCH instrument id keying the signed records (append-only feedIdOf[tickerId]); the // same call validates the per-push bounds (deviation band, TTL, σ seed) the feed will be held to. function batchPushSigned(bytes calldata blob, bytes calldata sigs) external; // k-of-n signed

blob = header(8) || record(22)×n, big-endian.

  • header = version(u8) | sourceTsMs(u48) | reserved(u8): one attested source time for the whole batch, reserved MUST be 0, BLOB_VERSION = 1.
  • record: 22 bytes, keyed by the NXR/MITCH tickerId (ExternalOracle.sol), resolved through the append-only feedIdOf[tickerId] map; an unregistered ticker reverts.
  • sigs = k concatenated 65-byte ECDSA signatures over the EIP-712 digest of keccak256(blob), sorted by recovered address ascending.
tickerId64bpriceB6464bsigmaPbps32bconfBps16b
BitsFieldNotes
0..63tickerIdu64
64..127priceB64u64
128..159sigmaPbpsu32
160..175confBpsu16

Byte-exact wire layout: decoded by @btr-protocol/sdk (decodeBlob), mirrored 1:1 by the keeper encoder.


6. Consumer guide

NeedEXTERNALINTERNAL
Quotemark1e18constant 1.0 (FeedMathLib.getPegFeed)
Pricing σsigmaPbpsSTABLE_SIGMA_PBPS = 1000
CIconfidenceBpsgate feed only