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

Submitting transactions anonymously

audience: integrators

Submission is entirely the zipnet organism’s job. The lattice consumes zipnet unchanged; the zipnet developer book is the authoritative reference. This page covers only the bits that are specific to submitting into a lattice rather than a standalone zipnet deployment.

Which Config you pass

Integrators pass the lattice’s zipnet config (ETH_MAINNET.zipnet), not a standalone zipnet config:

use zipnet::Zipnet;

let submitter = Zipnet::<Tx2718>::submit(&network, &ETH_MAINNET.zipnet).await?;

The lattice’s zipnet config is content + intent addressed under the lattice’s instance name (e.g. "ethereum.mainnet"). Two lattices sharing a zipnet committee is not a supported pattern; each lattice derives its own zipnet GroupId from its own root.

Picking a datum type

Every EVM lattice’s zipnet organism shuffles a chain-native sealed envelope type. The reference datum is Tx2718 for EIP- 2718 encoded transactions, sealed to the lattice’s unseal committee public key:

use zipnet::{DecodeError, ShuffleDatum, UniqueId, unique_id};

pub struct Tx2718(pub [u8; 1024]);

impl ShuffleDatum for Tx2718 {
    const TYPE_TAG:  UniqueId = unique_id!("builder.tx2718-v1");
    const WIRE_SIZE: usize    = 1024;

    fn encode(&self) -> Vec<u8>            { self.0.to_vec() }
    fn decode(bytes: &[u8]) -> Result<Self, DecodeError> {
        <[u8; 1024]>::try_from(bytes).map(Self).map_err(|e| DecodeError(e.to_string()))
    }
}

TYPE_TAG folds into the zipnet instance’s fingerprint, so a lattice that ships a Tx2718 v2 with a different size is a different zipnet GroupId and is not cross-compatible with the v1 lattice. This is by design; see the zipnet wire invariants.

Sealing the payload to unseal

Zipnet’s DC-net construction provides sender anonymity; it does not by itself provide payload confidentiality against the zipnet committee. The lattice achieves payload confidentiality by sealing the payload to the unseal committee’s threshold public key before writing it into the Tx2718 buffer.

Pseudocode:

let ct = unseal::seal(&ETH_MAINNET.unseal, &tx_rlp_bytes)?;
let padded = pad_to(ct, 1024);
submitter.send(Tx2718(padded)).await?;

unseal::seal is a pure function parameterised on the lattice’s unseal config; it encrypts the payload to the committee’s published threshold public key, producing a ciphertext of deterministic size (plaintext + AEAD overhead). pad_to right-pads to Tx2718::WIRE_SIZE. Zipnet rejects non-exact sizes.

Bytes you never put into the Tx2718 buffer:

  • Your wallet address in cleartext.
  • Any metadata linking your submission to prior on-chain activity.
  • Unpadded payloads; a variable-size envelope leaks sender identity.

Cover traffic

A Submitter<Tx2718> with no messages in flight sends a cover envelope per zipnet round. That is the default and you should not turn it off:

let submitter = Zipnet::<Tx2718>::submit(&network, &ETH_MAINNET.zipnet)
    .await?;
// submitter sends cover traffic every round while idle.
// drop(submitter) to stop.

The zipnet publishing page covers the tuning knobs — polling cadence, cover payload — which are unchanged in the lattice context.

Retry policy

Zipnet’s send returns SubmissionId once the envelope is queued. The envelope may or may not land in the round’s broadcast vector (deterministic slot assignment; collisions are possible until footprint scheduling ships). A searcher or wallet that needs guaranteed inclusion polls its Reader<Tx2718> for byte-equality on future rounds until the envelope lands:

let mut reader = Zipnet::<Tx2718>::read(&network, &ETH_MAINNET.zipnet).await?;
let expected = tx.clone();
let mut deadline = tokio::time::Instant::now() + Duration::from_secs(30);
loop {
    tokio::select! {
        Some(got) = reader.next() => {
            if got == expected { break; }
        }
        _ = tokio::time::sleep_until(deadline) => {
            submitter.send(expected.clone()).await?;
            deadline = tokio::time::Instant::now() + Duration::from_secs(30);
        }
    }
}

In production, wrap this in your agent’s retry backoff logic and surface failures to your operator.

What happens after submission

Your envelope lands in a zipnet::Broadcasts[S] entry for some slot S. The unseal committee produces the cleartext for that slot into unseal::UnsealedPool[S]. atelier includes transactions from the unsealed pool in its candidate block for slot S. If you care whether your transaction made it on-chain under this lattice, watch tally::Refunds[S] or the chain’s own state; see Receiving refunds and attributions.

Next reading