From 7402c8a5a9eb4a0888ec185dde3e9b0b4d26aea2 Mon Sep 17 00:00:00 2001 From: pats2sats <5664617+pats2sats@users.noreply.github.com> Date: Thu, 25 Jun 2026 22:25:33 -0600 Subject: [PATCH 1/3] Add marketplace auctions spec --- XX.md | 827 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 827 insertions(+) create mode 100644 XX.md 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. From d934deaca8380e5f1548a14e3c19a3c408751c1b Mon Sep 17 00:00:00 2001 From: pats2sats <5664617+pats2sats@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:23:00 -0600 Subject: [PATCH 2/3] docs: define deterministic auction settlement --- README.md | 841 +----------------------------------------------------- XX.md | 132 +++++++-- 2 files changed, 117 insertions(+), 856 deletions(-) diff --git a/README.md b/README.md index 2ba8eba..35c967c 100644 --- a/README.md +++ b/README.md @@ -1,837 +1,8 @@ -Marketplace Protocol ---------------------------- +# Marketplace Auction Proposal -`draft` +This branch contains the draft Nostr marketplace auction proposal. -A protocol specification for decentralized marketplaces on Nostr that provides an interoperable, full-featured e-commerce framework. - -## Table of Contents -1. Protocol Requirements -2. Core Protocol Components -3. Events and Kinds -4. Order Communication Flow and Payment Processing -5. Product Reviews -6. Implementation Guidelines - -## 1. Protocol Requirements - -The protocol defines both required core components and optional features to support diverse marketplace needs. - -### Required Components -Implementations MUST support the following core features: - -- Product listing events (Kind: 30402) -- Product collection events (Kind: 30405) for product-to-collection references -- Merchant's preferences -- Order communication and processing via [NIP-17](17.md) encrypted messages - -### Optional Components -These features MAY be implemented based on specific marketplace needs: - -- Extended product metadata -- Shipping options (Kind: 30406) -- Product collections (Kind: 30405) -- Drafts following [NIP-37](37.md) -- Product reviews (Kind: 31555) -- Service assisted order and payment processing - -#### Watch-only clients -Watch-only clients are applications that allow users to display products without implementing full e-commerce capabilities. These clients don't need to support all required components - product rendering alone can be sufficient. However, ideally, they should also handle logic for looking up collections, reviews, and shipping options. Support for order communication using [NIP-17](17.md) is optional. - -## 2. Core Protocol Components - -### Core Flows -1. Merchant Preferences - - Application preferences via [NIP-89](89.md) - - Payment method preferences via kind `0` tags - -2. Order Processing - - Encrypted buyer-seller communication - - Status updates and confirmations - -3. Shipping - - Option definition and pricing - - Geographic restrictions - -4. Payment - - Multiple payment methods - - Verification and receipts - -Standard e-commerce flow: -1. Product discovery -2. Cart addition -3. Merchant preference verification -4. Shipping calculation -5. Payment processing -6. Order confirmation -7. Encrypted message follow-up - -### Merchant Preferences -Merchants MAY specify preferences for how they want users to interact with them, including which applications to use and payment methods to accept. These preferences ensure a consistent experience and streamline operations. Merchants indicate their preferences through two mechanisms: - -1. Application Preferences ([NIP-89](89.md)): -- The recommended application MUST publish a kind `31990` event -- The merchant MUST publish a kind `31989` event recommending that application - -2. Payment Preferences: -- Set via `payment_preference` tag in the merchant's kind `0` event -- Valid values: `manual | ecash | lud16` -- Defaults to `manual` if not specified - -Applications implementing this NIP MUST handle preferences as follows: - -1. When `payment_preference` is `manual`: -- If merchant recommends an app: MUST direct users to that app -- If no app recommendation: Use traditional interactive flow (buyer places order and waits for merchant's payment request) - -2. When `payment_preference` is `ecash` or `lud16`: -- If merchant recommends an app: SHOULD direct users there first, but they MAY also offer to continue if compatible with the payment preference -- If no recommendations: Use specified payment method directly - -3. When no preferences are set: -- Use traditional interactive flow - - Buyer sends order - - Wait for merchant's payment request - -Buyers can verify merchant preferences by: -- Checking kind `31990` events for recommended applications -- Checking kind `0` events for payment preferences - -This verification helps buyers follow merchant-approved paths and avoid potential scams or poor experiences. - -## 3. Events and Kinds - -### Product Listing (Kind: 30402) - -Products are the core element in a marketplace. Each product listing MUST contain basic metadata and MAY contain additional details. Their configuration is the source of truth, overriding other possible configurations of other market elements such as collections, no configuration is cascaded to products, they MUST explicitly reference an attribute to inherit it. - -**Content**: Product description, markdown is allowed -**Required tags**: -- `d`: Unique product identifier for referencing the listing -- `title`: Product name/title for display -- `price`: Price information array `[, , ]` - - amount: Decimal number (e.g., "10.99") - - currency: ISO 4217 code (e.g., "USD", "EUR") - - frequency: Optional subscription interval using ISO 8601 duration units (e.g. 'D' for daily, 'W' for weekly, 'Y' for yearly). - -**Optional tags**: -- Product Details: - - `type`: Product classification `[, ]` - - type: "simple", "variable", or "variation" - - format: "digital" or "physical" - - Default/if not present: type: "simple", format: "digital" - - `visibility`: Display status ("hidden", "on-sale", "pre-order"). Default/if not present: "on-sale" - - `stock`: Available quantity as integer - - `summary`: Short product description - - `spec`: Product specifications `[, ]`, can appear multiple times - -- Media: - - `image`: Product images `[, , ]`, MAY appear multiple times - - url: Direct image URL - - dimensions: Optional, in pixels, "x" format, if not present the place in the array should be respected by using an empty string `""` - - sorting order: Optional integer for order sorting. Values are sorted from lowest to highest, independent of starting value (not restricted to start with 0 or 1) - -- Physical Properties: - - `weight`: Product weight `[, ]` using ISO 80000-1 - - `dim`: Dimensions `[xx, ]` using ISO 80000-1 - -- Location: - - `location`: Human-readable location string or collection coordinates - - `g`: Geohash for precise location lookup or collection coordinates - -- Organization: - - `t`: Product categories/tags, MAY appear multiple times - - `a`: Product reference "30402::", MUST appear only once to reference parent products in a variable/variation configuration - - `a`: Collection reference "30405::", MAY appear multiple times - - `shipping_option`: Shipping options, MAY appear multiple times - - Format: "30406::" for direct options - - Format: "30405::" for collection shipping - - `extra-cost`: Optional third element in the array, to add extra cost (in the product's currency) for the shipping method. In case of reference a collection the extra cost should be applied to all shipping options from the collection. - -```jsonc -{ - "kind": 30402, - "created_at": , - "content": "", - "tags": [ - // Required tags - ["d", ""], - ["title", ""], - ["price", "", "", ""], - - // Product details - ["type", "", ""], // Defaults: simple, digital - ["visibility", ""], // Default: on-sale - ["stock", ""], // Available quantity - ["summary", ""], - - // Media and specs - ["image", "", "", ""], - ["spec", "", ""], // Product specifications (e.g., "screen-size", "21 inch"). MAY appear multiple times - - // Physical properties (for shipping) - ["weight", "", ""], // ISO 80000-1 units (g, kg, etc) - ["dim", "xx", ""], // ISO 80000-1 units (mm, cm, m) - - // Location - ["location", "
"], - ["g", ""], - - // Classifications - ["t", ""], - - // References - ["shipping_option", "<30406|30405>::", ""], // Shipping options or collection, MAY appear multiple times - ["a", "30405::"] // Product collection - ] -} -``` - -#### Notes -1. Product Configuration: - - Products can be simple, variable (with options), or variations of variable products - - Digital products skip shipping requirements - - Visibility controls product display status - -2. Variable products: - - The parent or "root" product should use `variable` as value for `type` - - The variations of the parent product should use `variation` as value for `type`. - - Variations MUST include an `a` tag pointing to the `variable` parent product. - -2. Shipping Rules: - - Shipping options can be defined directly by pointing to a shipping event, or inherited from collections - - If the product specifies product-specific shipping, and also from a collection, shipping options MUST be merged. - -3. Collections and Categories: - - Products can refer to one o multiple collections using `a` tags, whether or not they are part of it, for discoverability purposes. - - Categories ("t" tags) aid in discovery and organization - -4. Location Support: - - Optional location data aids in local marketplace features, they can point to a collection event to inherit it's value - - Geohash enables precise location-based searches, they can point to a collection event to inherit it's value - -### Product Collection (Kind: 30405) -A specialized event type using [NIP-51](51.md) like list format to organize related products into groups. Collections allow merchants or any user to create meaningful product groupings and share common attributes that products can also reference, establishing one-to-many relationships. - -**Content**: Optional collection description - -**Required tags**: -- `d`: Unique collection identifier -- `title`: Collection display name/title -- `a`: Product references `["a", "30402::"]` - - Multiple product references allowed - - References must point to valid product listings - -**Optional tags**: -- Display: - - `image`: Collection banner/thumbnail URL - - `summary`: Brief collection description - -- Location: - - `location`: Human-readable location string - - `g`: Geohash for precise location lookup - -- Reference Options: - - `shipping_option`: Available shipping options `["shipping_option", "30406::"]`, MAY appear multiple times - -```jsonc -{ - "kind": 30405, - "created_at": , - "content": "", - "tags": [ - // Required tags - ["d", ""], - ["title", ""], - ["a", "30402::"], // Product reference - - // Optional tags - ["image", ""], - ["summary", ""], - - // Location - ["location", ""], - ["g", ""], - - // Reference Options - ["shipping_option", "30406::"], // Available shipping options, MAY appear multiple times - ] -} -``` - -#### Notes -1. Collection Management: - - Collections can contain any number of products - - Products can belong to multiple collections - -2. Reference Model: - - Collection settings (shipping, location, geohash) serve as references only - - Products MUST explicitly reference collection resources to inherit collection attributes (e.g. shipping, location, geohash). - - No automatic cascading of settings to products - -3. Location Support: - - Optional location data helps with marketplace organization - - Enables geographic grouping of related products - -### Drafts -Products and collections can be saved as private drafts while being prepared for publication. This allows merchants to work on listings before making them publicly visible. Implementation MUST follow [NIP-37](https://github.com/nostr-protocol/nips/blob/master/37.md) for draft management. - -### Shipping Option (Kind: 30406) -A specialized event type for defining shipping methods, costs, and constraints. Shipping options can be published by merchants or third-party providers (delivery companies, DVMs, etc.) and referenced by product listings or collections. - -**Content**: Optional human-friendly shipping description - -**Required tags**: -- `d`: Unique shipping option identifier -- `title`: Display title for the shipping method -- `price`: Base cost array `[, ]` -- `country`: Array of ISO 3166-1 alpha-2 country codes `[, , ...]` -- `service`: Service type ("standard", "express", "overnight", "pickup") - -**Optional tags**: -- Extra details: - - `carrier`: The name of the carrier that will be used for the delivery -- Time and Location: - - `region`: Array of ISO 3166-2 region codes for which shipping method is available `[, , ...]` - - `duration`: Delivery window `[, , ]` using ISO 8601 duration units - - min: Minimum delivery time - - max: Maximum delivery time - - unit: "H" (hours), "D" (days), "W" (weeks) - - `location`: Physical address for pickup - - `g`: Geohash for precise location - -- Constraints: - - `weight-min`: Minimum weight `[, ]` (ISO 80000-1) - - `weight-max`: Maximum weight `[, ]` - - `dim-min`: Minimum dimensions `[xx, ]` - - `dim-max`: Maximum dimensions `[xx, ]` - -- Price Calculations: - - `price-weight`: Per weight pricing `[, ]` - - `price-volume`: Per volume pricing `[, ]` - - `price-distance`: Per distance pricing `[, ]` - -```jsonc -{ - "kind": 30406, - "created_at": , - "content": "", - "tags": [ - // Required tags - ["d", ""], - ["title", ""], - ["price", "", ""], - ["country", "", "...", "..."], // Array of country codes - ["service", ""], - - // Extra details - ["carrier",""] - - // Time and Location - ["region", "", "...", "..."], // Array of region codes - ["duration", "", "", ""], // ISO 8601 duration units (H/D/W) - ["location", "
"], - ["g", ""], - - // Constraints - ["weight-min", "", ""], - ["weight-max", "", ""], - ["dim-min", "xx", ""], - ["dim-max", "xx", ""], - - // Price Calculations - ["price-weight", "", ""], - ["price-volume", "", ""], - ["price-distance", "", ""] - ] -} -``` - -#### Implementation Examples - -Local Pickup: -```jsonc -{ - "kind": 30406, - "created_at": 1703187600, - "content": "Downtown Store Pickup", - "tags": [ - ["d", "downtown-pickup"], - ["title", "Downtown Store Pickup"], - ["price", "0", "USD"], - ["country", "US"], - ["region", "US-FL"], - ["service", "pickup"], - ["location", "123 Main St, Downtown, FL"], - ["g", "dhwm9c4ws"] - ] -} -``` - -Standard Shipping: -```jsonc -{ - "kind": 30406, - "created_at": 1703187600, - "content": "Standard Regional Shipping", - "tags": [ - ["d", "standard-regional"], - ["title", "Standard Shipping"], - ["price", "5.99", "USD"], - ["country", "US"], - ["region", "US-FL"], - ["service", "standard"], - ["duration", "24", "72", "H"], // 24-72 hours delivery window - ["weight-max", "30", "kg"], - ["dim-max", "120x60x60", "cm"], - ["price-weight", "0.75", "USD", "kg"] - ] -} -``` - -#### Notes -1. Event Management: - - Create separate events for each distinct shipping option - - Each option needs a unique `d` tag identifier - - Merchants can reference third-party shipping options - -2. Shipping Rules: - - Physical pickup requires location and/or geohash - - Weight/dimension constraints use ISO 80000-1 units - -3. Client Behavior: - - Group options by service type and location - - Use geohash for distance-based sorting - - Validate package constraints before offering options - -## 4. Order Communication Flow and Payment Processing - -Order processing and status updates use [NIP-17](17.md) encrypted direct messages, with three event kinds serving different purposes: - -- Kind `14`: Regular communication between parties - - General inquiries and responses - - Order clarifications - - Subject can be order ID or empty - -- Kind `16`: Order processing and status. These messages include a `type` field that indicates the specific kind of message - - Order creation and details. `type`: 1 - - Payment requests. `type`: 2 - - Status updates. `type`: 3 - - Shipping information. `type`: 4 - -- Kind `17`: Payment receipts and verification - -Message direction is determined by the author, and `p` tag: -- Buyer → Merchant: event author is the buyer, `p` tag contains merchant's pubkey -- Merchant → Buyer: event author is the merchant, `p` tag contains buyer's pubkey - -The payment request flow can operate in two modes: -1. Direct: Merchant processes requests manually. Payment request is initiated by the merchant -2. Service-assisted: Merchant's payment service handles requests. Payment request is initiated by the buyer - -### Message Types -#### 1. Order Creation -Sent by buyer to initiate order process. - -**Content:** (Optional) Human readable order notes or special requests - -**Required tags:** -- `p`: Merchant's public key -- `subject`: Human-friendly subject line for order information -- `type`: Must be "1" to indicate order creation -- `order`: Unique identifier for the order -- `amount`: Total order amount in satoshis -- `item`: Product reference in format "30402::" with quantity. MAY appear multiple times - -**Optional tags:** -- `shipping`: Reference to shipping option "30406::" -- `address`: Shipping address details -- `email`: Customer email for contact -- `phone`: Customer phone number for contact -- Other optional tags can be added with more details from the customer - -```jsonc -{ - "kind": 16, - "tags": [ - // Required tags - ["p", ""], - ["subject", ""], - ["type", "1"], // Order creation - ["order", ""], // Unique order identifier - ["amount", ""], - - // Order items (can repeat) - ["item", "30402::", ""], - - // Shipping details - ["shipping", "30406::"], - ["address", ""], - - // Customer contact - ["email", ""], // Optional - ["phone", ""], // Optional - ], - "content": "Order notes or special requests" -} -``` -#### 2. Payment Request -There are two variants depending on payment processing mode: manual or automatic processing. After the buyer pays the payment request, they MUST send a payment receipt to the merchant using a kind:`17` dm. - -##### Manual Processing (merchant → buyer) - -In this mode, the merchant manually initiates the payment by sending a payment request to the buyer. This requires either: -- The merchant being online to process requests, or -- Having an automated system for processing payment requests, which can be run by the merchant or a service they rely on according to merchant preferences. - -Important considerations: -- Merchant shouldn't have a recommended application in their merchant's preferences -- Final price may differ from the order creation time -- Merchants decide whether to honor original prices -- Buyers can cancel orders if they don't agree with price changes - -**Content:** (Optional) Human readable payment instructions and notes - -**Required tags:** -- `p`: Buyer's public key -- `subject`: Human-friendly subject line for order payment requests -- `type`: Must be "2" to indicate payment request -- `order`: The unique order identifier from the original order -- `amount`: Total payment amount in satoshis - -**Optional tags:** -- `payment`: Payment method details, can appear multiple times for different options: - - Lightning format: `["payment", "lightning", ""]` - - Bitcoin format: `["payment", "bitcoin", ""]` - - eCash format: `["payment", "ecash", ""]` -- `expiration`: Include if the payment format has a defined expiration time - -```jsonc -{ - "kind": 16, - "tags": [ - // Required tags - ["p", ""], - ["subject", "order-payment"], - ["type", "2"], // Payment request - ["order", ""], - ["amount", ""], - - // Payment options (can include multiple) - ["payment", "lightning", ""], - ["payment", "bitcoin", ""], - ["payment", "ecash", ""], - ["expiration", ""], - ], - "content": "Payment instructions and notes" -} -``` - -##### Automatic Processing (buyer → merchant) -In this mode, the merchant MUST set valid payment options in their kind:`0` event (such as `cashu` or `lud16`). The key difference is that the buyer initiates the payment request using information provided by the merchant. For merchants using `manual` payment preference, they SHOULD use [NIP-89](89.md) to specify their preferred payment processing service, which can then automatically handle payment requests on their behalf, as described in the merchant preferences section above. - -```jsonc -{ - "kind": 16, - "tags": [ - // Required tags - ["p", ""], - ["subject", "order-payment"], - ["type", "2"], // Payment request - ["order", ""], - ["amount", ""], - - // Payment details from service - ["payment", "lightning", ""], - ["payment", "bitcoin", ""], - ["payment", "ecash", ""], - ], - "content": "Service-generated payment details" -} -``` - -#### 3. Order Status Updates -Once the merchant receives payment, they MUST update the status to "confirmed". Status updates can be sent as soon as a new order is acknowledged, initially setting the status to "pending". The "pending" status is optional and can be skipped, starting directly with "confirmed" once payment is received. - -**Content:** (Optional) Human readable status update - -**Required tags:** -- `p`: Buyer's public key -- `subject`: Human-friendly subject line for status updates -- `type`: Must be "3" to indicate status update -- `order`: The original order identifier -- `status`: Current order status: - - `pending`: Order received but awaiting payment - - `confirmed`: Payment received and verified - - `processing`: Order is being prepared - - `completed`: Order fulfilled - - `cancelled`: Order cancelled by either party - -```jsonc -{ - "kind": 16, - "tags": [ - // Required tags - ["p", ""], - ["subject", "order-info"], - ["type", "3"], // Status update - ["order", ""], - - // Status information - ["status", ""], // pending|confirmed|processing|completed|cancelled - ], - "content": "Human readable status update" -} -``` - -Buyers may also send a status update to cancel an order, ideally before the status has been set to "confirmed": - -```jsonc -{ - "kind": 16, - "tags": [ - // Required tags - ["p", ""], - ["subject", "order-info"], - ["type", "3"], // Status update - ["order", ""], - - // Status information - ["status", ""], // cancelled - ], - "content": "Human readable status update" -} -``` - -#### 4. Shipping Updates -Sent by merchant to provide delivery tracking and status information. - -**Content:** (Optional) Human readable shipping status and tracking information - -**Required tags:** -- `p`: Buyer's public key -- `subject`: Human-friendly subject line for shipping updates -- `type`: Must be "4" to indicate shipping update -- `order`: The original order identifier -- `status`: Current shipping status: - - `processing`: Order is being prepared for shipping - - `shipped`: Package has been handed to carrier - - `delivered`: Successfully delivered to destination - - `exception`: Delivery issue or delay encountered - -**Optional tags:** -- `tracking`: Carrier's tracking number -- `carrier`: Name of shipping carrier -- `eta`: Expected delivery time as unix timestamp - -```jsonc -{ - "kind": 16, - "tags": [ - // Required tags - ["p", ""], - ["subject", "shipping-info"], - ["type", "4"], // Shipping update - ["order", ""], - - // Shipping details - ["status", ""], // processing|shipped|delivered|exception - ["tracking", ""], - ["carrier", ""], - ["eta", ""], - ], - "content": "Shipping status and tracking information" -} -``` - -#### 5. General Communication -Used for any order-related messages (Kind 14) - -```jsonc -{ - "kind": 14, - "tags": [ - // Required tags - ["p", ""], - ["subject", ""], // Optional, can be empty - ], - "content": "General communication message" -} -``` - -#### 6. Payment Receipt -Sent by buyer to confirm payment completion. The receipt can include proof of payment from any payment system, including traditional fiat gateways. - -**Content:** (Optional) Human readable payment confirmation details - -**Required tags:** -- `p`: Merchant's public key -- `subject`: Human-friendly subject line for order receipt -- `order`: The original order identifier -- `payment`: Payment proof details (at least one required): - - Generic format: `["payment", "", "", ""]` - - Common examples: - - Lightning: `["payment", "lightning", "", ""]` - - Bitcoin: `["payment", "bitcoin", "
", ""]` - - eCash: `["payment", "ecash", "", ""]` - - Fiat: `["payment", "fiat", "", ""]` -- `amount`: Payment amount - -```jsonc -{ - "kind": 17, - "tags": [ - // Required tags - ["p", ""], - ["subject", "order-receipt"], - ["order", ""], - - // Payment proof (one required) - ["payment", "", "", ""], - ["payment", "lightning", "", ""], - ["payment", "bitcoin", "
", ""], - ["payment", "ecash", "", ""], - ["payment", "fiat", "", ""], - - // Metadata - ["amount", ""] - ], - "content": "Payment confirmation details" -} -``` - -#### Notes -1. Message Flow: - - Receipts should include verifiable proofs - -2. Payment Processing: - - Manual mode provides more flexibility - - Automatic mode enables faster processing and convenience - - Multiple payment options can be offered - -3. Status Tracking: - - Use consistent status codes - - Include timestamps for all updates - - Provide clear user messages - -## 5. Product Reviews (Kind: 31555) - -Product reviews follow [NIP-85](https://github.com/nostr-protocol/nips/blob/b1432b705f553bde6c4eb5fcfde8525d2913b477/85.md) and [QTS](https://habla.news/u/arkinox@arkinox.tech/DLAfzJJpQDS4vj3wSleum) guidelines with additional marketplace-specific rating criteria. Reviews provide structured feedback about products, merchants, and the overall purchase experience. - -**Content:** Detailed review text - -**Required tags:** -- `d`: Reference to product `["d", "a:30402::"]` -- `rating`: Primary rating `["rating", "", "thumb"]` - - score: 0 (negative) to 1 (positive) - - "thumb" label MUST be present as primary rating - -**Optional tags:** -- Additional Ratings: - - `rating`: Category scores `["rating", "", ""]` - - score: 0 to 1 (supports fractional values) - - category: These are optional, some standard categories may include: - - "value": Price vs quality - - "quality": Product quality - - "delivery": Shipping experience - - "communication": Merchant responsiveness - -```jsonc -{ - "kind": 31555, - "created_at": , - "tags": [ - // Required tags - ["d", "a:30402::"], - ["rating", "1", "thumb"], // Primary rating - - // Optional rating categories - ["rating", "0.8", "value"], - ["rating", "1.0", "quality"], - ["rating", "0.6", "delivery"], - ["rating", "0.9", "communication"] - ], - "content": "Detailed review text" -} -``` - -#### Rating Calculation -The final score combines the primary "thumb" rating (50% weight) with additional category ratings (50% combined weight): - -``` -Total Score = (Thumb × 0.5) + (0.5 × (∑(Category Ratings) ÷ Number of Categories)) -``` - -#### Notes -1. Rating System: - - Primary thumb rating is required, it determines the overall an overall rating. - - Additional categories are optional - - Scores support fractional values between 0-1 - - Custom categories can be added - -## 6. Implementation Guidelines - -### Payment Flow Details - -#### Payment Preferences -Merchants can specify their payment preferences in their kind:`0` event using the `payment_preference` tag: -``` -["payment_preference", ""] -``` - -If not present, it defaults to `manual`. The preferences are processed in this order of complexity: -1. Manual (default): Merchant provides payment requests directly -2. eCash: Ideally the merchant has a kind `10019` event to know what mint they prefer. - - If the `10019` event is not present, payment can be made by sending the token embedded directly in the order receipt message from a previously set mint (whether default or user selected); otherwise, the merchant's preferred mint SHOULD be used. -3. Lightning: Requires `lud16` or related lightning fields in kind `0` - -#### Payment Processing Scenarios - -1. **Manual Processing** - - Merchant initiates payment request - - Used when no application is recommended or automatic preferences are set - - Merchant must manually send payment requests - - Buyer waits for merchant's payment instructions - - Merchants can have their own service that listens for new orders and then sends the payment request - -2. **Automatic Processing** - - Buyer initiates payment request - - Requires a valid `payment_preference` in merchant's kind `0` - - Service-Based Processing processing if `payment_preference` is `manual` and the merchant have a recommended application - - Supports automatic payments via: - - eCash tokens (locked to merchant's pubkey) - - Lightning (using merchant's `lud16` address) - -3. **Service-Based Processing** - - Merchant MUST set `payment_preference` to `manual` - - Merchant SHOULD have a [NIP-89](89.md) kind `31989` event recommending their preferred service - - Buyers can immediately request payment using the service - - Service handles payment details and completion monitoring - -For all scenarios: -- Buyer MUST send payment receipt after completion -- Message direction is determined by `p` tag -- Merchant's [NIP-89](89.md) application preferences SHOULD be respected - -### Marketplace Application Role -Marketplace applications can optionally facilitate the order processing and payment request by: - -1. Generating payment requests based on merchant preferences when buyers initiate orders -2. Verifying payments and generating receipts automatically by prompting the buyer to sign the event -3. Helping merchants managing inventory and order status updates -4. Coordinating shipping information -5. Price calculations - -This provides a smoother user experience while maintaining the ability for direct merchant-buyer communication as a fallback mechanism. - -### Notes and Considerations - -1. Tags are used for all structured, machine-readable data to facilitate easier parsing and filtering. - -2. The content field is reserved for human-readable messages and additional information that doesn't require machine parsing. - -3. All timestamps should be in Unix format (seconds since epoch). - -4. Order IDs should be used consistently across all related messages. - -5. Message threading should follow [NIP-10](10.md) conventions when replies are needed. +The normative proposal is [XX.md](./XX.md). It defines auction listings, funded +bid chains, deterministic winner selection, refunds, and promotion into an +order. The proposal is a draft and may change before NIP assignment and +acceptance. diff --git a/XX.md b/XX.md index c3e37fb..ef142c4 100644 --- a/XX.md +++ b/XX.md @@ -455,7 +455,7 @@ even if the proof otherwise exists. ["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...\"}}}}" + "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\":\"1000\"}},\"paths\":[]}},\"params\":{\"txHash\":\"0x...\"}}}}" } ``` @@ -528,10 +528,10 @@ 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. +Cashu bearer tokens, proofs, serialized inputs, output secrets, swap previews, +or pre-signatures MUST NOT be published in plaintext on public relays. The +complete Cashu payment proof MUST be sealed, and its disclosure key MUST be +wrapped only for the participants that need to validate or spend it. ## Payment Acknowledgment and Rejection @@ -640,10 +640,11 @@ event explicitly defines a cancellation policy. 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. +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`. If the method has an explicit refund +percentage, it MUST be the integer `100`; a smaller or caller-selectable +percentage is invalid. 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 @@ -668,6 +669,20 @@ Example tags: ] ``` +The driver-returned settlement proof MAY contain spend authority and therefore +MUST be treated as confidential by default. A `kind:32125` settlement carrying +such a proof MUST put the complete payment proof object in a `sealed:v1` +envelope in the top-level `proof` content field and attach matching +`payment_proof_key` tags. For `auction_refund`, disclosure MUST be limited to +the refunding arbiter and the bid buyer. The public `data` object MAY contain a +SHA-256 proof commitment and a non-secret operation receipt, but MUST NOT +contain clear proof params, Cashu proofs, swap output secrets, or recovery +material. + +Financial refund/promotion operations and their durable operation receipts MUST +complete before settlement publication. A retry MUST reuse the same operation +identifier and the exact previously signed events. + ### Winner Promotion into an Order For the selected winning chain, the arbiter MUST: @@ -675,20 +690,23 @@ 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, +2. 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, +3. 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 +4. 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 +5. Publish a `kind:32124` payment acknowledgment for each promoted order payment. +6. Publish the terminal `kind:1024` auction complete event only after every + required settlement, promoted order, payment, and acknowledgment above has + been durably published. Implementations MAY sign and journal this event + earlier so dependent events can reference its id, but MUST publish it last. The promoted order SHOULD copy participant proofs from the winning bid-chain head. Promoted order payments SHOULD preserve their own payment amounts, and @@ -710,8 +728,8 @@ 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. +commitments and non-secret driver metadata; buyer signatures, witnesses, +serialized proofs, and swap previews remain inside the whole-proof seal. 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 @@ -719,6 +737,74 @@ 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. +### Cashu 100% Refund Authorization + +A Cashu auction bid that supports an arbiter-executed refund MUST include the +following `refundArgs` inside its confidential payment-proof params: + +```jsonc +{ + "version": 1, + "type": "cashu:p2pk-auction-refund-v1", + "refundPercent": 100, + "source": { + "tradeId": "", + "settlementId": "", + "policyType": "cashu:p2pk-auction-v1", + "mint": "https://mint.example", + "unit": "sat", + "sourceValue": "", + "inputFee": "", + "keysetId": "" + }, + "target": { + "policyType": "cashu:p2pk-refund-v1", + "buyerPubkey": "", + "buyerOutputValue": "" + }, + "message": "", + "messageHash": "0x", + "signerPubkey": "", + "signature": "", + "swap": { + "version": 1, + "amount": "", + "fees": "", + "keysetId": "", + "inputs": [""], + "sendOutputs": [{ "...": "" }], + "keepOutputs": [], + "unselectedProofs": [] + } +} +``` + +`message` MUST be the UTF-8 JSON serialization, in the field order shown, of +exactly `{version,type,refundPercent,source,target,swap}` with no whitespace or +additional fields. `messageHash` MUST be SHA-256 of those exact bytes and the +BIP-340 signature MUST verify against both `signerPubkey` and +`target.buyerPubkey`. The serialized swap inputs MUST also contain the buyer's +valid Cashu `SIG_ALL` witness over the exact authorized output set. + +Before any mint request, the refunding driver MUST verify the outer signature, +the `SIG_ALL` witness, source proof equality, mint, unit, keyset, policy tags, +buyer target, and these value equations: + +```text +sourceValue = buyerOutputValue + inputFee +swap.amount = buyerOutputValue +swap.fees = inputFee +``` + +The refund output MUST be an independently spendable buyer-only P2PK proof with +policy `cashu:p2pk-refund-v1` and tags binding the original trade and settlement. +On retry after ambiguous mint completion, the driver MUST use the same durable +operation identifier and restore the exact pre-authorized outputs through +NUT-09; it MUST NOT generate replacement secrets or a different output set. +The returned proof MUST be whole-proof sealed in the `auction_refund` +settlement event as specified above. Public receipt evidence SHOULD expose only +`sourceValue`, `inputFee`, `buyerOutputValue`, and the source message hash. + ## EVM Arbiter-Canonical Profile In a mixed-method auction, an EVM contract SHOULD be treated as a lockbox, not @@ -810,11 +896,15 @@ 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. +2. If amounts are equal, the chain whose head bid has the smallest + `created_at` wins. +3. If head-bid timestamps are equal, the lexicographically smallest lowercase + hexadecimal head bid event id wins. + +These rules are normative. Relay receipt order, payment-ack receipt order, and +local clocks MUST NOT be used as alternate tie-breakers. For example, equal +totals with `(created_at,id)` values `(100,"bb...")`, `(100,"aa...")`, and +`(101,"00...")` are ordered `aa...`, `bb...`, `00...`. ## Notes From b18503a0f1c24fa1b017948ab05bdbc93728c55b Mon Sep 17 00:00:00 2001 From: pats2sats <5664617+pats2sats@users.noreply.github.com> Date: Sun, 9 Aug 2026 10:23:00 -0600 Subject: [PATCH 3/3] docs: bind Cashu auction keyset horizon --- XX.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/XX.md b/XX.md index ef142c4..e49d368 100644 --- a/XX.md +++ b/XX.md @@ -737,6 +737,34 @@ 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. +An auction-capable mint MUST advertise NUT-11 and NUT-09. Before accepting a +bid, the implementation MUST also have an explicit operator policy committing +one output keyset id to remain active through a stated Unix timestamp. It MUST +verify that keyset is currently active and that the committed timestamp covers +the bid locktime. NUT-02 `final_expiry` is a final redemption deadline, not an +active-through promise; a missing or null value MUST NOT be promoted into one. +The exact keyset id and operator horizon are buyer-signed in both settlement +paths and MUST be rechecked immediately before the mint request. + +The promotion `recycleArgs.source` object MUST contain: + +```json +{ + "tradeId": "", + "settlementId": "", + "policyType": "cashu:p2pk-auction-v1", + "mint": "https://mint.example", + "unit": "sat", + "outputKeysetId": "", + "outputKeysetActiveUntil": 1800000000 +} +``` + +Those fields are part of the canonical buyer-signed promotion message together +with the exact target and swap. The mint/unit, active keyset, operator horizon, +source proofs, target policy, amounts, and `SIG_ALL` output commitment MUST all +match before promotion. + ### Cashu 100% Refund Authorization A Cashu auction bid that supports an arbiter-executed refund MUST include the @@ -755,7 +783,8 @@ following `refundArgs` inside its confidential payment-proof params: "unit": "sat", "sourceValue": "", "inputFee": "", - "keysetId": "" + "keysetId": "", + "keysetActiveUntil": 1800000000 }, "target": { "policyType": "cashu:p2pk-refund-v1", @@ -788,7 +817,7 @@ valid Cashu `SIG_ALL` witness over the exact authorized output set. Before any mint request, the refunding driver MUST verify the outer signature, the `SIG_ALL` witness, source proof equality, mint, unit, keyset, policy tags, -buyer target, and these value equations: +buyer target, configured active-through horizon, and these value equations: ```text sourceValue = buyerOutputValue + inputFee