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

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

  1. Universe NetworkId. In almost every case the constant builder::UNIVERSE — do not override unless the operator explicitly runs an isolated federation.
  2. LatticeConfig. Either as a Rust const exported from a deployment crate on crates.io (preferred), or as a hex-encoded LatticeConfig::from_hex fingerprint, or as six individual organism Config fingerprints you reassemble.
  3. MR_TDs for every TDX-gated organism in the lattice. At minimum atelier and unseal; optionally relay. 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, &ETH_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::UNIVERSE suffices. 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 full LatticeConfig. 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 LatticeConfig but 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