# Nostr Auction Protocols Specification **Version:** 1.0.0 **Status:** Draft **Author:** ORLY Development Team ## Table of Contents 1. [Overview](#overview) 2. [Auction Types](#auction-types) 3. [Payment Mechanism: Cashu Proof of Funds](#payment-mechanism-cashu-proof-of-funds) 4. [Core Protocol Architecture](#core-protocol-architecture) 5. [Event Kinds](#event-kinds) 6. [Auction Type Implementations](#auction-type-implementations) 7. [Security Considerations](#security-considerations) 8. [Implementation Guide](#implementation-guide) --- ## Overview This specification defines a suite of auction protocols built on Nostr, leveraging the relay network as both a database and real-time message broker. The protocols support multiple auction formats with cryptographic proof of funds using Cashu ecash tokens. ### Key Features - **Decentralized**: No central auction server required, relays coordinate all activity - **Privacy-Preserving**: Cashu ecash provides blind payment proofs - **Real-Time**: Ephemeral events deliver instant bid updates - **Trustless**: Cryptographic proofs replace escrow requirements - **Flexible**: Multiple auction formats for different use cases ### Architecture Principles 1. **Regular Events (replaceable)**: Auction listings, final results (stored in relay DB) 2. **Ephemeral Events**: Bid updates, status changes (real-time, not stored) 3. **Encrypted Events**: Payment proofs, seller notifications (privacy) 4. **Cashu Proofs**: Locked ecash tokens prove bidder capacity --- ## Auction Types ### 1. English Auction (Ascending Price) **Description**: The classic open-outcry auction where bidders compete by placing increasingly higher bids. The highest bidder when the timer expires wins. **Use Cases**: - Art and collectibles - Real estate - High-value items where price discovery is important - Charity fundraising **Characteristics**: - Open bidding (all bids visible) - Price increases with each bid - Time extensions on last-minute bids (anti-sniping) - Winner pays their bid amount ### 2. Dutch Auction (Descending Price) **Description**: Price starts high and decreases at regular intervals until a buyer accepts the current price or reserve is reached. **Use Cases**: - Perishable goods (flowers, fish) - Inventory liquidation - IPO share allocation - Bulk commodity sales **Characteristics**: - Price descends over time - First bidder to accept wins - Fast execution - Seller sets starting price and decrement rate ### 3. Sealed-Bid First-Price Auction **Description**: Bidders submit secret bids, highest bid wins and pays their bid amount. **Use Cases**: - Government contracts - Real estate sales - Private asset sales - Situations requiring confidentiality **Characteristics**: - Bids remain secret until deadline - Single round - Winner pays exactly their bid - Strategic underbidding incentivized ### 4. Vickrey Auction (Sealed-Bid Second-Price) **Description**: Sealed bids where highest bidder wins but pays the second-highest bid amount. **Use Cases**: - Domain name sales - Advertising space (similar to Google AdWords) - Academic research applications - Truthful bidding scenarios **Characteristics**: - Bids remain secret until deadline - Winner pays second-highest price - Incentivizes truthful bidding - Requires honest bid revelation ### 5. Reverse Auction **Description**: Buyers post requirements, sellers compete by offering decreasing prices. **Use Cases**: - Procurement and sourcing - Service contracts - Freelance project bidding - B2B purchasing **Characteristics**: - Seller competition (not buyer) - Price decreases over time - Buyer specifies requirements - Lowest qualified bid wins ### 6. Candle Auction **Description**: Auction with random, unpredictable end time. Historically, auction ended when a candle flame went out. **Use Cases**: - NFT drops - Anti-sniping scenarios - Parachain slot auctions (Polkadot) - High-stakes competitive bidding **Characteristics**: - Random termination within window - Prevents last-second sniping - Creates strategic uncertainty - Fair time allocation for all bidders ### 7. All-Pay Auction **Description**: All bidders pay their bid regardless of winning, highest bidder wins the item. **Use Cases**: - Political lobbying models - Research & development competitions - Patent races - Charity fundraisers with prizes **Characteristics**: - All participants pay - Highest bidder gets item - High strategic complexity - Risk-reward imbalance --- ## Payment Mechanism: Cashu Proof of Funds ### Overview The auction system uses **Cashu ecash** for trustless proof of funds. Bidders lock tokens with cryptographic proofs without revealing actual funds until payment is required. ### Proof of Funds Flow ``` ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌────────┐ │ Bidder │ │ Mint │ │ Auction │ │ Seller │ └────┬────┘ └────┬─────┘ └────┬────┘ └───┬────┘ │ │ │ │ │ 1. Request Token │ │ │ ├──────────────────>│ │ │ │ │ │ │ │ 2. Blinded Token │ │ │ │<──────────────────┤ │ │ │ │ │ │ │ 3. Generate Proof │ │ │ │ (DLEQ Proof) │ │ │ │ │ │ │ │ 4. Submit Bid + Encrypted Proof │ │ ├───────────────────────────────────────>│ │ │ │ │ │ │ │ 5. Verify Proof │ │ │ │<───────────────────┤ │ │ │ │ │ │ │ 6. Proof Valid │ │ │ ├───────────────────>│ │ │ │ │ │ │ │ │ 7. Bid Accepted │ │<───────────────────────────────────────┤ │ │ │ │ │ │ │ │ [AUCTION ENDS] │ │ │ │ │ │ │ │ 8. Winner Notice │ │ │ ├──────────────────>│ │ │ │ │ │ 9. Request Unlock │ │ │ │<──────────────────────────────────────────────────────────┤ │ │ │ │ │ 10. Provide Token │ │ │ ├──────────────────────────────────────────────────────────>│ │ │ │ │ │ │ 11. Redeem Token │ │ │ │<───────────────────────────────────────┤ │ │ │ │ │ │ 12. Payment │ │ │ ├───────────────────────────────────────>│ ``` ### Cashu Token Structure ```json { "token": { "mint": "https://mint.example.com", "proofs": [ { "id": "00882760bfa2eb41", "amount": 1000, "secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9", "C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6e3f114d0" } ] } } ``` ### DLEQ (Discrete Log Equality) Proof Proves knowledge of secret without revealing it: ```json { "dleq_proof": { "e": "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9", "s": "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9", "r": "a6d13fcd7a18442e6076f5e1e7c887ad5de40a019e7f1ae9bae5feb9046acf2f" } } ``` ### Proof Verification Steps 1. **Bidder generates blinded token** from Cashu mint 2. **Creates DLEQ proof** showing control without revealing secret 3. **Encrypts proof** using auction server's public key (NIP-04 or NIP-44) 4. **Auction server verifies** with mint that proof is valid and amount matches bid 5. **Token remains locked** until winner is determined 6. **Winner provides unlock** (reveals secret) to complete payment 7. **Seller redeems** token from mint --- ## Core Protocol Architecture ### Event Types | Kind | Type | Purpose | Storage | |------|------|---------|---------| | 30020 | Replaceable | Auction Listing | Persistent | | 30021 | Replaceable | Auction State Update | Persistent | | 30022 | Replaceable | Final Auction Result | Persistent | | 1020 | Regular | Bid Submission | Persistent (sealed) | | 20020 | Ephemeral | Bid Update Notification | Not stored | | 20021 | Ephemeral | Auction Status Change | Not stored | | 4 | Encrypted | Payment Proof | Persistent | | 14 | Encrypted | Seller Notification | Persistent | ### Tag Conventions - `d`: Auction unique identifier (for replaceable events) - `auction_type`: english, dutch, sealed_first, vickrey, reverse, candle, all_pay - `mint`: Cashu mint URL - `amount`: Starting/current price (sats) - `reserve`: Reserve price (minimum acceptable) - `currency`: unit of account (sat, btc, usd) - `start_time`: Unix timestamp - `end_time`: Unix timestamp (or duration for dutch) - `item`: Item identifier or description hash - `status`: active, ended, cancelled, settled - `winner`: npub of winning bidder - `bid_amount`: Final winning bid --- ## Event Kinds ### Kind 30020: Auction Listing **Purpose**: Create new auction listing **Replaceable**: Yes (by `d` tag) **Storage**: Persistent ```json { "kind": 30020, "pubkey": "", "created_at": 1234567890, "tags": [ ["d", ""], ["auction_type", "english"], ["title", "Rare Satoshi Nakamoto Signature"], ["description", "First known digital signature by Satoshi"], ["image", "https://example.com/image.jpg"], ["mint", "https://mint.example.com"], ["amount", "1000000"], ["reserve", "500000"], ["currency", "sat"], ["start_time", "1234567890"], ["end_time", "1234657890"], ["increment", "10000"], ["extension_time", "300"], ["status", "pending"] ], "content": "Detailed item description and auction terms..." } ``` ### Kind 1020: Bid Submission **Purpose**: Submit a bid **Replaceable**: No **Storage**: Persistent (for sealed auctions, content encrypted) ```json { "kind": 1020, "pubkey": "", "created_at": 1234567890, "tags": [ ["e", ""], ["d", ""], ["auction_type", "english"], ["amount", "1100000"], ["proof_hash", ""] ], "content": "" } ``` For **sealed-bid auctions**, the amount is encrypted: ```json { "kind": 1020, "pubkey": "", "created_at": 1234567890, "tags": [ ["e", ""], ["d", ""], ["auction_type", "sealed_first"], ["proof_hash", ""] ], "content": "" } ``` ### Kind 4: Encrypted Payment Proof **Purpose**: Send Cashu proof to auction server **Encrypted**: Yes (NIP-04 or NIP-44) **Storage**: Persistent ```json { "kind": 4, "pubkey": "", "created_at": 1234567890, "tags": [ ["p", ""], ["e", ""], ["d", ""] ], "content": "" } ``` Decrypted content: ```json { "cashu_token": { "mint": "https://mint.example.com", "proofs": [...] }, "dleq_proof": { "e": "...", "s": "...", "r": "..." }, "amount": 1100000 } ``` ### Kind 20020: Bid Update Notification (Ephemeral) **Purpose**: Real-time bid update broadcast **Ephemeral**: Yes (not stored by relay) **Storage**: None ```json { "kind": 20020, "pubkey": "", "created_at": 1234567890, "tags": [ ["e", ""], ["d", ""], ["current_bid", "1100000"], ["bid_count", "15"], ["time_remaining", "3600"], ["leading_bidder_hash", ""] ], "content": "New bid received" } ``` ### Kind 30021: Auction State Update **Purpose**: Update auction status (price changes, extensions) **Replaceable**: Yes (by `d` tag) **Storage**: Persistent ```json { "kind": 30021, "pubkey": "", "created_at": 1234567890, "tags": [ ["d", ""], ["status", "active"], ["current_price", "1100000"], ["bid_count", "15"], ["end_time", "1234657890"], ["last_bid_time", "1234567800"] ], "content": "" } ``` ### Kind 30022: Final Auction Result **Purpose**: Record final outcome **Replaceable**: Yes (by `d` tag) **Storage**: Persistent ```json { "kind": 30022, "pubkey": "", "created_at": 1234567890, "tags": [ ["d", ""], ["status", "ended"], ["winner", ""], ["winning_bid", "1100000"], ["bid_count", "15"], ["settlement_status", "pending"] ], "content": "Auction ended successfully" } ``` ### Kind 14: Encrypted Seller Notification **Purpose**: Notify seller of payment proof **Encrypted**: Yes **Storage**: Persistent ```json { "kind": 14, "pubkey": "", "created_at": 1234567890, "tags": [ ["p", ""], ["d", ""] ], "content": "" } ``` Decrypted content: ```json { "auction_id": "", "winner": "", "amount": 1100000, "cashu_token_preview": { "mint": "https://mint.example.com", "amount": 1100000, "proof_count": 3 }, "unlock_request_method": "Request unlock via DM to winner" } ``` --- ## Auction Type Implementations ### 1. English Auction Implementation #### Setup Phase ```json { "kind": 30020, "tags": [ ["d", "eng_001"], ["auction_type", "english"], ["amount", "100000"], ["reserve", "80000"], ["increment", "5000"], ["start_time", "1234567890"], ["end_time", "1234657890"], ["extension_time", "300"], ["auto_extend", "true"] ] } ``` #### Bidding Phase 1. **Client subscribes** to auction updates: ```json ["REQ", "auction_001", { "kinds": [20020, 30021], "tags": [["d", ["eng_001"]]] }] ``` 2. **Bidder places bid**: - Creates Kind 1020 bid event - Sends Kind 4 encrypted payment proof - Server validates: `bid >= current_price + increment` 3. **Server broadcasts** Kind 20020 ephemeral update: ```json { "kind": 20020, "tags": [ ["d", "eng_001"], ["current_bid", "105000"], ["bid_count", "3"], ["time_remaining", "7200"] ] } ``` 4. **Anti-sniping**: If bid placed in last 5 minutes, extend by `extension_time` #### Completion Phase 1. **Timer expires** or no new bids for extension period 2. **Server publishes** Kind 30022 final result 3. **Server sends** Kind 14 encrypted notification to seller 4. **Seller requests unlock** via DM to winner 5. **Winner provides** full Cashu token secret 6. **Seller redeems** token from mint #### Example Flow ``` Time Event ------ -------------------------------------------------- T+0 Seller creates auction (Kind 30020) T+10 Bidder A bids 105k (Kind 1020 + Kind 4 proof) T+11 Server broadcasts update (Kind 20020) T+20 Bidder B bids 110k T+3595 Bidder C bids 115k (5 min before end) T+3600 Timer extends to T+3900 T+3900 Auction ends (Kind 30022) T+3901 Seller gets notification (Kind 14) T+3920 Seller DMs winner for unlock T+3925 Winner provides token secret T+3930 Seller redeems 115k sats ``` --- ### 2. Dutch Auction Implementation #### Setup Phase ```json { "kind": 30020, "tags": [ ["d", "dutch_001"], ["auction_type", "dutch"], ["start_price", "500000"], ["reserve_price", "100000"], ["decrement", "10000"], ["interval", "60"], ["start_time", "1234567890"], ["max_duration", "3600"] ] } ``` #### Price Descent Server publishes Kind 30021 every `interval` seconds: ```json { "kind": 30021, "tags": [ ["d", "dutch_001"], ["current_price", "450000"], ["time_elapsed", "300"], ["status", "active"] ] } ``` Price formula: `current_price = max(start_price - (decrement * intervals_elapsed), reserve_price)` #### Acceptance Phase 1. **First buyer** to submit valid bid wins at current price 2. **Bid validation**: - Payment proof amount >= current_price - No prior accepted bid exists 3. **Immediate settlement**: ```json { "kind": 30022, "tags": [ ["d", "dutch_001"], ["status", "ended"], ["winner", ""], ["winning_bid", "450000"], ["final_price", "450000"] ] } ``` #### Example Flow ``` Time Price Event ------ ------- ------------------------------------------ T+0 500000 Auction starts T+60 490000 Price drops (Kind 30021) T+120 480000 Price drops T+180 470000 Price drops T+240 460000 Price drops T+300 450000 Buyer accepts! (Kind 1020 + proof) T+301 450000 Auction ends (Kind 30022) ``` --- ### 3. Sealed-Bid First-Price Auction #### Setup Phase ```json { "kind": 30020, "tags": [ ["d", "sealed_001"], ["auction_type", "sealed_first"], ["reserve", "100000"], ["start_time", "1234567890"], ["end_time", "1234657890"], ["reveal_time", "1234668890"] ] } ``` #### Sealed Bidding Phase Bids are encrypted using auction server's public key: ```json { "kind": 1020, "tags": [ ["d", "sealed_001"], ["auction_type", "sealed_first"], ["proof_hash", ""] ], "content": "" } ``` Encrypted content structure: ```json { "amount": 250000, "bid_secret": "", "timestamp": 1234567900 } ``` #### Reveal Phase After `end_time`, server decrypts all bids and publishes results: ```json { "kind": 30022, "tags": [ ["d", "sealed_001"], ["status", "ended"], ["winner", ""], ["winning_bid", "250000"], ["second_bid", "230000"], ["bid_count", "12"] ] } ``` Optional: Publish proof of all bids (privacy-preserving): ```json { "kind": 30023, "tags": [ ["d", "sealed_001"], ["bid_proof", "", "250000"], ["bid_proof", "", "230000"], ["bid_proof", "", "210000"] ], "content": "Bid verification data" } ``` #### Security Considerations - **Commitment scheme**: `proof_hash = SHA256(amount || bid_secret || bidder_pubkey)` - **Prevents bid manipulation**: Hash committed before reveal - **Verifiability**: Bidders can verify their bid was included --- ### 4. Vickrey Auction (Second-Price Sealed-Bid) #### Setup Phase ```json { "kind": 30020, "tags": [ ["d", "vickrey_001"], ["auction_type", "vickrey"], ["reserve", "100000"], ["start_time", "1234567890"], ["end_time", "1234657890"] ] } ``` #### Bidding and Reveal Identical to sealed-bid first-price, but winner pays **second-highest bid**: ```json { "kind": 30022, "tags": [ ["d", "vickrey_001"], ["winner", ""], ["winning_bid", "250000"], ["payment_amount", "230000"], ["bid_count", "12"] ] } ``` #### Payment Adjustment Winner proves overpayment and receives refund: 1. Winner locked 250k in Cashu proof 2. Winner provides unlock for 230k portion 3. Winner retains 20k difference Alternative: Winner provides two separate proofs (230k + 20k) and only unlocks first. --- ### 5. Reverse Auction Implementation #### Setup Phase (Buyer Posts Requirement) ```json { "kind": 30020, "tags": [ ["d", "reverse_001"], ["auction_type", "reverse"], ["max_budget", "500000"], ["start_time", "1234567890"], ["end_time", "1234657890"], ["requirements", ""] ], "content": "Need custom Nostr client with features X, Y, Z" } ``` #### Seller Bids (Decreasing Prices) ```json { "kind": 1020, "tags": [ ["d", "reverse_001"], ["amount", "450000"], ["delivery_time", "30"], ["portfolio", "https://example.com/portfolio"] ], "content": "Proposal details..." } ``` #### Winner Selection Buyer evaluates and selects winning proposal: ```json { "kind": 30022, "tags": [ ["d", "reverse_001"], ["winner", ""], ["winning_bid", "400000"], ["selection_criteria", "best_value"] ] } ``` --- ### 6. Candle Auction Implementation #### Setup Phase ```json { "kind": 30020, "tags": [ ["d", "candle_001"], ["auction_type", "candle"], ["amount", "100000"], ["increment", "5000"], ["start_time", "1234567890"], ["end_window_start", "1234657890"], ["end_window_end", "1234667890"], ["termination_method", "vrf"] ] } ``` #### Random Termination Server uses **Verifiable Random Function (VRF)** to determine end time: ```json { "kind": 30022, "tags": [ ["d", "candle_001"], ["winner", ""], ["winning_bid", "145000"], ["termination_time", "1234662500"], ["vrf_proof", ""], ["vrf_seed", ""] ] } ``` #### VRF Termination 1. **Public seed** announced at auction start 2. **VRF evaluated** using server's private key + seed 3. **Random timestamp** generated within [start, end] window 4. **Winning bid** is highest valid bid before random timestamp 5. **VRF proof** allows anyone to verify randomness #### Example ``` Time Bid Status --------- ------ ---------------------------------- 1234657890 100k Window opens 1234659000 105k Bid A 1234660000 110k Bid B 1234662000 115k Bid C 1234662500 --- [Random termination time via VRF] 1234664000 120k Bid D (too late!) 1234667890 --- Window closes Winner: Bid C (115k) - last valid bid before VRF time ``` --- ### 7. All-Pay Auction Implementation #### Setup Phase ```json { "kind": 30020, "tags": [ ["d", "allpay_001"], ["auction_type", "all_pay"], ["prize_value", "1000000"], ["min_bid", "10000"], ["start_time", "1234567890"], ["end_time", "1234657890"] ] } ``` #### Bidding Phase All bidders **immediately pay** when bidding: ```json { "kind": 1020, "tags": [ ["d", "allpay_001"], ["amount", "50000"], ["payment_status", "paid"] ] } ``` Payment proof required **before bid acceptance**: ```json { "kind": 4, "tags": [ ["p", ""], ["d", "allpay_001"] ], "content": "" } ``` #### Settlement 1. **All bidders unlock** Cashu tokens → seller/beneficiary receives all payments 2. **Highest bidder** receives the prize 3. **Total collected** = sum of all bids ```json { "kind": 30022, "tags": [ ["d", "allpay_001"], ["winner", ""], ["winning_bid", "250000"], ["total_collected", "1500000"], ["bid_count", "25"] ] } ``` #### Use Case Example: Charity Auction - Prize: Signed book worth $100 - Bids: 30 people bid $10-$50 each - Total raised: $800 - Charity receives all $800 - Highest bidder gets the book --- ## Security Considerations ### 1. Payment Proof Verification **Threat**: Fake payment proofs **Mitigation**: - Auction server verifies DLEQ proofs with mint - Cashu token amounts must match bid amounts - Proofs must be fresh (timestamp validation) - Each proof can only be used once (double-spend prevention) ### 2. Bid Manipulation **Threat**: Sybil attacks, shill bidding **Mitigation**: - Payment proofs required for all bids (economic cost) - Minimum bid increments prevent spam - WoT (Web of Trust) filters for trusted participants - Bid history analysis for pattern detection ### 3. Sealed-Bid Privacy **Threat**: Bid leakage before reveal **Mitigation**: - Client-side encryption before transmission - Commitment hashes prevent tampering - Auction server key rotation - Zero-knowledge proofs for bid validity (optional advanced feature) ### 4. Candle Auction Fairness **Threat**: Predictable termination time **Mitigation**: - VRF with public verifiability - Seed derived from blockchain hash (unpredictable) - Multiple entropy sources - Open-source VRF implementation ### 5. Seller Trust **Threat**: Seller doesn't deliver item **Mitigation**: - Reputation systems (NIP-58 badges, WoT) - Escrow services (optional third party) - Cashu tokens remain locked until dispute resolution - Attestation events for delivery confirmation ### 6. Network Attacks **Threat**: Relay censorship, DoS **Mitigation**: - Multi-relay auction broadcasting - Fallback relay lists - Client retry logic - Rate limiting on auction creation ### 7. Time Synchronization **Threat**: Clock skew causing unfair bid timing **Mitigation**: - Use relay timestamps (NIP-XX) - Client clock offset detection - Grace periods for edge cases - NTP synchronization recommendations --- ## Implementation Guide ### Server Implementation (Relay Plugin) #### 1. Core Auction Manager ```go type AuctionManager struct { db database.Database publishers *publish.Publishers mintClient *cashu.MintClient activeAuctions map[string]*Auction } func (am *AuctionManager) HandleAuctionEvent(event *protocol.Event) error { switch event.Kind { case 30020: // New auction return am.createAuction(event) case 1020: // New bid return am.processBid(event) case 4: // Payment proof return am.verifyPaymentProof(event) } } ``` #### 2. Auction State Machine ```go type AuctionState int const ( StatePending AuctionState = iota StateActive StateEnded StateSettled StateCancelled ) type Auction struct { ID string Type AuctionType State AuctionState CurrentPrice uint64 Bids []*Bid Winner *Bid EndTime time.Time } ``` #### 3. Bid Validation ```go func (am *AuctionManager) validateBid(auction *Auction, bid *Bid) error { // Check auction is active if auction.State != StateActive { return ErrAuctionNotActive } // Verify payment proof proof, err := am.getPaymentProof(bid.ProofHash) if err != nil { return err } // Verify with Cashu mint valid, err := am.mintClient.VerifyProof(proof) if err != nil || !valid { return ErrInvalidPaymentProof } // Check amount meets requirements switch auction.Type { case AuctionTypeEnglish: minBid := auction.CurrentPrice + auction.Increment if bid.Amount < minBid { return ErrBidTooLow } case AuctionTypeDutch: if bid.Amount < auction.CurrentPrice { return ErrBidTooLow } } return nil } ``` #### 4. Real-Time Updates ```go func (am *AuctionManager) broadcastBidUpdate(auction *Auction, bid *Bid) { update := &protocol.Event{ Kind: 20020, // Ephemeral bid update CreatedAt: time.Now().Unix(), Tags: [][]string{ {"d", auction.ID}, {"current_bid", strconv.FormatUint(bid.Amount, 10)}, {"bid_count", strconv.Itoa(len(auction.Bids))}, {"time_remaining", strconv.Itoa(int(time.Until(auction.EndTime).Seconds()))}, }, } am.publishers.Publish(update) } ``` #### 5. Cashu Integration ```go type CashuClient struct { mintURL string client *http.Client } func (c *CashuClient) VerifyProof(proof *CashuProof) (bool, error) { // Verify DLEQ proof if !c.verifyDLEQ(proof) { return false, nil } // Check proof validity with mint resp, err := c.client.Post( c.mintURL + "/check", "application/json", encodeProof(proof), ) if err != nil { return false, err } var result CheckProofResponse json.NewDecoder(resp.Body).Decode(&result) return result.Valid, nil } ``` ### Client Implementation #### 1. Auction Discovery ```javascript // Subscribe to active auctions const filter = { kinds: [30020], tags: [["status", "active"]], since: Math.floor(Date.now() / 1000) - 86400 // Last 24 hours }; relay.subscribe([filter], (event) => { displayAuction(parseAuction(event)); }); ``` #### 2. Real-Time Bid Updates ```javascript // Subscribe to specific auction updates const auctionId = "eng_001"; const updateFilter = { kinds: [20020, 30021], // Ephemeral + state updates tags: [["d", auctionId]] }; relay.subscribe([updateFilter], (event) => { if (event.kind === 20020) { updateBidDisplay(event); } else if (event.kind === 30021) { updateAuctionState(event); } }); ``` #### 3. Placing a Bid ```javascript async function placeBid(auctionId, amount) { // 1. Get Cashu token from mint const token = await cashuWallet.requestToken(amount); // 2. Generate DLEQ proof const proof = await cashuWallet.generateProof(token); // 3. Create bid event const bidEvent = { kind: 1020, created_at: Math.floor(Date.now() / 1000), tags: [ ["d", auctionId], ["amount", amount.toString()], ["proof_hash", sha256(JSON.stringify(proof))] ], content: "" }; const signedBid = await nostr.signEvent(bidEvent); // 4. Encrypt and send payment proof const encryptedProof = await nostr.nip04.encrypt( auctionServerPubkey, JSON.stringify({ token, proof, amount }) ); const proofEvent = { kind: 4, created_at: Math.floor(Date.now() / 1000), tags: [ ["p", auctionServerPubkey], ["e", signedBid.id], ["d", auctionId] ], content: encryptedProof }; const signedProof = await nostr.signEvent(proofEvent); // 5. Publish both events await relay.publish(signedBid); await relay.publish(signedProof); } ``` #### 4. Handling Win Notification ```javascript // Subscribe to auction results const resultFilter = { kinds: [30022], tags: [["d", auctionId]] }; relay.subscribe([resultFilter], async (event) => { const winner = event.tags.find(t => t[0] === "winner")?.[1]; if (winner === myNpub) { // We won! Wait for seller to request unlock await handleWinnerFlow(event); } }); async function handleWinnerFlow(resultEvent) { // Subscribe to encrypted DMs from seller const dmFilter = { kinds: [4], authors: [sellerPubkey], "#p": [myPubkey] }; relay.subscribe([dmFilter], async (dm) => { const decrypted = await nostr.nip04.decrypt(sellerPubkey, dm.content); const request = JSON.parse(decrypted); if (request.type === "unlock_request") { // Send the Cashu token secret await sendTokenUnlock(request.auction_id); } }); } ``` ### Relay Integration #### 1. Database Schema ```sql -- Auctions table CREATE TABLE auctions ( id TEXT PRIMARY KEY, type TEXT NOT NULL, seller_pubkey TEXT NOT NULL, state TEXT NOT NULL, start_time INTEGER NOT NULL, end_time INTEGER NOT NULL, current_price INTEGER NOT NULL, reserve_price INTEGER, winner_pubkey TEXT, created_at INTEGER NOT NULL ); -- Bids table CREATE TABLE bids ( id TEXT PRIMARY KEY, auction_id TEXT NOT NULL, bidder_pubkey TEXT NOT NULL, amount INTEGER NOT NULL, proof_hash TEXT NOT NULL, proof_verified BOOLEAN DEFAULT FALSE, created_at INTEGER NOT NULL, FOREIGN KEY (auction_id) REFERENCES auctions(id) ); -- Payment proofs table CREATE TABLE payment_proofs ( bid_id TEXT PRIMARY KEY, cashu_token_hash TEXT NOT NULL, mint_url TEXT NOT NULL, verified BOOLEAN DEFAULT FALSE, unlocked BOOLEAN DEFAULT FALSE, FOREIGN KEY (bid_id) REFERENCES bids(id) ); ``` #### 2. Event Handlers ```go // In app/handle-auction.go func (s *Server) handleAuctionMessage(ws *websocket.Conn, msg []byte) error { var env protocol.Envelope if err := json.Unmarshal(msg, &env); err != nil { return err } switch env.Label { case "EVENT": return s.handleAuctionEvent(ws, env.Event) case "REQ": return s.handleAuctionQuery(ws, env.SubscriptionID, env.Filters) } return nil } func (s *Server) handleAuctionEvent(ws *websocket.Conn, event *protocol.Event) error { switch event.Kind { case 30020: // Auction listing return s.auctionManager.CreateAuction(event) case 1020: // Bid return s.auctionManager.ProcessBid(event) case 4: // Payment proof return s.auctionManager.VerifyPaymentProof(event) } return nil } ``` --- ## Advanced Features ### 1. Multi-Item Auctions Support selling multiple identical items: ```json { "kind": 30020, "tags": [ ["d", "multi_001"], ["auction_type", "english_multi"], ["quantity", "10"], ["amount", "50000"], ["winners", "10"] ] } ``` Top 10 bidders each win one item at their bid price. ### 2. Batch Auctions Collect bids over time, execute all at once: ```json { "kind": 30020, "tags": [ ["d", "batch_001"], ["auction_type", "batch"], ["execution_time", "1234567890"], ["clearing_method", "uniform_price"] ] } ``` All winners pay same clearing price. ### 3. Combinatorial Auctions Bidders can bid on bundles of items: ```json { "kind": 1020, "tags": [ ["d", "combo_001"], ["bundle", "item_a", "item_b", "item_c"], ["amount", "500000"] ] } ``` Requires optimization algorithm to determine winning combinations. ### 4. Proxy Bidding Automated bidding on behalf of user up to max price: ```json { "kind": 1021, "tags": [ ["d", "eng_001"], ["max_bid", "500000"], ["increment", "5000"], ["auto_bid", "true"] ] } ``` Server automatically places bids when outbid, up to max. --- ## Testing & Validation ### Test Auction Server ```bash # Start test relay with auction support export ORLY_AUCTION_ENABLED=true export ORLY_CASHU_MINT_URL=https://testnut.cashu.space ./orly ``` ### Test Client ```javascript // Create test auction const auction = await createAuction({ type: "english", startPrice: 10000, reserve: 5000, duration: 3600 }); // Simulate bids await placeBid(auction.id, 11000); await placeBid(auction.id, 12000); // Verify winner const result = await waitForResult(auction.id); assert(result.winner === expectedWinner); ``` ### Compliance Checklist - [ ] All bids require valid payment proofs - [ ] Sealed bids remain encrypted until reveal - [ ] Ephemeral events not stored by relay - [ ] Winner selection matches auction rules - [ ] Payment settlement completes successfully - [ ] Edge cases handled (ties, no bids, cancellation) --- ## Future Enhancements ### 1. Cross-Chain Settlements Support Bitcoin Lightning, liquid, or other payment rails: ```json { "kind": 4, "tags": [ ["payment_method", "lightning"], ["invoice", "lnbc..."] ] } ``` ### 2. Dispute Resolution Third-party arbitration system: ```json { "kind": 30024, "tags": [ ["d", "dispute_001"], ["auction_id", "eng_001"], ["arbitrator", ""], ["status", "pending"] ] } ``` ### 3. Reputation System Track seller/buyer reliability: ```json { "kind": 30025, "tags": [ ["d", ""], ["auctions_completed", "150"], ["average_rating", "4.8"], ["badges", "trusted_seller", "fast_payer"] ] } ``` ### 4. Analytics Dashboard Track auction performance metrics in web UI. --- ## Conclusion This specification provides a comprehensive framework for implementing decentralized auctions on Nostr. The combination of relay infrastructure, Cashu payment proofs, and flexible event types enables trustless, private, and efficient auction mechanisms for diverse use cases. **Next Steps:** 1. Implement auction manager in ORLY relay 2. Create reference client library 3. Deploy test auctions on testnet 4. Gather community feedback 5. Submit formal NIP proposal **Contributing:** Feedback and contributions welcome at the ORLY repository. --- **Document Version:** 1.0.0 **Last Updated:** 2025-11-17 **License:** MIT