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

Placing bundle bids

audience: integrators

Bidding is the offer organism’s job. As a searcher, you submit sealed bids over bundles that reference the lattice’s current UnsealedPool[S], and you read the committed auction outcome from offer::AuctionOutcome.

Open the handles

use offer::Offer;

let bidder       = Offer::<Bundle>::bid      (&network, &ETH_MAINNET.offer).await?;
let mut outcomes = Offer::<Bundle>::outcomes (&network, &ETH_MAINNET.offer).await?;

bidder is a Submitter<Bundle>; outcomes is a Stream<Item = AuctionOutcome>. You need both for a closed loop.

The Bundle datum

Each lattice pins a Bundle type its offer organism auctions over. The reference shape:

pub struct Bundle {
    /// Target slot. offer auctions one outcome per slot.
    pub slot: u64,
    /// Your bid in the lattice's accounting currency (wei on L1).
    pub bid:  u128,
    /// The transactions you want included, in the order you want them.
    pub txs:  Vec<Tx2718>,
    /// Optional reference into zipnet's UnsealedPool slot content
    /// that your bundle depends on (e.g. a backrun target).
    pub depends_on: Option<UnsealedRef>,
}

The exact fields are the lattice’s offer::Config responsibility; offer::Bundle::SCHEMA_TAG folds into the organism fingerprint, so a version bump is a new organism id, not a silent upgrade.

Sealing the bid

Bids ride an Offer::<Bundle>::bid stream that is threshold- encrypted to the offer committee’s published public key before any committee member sees it. The encryption happens inside bidder.send; from your point of view the call is just:

let bid_id = bidder.send(Bundle {
    slot:        target_slot,
    bid:         2_500_000_000_000u128, // 2.5 gwei
    txs:         vec![signed_backrun_tx],
    depends_on:  Some(UnsealedRef::Slot(target_slot)),
}).await?;

The offer committee unseals the bid inside its state machine at auction close. Until close, no party — not the committee members, not competing searchers, not the lattice operator — sees your bid value.

Bid withdrawal

You can withdraw a bid while the auction is still open, by sending a BundleWithdraw { bid_id } on the same stream. Once CloseAuction commits, withdrawals for that slot are rejected.

Outcome

The auction commits one AuctionOutcome per slot. Watch the stream until you see your target slot:

use futures::StreamExt;

while let Some(outcome) = outcomes.next().await {
    if outcome.slot == target_slot {
        if outcome.winner == self_addr {
            println!("you won slot {} at {} wei", outcome.slot, outcome.bid);
        } else {
            println!("you lost slot {}; winner {:?}", outcome.slot, outcome.winner);
        }
        break;
    }
}

AuctionOutcome is a committed fact; every offer committee member has applied the same decision. You do not need to vote or ack; you only read.

Verifying the auction’s honesty

Offer’s state machine enforces:

  • Monotonic slots. AuctionOutcome[S+1] cannot commit before AuctionOutcome[S].
  • Unique winner per slot. Exactly one outcome per slot.
  • Bid decryption inside apply. Losing bids are never materialised outside the apply step; they are discarded after the winner is picked.

A majority-malicious offer committee can still commit a non-max-bid winner (see threat-model — offer). To detect it, watch tally::Refunds[S]: a legitimate winner receives a refund proportional to their bid; an offer-committee- chosen non-winner would receive nothing, and a searcher who expected to win can escalate. The lattice does not automate escalation; tally is an audit surface.

Dependencies on unsealed order flow

Backrun-style bundles reference transactions the lattice has already unsealed for the same slot. UnsealedRef::Slot(S) instructs the atelier to include your bundle’s txs after any transactions in UnsealedPool[S]. If the pool is empty for that slot, the bundle still applies; if your bundle depends on a specific tx in the pool that is not present, offer’s state machine rejects the bid at apply time and your send returns Error::Protocol.

Next reading