Quickstart — submit, bid, read
audience: integrators
You bring a mosaik::Network; the organism crates layer a lattice
on top of it as a composition of mosaik-native services on a
shared universe. Every lattice is a LatticeConfig you compile
in; every organism inside it is a typed free-function constructor.
This page assumes you have read What a lattice gives you. If you only care about anonymous submission, the zipnet quickstart is a shorter path to the same code; this page is for integrators touching two or more organisms in one agent.
One-paragraph mental model
A mosaik universe is a single shared NetworkId. Many lattices
and many other mosaik services live on it. An operator stands up
a lattice by publishing a LatticeConfig — instance name, chain
id, and the six organism configs — plus any TDX MR_TDs their
lattice pins. You compile the LatticeConfig in. Each organism
exposes a tiny typed surface: Zipnet::<D>::submit,
Offer::<B>::bid, Atelier::<Block>::read, etc. Open a handle
against whichever organisms your use case touches; your
Arc<Network> serves all of them at once, and the same handle can
serve several lattices side by side.
Cargo.toml
[dependencies]
builder = "0.1" # meta-crate; re-exports UNIVERSE and LatticeConfig
zipnet = "0.1"
offer = "0.1"
atelier = "0.1"
relay = "0.1"
tally = "0.1"
mosaik = "=0.3.17"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
anyhow = "1"
Only pull the organism crates you actually use. A pure wallet
needs zipnet and tally; a pure searcher needs offer and
tally; a rollup operator consuming candidate blocks needs
atelier and relay.
builder re-exports mosaik::{Tag, UniqueId, unique_id!} and
NetworkId, so you rarely reach for mosaik directly in small
agents, but you will usually keep mosaik as a direct dep since
you own the Network.
Pin the lattice in a const
Operators publish a LatticeConfig the same way zipnet operators
publish a zipnet::Config. The canonical shape is a Rust const
in a published deployment crate; the from_hex route exists for
agents that cannot take a compile-time dep on that crate.
use builder::{LatticeConfig, UNIVERSE};
const ETH_MAINNET: LatticeConfig = LatticeConfig {
name: "ethereum.mainnet",
chain_id: 1,
// Each organism's own Config folds in its own content +
// intent + acl. Operators publish every field; integrators
// compile them in verbatim.
zipnet: zipnet::Config::new("ethereum.mainnet")
.with_window(zipnet::ShuffleWindow::interactive())
.with_init([0u8; 32]),
unseal: unseal::Config::new("ethereum.mainnet")
.with_threshold(5, 7)
.with_share_pubkeys(&[/* 7 BLS12-381 points */]),
offer: offer::Config::new("ethereum.mainnet")
.with_auction_window(std::time::Duration::from_millis(800))
.with_offer_pubkey(/* BLS12-381 point */),
atelier: atelier::Config::new("ethereum.mainnet")
.with_mrtd(/* 48-byte MR_TD */)
.with_block_template_schema(atelier::BlockSchema::L1Post4844),
relay: relay::Config::new("ethereum.mainnet")
.with_policy(relay::Policy::L1MevBoost),
tally: tally::Config::new("ethereum.mainnet")
.with_settlement_addr(/* 20-byte addr */),
};
LatticeConfig::lattice_id(Ð_MAINNET) is a pure function
returning the 32-byte UniqueId that every organism in this
lattice derives from. Print it and compare against the
operator’s published fingerprint to verify your build matches
theirs without any wire round-trip. Mismatched configs produce
different organism GroupIds; you silently do not bond and get
ConnectTimeout on any verb() call instead.
Build the network
use std::sync::Arc;
use mosaik::Network;
use builder::UNIVERSE;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let network = Arc::new(Network::new(UNIVERSE).await?);
// ... open handles below ...
Ok(())
}
One Arc<Network> serves every organism and every lattice you
bind. Bring your own mosaik builder if you need specific
discovery, TLS, or Prometheus configuration — see
Connecting to a lattice.
A wallet: submit + track refunds
use futures::StreamExt;
use zipnet::Zipnet;
use tally::Tally;
let submitter = Zipnet::<Tx2718>::submit(&network, Ð_MAINNET.zipnet).await?;
let mut refunds = Tally::<Attribution>::read(&network, Ð_MAINNET.tally).await?;
// Fire and forget.
let receipt = submitter.send(tx).await?;
println!("submitted, tracking id = {receipt:?}");
// Watch attributions as they land.
while let Some(attr) = refunds.next().await {
if attr.concerns(&receipt) {
println!("refund {} wei on slot {}", attr.amount, attr.slot);
break;
}
}
submit returns when zipnet’s round accepts the envelope — a
few hundred ms in the interactive window. refunds may land
seconds to minutes later depending on the chain’s block cadence
and whether any of your submissions made it into a winning
block.
A searcher: bid + verify outcome
use offer::Offer;
use tally::Tally;
let bidder = Offer::<Bundle>::bid (&network, Ð_MAINNET.offer).await?;
let mut wins = Offer::<Bundle>::outcomes(&network, Ð_MAINNET.offer).await?;
let mut refunds = Tally::<Attribution>::read(&network, Ð_MAINNET.tally).await?;
let bid_id = bidder.send(Bundle { slot: target_slot, bid: 1_000_000, txs: vec![/* */] }).await?;
// Wait for the auction to commit for this slot.
while let Some(outcome) = wins.next().await {
if outcome.slot == target_slot {
println!("auction for slot {} won by {:?}", outcome.slot, outcome.winner);
break;
}
}
// Later: check whether tally paid you.
while let Some(attr) = refunds.next().await {
if attr.slot == target_slot && attr.recipient == self_addr {
println!("tally credited {} wei", attr.amount);
break;
}
}
Offer::<B>::outcomes is a Stream<Item = AuctionOutcome>; every
committed auction lands on it in slot order. Reads filter by slot
on the consumer side; the organism does not push filtered
subscriptions in v0.
A proposer / sequencer: consume candidate blocks
use atelier::Atelier;
use relay::Relay;
let mut candidates = Atelier::<Block>::read (&network, Ð_MAINNET.atelier).await?;
let mut accepted = Relay::<Header>::watch (&network, Ð_MAINNET.relay ).await?;
while let Some(candidate) = candidates.next().await {
// Verify the atelier committee's collective signature before trusting.
if !candidate.verify_against(Ð_MAINNET.atelier) {
eprintln!("invalid candidate signature, skipping slot {}", candidate.slot);
continue;
}
// Ship the header to your proposer / sequencer endpoint.
your_proposer_endpoint.submit(&candidate.header).await?;
}
// Relay commits the proposer ack once it lands.
while let Some(ack) = accepted.next().await {
println!("slot {} accepted by proposer {:?}", ack.slot, ack.proposer);
}
The verify_against call validates the block’s BLS aggregate
signature under the atelier committee’s published public keys.
See Reading built blocks for the full
verification path.
Share one Network across lattices
Because every constructor only takes &Arc<Network>, one handle
can serve many lattices:
const ETH_MAINNET: LatticeConfig = /* ... */;
const UNICHAIN_MAINNET: LatticeConfig = /* ... */;
let eth_offer = Offer::<Bundle>::bid(&network, Ð_MAINNET.offer ).await?;
let uni_offer = Offer::<Bundle>::bid(&network, &UNICHAIN_MAINNET.offer).await?;
// ... and any unrelated mosaik services on the same universe ...
Every lattice derives its organism IDs disjointly from its own
LatticeConfig, so they coexist on the shared peer catalog
without collision. You pay for one mosaik endpoint, one DHT
record, one gossip loop — not one per lattice. See
Cross-lattice coordination for
the full cross-chain integrator shape.
Error model
Every organism’s constructor returns the same small error set (carried over from zipnet):
pub enum Error {
WrongUniverse { expected: mosaik::NetworkId, actual: mosaik::NetworkId },
ConnectTimeout,
Attestation(String),
Shutdown,
Protocol(String),
}
ConnectTimeout is the one you will hit in development — usually
a mismatched LatticeConfig (different instance name, wrong
chain id, or stale organism config) or an operator whose lattice
is not up yet. Compare LatticeConfig::lattice_id(&YOUR_CFG)
against the fingerprint the operator published before debugging
anything else.
Shutdown
drop(submitter);
drop(refunds);
// network stays up as long as any handle holds it
Handles are independent. Dropping one closes that handle; the
others stay live. The Arc<Network> stays up while any handle
holds it.
Next reading
- What you need from the operator — the exact fact sheet the operator hands you before you code.
- Submitting transactions anonymously — the zipnet side of the wallet quickstart, with cover traffic, retry, and size discipline.
- Placing bundle bids — offer auction cadence, bid sealing, and withdrawal.
- Reading built blocks — committee signature verification.
- Receiving refunds and attributions —
tally::Attestationsverification against a settlement contract. - TEE-gated lattices — when the lattice requires your own agent to run inside TDX.
- Designing block-building topologies on mosaik — the underlying pattern if you are curious or if you are planning to extend the topology.