Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reading price sources

audience: ai

The oracle’s inputs are exchange and chain feeds. None of them are mosaik-native; all of them live on the other side of the enclave boundary. This chapter specifies what the oracle subscribes to and what the admission criterion for each source is. The next chapter (wrapping) walks how the subscriptions terminate inside the enclave.

The source catalog

The oracle ships a declared source catalog — the set of upstream feeds it reads, broken down per token pair. The catalog’s blake3 digest is folded into OracleParameters.source_catalog_digest (setup), so any change to the catalog is a new oracle Config.content.

pub struct SourceCatalog {
    pub entries: &'static [(TokenPair, &'static [Source])],
}

pub enum Source {
    CexSpot {
        exchange:     CexExchange,        // Binance, Coinbase, Kraken, …
        symbol:       &'static str,       // "ETHUSDC", "ETH-USD", …
        endpoint:     &'static str,       // wss://…
        tls_spki:     &'static [u8],      // pinned SPKI digest
    },
    OnChainAmm {
        chain_id:     u64,
        pool_address: Address,
        protocol:     AmmProtocol,        // UniswapV3, Curve, …
        read_mode:    OnChainReadMode,    // ArchiveNode or LightClient
        quorum:       u8,                 // minimum node agreement
    },
    OnChainOracle {
        // References another mosaik oracle organism as a
        // source. Used for long-tail pairs where this
        // oracle aggregates peers.
        organism:     OrganismRef<'static>,
        pair:         TokenPair,
    },
}

Three classes of source, each with its own admission criterion:

  • CEX spot feeds — pinned TLS SPKI. The enclave’s HTTPS client refuses any cert chain whose leaf SPKI does not match tls_spki. A forced MITM would show as a handshake failure and a source-unavailable state (see below), not as a silently wrong price.
  • On-chain AMM reads — either an archive node the oracle operator runs (trust folded into the Measurements through chain_id and the client binary) or a light client verifying against Ethereum consensus (trustless in the light-client sense). quorum controls how many independent archive or light-client reads must agree before the source is considered fresh.
  • On-chain oracle (peer) — another mosaik oracle organism. The enclave subscribes to that organism’s stream exactly the way any consumer does (binding). This is how long-tail pairs get coverage: aggregate two or three other oracles whose Measurements the enclave admits.

Per-pair source selection

A pair’s entry in the catalog is the ordered list of sources the oracle will consult each tick. The aggregation policy (aggregation) decides what to do with the collected quotes; the source list only decides what to ask.

pub static SOURCE_CATALOG: SourceCatalog = SourceCatalog {
    entries: &[
        (TokenPair::new("USDC", "ETH"), &[
            Source::CexSpot {
                exchange: CexExchange::Binance,
                symbol:   "ETHUSDC",
                endpoint: "wss://stream.binance.com:9443/ws/ethusdc@bookTicker",
                tls_spki: BINANCE_WSS_SPKI_2026Q2,
            },
            Source::CexSpot {
                exchange: CexExchange::Coinbase,
                symbol:   "ETH-USD",
                endpoint: "wss://ws-feed.exchange.coinbase.com",
                tls_spki: COINBASE_WSS_SPKI_2026Q2,
            },
            Source::OnChainAmm {
                chain_id:     1,
                pool_address: uniswap_v3::ETH_USDC_POOL,
                protocol:     AmmProtocol::UniswapV3,
                read_mode:    OnChainReadMode::LightClient,
                quorum:       1,
            },
        ]),
        // further pairs …
    ],
};

Reasonable mixed catalogs combine CEX and on-chain sources; a pure-CEX catalog is cheapest to run but vulnerable to exchange-side outages and pair-delisting events. A pure-on-chain catalog is settlement-honest for the chain in question but lags the CEX book during volatile periods.

Staleness accounting

Each source carries a per-source timestamp. The oracle’s OracleParameters.max_source_age_ms (setup) is the threshold past which a source is considered stale. The aggregation policy decides what to do with a stale source; commonly: drop it from the quorum and flag the resulting tick if the drop pushes the source count below a declared minimum.

The in-enclave source state machine per source:

                 handshake OK, first msg within window
        ┌─────── ──────────── ──────────── ──────┐
        ▼                                         │
   fresh ──── msg older than max_age_ms ────► stale
        ▲                                         │
        │                                         ▼
        └─── reconnect + first msg within ── reconnecting

A source in reconnecting drops out of the aggregation quorum. The oracle publishes a tick anyway if the remaining sources meet the minimum; otherwise it publishes no tick and the consumer sees a gap in the stream cadence.

What the catalog is not

  • Not a price index. The catalog names sources, not weightings. Weightings and outlier rejection are the aggregation policy’s job (aggregation).
  • Not a failover list. Sources are consulted in parallel each tick; there is no primary/secondary distinction. Aggregation and quorum decide which sources contribute to the published tick.
  • Not operator-confidential. The catalog digest is part of OracleParameters, which is part of the oracle’s Config.content, which is reproducible from the operator’s release page. Consumers pin it transitively through the content hash.

Forward

The sources listed above live outside mosaik. The next chapter walks how they are terminated inside the enclave so that nothing outside the Measurements set can observe or tamper with their reads.