What you need from the operator
audience: integrators
Before you write code against a lattice, the operator owes you a small, finite fact sheet. This page is the complete list. If you do not have every item, you are not ready to build; push back on the operator before spending the time.
The handshake is identical in shape to zipnet’s — a Config plus
optional MR_TDs — generalised to six organisms instead of one.
The three-bullet handshake
- Universe
NetworkId. In almost every case the constantbuilder::UNIVERSE— do not override unless the operator explicitly runs an isolated federation. LatticeConfig. Either as a Rustconstexported from a deployment crate on crates.io (preferred), or as a hex-encodedLatticeConfig::from_hexfingerprint, or as six individual organismConfigfingerprints you reassemble.- MR_TDs for every TDX-gated organism in the lattice. At
minimum
atelierandunseal; optionallyrelay. These are 48-byte hex strings the operator publishes out of band.
That is the entire on-the-wire-discovery-free handshake. Every
other piece of information you might think you need — committee
member count, relay endpoint URL, settlement contract addresses —
either falls out of the LatticeConfig or is not your concern
as an integrator.
LatticeConfig, in detail
A LatticeConfig is a plain struct the operator publishes:
pub struct LatticeConfig {
pub name: &'static str, // e.g. "ethereum.mainnet"
pub chain_id: u64, // EIP-155 chain id
pub zipnet: zipnet::Config,
pub unseal: unseal::Config,
pub offer: offer::Config,
pub atelier: atelier::Config,
pub relay: relay::Config,
pub tally: tally::Config,
}
You do not construct one yourself. A lattice is operator-scoped; integrators compile in the operator’s published struct.
Typos in any field surface as ConnectTimeout on the next
verb() call. Because every field folds into the
LatticeConfig::lattice_id(...) fingerprint, a mis-copied 32-byte
init salt changes every organism’s GroupId and your agent fails
to bond with the operator’s infrastructure. Compare fingerprints
out of band before opening a support ticket.
Preferred distribution: a deployment crate
The cleanest handoff is an operator-published crate whose
entire contents is a single const and a minimum of types:
// Cargo.toml of the operator's crate
[package]
name = "eth-mainnet-lattice"
version = "2026.04.0"
// lib.rs
pub const ETH_MAINNET: builder::LatticeConfig = builder::LatticeConfig { /* ... */ };
pub const ATELIER_MRTD: [u8; 48] = [ /* ... */ ];
pub const UNSEAL_MRTD: [u8; 48] = [ /* ... */ ];
You depend on it:
[dependencies]
eth-mainnet-lattice = "=2026.04.0"
and use it:
use eth_mainnet_lattice::ETH_MAINNET;
let submitter = zipnet::Zipnet::<Tx>::submit(&network, Ð_MAINNET.zipnet).await?;
Benefits:
- Compile-time pinning; the version-number convention tells you when to expect an upgrade.
- The operator can bump the crate’s minor version when they rotate parameters without changing your own crate’s logic.
- If the operator deletes or yanks the crate, your build breaks loudly.
Fallback: hex fingerprint
When a deployment crate is not practical (closed-source integrator, external contractor, short-term agent), the operator publishes a hex string:
LATTICE_CONFIG_HEX = "7f3a9b1c..." (some hundreds of bytes)
You decode it:
let cfg: builder::LatticeConfig =
builder::LatticeConfig::from_hex(LATTICE_CONFIG_HEX)?;
Hex fingerprint is strictly less convenient than a crate dep:
- You lose compile-time types for the organism configs.
- You have to re-check the fingerprint at startup.
- Operators that rotate often burn you with stale hex strings.
Use it when you have to. Prefer a deployment crate.
MR_TDs
Every TDX-gated organism in the lattice has a reproducible image build with a precomputed MR_TD (the measurement register that binds the image to the hardware root of trust). The operator publishes:
- The MR_TD hex string — 48 bytes, lowercase — per gated organism.
- The reproducible image build instructions, so you can check the MR_TD yourself if you do not trust the operator’s published value.
In the default pattern the operator publishes MR_TDs alongside
the LatticeConfig in the deployment crate (see above). If you
compile with the tee-tdx feature on the organism crate, the
organism’s TicketValidator pins the MR_TD into the admission
ticket.
If you do not compile with tee-tdx, you are not verifying
MR_TDs yourself — you are trusting the committee’s self-
attestation via mosaik ticket validation. That is an acceptable
posture for read-only analytics; it is not an acceptable posture
for wallets whose anonymity depends on the unseal committee.
What the operator does not owe you
- Bootstrap peers. Peer discovery is universe-level, via
mosaik’s standard gossip + pkarr + mDNS stack. You do not need
an explicit bootstrap list to reach an operator’s lattice; any
reachable peer on
builder::UNIVERSEsuffices. If you need an explicit bootstrap for a cold-start agent, the operator’s aggregator addresses are fine — but they are a convenience, not a requirement. - A status endpoint. The public commit logs in every organism’s collections are the status. If you need a dashboard, build one; mosaik’s Prometheus exporter + the organism’s metrics reference covers the surface.
- A service-level agreement. Lattices are permissioned pipelines with any-trust, threshold, or majority-honest trust models spelled out in threat-model.md. Liveness guarantees, when given, are operator-level commercial commitments, not protocol-level.
Checking your handshake
Before writing any application code, verify you have everything:
let cfg: LatticeConfig = /* from the operator */;
// 1. Universe matches.
let network = Arc::new(Network::new(builder::UNIVERSE).await?);
assert_eq!(network.network_id(), builder::UNIVERSE);
// 2. Fingerprint matches the one the operator published.
assert_eq!(
hex::encode(cfg.lattice_id()),
"7f3a9b1c....", // from operator release notes
);
// 3. Per-organism fingerprints also match, if the operator
// published them individually for independent rotation.
assert_eq!(hex::encode(cfg.atelier.fingerprint()), "....");
assert_eq!(hex::encode(cfg.unseal .fingerprint()), "....");
// ... for each organism you bind ...
fingerprint() is a pure function on each organism’s Config.
It never touches the network; any divergence is a compile-time
or release-notes-time bug.
If something does not add up
A partial or inconsistent handshake is a red flag. Common patterns and their fix:
- Operator gives you only zipnet’s
Config, not the fullLatticeConfig. Ask for the rest; if they cannot produce it, they are running a zipnet deployment, not a lattice. The zipnet book covers that case. - Operator gives you a
LatticeConfigbut says some organisms are “not deployed yet”. Acceptable for a development lattice; unacceptable for production. Clarify the schedule in writing. - Operator will not publish MR_TDs for TDX-gated organisms.
Do not compile with
tee-tdx. You are implicitly trusting the operator’s committee to self-attest. Acceptable for read-only uses; not acceptable for wallets that rely on submission anonymity.
Next reading
- Connecting to a lattice — what you do with the handshake once you have it.
- TEE-gated lattices — the MR_TDs side in detail.