Atomic settlement
In SealedIP, everything that needs to happen for an auction to close happens in a single transaction. Either it all succeeds, or none of it does. There's no partial state where the seller got paid but the license wasn't minted, or the winner got their license but the loser's refund didn't arrive.
This is what "atomic settlement" means in the contract sense:
What "atomic" guarantees
When settle() returns successfully:
- The PIL license token is in the winner's wallet.
- The winning bid amount in WIP is in the seller's wallet.
- The winner's deposit minus the winning bid amount is in the winner's wallet as a refund.
- Every losing bidder's full deposit is in their wallet as a refund.
- The auction state is
Settled.
When settle() reverts:
- None of those things happen.
- Every bidder still has their deposit escrowed in the contract.
- The state stays
Triggeredso settle can be retried with corrected inputs.
There is no in-between. The Solidity ABI guarantees this.
Why it matters
Without atomicity, you'd need claim flows. The winner would need to call
claimLicense(). The seller would need to call withdraw(). Losers would
need to call refund(). Each of those is a wallet popup, gas cost, and
failure point. Worse, a malicious seller could refuse to mint a license that
goes into a wallet they don't like.
Atomic settlement makes the contract — not any human — the executor of every state transition. The license must mint, the payment must clear, and the refunds must process, or the whole thing reverts.
What the contract checks before paying out
At settle time, the contract first verifies the sealed reserve, then verifies every bid reveal.
Reserve verification (before any bid is evaluated):
The orchestrator passes a ReserveReveal {amount, nonce, signature}. If the
seller sealed a reserve, BidPayload.recoverSigner runs on this struct and
must recover the seller's address. A mismatch reverts the entire settlement
with InvalidReserveReveal. If the seller never submitted a reserve
ciphertext, the floor defaults to 0 and this step is skipped.
Per-bid verification:
- Signature recovery. The decrypted payload contains a signature over
keccak256(auctionId, bidder, amount, nonce). The contract recovers the signer and rejects the reveal if it does not matchb.bidder. - Deposit cap. If
reveal.amount > b.deposit, the reveal is skipped. You cannot bid more than you escrowed. - Reserve floor. Bids below the revealed reserve are skipped.
- Ciphertext presence. Slots that were allocated but never had ciphertext written (
hasCiphertext == false) are skipped and refunded.
Bids that fail any of these checks are dropped silently and refunded. A corrupt or malicious bid cannot block settlement.
What happens when there's no winner
Three reasons the auction can close with no winner:
ExpiredEmpty— nobody bid at all. The contract has no deposits to move; state flips toExpiredEmptywith one tx.ExpiredNoWinner— bidders bid, but no revealed bid cleared the reserve. Every deposit is refunded; no license mints; state flips toExpiredNoWinner.- Revert during settle — something in the verification logic threw. The state stays
Triggeredand the orchestrator can retry once the bad input is identified.
In all three "no winner" outcomes, sellers receive nothing and bidders get their deposits back. The license token never mints to anyone.
Implementation note
The atomic settlement is implemented in
SealedAuction.settle().
The function body is mostly a single transaction's worth of:
// 1. Verify reserveReveal signature; recover floor (or use 0 if none sealed).
// 2. Verify each bid reveal; drop any that fail signature / deposit cap / floor.
// 3. Pick highest valid bid.
// 4. LicensingModule.mintLicenseTokens(winner, ...) — reverts on failure.
// 5. WIP.transfer(seller, winningAmount) — reverts on failure.
// 6. WIP.transfer(winner, deposit - winningAmount) — reverts on failure.
// 7. for each loser: WIP.transfer(loser, deposit) — reverts on failure.
// 8. Emit AuctionSettled.
Because every external call uses transfer/mint semantics that revert on
failure, the whole transaction reverts atomically if any single piece fails.
This is the contract's most important property. Everything else flows from it.