Skip to content

PayPal sandbox only. No real money moves.

BuildersBuildersPackages

engine

The pure auction core: decide, evolve, the hash-chained ledger and the redacted public export.

Verifiedbacked by a real run or committed evidence

Page last changed 7 Nov 2026 (from the git history)

@gavel/engine (packages/engine) decides what an auction does. It has no clock, no randomness, no network and no disk in its core: a command goes in, ledger entries come out, and the state of the auction is a fold over those entries. It has no runtime dependencies.

Responsibilities

  • Decide. decide(state, command) returns either entries to append or a rejection with a reason from REJECT_REASONS. Every command type has its own file under src/decide/ (place-bid.ts, hammer.ts, open-lot.ts, abort-lot.ts, pause.ts, promote.ts, revoke.ts, settlement.ts, setup.ts, close-auction.ts).
  • Evolve. evolve(state, entry) folds one entry into the state. replay rebuilds the state from a verified chain.
  • Rules. Pricing for the three modes (english, proxy, second_price), increments, soft close, exposure against a bidder's ceiling, ranking and the fallback candidate list live in src/rules/.
  • Ledger. Each entry is hashed with the hash of the entry before it (ledger/chain.ts). verifyChain finds the first bad entry; exportJsonl and verifyJsonl write and check a file; the verify-ledger binary checks an export without a server.
  • Projections. projectPublic, projectBidder and projectAuditor decide who may see what. Sealed bids stay hidden until the hammer.
  • Public export. publicExport writes a second, redacted chain (version 4) that anyone can verify; matchesLedger lets an auditor holding the private ledger check it.
  • Runtime. AuctionRuntime runs commands of one auction one after another, rebuilds state from the store by verified replay on first use, and tells observers only after the store acknowledged the append.

Public interface

Exported from packages/engine/src/index.ts; a second entry @gavel/engine/node (src/node/index.ts) holds Node-only helpers.

ExportRole
decide, evolve, evolveAll, initialStatethe pure core
parseCommand, COMMAND_TYPESvalidates an incoming command
AuctionRuntime, LedgerStore, InMemoryLedgerStore, LedgerConflictsingle-writer orchestration over a store
replay, stateDigest, verifyChain, sealBatch, headOf, genesisHeadchain and replay
exportJsonl, verifyJsonl, tamperAtfile export, check, and a test helper that damages an entry
publicExport, verifyPublicExport, matchesLedger, PUBLIC_EXPORT_VERSIONredacted public chain
projectPublic, projectBidder, projectAuditorviews
candidatesordered fallback list of winner and runners-up, each priced as if earlier ones were out
validateAuctionConfig, validateLotConfig, DEFAULT_AUCTION_CONFIG, LIMITSconfiguration and caps
SETTLEMENT_STATES, SETTLEMENT_EDGESthe edges the settlement saga may record

Prop

Type

Key files

evolve.ts
state.ts
replay.ts
projections.ts
runtime.ts
canonical.ts
reasons.ts
bin/verify-ledger.mjs
scripts/check-imports.mjs
FileNote
decide/index.tschecks ids and time for every command type, answers a repeated commandId from state, then dispatches
rules/exposure.tscommitted: sum of hammer prices of lots the bidder holds within the hold scope key
rules/softclose.tsextension and hardCloseOf: when an accepted bid extends the lot and the latest end it may reach
ledger/chain.tsgenesis prev of 64 zeros, sealBatch, verifyChain and its failure reasons
ledger/public-export.tsredaction rules and the keyed commitment for entries after the first redaction
runtime.tsthe per-auction queue and the store-acknowledged-before-observers rule
canonical.tsdeterministic bytes that are hashed
bin/verify-ledger.mjsverifies an export without any server; --head catches a shortened file

Invariants and the tests that guard them

Where it sits

engine may import shared (ALLOWED.engine = ['shared'] in .dependency-cruiser.cjs). In practice it imports nothing from @gavel/shared at run time: src/types.ts declares its own Cents and EpochMs, and test/contract/shared.test.ts compares the engine's constants (config limits, ceiling presets, reject reasons) with the shared copies. payments and server import the engine; web does not.

The engine records settlement states but does not own the transitions: SETTLEMENT_EDGES in src/events.ts is the same set as the payments state machine, and RecordSettlement refuses any other pair. See Settlement saga and Engine and ledger.

Edit on GitHub

Last updated on