NIP-XX: Dutch Auctions & Reverse Procurement
NIP-XX: Dutch Auctions & Reverse Procurement
draft optional
This NIP defines time-based price discovery mechanisms for Nostr: descending (Dutch) auctions where a seller's price ticks down from a start price toward a floor, and ascending (reverse-Dutch/procurement) auctions where a buyer's offered price ticks up toward a ceiling. In both modes the price schedule is published once as a signed event; any client computes the current price deterministically from the clock. First valid commit clears the auction.
Motivation
NIP-15 covers fixed-price stalls and traditional ascending bid auctions (kind 30020/1021/1022). This NIP adds a fundamentally different primitive: deterministic, time-based price curves where no live bidding occurs. The seller/buyer publishes a schedule; the first counterparty to commit at the current price wins. This is settlement-light (one payment, one winner), manipulation-resistant (no sniping, no gas wars), and requires no server to referee bids.
Event Kinds
kind 30402 — Auction Listing (addressable, replaceable)
An addressable event that defines a complete auction schedule.
Tags (required):
| Tag | Value | Description |
|---|---|---|
d | <string> | Unique auction identifier |
title | <string> | Auction title |
mode | descending \ | ascending |
start_price | <int msats> | Starting price in millisatoshis |
end_price | <int msats> | Floor (descending) or ceiling (ascending) in millisatoshis |
start_at | <int unix> | Auction start timestamp |
end_at | <int unix> | Auction end timestamp |
curve | linear \ | stepped \ |
settle | lightning \ | onchain \ |
alt | <string> | NIP-31 human-readable description |
Tags (optional):
| Tag | Value | Description |
|---|---|---|
summary | <string> | Short description |
image | <url> | Cover image URL |
t | <string> | Category tag (multiple allowed) |
step | <int seconds> | Step interval for stepped curves |
step_amount | <int msats> | Price change per step |
trust | reputation \ | hold-invoice \ |
p | <hex pubkey> | Arbiter pubkey (for escrow-2of3) |
requirements | <string> | Requirements for ascending (provider eligibility) |
min_reputation | <int> | Minimum cleared deals for eligibility |
tribe | <string> | Community/tribe scope identifier |
currency | <string> | Display currency code (default: sat) |
Content: Human-readable description of the auction item or commission.
kind 1402 — Clearing Commit (regular)
Published by the winner to clear the auction. For descending auctions this is the buyer; for ascending this is the accepting provider.
Tags:
| Tag | Value | Description |
|---|---|---|
a | 30402:<pubkey>:<d-tag> | Reference to the auction listing |
p | <hex pubkey> | Counterparty (auction creator) |
price | <int msats> | Computed price at commit time |
commit_at | <int unix> | Timestamp of commitment |
bolt11 | <string> | Lightning invoice (if applicable) |
alt | <string> | NIP-31 fallback |
Content: Optional message from the committer.
Validation: A commit is valid if and only if price matches the deterministic price function evaluated at commit_at within a ±30 second clock-skew tolerance. First valid commit (by created_at) wins; later commits for the same auction are void.
kind 1403 — Deal Acknowledgement / State (regular)
Published by the auction creator to acknowledge and track deal state transitions.
Tags:
| Tag | Value | Description |
|---|---|---|
a | 30402:<pubkey>:<d-tag> | Reference to the auction listing |
e | <event id> | Reference to the clearing commit (kind 1402) |
p | <hex pubkey> | Counterparty |
status | committed \ | funded \ |
alt | <string> | NIP-31 fallback |
Content: Optional status message or notes.
kind 1404 — Cancellation / No-clear (regular)
Published by the auction creator to cancel an auction or mark it as expired without clearing.
Tags:
| Tag | Value | Description |
|---|---|---|
a | 30402:<pubkey>:<d-tag> | Reference to the auction listing |
reason | cancelled \ | expired \ |
alt | <string> | NIP-31 fallback |
Content: Optional explanation.
kind 1986 — Deal Review (regular)
Published by either party after a deal is released. Provides on-graph reputation.
Tags:
| Tag | Value | Description |
|---|---|---|
p | <hex pubkey> | The reviewed counterparty |
a | 30402:<pubkey>:<d-tag> | Reference to the auction listing |
e | <event id> | Reference to the clearing commit |
rating | 1 \ | 2 \ |
role | buyer \ | provider |
alt | <string> | NIP-31 fallback |
Content: Review text (optional).
Deterministic Price Function (normative)
All clients MUST compute the current price using the same pure function. Given an auction's parameters, the current price at time now is:
Linear Curve
elapsed = clamp(now - start_at, 0, end_at - start_at)
progress = elapsed / (end_at - start_at)
For descending:
current_price = start_price - progress * (start_price - end_price)
For ascending:
current_price = start_price + progress * (end_price - start_price)Stepped Curve
elapsed = clamp(now - start_at, 0, end_at - start_at)
steps_elapsed = floor(elapsed / step)
For descending:
current_price = max(end_price, start_price - steps_elapsed * step_amount)
For ascending:
current_price = min(end_price, start_price + steps_elapsed * step_amount)Exponential Curve
elapsed = clamp(now - start_at, 0, end_at - start_at)
progress = elapsed / (end_at - start_at)
For descending:
current_price = end_price + (start_price - end_price) * (1 - progress)^2
For ascending:
current_price = start_price + (end_price - start_price) * progress^2All prices are rounded to the nearest integer (millisatoshi). After end_at, the price rests at end_price.
Protocol Invariants
- Deterministic price — pure function of signed schedule + time. Clients reject commits whose price doesn't match (±30s tolerance).
- First-commit-wins — earliest validly-signed kind 1402 whose price matches clears the auction.
- Reputation on-graph — cleared-deal history and reviews as public Nostr events. No proprietary scores.
- No sealed bids — only settlement details (invoices) may use NIP-44 encryption where needed.
- Non-custodial — funds flow directly between parties or through named arbiter keys. The marketplace client never custodies.
Clock Skew Tolerance
Commits are valid if the claimed commit_at is within ±30 seconds of the relay's received timestamp (created_at). Clients SHOULD use NTP-synchronized clocks and SHOULD warn users when their system clock appears to be significantly off.