Receiving refunds and attributions
audience: integrators
Refund accounting is the tally organism’s job. Tally commits
one Refunds[S] entry per slot once the chain has included the
lattice’s winning block, and publishes an Attestations[S] entry
that is presentable to an on-chain settlement contract for
independent verification.
Open the handles
use tally::Tally;
let mut refunds = Tally::<Attribution>::read (&network, Ð_MAINNET.tally).await?;
let mut attestations = Tally::<Attestation>::attestations(&network, Ð_MAINNET.tally).await?;
Both are read-only Streams and world-readable by any lattice
ticket holder. Attestations are specifically designed to be
safe to publish — they carry no cleartext sender identity, only
on-chain-presentable signatures over commitment-hash inputs.
What an Attribution carries
pub struct Attribution {
pub slot: u64,
pub block_hash: [u8; 32], // included on chain
pub recipients: Vec<Recipient>, // who gets paid what
pub evidence: Evidence, // refs into upstream commits
}
pub struct Recipient {
pub addr: [u8; 20],
pub amount: u128, // in chain native units
pub kind: RecipientKind,
}
pub enum RecipientKind {
/// Wallet whose zipnet submission made it into the block.
OrderflowProvider { submission: SubmissionRef },
/// Searcher whose offer bid won the slot's auction.
BidWinner { bid: BidRef },
/// Co-builder operator whose atelier hint made it into the block.
CoBuilder { member: BlsPub },
}
SubmissionRef, BidRef, and the BlsPub committee member are
all opaque handles into the lattice’s upstream commit logs.
Recipients prove their claim by matching one of these references
against their local record (your wallet’s SubmissionId from
zipnet; your searcher’s BidId from offer; your co-builder’s
public key).
Filtering attributions concerning you
A wallet or searcher typically only cares about attributions referencing their own prior activity:
use futures::StreamExt;
while let Some(attr) = refunds.next().await {
for recipient in &attr.recipients {
if recipient.addr == self_addr {
println!("slot {} block {:x?}: {} wei for {:?}",
attr.slot, attr.block_hash, recipient.amount, recipient.kind);
}
}
}
There is no server-side filter in v0; agents scan every attribution and match locally. For a high-volume analytics use case, index attributions into your own store keyed by recipient address.
Claiming on-chain
An Attestation is the cryptographic proof you present to the
lattice’s settlement contract to claim the refund:
pub struct Attestation {
pub slot: u64,
pub block_hash: [u8; 32],
pub recipient: [u8; 20],
pub amount: u128,
pub kind_digest: [u8; 32], // blake3 of the RecipientKind payload
pub signatures: Vec<(TallyMemberId, Secp256k1Sig)>,
}
Submit the Attestation to the settlement contract’s claim
function. The contract verifies:
- The signature set is at least
t-of-nof the lattice’s tally committee (wheretis pinned in the contract at deployment). - The signatures cover
blake3(slot ‖ block_hash ‖ recipient ‖ amount ‖ kind_digest). - The
block_hashmatches an on-chain block at the givenslot.
A tally committee that attempts to mis-attest — signs a claim
for a block that was never included, or to a recipient that has
no upstream reference — is rejected by the contract. The
contract is the ground truth for claim validity; tally’s
Refunds collection is the authoritative history.
Verifying the upstream evidence yourself
For wallets or searchers that do not want to trust tally’s
attestation at face value, the evidence field in Attribution
names the upstream commits. You read them out of the upstream
organisms and reconstruct the attribution yourself:
use zipnet::Zipnet;
use offer::Offer;
let mut zipnet_reader = Zipnet::<Tx2718>::read(&network, Ð_MAINNET.zipnet).await?;
let mut offer_outcomes = Offer::<Bundle>::outcomes(&network, Ð_MAINNET.offer).await?;
// For each Attribution's evidence, go read the referenced slot in
// the upstream organism and check it matches.
This is work you only need to do if tally’s trust assumption
(majority-honest) is inadequate for your use case. Most
integrators treat Attestations as authoritative.
If your submission did not produce a refund
Not every submission produces a refund. Reasons:
- Your zipnet envelope was a cover packet or a collision — nothing was in the winning block.
- The winning block did not include your transaction (fee market, gas limit, etc.).
- The lattice had a liveness failure for the slot (tally did not commit); the chain used a different builder.
- The tally committee is majority-malicious and dropped your attribution. You escalate to the lattice operator; the on-chain record is your ground truth.
tally::Refunds[S] is an append-only collection. The absence of
a recipient record for your address in a given slot is a
negative fact — the lattice is saying “no refund for you on
this slot”. Lattice operators SLA around liveness (percentage of
slots where Refunds commits within a deadline); missed slots
beyond the SLA are an operator concern, not a protocol one.
Next reading
- tally organism spec — the full state-machine contract.
- Reading built blocks — the upstream block whose inclusion tally verifies.
- threat-model — tally — what the majority-honest assumption buys.