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:
tally::Refunds— the settled refund flow, authoritative attribution of who contributed to each block’s MEV.offer::Orders— the order-flow stream, the market surface the searcher’s bundles compete on.- 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::Refundsoroffer::Orderscarries 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-1aships anAlmanacinstance (see basic services — Almanac); every commit carries anAlmanacTick. 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
TicketValidatorkeeps 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
Orderinto 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
TicketValidatorclauses. 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::OrderBookcollection (a keyed snapshot) rather than, or in addition to, theoffer::Ordersstream. The snapshot gives current depth; the stream gives deltas. Market-makers typically need both. - Different refund filter.
tally::Refundsentries tagged as market-maker fills (a separateFillEntryshape) are the market-maker’s outcome stream; the filter onsearcher_idbecomes a filter onmarket_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 publicfillscollection.
Cross-references
- Part I — emergent coordination — shared policy — the reason every on-mosaik read returns evidence pointers the searcher can fold.
- Part I — agent shapes — shape 1 vs 2 — a
shape-1 integrator could do the reads in this
chapter without ever publishing an
Observationcommit; the searcher here is shape 2. - builder book — reading blocks
- builder book — refunds