Version: 1.0.0 Status: Draft Author: ORLY Development Team
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.
Description: The classic open-outcry auction where bidders compete by placing increasingly higher bids. The highest bidder when the timer expires wins.
Use Cases:
Characteristics:
Description: Price starts high and decreases at regular intervals until a buyer accepts the current price or reserve is reached.
Use Cases:
Characteristics:
Description: Bidders submit secret bids, highest bid wins and pays their bid amount.
Use Cases:
Characteristics:
Description: Sealed bids where highest bidder wins but pays the second-highest bid amount.
Use Cases:
Characteristics:
Description: Buyers post requirements, sellers compete by offering decreasing prices.
Use Cases:
Characteristics:
Description: Auction with random, unpredictable end time. Historically, auction ended when a candle flame went out.
Use Cases:
Characteristics:
Description: All bidders pay their bid regardless of winning, highest bidder wins the item.
Use Cases:
Characteristics:
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.
┌─────────┐ ┌──────────┐ ┌─────────┐ ┌────────┐
│ 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 │ │
│ ├───────────────────────────────────────>│
{
"token": {
"mint": "https://mint.example.com",
"proofs": [
{
"id": "00882760bfa2eb41",
"amount": 1000,
"secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9",
"C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6e3f114d0"
}
]
}
}
Proves knowledge of secret without revealing it:
{
"dleq_proof": {
"e": "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9",
"s": "9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9",
"r": "a6d13fcd7a18442e6076f5e1e7c887ad5de40a019e7f1ae9bae5feb9046acf2f"
}
}
| 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 |
d: Auction unique identifier (for replaceable events)auction_type: english, dutch, sealedfirst, vickrey, reverse, candle, allpaymint: Cashu mint URLamount: Starting/current price (sats)reserve: Reserve price (minimum acceptable)currency: unit of account (sat, btc, usd)start_time: Unix timestampend_time: Unix timestamp (or duration for dutch)item: Item identifier or description hashstatus: active, ended, cancelled, settledwinner: npub of winning bidderbid_amount: Final winning bidPurpose: Create new auction listing
Replaceable: Yes (by d tag)
Storage: Persistent
{
"kind": 30020,
"pubkey": "<seller_pubkey>",
"created_at": 1234567890,
"tags": [
["d", "<auction_id>"],
["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..."
}
Purpose: Submit a bid Replaceable: No Storage: Persistent (for sealed auctions, content encrypted)
{
"kind": 1020,
"pubkey": "<bidder_pubkey>",
"created_at": 1234567890,
"tags": [
["e", "<auction_event_id>"],
["d", "<auction_id>"],
["auction_type", "english"],
["amount", "1100000"],
["proof_hash", "<sha256_of_payment_proof>"]
],
"content": ""
}
For sealed-bid auctions, the amount is encrypted:
{
"kind": 1020,
"pubkey": "<bidder_pubkey>",
"created_at": 1234567890,
"tags": [
["e", "<auction_event_id>"],
["d", "<auction_id>"],
["auction_type", "sealed_first"],
["proof_hash", "<sha256_of_payment_proof>"]
],
"content": "<encrypted_bid_data>"
}
Purpose: Send Cashu proof to auction server Encrypted: Yes (NIP-04 or NIP-44) Storage: Persistent
{
"kind": 4,
"pubkey": "<bidder_pubkey>",
"created_at": 1234567890,
"tags": [
["p", "<auction_server_pubkey>"],
["e", "<bid_event_id>"],
["d", "<auction_id>"]
],
"content": "<encrypted_json>"
}
Decrypted content:
{
"cashu_token": {
"mint": "https://mint.example.com",
"proofs": [...]
},
"dleq_proof": {
"e": "...",
"s": "...",
"r": "..."
},
"amount": 1100000
}
Purpose: Real-time bid update broadcast Ephemeral: Yes (not stored by relay) Storage: None
{
"kind": 20020,
"pubkey": "<auction_server_pubkey>",
"created_at": 1234567890,
"tags": [
["e", "<auction_event_id>"],
["d", "<auction_id>"],
["current_bid", "1100000"],
["bid_count", "15"],
["time_remaining", "3600"],
["leading_bidder_hash", "<partial_hash_for_privacy>"]
],
"content": "New bid received"
}
Purpose: Update auction status (price changes, extensions)
Replaceable: Yes (by d tag)
Storage: Persistent
{
"kind": 30021,
"pubkey": "<auction_server_pubkey>",
"created_at": 1234567890,
"tags": [
["d", "<auction_id>"],
["status", "active"],
["current_price", "1100000"],
["bid_count", "15"],
["end_time", "1234657890"],
["last_bid_time", "1234567800"]
],
"content": ""
}
Purpose: Record final outcome
Replaceable: Yes (by d tag)
Storage: Persistent
{
"kind": 30022,
"pubkey": "<auction_server_pubkey>",
"created_at": 1234567890,
"tags": [
["d", "<auction_id>"],
["status", "ended"],
["winner", "<winner_npub>"],
["winning_bid", "1100000"],
["bid_count", "15"],
["settlement_status", "pending"]
],
"content": "Auction ended successfully"
}
Purpose: Notify seller of payment proof Encrypted: Yes Storage: Persistent
{
"kind": 14,
"pubkey": "<auction_server_pubkey>",
"created_at": 1234567890,
"tags": [
["p", "<seller_pubkey>"],
["d", "<auction_id>"]
],
"content": "<encrypted_json>"
}
Decrypted content:
{
"auction_id": "<auction_id>",
"winner": "<winner_npub>",
"amount": 1100000,
"cashu_token_preview": {
"mint": "https://mint.example.com",
"amount": 1100000,
"proof_count": 3
},
"unlock_request_method": "Request unlock via DM to winner"
}
{
"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"]
]
}
`json
["REQ", "auction_001", {
"kinds": [20020, 30021],
"tags": [["d", ["eng_001"]]]
}]
`
- Creates Kind 1020 bid event
- Sends Kind 4 encrypted payment proof
- Server validates: bid >= current_price + increment
`json
{
"kind": 20020,
"tags": [
["d", "eng_001"],
["current_bid", "105000"],
["bid_count", "3"],
["time_remaining", "7200"]
]
}
`
extension_timeTime 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
{
"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"]
]
}
Server publishes Kind 30021 every interval seconds:
{
"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)
- Payment proof amount >= current_price - No prior accepted bid exists
`json
{
"kind": 30022,
"tags": [
["d", "dutch_001"],
["status", "ended"],
["winner", "<buyer_npub>"],
["winning_bid", "450000"],
["final_price", "450000"]
]
}
`
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)
{
"kind": 30020,
"tags": [
["d", "sealed_001"],
["auction_type", "sealed_first"],
["reserve", "100000"],
["start_time", "1234567890"],
["end_time", "1234657890"],
["reveal_time", "1234668890"]
]
}
Bids are encrypted using auction server's public key:
{
"kind": 1020,
"tags": [
["d", "sealed_001"],
["auction_type", "sealed_first"],
["proof_hash", "<hash>"]
],
"content": "<encrypted_bid>"
}
Encrypted content structure:
{
"amount": 250000,
"bid_secret": "<random_nonce>",
"timestamp": 1234567900
}
After end_time, server decrypts all bids and publishes results:
{
"kind": 30022,
"tags": [
["d", "sealed_001"],
["status", "ended"],
["winner", "<winner_npub>"],
["winning_bid", "250000"],
["second_bid", "230000"],
["bid_count", "12"]
]
}
Optional: Publish proof of all bids (privacy-preserving):
{
"kind": 30023,
"tags": [
["d", "sealed_001"],
["bid_proof", "<bidder_hash_1>", "250000"],
["bid_proof", "<bidder_hash_2>", "230000"],
["bid_proof", "<bidder_hash_3>", "210000"]
],
"content": "Bid verification data"
}
proof_hash = SHA256(amount || bid_secret || bidder_pubkey){
"kind": 30020,
"tags": [
["d", "vickrey_001"],
["auction_type", "vickrey"],
["reserve", "100000"],
["start_time", "1234567890"],
["end_time", "1234657890"]
]
}
Identical to sealed-bid first-price, but winner pays second-highest bid:
{
"kind": 30022,
"tags": [
["d", "vickrey_001"],
["winner", "<winner_npub>"],
["winning_bid", "250000"],
["payment_amount", "230000"],
["bid_count", "12"]
]
}
Winner proves overpayment and receives refund:
Alternative: Winner provides two separate proofs (230k + 20k) and only unlocks first.
{
"kind": 30020,
"tags": [
["d", "reverse_001"],
["auction_type", "reverse"],
["max_budget", "500000"],
["start_time", "1234567890"],
["end_time", "1234657890"],
["requirements", "<hash_of_detailed_specs>"]
],
"content": "Need custom Nostr client with features X, Y, Z"
}
{
"kind": 1020,
"tags": [
["d", "reverse_001"],
["amount", "450000"],
["delivery_time", "30"],
["portfolio", "https://example.com/portfolio"]
],
"content": "Proposal details..."
}
Buyer evaluates and selects winning proposal:
{
"kind": 30022,
"tags": [
["d", "reverse_001"],
["winner", "<seller_npub>"],
["winning_bid", "400000"],
["selection_criteria", "best_value"]
]
}
{
"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"]
]
}
Server uses Verifiable Random Function (VRF) to determine end time:
{
"kind": 30022,
"tags": [
["d", "candle_001"],
["winner", "<winner_npub>"],
["winning_bid", "145000"],
["termination_time", "1234662500"],
["vrf_proof", "<proof>"],
["vrf_seed", "<public_seed>"]
]
}
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
{
"kind": 30020,
"tags": [
["d", "allpay_001"],
["auction_type", "all_pay"],
["prize_value", "1000000"],
["min_bid", "10000"],
["start_time", "1234567890"],
["end_time", "1234657890"]
]
}
All bidders immediately pay when bidding:
{
"kind": 1020,
"tags": [
["d", "allpay_001"],
["amount", "50000"],
["payment_status", "paid"]
]
}
Payment proof required before bid acceptance:
{
"kind": 4,
"tags": [
["p", "<auction_server_pubkey>"],
["d", "allpay_001"]
],
"content": "<encrypted_cashu_proof>"
}
{
"kind": 30022,
"tags": [
["d", "allpay_001"],
["winner", "<winner_npub>"],
["winning_bid", "250000"],
["total_collected", "1500000"],
["bid_count", "25"]
]
}
Threat: Fake payment proofs Mitigation:
Threat: Sybil attacks, shill bidding Mitigation:
Threat: Bid leakage before reveal Mitigation:
Threat: Predictable termination time Mitigation:
Threat: Seller doesn't deliver item Mitigation:
Threat: Relay censorship, DoS Mitigation:
Threat: Clock skew causing unfair bid timing Mitigation:
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)
}
}
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
}
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
}
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)
}
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
}
// 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));
});
// 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);
}
});
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);
}
// 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);
}
});
}
-- 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)
);
// 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
}
Support selling multiple identical items:
{
"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.
Collect bids over time, execute all at once:
{
"kind": 30020,
"tags": [
["d", "batch_001"],
["auction_type", "batch"],
["execution_time", "1234567890"],
["clearing_method", "uniform_price"]
]
}
All winners pay same clearing price.
Bidders can bid on bundles of items:
{
"kind": 1020,
"tags": [
["d", "combo_001"],
["bundle", "item_a", "item_b", "item_c"],
["amount", "500000"]
]
}
Requires optimization algorithm to determine winning combinations.
Automated bidding on behalf of user up to max price:
{
"kind": 1021,
"tags": [
["d", "eng_001"],
["max_bid", "500000"],
["increment", "5000"],
["auto_bid", "true"]
]
}
Server automatically places bids when outbid, up to max.
# Start test relay with auction support
export ORLY_AUCTION_ENABLED=true
export ORLY_CASHU_MINT_URL=https://testnut.cashu.space
./orly
// 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);
Support Bitcoin Lightning, liquid, or other payment rails:
{
"kind": 4,
"tags": [
["payment_method", "lightning"],
["invoice", "lnbc..."]
]
}
Third-party arbitration system:
{
"kind": 30024,
"tags": [
["d", "dispute_001"],
["auction_id", "eng_001"],
["arbitrator", "<npub>"],
["status", "pending"]
]
}
Track seller/buyer reliability:
{
"kind": 30025,
"tags": [
["d", "<user_pubkey>"],
["auctions_completed", "150"],
["average_rating", "4.8"],
["badges", "trusted_seller", "fast_payer"]
]
}
Track auction performance metrics in web UI.
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:
Contributing: Feedback and contributions welcome at the ORLY repository.
Document Version: 1.0.0 Last Updated: 2025-11-17 License: MIT