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 basic
service (the gov’s Atlas) — all bound through a single
GovConfig. It assumes familiarity with the
builder integrators quickstart when the gov
includes block-building lattices; the gov layer extends
that pattern rather than replacing it.
If the gov 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
GovConfigfrom the gov operator (struct definition, or a serialised fingerprint you copy into your crate). - A
LatticeConfigper referenced lattice (inline in theGovConfigthe operator published, or pulled from each lattice operator’s handshake page). - A standalone-organism reference per referenced organism
(role name +
Configfingerprint + pointer to the organism’s own handshake). - A ticket from each per-citizen operator you intend to write to. Per-citizen admission is per-citizen; the gov operator does not issue per-citizen tickets.
- A ticket from each confluence or basic-service operator whose write-side primitive you intend to use. Usually none: most confluences and basic services 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 GovConfig in your crate
Paste the operator’s published GovConfig definition, or
import it from the operator’s handshake crate if they
publish one:
use builder::LatticeConfig;
use gov::{GovConfig, ConfluenceConfig, OrganismRef};
// Exactly the bytes the operator publishes.
const ETH_MAINNET: LatticeConfig = /* from ethereum.mainnet operator */;
const UNICHAIN_MAINNET: LatticeConfig = /* from unichain.mainnet operator */;
const BASE_MAINNET: LatticeConfig = /* from base.mainnet operator */;
// A standalone organism the gov references — here, an
// attestation aggregator with its own Config fingerprint.
const ATTEST_AGG: OrganismRef = OrganismRef {
role: "attest",
fingerprint: /* published by the attest-aggregator operator */,
};
// The gov's Atlas basic service (if shipped).
const SUPERCHAIN_ATLAS: ConfluenceConfig = /* from gov operator */;
const INTENTS_CONF: ConfluenceConfig = /* from gov operator */;
const LEDGER_CONF: ConfluenceConfig = /* from gov operator */;
pub const ETH_SUPERCHAIN: GovConfig = GovConfig {
name: "ethereum.superchain",
lattices: &[ETH_MAINNET, UNICHAIN_MAINNET, BASE_MAINNET],
organisms: &[ATTEST_AGG],
confluences: &[SUPERCHAIN_ATLAS, INTENTS_CONF, LEDGER_CONF],
};
For a gov-agnostic library, take &'static GovConfig 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 basic services are bound.
Step 3 — bind the lattice handles you need
Indexes into lattices[] match the order in the
GovConfig. 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 fingerprint; 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 gov operator embeds the full Config
struct in their published GovConfig, or because the
organism crate ships a helper that rebuilds the Config
from the published fingerprint and known role constants.
Either way, the concrete organism crate is what gives you
the typed handle.
Step 5 — bind the basic services you want
Basic services are confluences with well-known shapes.
GovConfig exposes helpers:
use atlas::Atlas;
use almanac::Almanac;
// The Atlas lists every citizen in the gov 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 gov" reference.
}
// The Almanac is a shared tick beacon for timing correlations.
if let Some(almanac_cfg) = ETH_SUPERCHAIN.almanac() {
let almanac = Almanac::<Tick>::read(&network, almanac_cfg).await?;
// Use almanac ticks as the time axis for cross-citizen commits.
}
A gov may ship zero, one, two, three, or all four basic
services. 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 gov 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 gov 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 the
gov’s intents confluence if one is commissioned.
Step 8 — read the aggregated result
With a shared-ledger confluence, a single subscription surfaces refunds originating from any of the gov’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 basic-service handle. TheConfluenceConfigdoes not match, or the committee is down and has not yet published peers on the universe, or the confluence has been retired and the gov operator should have sent an updatedGovConfig.- Admission denied on a write-side stream. Ticket missing or expired. Contact the per-citizen operator, not the gov operator.
- Basic-service helper returns
None. The gov does not ship that service. See contributor basic services for the spec if proposing addition.