SealedAuction
The core contract. Every auction record, bid slot, reserve vault, and state transition.
Source: contracts/src/SealedAuction.sol
Aeneid address: 0xb41B87f2E58E3f6139d6B6D2323C7AdFd47FC9ee
Storage
nextAuctionId
uint256 public nextAuctionId;
The id of the next auction to be created. First auction has id 1.
Enumerate all auctions: for (i = 1; i < nextAuctionId; i++).
auctions(uint256)
mapping(uint256 => Auction) public auctions;
struct Auction {
address seller;
address ipId;
uint256 licenseTermsId;
uint32 reserveUuid; // CDR vault uuid for the sealed reserve
bool reserveHasCiphertext; // true once seller calls submitEncryptedReserve
uint64 deadline;
uint8 state; // 0=Open, 1=Triggered, 2=Settled, 3=ExpiredNoWinner, 4=ExpiredEmpty
uint16 bidCount;
}
Returns the auction record as a tuple in field-declaration order.
There is no plaintext reservePrice field. The reserve is sealed into a CDR vault
(reserveUuid) and only revealed at settlement via a signed ReserveReveal struct.
bids(uint256, uint256)
mapping(uint256 => mapping(uint256 => Bid)) public bids;
struct Bid {
address bidder;
uint256 deposit;
uint32 ciphertextUuid;
bool hasCiphertext;
uint64 blockNumber;
uint64 txIndex;
}
bids[auctionId][bidIndex] returns one bid's escrow record.
bidIndex ranges from 0 to auctions[auctionId].bidCount - 1.
isTriggered(uint256)
function isTriggered(uint256 auctionId) external view returns (bool);
Returns true if the auction's state is Triggered. Called by
AuctionRevealCondition.checkReadCondition as the second gate before CDR
validators publish decryption shares.
Structs used at settle
BidReveal
struct BidReveal {
uint256 bidIndex;
uint256 amount;
bytes32 nonce;
bytes signature;
}
One decrypted bid, supplied to settle. The contract re-verifies the signature
on-chain; bids that fail verification are silently skipped.
ReserveReveal
struct ReserveReveal {
uint256 amount;
bytes32 nonce;
bytes signature;
}
The decrypted sealed reserve, supplied to settle. The contract recovers
the signer using the same BidPayload digest (signer must equal auction.seller).
Functions
createAuction
function createAuction(
address ipId,
uint256 licenseTermsId,
uint64 deadline
) external returns (uint256 auctionId);
Lists a new sealed-bid auction. Callable by anyone; the caller becomes
auction.seller.
Allocates the seller-side CDR reserve vault (reserveUuid) and registers it
on AuctionRevealCondition with the same two-gate condition that governs
bid vaults (deadline elapsed AND auction triggered).
The reserve is sealed: this call creates the vault but does not write
ciphertext. The seller calls submitEncryptedReserve separately to seal their
price floor. If the seller never submits a reserve, the floor defaults to 0 at
settlement (any valid bid can win).
Reverts:
DeadlineInPastifdeadline <= block.timestamp
Emits: AuctionCreated
submitEncryptedReserve
function submitEncryptedReserve(
uint256 auctionId,
bytes calldata ciphertext
) external;
Seller writes the TDH2-encrypted reserve payload into the reserve CDR vault.
Seller-only; Open state only; before deadline; one write per auction.
Uses the same 149-byte BidPayload format, with bidder = seller address and
the inner digest keccak256(abi.encode(auctionId, seller, amount, nonce)).
Reverts:
UnknownAuctionBiddingClosedif not inOpenstate or past deadlineNotSellerifmsg.sender != auction.sellerReserveAlreadyWrittenif ciphertext already submitted
Emits: EncryptedReserveSubmitted
allocateBidSlot
function allocateBidSlot(
uint256 auctionId,
uint256 deposit
) external returns (uint32 uuid);
First step of the two-call bid flow. Pulls deposit WIP from the caller
(transferFrom — caller must have approved the contract). Asks the CDR
network for a fresh ciphertextUuid bound to this auction's two-gate read
condition. Registers the deadline with AuctionRevealCondition.
Reverts:
UnknownAuctionifauctionIddoesn't existBiddingClosedif not inOpenstate or past deadlineZeroDepositifdeposit == 0DepositTransferFailedif the WIP transfer fails
Emits: BidSlotAllocated
submitEncryptedBid
function submitEncryptedBid(
uint256 auctionId,
uint32 uuid,
bytes calldata ciphertext
) external;
Second step of the two-call bid flow. Writes the TDH2-encrypted 149-byte
BidPayload to the vault identified by uuid. Caller must be the address
that called allocateBidSlot for this uuid.
Reverts:
UnknownAuctionBiddingClosedif not inOpenstate or past deadlineUnknownBidSlotif no slot was allocated for this uuidNotBidderifmsg.sender != slot.bidderCiphertextAlreadyWrittenif ciphertext already exists for this slot
Emits: EncryptedBidSubmitted
trigger
function trigger(uint256 auctionId) external;
Flips the auction from Open to Triggered. Permissionless; anyone can
call. This is the second gate that AuctionRevealCondition checks before
CDR validators publish decryption shares. Calling trigger is therefore a
prerequisite for settlement.
Reverts:
UnknownAuctionDeadlineNotReachedifblock.timestamp < deadlineWrongStateif state is notOpen
Emits: AuctionTriggered
settle
function settle(
uint256 auctionId,
BidReveal[] calldata reveals,
ReserveReveal calldata reserveReveal
) external;
Settles the auction with the decrypted bid and reserve payloads. Permissionless; in practice only an orchestrator with access to the CDR-decrypted plaintexts can construct a valid call.
The contract performs the following in a single transaction (revert-all; partial settlement is impossible):
- Resolves the sealed reserve floor:
- If
reserveHasCiphertext == true, callsBidPayload.recoverSigneronreserveReveal; revertsInvalidReserveRevealif recovered address does not equalauction.seller. Uses the revealedamountas the floor. - If no reserve was sealed, floor defaults to 0.
- If
- For each
BidRevealinreveals:- Looks up
bids[auctionId][reveal.bidIndex]. - Calls
BidPayload.recoverSigner; skips if recovered address does not equalslot.bidder. - Skips if
reveal.amount > slot.deposit. - Skips if
slot.hasCiphertext == false.
- Looks up
- Picks the highest valid bid at or above the reserve floor. Ties broken by
earliest
blockNumber. - Winner path: mints a Story PIL license token to the winner via
LicensingModule.mintLicenseTokens; transferswinningAmountWIP to seller; refunds winner's excess deposit; refunds every losing deposit. - No-winner path: refunds all deposits; transitions to
ExpiredNoWinner.
Reverts:
UnknownAuctionWrongStateForSettleif state is notTriggeredInvalidReserveRevealifreserveHasCiphertext == trueand the reserve reveal does not recover toauction.sellerMintFailedifLicensingModule.mintLicenseTokensreverts
Emits: AuctionSettled on the winner path;
AuctionExpiredNoWinner if no valid bid cleared
the reserve.
State enum
enum State { Open, Triggered, Settled, ExpiredNoWinner, ExpiredEmpty }
| Value | Uint8 | Set by |
|---|---|---|
Open | 0 | createAuction |
Triggered | 1 | trigger |
Settled | 2 | settle (winner found) |
ExpiredNoWinner | 3 | settle (no bid cleared reserve) |
ExpiredEmpty | 4 | Reserved state; not currently set by any function |
Function reference table
| Function | Caller | State required |
|---|---|---|
nextAuctionId (view) | anyone | any |
auctions(id) (view) | anyone | any |
bids(id, idx) (view) | anyone | any |
isTriggered(id) (view) | anyone (AuctionRevealCondition) | any |
createAuction(...) | anyone (becomes seller) | n/a |
submitEncryptedReserve(...) | seller only | Open, before deadline |
allocateBidSlot(...) | anyone (becomes bidder) | Open, before deadline |
submitEncryptedBid(...) | slot allocator only | Open, before deadline |
trigger(id) | anyone | Open, at or after deadline |
settle(id, reveals, reserveReveal) | anyone (in practice: orchestrator) | Triggered |
ABI
The full ABI ships with the SDK:
import {sealedAuctionAbi} from '@sealedip/sdk/abi/sealed-auction'
Or find it on Storyscan under the Contract tab.