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 — bind many members from one agent

audience: integrators

This quickstart stands up an agent that submits and reads across three block-building lattices, one standalone attestation organism, one composite organism, and one module (the coalition’s Atlas) — all bound through a single CoalitionConfig. It assumes familiarity with the builder integrators quickstart when the coalition includes block-building lattices; the coalition layer extends that pattern rather than replacing it.

If the coalition contains no lattices (for example, a composition of oracle organisms plus a cross-oracle composite organism), the same pattern applies with different crates on the right-hand side of each handle.

What you need before you start

  • A CoalitionConfig from the coalition operator (struct definition, or a serialised fingerprint you copy into your crate).
  • A LatticeConfig per referenced lattice (inline in the CoalitionConfig the operator published, or pulled from each lattice operator’s handshake page).
  • An OrganismRef per referenced organism (role name + stable id + optional content hash + pointer to the organism’s own handshake). Both standalone and composite organisms are referenced this way.
  • A ticket from each per-member operator you intend to write to. Per-member admission is per-member; the coalition operator does not issue per-member tickets.
  • A ticket from each composite-organism or module operator whose write-side primitive you intend to use. Usually none: most composite organisms and modules are read-only for external integrators.
  • Your TDX image (if any member or service you write to requires attested client admission).

Step 1 — declare the CoalitionConfig in your crate

Paste the operator’s published CoalitionConfig definition, or import it from the operator’s handshake crate if they publish one:

use builder::LatticeConfig;
use coalition::{CoalitionConfig, OrganismRef};

// Exactly the bytes the operator publishes.
const ETH_MAINNET:      LatticeConfig<'static> = /* from ethereum.mainnet operator */;
const UNICHAIN_MAINNET: LatticeConfig<'static> = /* from unichain.mainnet operator */;
const BASE_MAINNET:     LatticeConfig<'static> = /* from base.mainnet operator */;

// A standalone organism the coalition references — here,
// an attestation aggregator with its own stable id.
const ATTEST_AGG: OrganismRef<'static> = OrganismRef {
    role: "attest",
    stable_id: /* published by the attest-aggregator operator */,
    content_hash: None,
};

// Two composite organisms the coalition references — an
// intent router and a shared ledger.
const INTENTS_CONF: OrganismRef<'static> = OrganismRef {
    role: "intents",
    stable_id: /* from the intents operator */,
    content_hash: None,
};
const LEDGER_CONF:  OrganismRef<'static> = OrganismRef {
    role: "ledger",
    stable_id: /* from the ledger operator */,
    content_hash: None,
};

pub const ETH_SUPERCHAIN: CoalitionConfig<'static> = CoalitionConfig {
    name: "ethereum.superchain",
    lattices:  &[ETH_MAINNET, UNICHAIN_MAINNET, BASE_MAINNET],
    organisms: &[ATTEST_AGG, INTENTS_CONF, LEDGER_CONF],
    ticket_issuer: None,
};

For a coalition-agnostic library, take &'static CoalitionConfig<'static> as a constructor argument and let the binary crate pick the constant.

Step 2 — bring up the network handle

Same UNIVERSE as every other mosaik service:

use std::sync::Arc;
use mosaik::Network;
use builder::UNIVERSE;

let network = Arc::new(Network::new(UNIVERSE).await?);

One Arc<Network> per agent regardless of how many members, organisms, or modules are bound.

Step 3 — bind the lattice handles you need

Indexes into lattices[] match the order in the CoalitionConfig. A cross-chain searcher that bids on all three:

use offer::Offer;

let eth_bid  = Offer::<Bundle>::bid (&network, &ETH_SUPERCHAIN.lattices[0].offer).await?;
let uni_bid  = Offer::<Bundle>::bid (&network, &ETH_SUPERCHAIN.lattices[1].offer).await?;
let base_bid = Offer::<Bundle>::bid (&network, &ETH_SUPERCHAIN.lattices[2].offer).await?;

let eth_wins  = Offer::<Bundle>::outcomes(&network, &ETH_SUPERCHAIN.lattices[0].offer).await?;
let uni_wins  = Offer::<Bundle>::outcomes(&network, &ETH_SUPERCHAIN.lattices[1].offer).await?;
let base_wins = Offer::<Bundle>::outcomes(&network, &ETH_SUPERCHAIN.lattices[2].offer).await?;

Keep handles in scope while needed; drop them when done. Each handle carries its own subscriptions and is cheap atop the shared Network.

Step 4 — bind the organism handles you need

Each OrganismRef carries the stable id; the concrete organism crate gives you the typed constructor. Standalone and composite organisms share the same reference shape:

use attest::Aggregator;

let attest = Aggregator::<Quote>::read(
    &network, ETH_SUPERCHAIN.organisms[0].config(),
).await?;

OrganismRef::config() resolves to the organism’s Config — either because the coalition operator embeds the full Config struct in their published CoalitionConfig, or because the organism crate ships a helper that rebuilds the Config from the published stable id and known role constants. Either way, the concrete organism crate is what gives you the typed handle.

Step 5 — bind the modules you want

Modules are coalition-scoped organisms with well-known shapes. CoalitionConfig exposes helpers:

use atlas::Atlas;
use almanac::Almanac;

// The Atlas lists every member in the coalition with operator metadata.
if let Some(atlas_cfg) = ETH_SUPERCHAIN.atlas() {
    let atlas = Atlas::<MemberCard>::read(&network, atlas_cfg).await?;
    // Use the atlas as your canonical "what's in this coalition" reference.
}

// The Almanac is a shared tick beacon for timing correlations.
if let Some(almanac_cfg) = ETH_SUPERCHAIN.almanac() {
    let almanac = Almanac::<AlmanacTick>::read(&network, almanac_cfg).await?;
    // Use almanac ticks as the time axis for cross-member commits.
    // Each tick's `observed_at` is a Vec<(MemberId, SystemTime)>;
    // compute median or bracket yourself.
}

A coalition may ship zero, one, two, three, or all four modules. Always check the helper’s Option return; do not assume presence.

Step 6 — bind the composite-organism handles you need

The shared ledger is typical for a searcher seeking aggregated refund attribution:

use ledger::Shared;

let refunds = Shared::<Refund>::read(
    &network, ETH_SUPERCHAIN.organisms[2].config(),
).await?;

tokio::spawn(async move {
    while let Some(r) = refunds.next().await {
        // Each entry carries (origin_member, slot, share, evidence).
        handle_refund(r);
    }
});

Step 7 — submit paired bundles

Cross-chain bundles are a searcher-level problem. The coalition layer surfaces the handles; reconciliation logic is the integrator’s responsibility.

async fn submit_paired_bundle(
    eth_bid: &Offer<Bundle>::Bid,
    uni_bid: &Offer<Bundle>::Bid,
    sell_on_eth: Bundle,
    buy_on_uni:  Bundle,
) -> anyhow::Result<()> {
    let eth_ack = eth_bid.send(sell_on_eth).await?;
    let uni_ack = uni_bid.send(buy_on_uni ).await?;
    // Reconcile: if only one leg commits, post-bond collateral or
    // on-chain HTLC logic applies. The coalition layer does NOT
    // provide cross-member atomicity. See
    // /contributors/atomicity.md.
    Ok(())
}

A paired-bundle correlator joining AuctionOutcome[L1, S] to AuctionOutcome[L2, S'] is the most common in-agent utility. Compute it in-memory as in builder’s Shape 1, or rely on a referenced intents composite organism if one exists.

Step 8 — read the aggregated result

With a shared-ledger composite organism, a single subscription surfaces refunds originating from any of the coalition’s lattices.

while let Some(entry) = refunds.next().await {
    tracing::info!(
        origin = %entry.origin_member,
        slot   = entry.slot,
        share  = %entry.share,
        "refund attributed",
    );
}

Without a shared-ledger composite organism, open three per-lattice tally::Tally::<Attribution>::read handles and join in memory. Both paths are valid; the composite organism is a shared precomputation.

What to do when things go wrong

  • ConnectTimeout on a lattice or organism handle. The per-member Config does not match what the member’s operator is running. Recompile against that operator’s latest handshake.
  • ConnectTimeout on an organism or module handle. The OrganismConfig does not match, or the committee is down and has not yet published peers on the universe, or the organism has retired (check for a RetirementMarker on its public stream) and the coalition operator should have sent an updated CoalitionConfig.
  • Admission denied on a write-side stream. Ticket missing or expired. Contact the per-member operator, not the coalition operator.
  • Module helper returns None. The coalition does not ship that module. See contributor basic services for the spec if proposing addition.

Cross-references