diff --git a/XX.md b/XX.md new file mode 100644 index 0000000..c3e37fb --- /dev/null +++ b/XX.md @@ -0,0 +1,827 @@ +NIP-XX +====== + +Marketplace Auctions +-------------------- + +`draft` `optional` + +This NIP defines auction events for marketplace listings. A listing describes +what is being sold. An auction describes how that listing is being sold for a +limited period of time. + +The auction event is intentionally separate from the listing event. This allows +the same listing to be sold fixed-price, auctioned, re-auctioned, or auctioned +through different coordinators without mutating the canonical listing. + +## Design Goals + +- Keep marketplace listings as normal `kind:30402` classified listing events. +- Attach auctions to listings with a separate addressable event. +- Reuse the marketplace payment lifecycle for bid collateral and payment proof. +- Require exactly one auction arbiter/coordinator per auction. +- Allow the arbiter to support multiple payment methods, such as Cashu and EVM, + without making the auction event method-specific. +- Avoid split-brain auctions where different arbiters observe different bid + sets, clocks, or winner state. + +## Event Kinds + +The following kind numbers are proposal values. + +| Kind | Name | Description | +| --- | --- | --- | +| `30421` | Marketplace auction | Addressable auction attached to a listing | +| `1023` | Auction bid | Bid intent authored by the bidder's temporary trade key | +| `1024` | Auction complete | Final auction result and winner declaration | + +This NIP also reuses the marketplace payment lifecycle: + +| Kind | Name | Description | +| --- | --- | --- | +| `32123` | Marketplace payment | Payment proof or locked bid collateral | +| `32124` | Marketplace payment ack | Arbiter accepts the payment-backed bid | +| `32127` | Marketplace payment nack | Arbiter rejects the payment-backed bid | +| `32125` | Marketplace payment settlement | Release, payout, or refund action | + +## The One-Arbiter Rule + +Each auction MUST declare exactly one auction arbiter. Only this arbiter can +accept bids, reject bids, compute the effective close, refund losing bids, +promote the winning bid-chain payments into order escrow, and publish the final +auction complete event. + +This is required because auctions have global state: + +- current high bid +- valid bid set +- anti-sniping window +- reserve satisfaction +- winner +- loser refunds + +If two arbiters independently accept bids, they can observe different payment +methods, different clocks, and different bid histories. That creates two +parallel auctions instead of one auction. + +Seller arbitration methods and trusted arbiters are used for discovery before an +auction is created. Once an auction is published, the auction event pins one +arbiter pubkey for that auction. + +## Marketplace Auction Event + +A marketplace auction is a parameterized replaceable event of kind `30421`. + +The auction event MUST include: + +- a `d` tag identifying the auction +- an `a` tag referencing the listing being auctioned +- exactly one `p` tag with marker `auction-arbiter` +- a currency and decimal precision +- timing rules +- bid amount rules + +The auction event SHOULD NOT be tied to one concrete payment method unless the +auction is intentionally single-method. Bidders choose a method from the +intersection of: + +- the seller's advertised arbitration/payment methods +- the auction arbiter's advertised capabilities +- the auction currency +- the bidder's wallet capabilities + +### Example + +```jsonc +{ + "kind": 30421, + "pubkey": "", + "created_at": 1766200000, + "tags": [ + ["d", "auction-123"], + ["a", "30402::listing-456", "", "listing"], + ["p", "", "", "auction-arbiter"], + + ["currency", "USD"], + ["decimals", "2"], + + ["auction_type", "english"], + ["start_at", "1766202000"], + ["end_at", "1766288400"], + ["max_end_at", "1766289000"], + ["settlement_grace", "3600"], + + ["starting_bid", "10000"], + ["min_increment", "500"], + ["reserve", "25000"] + ], + "content": "" +} +``` + +### Required Tags + +#### `d` + +Unique auction identifier. + +```json +["d", ""] +``` + +#### `a` listing reference + +Addressable reference to the listing being auctioned. + +```json +["a", "30402::", "", "listing"] +``` + +The listing remains the canonical product or service description. The auction +event only describes the sale mechanism. + +#### `p` auction arbiter + +The auction arbiter pubkey. + +```json +["p", "", "", "auction-arbiter"] +``` + +Clients MUST ignore bid acknowledgments, bid rejections, and auction complete +events that are not authored by this pubkey, unless a future extension defines +delegated arbiter signing. + +#### Currency + +```json +["currency", "USD"] +["decimals", "2"] +``` + +All bid amounts are integers in the currency's minor unit. For example, with +`USD` and `decimals=2`, `12500` means USD 125.00. + +For sat-denominated auctions: + +```json +["currency", "SAT"] +["decimals", "0"] +``` + +#### Timing + +```json +["start_at", ""] +["end_at", ""] +["max_end_at", ""] +["settlement_grace", ""] +``` + +`start_at` is when bids may begin. + +`end_at` is the nominal close time. + +`max_end_at` is the hard cutoff after which no new bid can be accepted. + +`settlement_grace` is the time after `max_end_at` during which the arbiter and +payment methods have to settle or release funds before bidder refund paths may +open. + +Implementations SHOULD make `max_end_at` explicit even when no anti-sniping +window is used. In that case: + +```text +max_end_at = end_at +``` + +#### Bid Rules + +```json +["auction_type", "english"] +["starting_bid", "10000"] +["min_increment", "500"] +["reserve", "25000"] +``` + +`auction_type=english` means an ascending auction where the highest accepted +bid wins. + +`reserve` MAY be `0`, meaning no reserve. + +### Optional Anti-Sniping Tags + +Auctions MAY define anti-sniping behavior. Only bids accepted by the declared +auction arbiter can affect anti-sniping state. + +One recommended approach is a fixed hard cutoff with a rising minimum bid floor: + +```json +["min_bid_curve", "none:1.0"] +["min_bid_curve", "linear:5.0"] +["min_bid_curve", "exponential:5.0"] +``` + +The curve applies after `end_at` and before `max_end_at`. + +Another approach is an extension rule: + +```json +["extension_rule", "anti_sniping::"] +``` + +When using an extension rule, the effective end time is computed only from +accepted bids and MUST NOT exceed `max_end_at`. + +## Payment Methods + +The auction event MAY omit concrete payment method tags. The arbiter's service +announcement defines which methods it can validate for auctions. + +For example, the same arbiter may support: + +- Cashu bid collateral +- EVM escrow deposits +- future payment profiles + +A bidder may choose any method that satisfies all of: + +- the auction currency +- the seller's supported arbitration/payment methods +- the auction arbiter's supported methods +- the bidder's own wallet capabilities + +The chosen method is expressed in the linked `kind:32123` payment event, not in +the auction bid event. + +## Auction Bid Event + +An auction bid is a regular event of kind `1023`. + +The bid event represents bidder intent. It is not an order. It is authored by +the bidder's temporary trade key, derived using the same local seed/account +index mechanism used for private marketplace orders. + +The bid's `d` tag is the marketplace trade ID for that individual bid payment. +There is no separate bid identifier field, and bid events MUST NOT include a +`trade` tag. If a bid chain wins, the promoted order uses the `bid_chain` id as +its trade ID when present. This lets multiple bid increments, each with its own +payment trade ID and recovery material, roll up into one promoted order group. +If no `bid_chain` tag exists, the winning bid's trade ID is used as the +promoted order trade ID. + +Bid events SHOULD include a `bid_chain` tag. The `bid_chain` tag gives every +increase by the same local bidder in the same auction a stable public chain +identifier even when each bid uses a new temporary pubkey and trade ID. Clients +can use this tag to group bid increments without waiting to reconstruct a +`prev_bid` chain. + +The linked marketplace payment event proves the bid is funded or +collateralized. The payment event links to the bid; the bid does not need to +predict the payment event id. + +The bid itself is not accepted merely because it exists. A bid counts only when +the auction arbiter publishes a valid `kind:32124` payment acknowledgment for +the linked payment and bid. + +### Example + +```jsonc +{ + "kind": 1023, + "pubkey": "", + "created_at": 1766203000, + "tags": [ + ["a", "30421::auction-123", "", "auction"], + ["a", "30402::listing-456", "", "listing"], + + ["d", ""], + ["bid_chain", ""], + + ["p", "", "", "buyer"], + ["p", "", "", "seller"], + ["p", "", "", "arbiter"], + + ["amount", "12500", "USD", "2"], + ["currency", "USD"], + ["decimals", "2"] + ], + "content": "{\"type\":\"auction_bid\",\"targetOrder\":{\"quantity\":1}}" +} +``` + +### Required Bid Tags + +#### Auction Reference + +```json +["a", "30421::", "", "auction"] +``` + +#### Listing Reference + +```json +["a", "30402::", "", "listing"] +``` + +#### Trade Identifier + +```json +["d", ""] +``` + +`d` identifies the bid's marketplace trade and SHOULD match the payment lock's +settlement identifier. Bid events MUST NOT include a `trade` tag; the `d` tag is +the trade ID for auction bids. + +#### Bid Chain Identifier + +```json +["bid_chain", ""] +``` + +`bid_chain` identifies one bidder's bid chain for one auction. When using the +marketplace seed derivation model, clients SHOULD derive: + +```text +bid-chain-id = sha256( + ) +``` + +The value MUST be a lowercase 64-character hexadecimal string. It MUST be stable +for the same bidder and auction, and SHOULD differ across auctions. A bid chain +identifier is not a payment proof and does not make a bid accepted; it is a +client and relay indexing hint for grouping bid increments. + +#### Amount + +```json +["amount", "", "", ""] +["currency", ""] +["decimals", ""] +``` + +The currency MUST match the auction currency. + +#### Participants + +```json +["p", "", "", "buyer"] +["p", "", "", "seller"] +["p", "", "", "arbiter"] +``` + +The bid author MUST be the buyer participant. The arbiter participant MUST be +the arbiter declared on the auction event. + +### Target Order Parameters + +The bid content MAY include `targetOrder`. These are the order parameters the +arbiter will use if this bid wins and the bid payment is promoted into a normal +marketplace order. + +Example: + +```json +{ + "type": "auction_bid", + "targetOrder": { + "quantity": 1, + "start": "2026-07-01T00:00:00.000Z", + "end": "2026-07-05T00:00:00.000Z" + } +} +``` + +Versioned `participant_proof` and `participant_proof_key` tags MAY be attached +to the bid. Public participant proofs deliberately reveal the bidder's real +pubkey to display a public bid profile chip; sealed proofs are readable only by +their wrapped recipients. If the bid wins, the arbiter SHOULD copy these +participant proofs onto the promoted order event. + +## Marketplace Payment Event for Bids + +Auction bids reuse `kind:32123` marketplace payment events. + +The payment event SHOULD include: + +- an `a` tag for the auction +- an `a` tag for the listing +- a `d` tag and a `trade` tag, both equal to the bid's trade ID +- an `e` tag with marker `auction-bid` referencing the bid event +- participant `p` tags matching the bid +- an `amount` content field that is either a public `{ value, denomination, + decimals }` object or a sealed payment amount envelope +- `payment_amount_key` tags for seller, arbiter, and self when the amount is + sealed +- a public driver-specific payment proof in content, or a sealed payment proof + envelope plus `payment_proof_key` tags for seller, arbiter, and self + +The public payment proof MUST include exactly one opaque `driver` identifier +for the driver that created the proof, plus public `terms` or sealed +`sealedTerms`, plus driver-defined `params`. `terms` is the +application-independent statement of the locked funds, controls, and possible +settlement paths. `params` is the method-specific evidence the driver needs to +verify that the lock really conforms to those terms. Payment proofs MUST NOT +embed a payment subject, human-readable payment method, listing event, or +product metadata. Validators select the driver directly from the proof: + +```ts +const driver = drivers[paymentProof.driver] +``` + +The payment event amount is an unproven claim until driver validation. When the +amount is sealed, a recipient resolves it with the matching `payment_amount_key` +tag using the same disclosure-key pattern as sealed participant proofs and +sealed payment proofs. An arbiter MUST publish `kind:32124` only when the +driver-verified amount exactly matches the resolved payment event amount. If the +arbiter cannot resolve a sealed amount, it MUST NOT acknowledge the payment. If +the driver proves a different amount, the payment is invalid for acknowledgment +even if the proof otherwise exists. + +### EVM Payment Example + +```jsonc +{ + "kind": 32123, + "pubkey": "", + "created_at": 1766202990, + "tags": [ + ["a", "30421::auction-123", "", "auction"], + ["a", "30402::listing-456", "", "listing"], + ["d", ""], + ["trade", ""], + ["e", "", "", "auction-bid"], + ["p", "", "", "buyer"], + ["p", "", "", "seller"], + ["p", "", "", "arbiter"] + ], + "content": "{\"amount\":{\"value\":\"12500\",\"denomination\":\"USD\",\"decimals\":2},\"proof\":{\"paymentProof\":{\"driver\":\"\",\"terms\":{\"version\":1,\"asset\":{\"value\":\"12500\",\"denomination\":\"USD\",\"decimals\":2},\"parties\":[{\"role\":\"buyer\",\"id\":\"\"},{\"role\":\"seller\",\"id\":\"\"},{\"role\":\"arbiter\",\"id\":\"\"}],\"lock\":{\"id\":\"\",\"policyId\":\"\",\"kind\":\"contract\",\"amount\":{\"value\":\"12500\",\"denomination\":\"USD\",\"decimals\":2},\"controls\":[{\"role\":\"buyer\",\"id\":\"\"},{\"role\":\"seller\",\"id\":\"\"},{\"role\":\"arbiter\",\"id\":\"\"}],\"conditions\":{\"arbitration\":{\"type\":\"continuous\",\"denominator\":\"1000000\"}},\"paths\":[]}},\"params\":{\"txHash\":\"0x...\"}}}}" +} +``` + +### Cashu Payment Example + +```jsonc +{ + "kind": 32123, + "pubkey": "", + "created_at": 1766202990, + "tags": [ + ["a", "30421::auction-123", "", "auction"], + ["a", "30402::listing-456", "", "listing"], + ["d", ""], + ["trade", ""], + ["e", "", "", "auction-bid"] + ], + "content": "{\"amount\":{\"value\":\"12500\",\"denomination\":\"USD\",\"decimals\":2},\"proof\":{\"paymentProof\":{\"driver\":\"\",\"terms\":{\"version\":1,\"asset\":{\"value\":\"12500\",\"denomination\":\"USD\",\"decimals\":2},\"parties\":[{\"role\":\"buyer\",\"id\":\"\"},{\"role\":\"seller\",\"id\":\"\"},{\"role\":\"arbiter\",\"id\":\"\"}],\"lock\":{\"id\":\"\",\"policyId\":\"\",\"kind\":\"threshold\",\"amount\":{\"value\":\"12500\",\"denomination\":\"USD\",\"decimals\":2},\"controls\":[{\"role\":\"buyer\",\"id\":\"\"},{\"role\":\"seller\",\"id\":\"\"},{\"role\":\"arbiter\",\"id\":\"\"}],\"paths\":[]}},\"params\":{\"commitment\":\"\",\"mint\":\"https://mint.example\"}}}}" +} +``` + +### Sealed Amount and Payment Proof Example + +```jsonc +{ + "kind": 32123, + "pubkey": "", + "created_at": 1766202990, + "tags": [ + ["a", "30421::auction-123", "", "auction"], + ["a", "30402::listing-456", "", "listing"], + ["d", ""], + ["trade", ""], + ["e", "", "", "auction-bid"], + ["p", "", "", "buyer"], + ["p", "", "", "seller"], + ["p", "", "", "arbiter"], + ["payment_amount_key", "1", "", "", "", "nip44", ""], + ["payment_amount_key", "1", "", "", "", "nip44", ""], + ["payment_amount_key", "1", "", "", "", "nip44", ""], + ["payment_proof_key", "1", "", "", "", "nip44", ""], + ["payment_proof_key", "1", "", "", "", "nip44", ""], + ["payment_proof_key", "1", "", "", "", "nip44", ""] + ], + "content": "{\"amount\":{\"version\":1,\"mode\":\"sealed:v1\",\"proofId\":\"\",\"payload\":\"\"},\"proof\":{\"version\":1,\"mode\":\"sealed:v1\",\"proofId\":\"\",\"payload\":\"\"}}" +} +``` + +The sealed amount payload decrypts to the same public amount object used by the +EVM and Cashu examples. The sealed payment proof payload decrypts to the same +public payment proof object used by those examples. The wider public can see the +funded bid lifecycle, but only wrapped recipients can inspect the amount claim +or driver-specific payment proof. + +Implementations MAY also seal only `paymentProof.terms` by replacing `terms` +with: + +```json +{ + "sealedTerms": { + "version": 1, + "mode": "sealed:v1", + "proofId": "", + "payload": "" + } +} +``` + +This hides the lock amount and settlement paths while keeping the driver and +params visible. Recipients decrypt sealed terms with `payment_proof_key` tags +using the sealed terms `proofId` as the key id. + +Cashu bearer tokens or proofs MUST NOT be published in plaintext on public +relays unless the auction profile explicitly accepts public pre-signatures for +v1 interoperability. Bearer tokens MUST be delivered through the arbiter's +private driver-specific transport. + +## Payment Acknowledgment and Rejection + +The auction arbiter accepts or rejects a bid by acknowledging or rejecting the +linked payment event. + +An acknowledgment MUST be authored by the auction arbiter. +An acknowledgment MUST NOT be published until the arbiter has validated the +payment proof with the indicated driver and confirmed that the driver-verified +amount equals the resolved payment event amount. + +### Accepted Bid + +```jsonc +{ + "kind": 32124, + "pubkey": "", + "created_at": 1766203010, + "tags": [ + ["a", "30421::auction-123", "", "auction"], + ["a", "30402::listing-456", "", "listing"], + ["e", "", "", "payment"], + ["e", "", "", "auction-bid"] + ], + "content": "{\"status\":\"accepted\"}" +} +``` + +Only accepted bids participate in: + +- current high bid calculation +- anti-sniping extension or bid-floor state +- reserve calculation +- final winner selection + +### Rejected Bid + +```jsonc +{ + "kind": 32127, + "pubkey": "", + "created_at": 1766203010, + "tags": [ + ["a", "30421::auction-123", "", "auction"], + ["e", "", "", "payment"], + ["e", "", "", "auction-bid"], + ["reason", "below_min_bid"] + ], + "content": "{\"status\":\"rejected\",\"message\":\"below minimum bid\"}" +} +``` + +Rejected bids do not participate in auction state. + +An accepted bid that later loses is not rejected. It remains a valid bid and is +settled or refunded after the auction closes. + +## Auction Complete Event + +The auction complete event is a regular event of kind `1024`. + +It declares the final auction result. It MUST be authored by the auction arbiter. +It is not the buyer's order. If there is a winner, the arbiter also promotes the +winning bid payment into the normal marketplace order/payment lifecycle. + +### Example + +```jsonc +{ + "kind": 1024, + "pubkey": "", + "created_at": 1766289010, + "tags": [ + ["a", "30421::auction-123", "", "auction"], + ["a", "30402::listing-456", "", "listing"], + + ["e", "", "", "winning-bid"], + ["e", "", "", "winning-payment"], + ["e", "", "", "auction-promote"], + + ["status", "closed"], + ["winner", ""], + ["final_amount", "22500", "USD", "2"], + ["currency", "USD"] + ], + "content": "{\"type\":\"auction_complete\",\"status\":\"closed\"}" +} +``` + +If the reserve is not met: + +```json +["status", "reserve_not_met"] +``` + +If the auction is cancelled before any accepted bid: + +```json +["status", "cancelled"] +``` + +Cancellation after the first accepted bid SHOULD be forbidden unless the auction +event explicitly defines a cancellation policy. + +## Settlement and Refunds + +When settling an auction, the arbiter validates all bid/payment pairs for the +auction. Accepted bid chains are ordered by total accepted chain value, not only +by the head bid event amount. Invalid bids and non-winning valid bids MUST be refunded or released +with the existing marketplace payment settlement event `kind:32125` using +`action=auction_refund` and a 100% refund where the method supports explicit +refund percentages. + +Winning bid-chain payments MUST NOT be paid directly to the seller merely +because the auction ended. Instead every accepted payment in the winning chain +is promoted or recycled into normal marketplace order escrow using +`action=auction_promote`. + +Settlement events SHOULD reference: + +- the auction event +- the bid event +- the payment event +- the auction complete event + +Example tags: + +```json +[ + ["a", "30421::auction-123", "", "auction"], + ["e", "", "", "auction-bid"], + ["e", "", "", "payment"], + ["e", "", "", "auction-complete"] +] +``` + +### Winner Promotion into an Order + +For the selected winning chain, the arbiter MUST: + +1. Call the driver-specific refund operation for each non-winning bid payment + and the driver-specific promotion/recycle operation for each winning-chain + payment. +2. Publish a `kind:1024` auction complete event. +3. Publish `kind:32125` payment settlement events for each bid payment, + including `action=auction_refund` for losers and one `action=auction_promote` + event for each promoted winning-chain payment. +4. Publish a normal `kind:32122` marketplace order authored by the arbiter, + with `recipient` set to the winner's temporary buyer trade pubkey and + `trade` set to the winning `bid_chain` id when present, otherwise the winning + bid trade ID. +5. Publish a new `kind:32123` marketplace payment event for each promoted + winning-chain payment, each carrying its evolved payment proof and linking to + the promoted order, the auction complete event, and the matching + `auction_promote` settlement. +6. Publish a `kind:32124` payment acknowledgment for each promoted order + payment. + +The promoted order SHOULD copy participant proofs from the winning bid-chain +head. Promoted order payments SHOULD preserve their own payment amounts, and +the promoted order amount SHOULD equal the total accepted winning bid-chain +value. + +## Cashu Arbiter-Canonical Profile + +In a Cashu-backed auction, the arbiter is also the oracle for: + +- whether the auction is active +- whether the bid meets the current auction rules +- whether the Cashu collateral is valid +- whether the bid should be accepted +- which bid wins + +The arbiter publishes `kind:32124` for accepted bids and `kind:32127` for +rejected bids. + +Cashu tokens or proofs MUST NOT be included in public bid events. They are sent +to the arbiter through a private transport. Public payment events carry only +commitments, public pre-signature metadata where explicitly supported, and +driver-specific metadata. + +For auction bids, the Cashu payment profile SHOULD support a bidder refund path +and a pre-authorized promotion path. If the bid wins, the arbiter uses the +bidder's public v1 authorization to move the locked bid funds into a normal +order escrow condition. If the bid loses or is invalid, the arbiter publishes an +`auction_refund` settlement proof. + +## EVM Arbiter-Canonical Profile + +In a mixed-method auction, an EVM contract SHOULD be treated as a lockbox, not +as the global auction brain. + +The EVM deposit transaction proves that a bidder locked funds for: + +- this auction +- this bid +- this arbiter +- a bidder refund timeout +- optional recycle parameters authorizing promotion into normal order escrow + +The auction arbiter still decides whether the EVM-backed bid is accepted into +the global auction state. This lets EVM bids and Cashu bids compete in the same +currency under one arbiter. + +The EVM contract SHOULD expose enough information in logs or calldata for the +arbiter and clients to verify: + +- auction identifier +- bid identifier or payment identifier +- bidder +- selected arbiter +- amount +- currency or asset +- refund conditions +- recycle authorization and timeout + +The arbiter publishes `kind:32124` when the deposit is valid and high enough, +or `kind:32127` when it is invalid or below the current minimum bid. + +If a bid-chain wins, the arbiter calls the contract's recycle/promote method +for every accepted payment in the winning chain using each payment's +pre-authorized recycle arguments. The resulting proofs are published in the +matching `auction_promote` settlement events and then in the promoted order +payment events. + +## EVM Contract-Canonical Profile + +Some auctions may choose to make a single EVM contract the canonical auction +state machine. In this profile: + +- all bids MUST be placed through the same contract +- mixed Cashu/EVM bids are not valid unless mirrored into the contract +- the contract enforces bid validity, timing, and winner selection + +The Nostr auction complete event MUST include proof that the contract picked the +winner. + +Example complete proof tags: + +```json +[ + ["settlement_profile", "evm_contract_canonical_v1"], + ["chain", "eip155:11155111"], + ["contract", "0x..."], + ["contract_auction_id", "0x..."], + ["tx", "0xcloseAuctionTxHash"], + ["log", "WinnerPicked", "3"] +] +``` + +The auction complete event content SHOULD include structured proof data: + +```json +{ + "type": "auction_complete", + "proof": { + "method": "evm", + "event": "WinnerPicked", + "txHash": "0x...", + "logIndex": 3, + "auctionId": "0x..." + } +} +``` + +This profile is simpler for EVM-only auctions, but it does not solve mixed +payment method coordination unless every non-EVM bid is also represented in the +contract. + +## Winner Selection + +For `auction_type=english`, the winner is the highest accepted bid chain after +the auction closes. The chain value is the sum of accepted payment-backed bid +increments in the chain. + +Tie-break order: + +1. Highest accepted bid-chain total wins. +2. If amounts are equal, earliest accepted bid wins. +3. If acceptance time is equal, lexicographically smallest bid event id wins. + +Implementations MAY instead use bid `created_at` for the second tie-breaker, +but the auction arbiter MUST apply one deterministic rule consistently. + +## Notes + +This draft intentionally keeps payment method selection out of the auction bid +event. The payment event links to the bid event and carries the driver-specific +proof. The auction arbiter decides whether that payment-backed bid becomes part +of the auction's canonical state. + +This avoids the main failure mode for multi-method auctions: different arbiters +accepting different bids and producing incompatible winners.