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

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(&ETH_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, &ETH_MAINNET.zipnet).await?;
let mut refunds = Tally::<Attribution>::read(&network, &ETH_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, &ETH_MAINNET.offer).await?;
let mut wins    = Offer::<Bundle>::outcomes(&network, &ETH_MAINNET.offer).await?;
let mut refunds = Tally::<Attribution>::read(&network, &ETH_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, &ETH_MAINNET.atelier).await?;
let mut accepted   = Relay::<Header>::watch (&network, &ETH_MAINNET.relay  ).await?;

while let Some(candidate) = candidates.next().await {
    // Verify the atelier committee's collective signature before trusting.
    if !candidate.verify_against(&ETH_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, &ETH_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