Auction lifecycle
A SealedIP auction passes through one or more of five states:
| State | Pill | Meaning |
|---|---|---|
Open | Open | Accepting sealed bids; seller may also seal reserve |
Open past deadline | Closing | Deadline passed; awaiting trigger() |
Triggered | Settling | Validators decrypting bids and reserve |
Settled | Settled | Winner has license, seller paid |
ExpiredNoWinner | Ended | No bid cleared the revealed reserve; all deposits refunded |
ExpiredEmpty | Ended | No bids placed; nothing to do |
Open past deadline is not a separate on-chain state. It is still Open,
but with block.timestamp >= deadline. The UI and SDK use the deadline to
distinguish "still bidding" from "waiting to settle".
State transitions
Each transition is gated by exactly one function on the SealedAuction
contract. Calling a function from the wrong state reverts with
WrongState or WrongStateForSettle.
Step 1 — Seller lists
createAuction takes three arguments: ipId, licenseTermsId, and deadline.
There is no plaintext reserve price argument. The contract allocates the
seller-side CDR vault (reserveUuid) and registers it on AuctionRevealCondition.
No deposit is escrowed yet. Listing costs only gas.
Reverts:
DeadlineInPastifdeadline <= block.timestamp
Step 2 — Seller seals reserve
The seller signs a BidPayload (the same 149-byte format used for bids, but
with signer = seller) and encrypts it under the CDR threshold key. The
ciphertext is written to the reserve vault. Seller-only, Open state, before
deadline, one write per auction.
If the seller never calls submitEncryptedReserve, the reserve defaults to 0
at settle: every bid is a valid winner candidate.
Reverts:
NotSellerif caller is not the auction's sellerBiddingClosedif deadline has passedReserveAlreadyWrittenon a second attempt
Step 3 — Bidder allocates slot
allocateBidSlot pulls the WIP deposit into escrow and allocates a fresh CDR
vault for this bid, also bound to the AuctionRevealCondition. The deposit is
an upper bound on the bid amount; the actual bid is hidden in the ciphertext.
Reverts:
BiddingClosedifblock.timestamp > deadlineZeroDepositifdeposit == 0
Step 4 — Bidder seals bid
The bidder signs and encrypts the bid payload, then writes the ciphertext to their CDR vault. Bidder-only (per uuid), Open state, before deadline, one write per slot.
A bidder can allocate multiple slots in the same auction to submit multiple independent sealed bids. Each gets its own uuid and its own deposit.
Reverts:
NotBidderif caller is not the slot's allocatorCiphertextAlreadyWrittenon a second attemptUnknownBidSlotif no slot was allocated for this uuid
Step 5 — Trigger
trigger() is permissionless. Anyone can pay gas to call it once the deadline
has passed. The contract checks the deadline, flips state to Triggered, and
emits AuctionTriggered. This second gate (alongside the time gate) is what
opens the AuctionRevealCondition for both bid vaults and the reserve vault.
If bidCount == 0, state goes directly to ExpiredEmpty and no validator
work happens.
Reverts:
DeadlineNotReachedif called before deadlineWrongStateif state is notOpen
Step 6 — Validators reveal
CDR validators detect the AuctionTriggered event off-chain. The
AuctionRevealCondition now returns true for every vault in this auction
(both bid vaults and the reserve vault), so validators begin submitting
partial decryption shares. Once a quorum (t-of-n) arrives per uuid, the
orchestrator can reconstruct the plaintext payload for each vault.
Validators only see their own shares. The complete plaintext is never persisted in CDR.
Step 7 — Settle
settle() accepts the array of bid reveals plus the reserve reveal
(ReserveReveal {amount, nonce, signature}). The contract verifies the
reserve signature (signer must recover to the seller address) before
evaluating any bid. If no reserve was sealed, the floor is 0. Every step is
atomic. See Settlement flow for the full per-step
breakdown.
Out-of-order or stalled auctions
If trigger() is never called, the auction sits in Open state. Deposits
remain escrowed; bidding is closed past the deadline.
If validators do not publish shares, the auction sits in Triggered state.
The orchestrator can retry settle() once shares arrive. If shares never
arrive, the auction is effectively stuck. This is the central availability
assumption: see Orchestrator availability.
If settle() reverts, state stays Triggered. The orchestrator identifies
the bad input, removes it, and retries.