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 the market — on-mosaik surfaces

audience: ai

With the handshake succeeded (binding), the searcher’s observation loop can start consuming testnet-1a’s public surfaces. This chapter catalogues the three on-mosaik reads that dominate an MEV searcher’s input side:

  1. tally::Refunds — the settled refund flow, authoritative attribution of who contributed to each block’s MEV.
  2. offer::Orders — the order-flow stream, the market surface the searcher’s bundles compete on.
  3. Cross-lattice subscriptions — other Flashbots lattices on other chains, wrapped through the same universe.

The chapter stops at on-mosaik surfaces. Chapter 4 (wrapping) handles mempool, CEX depth, oracle feeds — everything that does not speak mosaik natively and has to be lifted into a Stream by a feed organism. The same chapter covers the attestation pattern used when the searcher needs to trust a wrapped external source (Binance, say): bond against the feed image’s TDX Measurements instead of trusting the feed operator.

What on-mosaik surfaces give the searcher

Three properties the searcher relies on and off-chain replacements cannot supply:

  • Evidence pointers. Every commit on tally::Refunds or offer::Orders carries blake3 pointers into the upstream commits it folded in. A replayer can reconstruct “why this refund was attributed this way” without trusting the searcher.
  • Shared clock. testnet-1a ships an Almanac instance (see basic services — Almanac); every commit carries an AlmanacTick. The searcher’s observation loop, the inference scheduler, and the submission pipeline all sequence on the same clock.
  • Ticket-gated admission. The searcher is not subject to a centralised API key; its reads continue so long as its TicketValidator keeps admitting the Flashbots-published Measurements. This is the fingerprint-not-registry discipline carried through from zipnet.

Reading tally::Refunds

tally::Refunds is the searcher’s ground truth for trade-level outcomes. Every bundle the searcher submits that lands in a winning block produces a RefundEntry the tally committee commits:

use builder::testnet_1a::tally::{Refunds, RefundEntry};

let tally = network
    .subscribe::<Refunds>(TESTNET_1A.stable_id())
    .await?;

while let Some(entry) = tally.next().await {
    // entry is a RefundEntry committed by the tally
    // organism. Its shape is pinned by the builder
    // book; we read what we care about:
    let RefundEntry {
        bundle_id,
        block_number,
        refund_wei,
        searcher_id,
        evidence,
        ..
    } = entry;

    if searcher_id == SEARCHER_CFG.stable_id() {
        outcome_store.insert(
            CycleId(block_number),
            OutcomeReport {
                bundle_id,
                realised_refund_wei: refund_wei,
                evidence,
                // inference-time input snapshot is
                // attached out-of-band; see chapter 5
                inference_snapshot: None,
            },
        );
    }
}

The searcher filters on its own searcher_id because Refunds is a shared surface — every searcher bonded to testnet-1a reads the same stream. The filtering is the searcher’s choice, not an ACL partition; the shared stream is the shared policy the refund accounting mediates.

when() DSL for composite observation

For searchers whose strategy depends on cross-refund patterns (e.g., “accumulate ten consecutive refunds from the same origin”, “wait for a refund burst exceeding a threshold”), the mosaik when() DSL composes the observation into a single reactive expression:

use coalition::when;

let recent_bursts = when!(
    refund_stream.window(Duration::from_secs(30))
        .filter(|r| r.refund_wei > threshold_wei)
        .count() >= 5
);

// recent_bursts is itself a Stream<()>; each tick is
// the moment the condition became true. No polling.

The when() form is not a replacement for the raw stream — it is the compositional reading of the same substrate state. Both are available; the searcher picks per call site.

Reading offer::Orders

offer::Orders is the submission market — the stream of order-flow items that land in a testnet-1a block’s candidate set. Read access is attested-read-only (OFFER_READER_MEASUREMENTS is pinned in the searcher’s validator).

use builder::testnet_1a::offer::{Orders, Order};

let mut orders = network
    .subscribe::<Orders>(TESTNET_1A.stable_id())
    .await?;

while let Some(order) = orders.next().await {
    // order is an Order committed by the offer
    // organism. Its shape is pinned by the builder
    // book: opaque payload pointer, priority tag,
    // evidence pointer into the shuffle seal that
    // produced it, and a cleartext metadata blob
    // the offer organism curates.
    market_state.ingest(&order);
    policy_input_tx.send(order.into()).await.ok();
}

Two shapes a searcher typically combines:

  • Flat ingest. Pipe every Order into the policy’s input channel; the policy decides relevance.
  • Curated windows. Maintain a bounded in-memory window of recent orders keyed by priority tag and blob digest; feed the window as a snapshot to each inference cycle.

Which shape fits depends on how the policy reasons. The substrate does not force a choice.

Cross-lattice subscriptions

testnet-1a is one lattice on the mosaik universe. If the searcher wants to capture cross-chain opportunities — a mispricing between an Ethereum testnet and a Base testnet lattice that both live on the same universe — it subscribes to both lattices’ tally/offer streams in the same process:

use builder::testnet_1a;
use builder::testnet_base; // hypothetical, if Flashbots
                            // operates such an instance.

let mut tally_eth  = network
    .subscribe::<testnet_1a::tally::Refunds>(
        testnet_1a::LATTICE.stable_id(),
    )
    .await?;

let mut tally_base = network
    .subscribe::<testnet_base::tally::Refunds>(
        testnet_base::LATTICE.stable_id(),
    )
    .await?;

// Both streams commit on the same Almanac; the
// searcher's policy can reason about ordering
// across them by AlmanacTick, not by wall-clock.

Three structural notes:

  • Shared universe, independent lattices. The two lattices do not coordinate at the consensus layer. Cross-lattice inference is the searcher’s own policy, composed from two independent evidence trails.
  • Separate TicketValidator clauses. Each lattice has its own Measurements set. The searcher’s validator must bond to both.
  • Shared Almanac. The universe’s Almanac is the only sequence beacon — both lattices’ commits carry ticks from it, so cross-lattice ordering is deterministic.

Building the searcher’s observation commit

As the searcher consumes the three streams above, it periodically emits a compact Observation commit on its own public surface. The commit is not a cache of the upstream streams; it is the searcher’s reading of the market at one Almanac tick, with evidence pointers back to every upstream commit it folded in.

use searcher_alpha::public::Observation;

let snapshot = window.snapshot_at(current_tick);
let observation = Observation {
    tick: current_tick,
    refund_digest:  snapshot.refund_digest,
    order_digest:   snapshot.order_digest,
    cross_digest:   snapshot.cross_digest,
    policy_version: SEARCHER_CFG.content.version_tag(),
    evidence: &snapshot.evidence_pointers(),
};

searcher.observations.publish(observation).await?;

Consumers following the searcher’s reasoning — a reputation organism, a downstream market-maker that quotes off the searcher’s published view, another searcher learning from the searcher’s exposed state — read Observation rather than re-aggregating the three upstream streams themselves.

Market-maker variant — differences at reads

  • Order-book state preferred to order flow. Market-makers subscribe to an offer::OrderBook collection (a keyed snapshot) rather than, or in addition to, the offer::Orders stream. The snapshot gives current depth; the stream gives deltas. Market-makers typically need both.
  • Different refund filter. tally::Refunds entries tagged as market-maker fills (a separate FillEntry shape) are the market-maker’s outcome stream; the filter on searcher_id becomes a filter on market_maker_id.
  • Inventory observation. A market-maker keeps an internal inventory state that, in the searcher’s case, has no analogue. The inventory is not a mosaik surface — it is the market-maker’s own bookkeeping — but its reconciliations with FillEntrys land in the market-maker’s public fills collection.

Cross-references