Quickstart — bind many citizens 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 confluence, 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 confluence), 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). - A standalone-organism reference per referenced organism (role name + stable id + optional content hash + pointer to the organism’s own handshake).
- A
ConfluenceConfigper referenced confluence (from the confluence operator’s handshake page). - A ticket from each per-citizen operator you intend to write to. Per-citizen admission is per-citizen; the coalition operator does not issue per-citizen tickets.
- A ticket from each confluence or module operator whose write-side primitive you intend to use. Usually none: most confluences and modules are read-only for external integrators.
- Your TDX image (if any citizen 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, ConfluenceConfig, 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,
};
// The coalition's Atlas module (if shipped) and two
// referenced confluences.
const SUPERCHAIN_ATLAS: ConfluenceConfig<'static> = /* from coalition operator */;
const INTENTS_CONF: ConfluenceConfig<'static> = /* from confluence operator */;
const LEDGER_CONF: ConfluenceConfig<'static> = /* from confluence operator */;
pub const ETH_SUPERCHAIN: CoalitionConfig<'static> = CoalitionConfig {
name: "ethereum.superchain",
lattices: &[ETH_MAINNET, UNICHAIN_MAINNET, BASE_MAINNET],
organisms: &[ATTEST_AGG],
confluences: &[SUPERCHAIN_ATLAS, 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
citizens, confluences, 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 alive while needed; drop them when done.
Each handle carries its own subscriptions and is cheap
atop the shared Network.
Step 4 — bind the standalone-organism handles you need
Each OrganismRef carries the stable id; the concrete
organism crate gives you the typed constructor:
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 citizen in the coalition with operator metadata.
if let Some(atlas_cfg) = ETH_SUPERCHAIN.atlas() {
let atlas = Atlas::<CitizenCard>::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-citizen 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 confluence handles you need
The shared ledger is typical for a searcher seeking aggregated refund attribution:
use ledger::Shared;
let refunds = Shared::<Refund>::read(&network, Ð_SUPERCHAIN.confluences[2]).await?;
tokio::spawn(async move {
while let Some(r) = refunds.next().await {
// Each entry carries (origin_citizen, 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-citizen 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 confluence if one exists.
Step 8 — read the aggregated result
With a shared-ledger confluence, 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_citizen,
slot = entry.slot,
share = %entry.share,
"refund attributed",
);
}
Without a shared-ledger confluence, open three per-
lattice tally::Tally::<Attribution>::read handles and
join in memory. Both paths are valid; the confluence is a
shared precomputation.
What to do when things go wrong
ConnectTimeouton a lattice or organism handle. The per-citizenConfigdoes not match what the citizen’s operator is running. Recompile against that operator’s latest handshake.ConnectTimeouton a confluence or module handle. TheConfluenceConfigdoes not match, or the committee is down and has not yet published peers on the universe, or the confluence 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-citizen 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.