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
CoalitionConfigfrom the coalition operator (struct definition, or a serialised fingerprint you copy into your crate). - A
LatticeConfigper referenced lattice (inline in theCoalitionConfigthe operator published, or pulled from each lattice operator’s handshake page). - An
OrganismRefper 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, Ð_SUPERCHAIN.lattices[0].offer).await?;
let uni_bid = Offer::<Bundle>::bid (&network, Ð_SUPERCHAIN.lattices[1].offer).await?;
let base_bid = Offer::<Bundle>::bid (&network, Ð_SUPERCHAIN.lattices[2].offer).await?;
let eth_wins = Offer::<Bundle>::outcomes(&network, Ð_SUPERCHAIN.lattices[0].offer).await?;
let uni_wins = Offer::<Bundle>::outcomes(&network, Ð_SUPERCHAIN.lattices[1].offer).await?;
let base_wins = Offer::<Bundle>::outcomes(&network, Ð_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
ConnectTimeouton a lattice or organism handle. The per-memberConfigdoes not match what the member’s operator is running. Recompile against that operator’s latest handshake.ConnectTimeouton an organism or module handle. TheOrganismConfigdoes not match, or the committee is down and has not yet published peers on the universe, or the organism has retired (check for aRetirementMarkeron its public stream) and the coalition operator should have sent an updatedCoalitionConfig.- 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.