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.