Auction states and soft close
The states a lot moves through, who can hammer it, and how the soft-close clock extends the end.
An auction in GAVEL is one lot at a time, and a lot has a clock. This page shows the states a lot can be in, which event moves it from one to the next, and exactly how the clock can be extended when someone bids at the last moment.
Verified against the code, not measured
States and rules come from the engine and the room host. The clock numbers in the simulator are the demo lot of the server, not a general setting. Nothing here is a timing measurement; for latency see Latency.
Lot states, hammer and soft close
Scroll the figure sideways, or open it full size.
Hover, focus or tap a state for what it means and where it lives.
Pick how many seconds before the end a bid arrives, then place it.
packages/engine/src/evolve.ts (which entry sets which status), engine/src/rules/softclose.ts (the extension rule, copied into the simulator above), engine/src/decide/hammer.ts and packages/server/src/runtime/ledger-engine.ts (hard close margin). The clock numbers are the demo lot in packages/server/src/boot/compose.ts; the engine default is soft close off. This is a map of rules, not a measurement.Text version of this diagram
Lot states and the entry that moves between them:
- PENDING to OPEN: LOT_OPENED.
- OPEN to CLOSING: LOT_CLOSING, written at the deadline by a tick, or by an early hammer.
- CLOSING to SETTLING: LOT_HAMMERED.
- SETTLING to DONE: LOT_SETTLED, or LOT_UNSOLD when no candidate is left.
- CLOSING to DONE: a hammer with no bids writes LOT_UNSOLD.
- PENDING, OPEN or CLOSING to DONE: LOT_ABORTED, the kill switch before the hammer. A SETTLING lot cannot be aborted.
Soft close for the demo lot: the lot lasts 600 seconds, the window is 30 seconds, an extension sets the end to the bid time plus 30 seconds (never earlier than the old end), at most 5 extensions happen, and the hard close is at 750 seconds. A bid 30 seconds before the end changes nothing, a bid 1 second before the end moves it by 29 seconds.
The five states
| State | Entered by | What is allowed |
|---|---|---|
PENDING | LOT_ADDED | Nothing yet. Lots open one at a time, in lot order. |
OPEN | LOT_OPENED (OpenLot) | Bids. Pause, resume and extend by the auctioneer. |
CLOSING | LOT_CLOSING | No bids. The hammer. |
SETTLING | LOT_HAMMERED | Nothing on the auction side: the lot is frozen and the settlement saga runs. |
DONE | LOT_SETTLED, LOT_UNSOLD or LOT_ABORTED | Nothing. |
Two things that look like states are not:
- Paused is a flag on an
OPENlot.LOT_PAUSEDrecords how much time was left,LOT_RESUMEDrestores it. While paused, a bid is answeredauction_paused(retryable) and the clock does not tick. - SCHEDULED and PAUSED appear in the shared status list, but the server never sends them as the status of a room. The web client shows PAUSED when the snapshot says
paused: trueon anOPENroom.
How a lot ends
The deadline passes. The room arms a timer for endTs. When it fires, the room ticks the engine, which writes LOT_CLOSING if the deadline has passed. A bid that arrives after the deadline but before the timer is also rejected, and writes LOT_CLOSING first.
The lot is hammered. From CLOSING (or from OPEN, if the auctioneer closes early), the hammer ranks the bids, takes the top one as the winner, prices it and lists the fallback candidates. The result is LOT_HAMMERED. With no bids at all it is LOT_UNSOLD and the lot goes straight to DONE.
Money starts. After the hammer the room asks the payments side to settle, once per process, after saving the request. Registration for the room closes at the hammer.
Who hammers
The auctioneer console posts a hammer control to the HTTP control route (it is not a WebSocket frame). The control carries a controlId, so a repeat returns the same result and appends nothing. It may also carry the price and the leader paddle the auctioneer saw; if either has moved, the hammer is refused with conflict (price_changed or leader_changed) and nothing is hammered. Hammering an OPEN lot needs the explicit close-now argument; a paused lot cannot be hammered.
The soft-close clock
Soft close stops a lot from ending on a last-second bid by giving rivals time to answer. The rule is small and exact (extension() in the engine):
- It applies to an accepted bid only. A rejected bid never moves the clock.
- If the bid time is earlier than
endsAt - windowMs, nothing happens. - Otherwise the new end is
max(endsAt, bidTime + extensionMs), never later than the hard close. - It stops once
maxExtensionsextensions have been used.
So with a 30 second window and a 30 second extension, a bid 1 second before the end moves the end by 29 seconds, and a bid exactly 30 seconds before the end changes nothing.
Prop
Type
The server derives the hard close for a room as the lot duration plus the smaller of maxExtensions x extensionMs and one hour. For the demo lot (a ten minute lot, 30 second window, 30 second extension, at most five extensions) that is 750 seconds after opening. With those numbers the five-extension limit stops the clock before the hard close.
Pause freezes the clock and records the time left; a lot already past its deadline cannot be paused. Resume sets the end to now plus the time left, and the hard close moves later by the paused time, because nobody could bid. Extend adds 1 to 600000 milliseconds to a running lot. It does not count towards the soft-close limit, and it is refused if it would pass the hard close. All three are audited as control records and replayed after a restart.
The engine supports a sealed second-price mode, and a config with a soft-close window on a sealed lot is rejected. Sealed lots reveal neither the leader nor the rank before the hammer. The room host as shipped runs the English (open ascending) mode only.
The price frame carries endTs, the extension count so far and minNext. While the lot is paused it also carries paused: true and remainingMs, and the room computes endTs as now plus the time left. The snapshot carries endTs, paused and remainingMs too.
- packages/engine/src/types.tsLotStatus, SoftClose
- packages/engine/src/evolve.tswhich entry sets which status
- packages/engine/src/rules/softclose.tsthe extension rule and the hard close
- packages/engine/src/decide/hammer.tsTick, Hammer
- packages/engine/src/decide/pause.tspause, resume, extend
- packages/engine/src/decide/abort-lot.tsthe kill switch
- packages/server/src/room/room.tstimers, auto hammer, control, abort
- packages/server/src/runtime/ledger-engine.tshard close margin, status mapping
- packages/server/src/boot/compose.tsthe demo lot numbers
Last updated on