Skip to main content

NIP-15: Nostr Marketplace

NIP-15 defines a structured protocol for creating marketplaces on Nostr, enabling merchants to publish products and buyers to place orders using Lightning payments.

Overview

NIP-15 introduces three main event types:

KindNamePurpose
30017StallMerchant's store configuration
30018ProductIndividual product listings
4DMOrder communication (encrypted)

Stall Events (kind 30017)

A stall represents a merchant's store:

{
"kind": 30017,
"pubkey": "merchant_pubkey",
"content": "{\"id\":\"stall_id\",\"name\":\"My Store\",\"description\":\"Quality goods\",\"currency\":\"sat\",\"shipping\":[{\"id\":\"ship1\",\"name\":\"Standard\",\"cost\":5000,\"regions\":[\"worldwide\"]}]}",
"tags": [
["d", "stall_id"]
]
}

Stall Content Schema

interface Stall {
id: string;
name: string;
description?: string;
currency: string; // "sat", "USD", etc.
shipping: ShippingMethod[];
}

interface ShippingMethod {
id: string;
name: string;
cost: number;
regions: string[];
}

Example Stall

{
"id": "bitcoin-merch",
"name": "Bitcoin Merchandise",
"description": "Official Bitcoin-themed products",
"currency": "sat",
"shipping": [
{
"id": "domestic",
"name": "US Shipping",
"cost": 5000,
"regions": ["US"]
},
{
"id": "international",
"name": "International",
"cost": 15000,
"regions": ["worldwide"]
}
]
}

Product Events (kind 30018)

Products are items for sale within a stall:

{
"kind": 30018,
"pubkey": "merchant_pubkey",
"content": "{\"id\":\"product1\",\"stall_id\":\"stall_id\",\"name\":\"Bitcoin T-Shirt\",\"description\":\"100% cotton\",\"images\":[\"https://...\"],\"currency\":\"sat\",\"price\":21000,\"quantity\":50}",
"tags": [
["d", "product1"],
["t", "clothing"],
["t", "bitcoin"]
]
}

Product Content Schema

interface Product {
id: string;
stall_id: string;
name: string;
description?: string;
images?: string[];
currency: string;
price: number;
quantity?: number;
specs?: [string, string][]; // Key-value pairs
shipping?: ShippingCost[];
}

interface ShippingCost {
id: string; // References stall shipping method
cost: number; // Additional cost for this product
}

Product Example

{
"id": "btc-hoodie-black",
"stall_id": "bitcoin-merch",
"name": "Bitcoin Hoodie - Black",
"description": "Premium quality hoodie with embroidered Bitcoin logo",
"images": [
"https://example.com/hoodie-front.jpg",
"https://example.com/hoodie-back.jpg"
],
"currency": "sat",
"price": 50000,
"quantity": 25,
"specs": [
["Material", "80% Cotton, 20% Polyester"],
["Sizes", "S, M, L, XL"],
["Color", "Black"]
]
}

Order Flow

1. Customer Creates Order

Customer sends encrypted DM to merchant:

{
"kind": 4,
"content": "<encrypted order JSON>",
"tags": [
["p", "merchant_pubkey"]
]
}

Order content (before encryption):

{
"type": 0,
"id": "order_id",
"items": [
{
"product_id": "btc-hoodie-black",
"quantity": 1
}
],
"shipping_id": "domestic",
"contact": {
"nostr": "customer_npub",
"email": "customer@example.com"
},
"address": "123 Bitcoin St, Satoshi City, 12345"
}

2. Merchant Responds

Merchant sends payment request:

{
"type": 1,
"id": "order_id",
"message": "Thanks for your order!",
"payment_options": [
{
"type": "ln",
"link": "lnbc500000n1..."
}
]
}

3. Customer Pays

Customer pays Lightning invoice.

4. Merchant Confirms

{
"type": 2,
"id": "order_id",
"message": "Payment received! Shipping in 2-3 days.",
"shipped": false
}

Order Types

TypePurpose
0New order from customer
1Payment request from merchant
2Order status update

Client-Side Architecture

NIP-15 specifies that marketplace clients should be:

"Entirely client-side, either as a stand-alone app, or as a purely frontend webpage."

How It Works

  1. Client subscribes to merchant pubkeys
  2. Fetches stalls and products
  3. Displays searchable catalog
  4. Handles order DMs
  5. Integrates wallet for payments

Search and Discovery

Clients can filter products by:

  • Tags (t tag)
  • Stall ID
  • Price range
  • Merchant reputation

Implementation Example

Creating a Stall

const stall = {
kind: 30017,
content: JSON.stringify({
id: "my-store",
name: "My Bitcoin Store",
description: "Quality products for Bitcoiners",
currency: "sat",
shipping: [{
id: "standard",
name: "Standard Shipping",
cost: 10000,
regions: ["worldwide"]
}]
}),
tags: [
["d", "my-store"]
]
};

await publishEvent(stall);

Listing a Product

const product = {
kind: 30018,
content: JSON.stringify({
id: "btc-mug",
stall_id: "my-store",
name: "Bitcoin Coffee Mug",
description: "Start your day with Bitcoin",
images: ["https://example.com/mug.jpg"],
currency: "sat",
price: 15000,
quantity: 100
}),
tags: [
["d", "btc-mug"],
["t", "drinkware"],
["t", "bitcoin"]
]
};

await publishEvent(product);

Challenges

Current Limitations

  • Manual processing - No automated fulfillment
  • Trust - No built-in escrow
  • Scale - Hard to manage many orders
  • Inventory - Manual stock updates

Being Addressed

  • Merchant tools development
  • Escrow proposals
  • Order management systems
  • Integration with fulfillment APIs

See Also


Origin Story

NIP-15 was inspired by Diagon Alley, a LNbits marketplace extension. The concepts around resilient commerce helped influence Nostr protocol development.