nips/15.md

9.1 KiB

NIP-15

Nostr Marketplace (for resilient marketplaces)

draft optional author:fiatjaf author:benarc author:motorina0 author:talvasconcelos

Based on https://github.com/lnbits/Diagon-Alley

Implemented here https://github.com/lnbits/nostrmarket

Terms

  • merchant - seller of products with NOSTR key-pair
  • customer - buyer of products with NOSTR key-pair
  • product - item for sale by the merchant
  • stall - list of products controlled by merchant (a merchant can have multiple stalls)
  • marketplace - clientside software for searching stalls and purchasing products

Nostr Marketplace Clients

Merchant admin

Where the merchant creates, updates and deletes stalls and products, as well as where they manage sales, payments and communication with customers.

The merchant admin software can be purely clientside, but for convenience and uptime, implementations will likely have a server client listening for NOSTR events.

Marketplace

Marketplace software should be entirely clientside, either as a stand-alone app, or as a purely frontend webpage. A customer subscribes to different merchant NOSTR public keys, and those merchants stalls and products become listed and searchable. The marketplace client is like any other ecommerce site, with basket and checkout. Marketplaces may also wish to include a customer support area for direct message communication with merchants.

Merchant publishing/updating products (event)

A merchant can publish these events:

Kind Description NIP
0 set_meta The merchant description (similar with any nostr public key). NIP01
30017 set_stall Create or update a stall. NIP33 (Parameterized Replaceable Event)
30018 set_product Create or update a product. NIP33 (Parameterized Replaceable Event)
4 direct_message Communicate with the customer. The messages can be plain-text or JSON. NIP04
5 delete Delete a product or a stall. NIP09

Event 30017: Create or update a stall.

Event Content:

{
    "id": <String, UUID generated by the merchant. Sequential IDs (`0`, `1`, `2`...) are discouraged>,
    "name": <String, stall name>,
    "description": <String (optional), stall description>,
    "currency": <String, currency used>,
    "shipping": [
        {
            "id": <String, UUID of the shipping zone, generated by the merchant>,
            "name": <String (optional), zone name>,
            "cost": <float, cost for shipping. The currency is defined at the stall level>,
            "countries": [<String, countries included in this zone>],
        }
    ]
}

Fields that are not self-explanatory:

  • shipping:
    • an array with possible shipping zones for this stall. The customer MUST choose exactly one shipping zone.
    • shipping to different zones can have different costs. For some goods (digital for example) the cost can be zero.
    • the id is an internal value used by the merchant. This value must be sent back as the customer selection.

Event Tags:

  "tags": [["d", <String, id of stall]]
  • the d tag is required by NIP33. Its value MUST be the same as the stall id.

Event 30018: Create or update a product

Event Content:

{
    "id": <String, UUID generated by the merchant.Sequential IDs (`0`, `1`, `2`...) are discouraged>,
    "stall_id": <String, UUID of the stall to which this product belong to>,
    "name": <String, product name>,
    "description": <String (optional), product description>,
    "images": <[String], array of image URLs, optional>,
    "currency": <String, currency used>,
    "price": <float, cost of product>,
    "quantity": <int, available items>,
    "specs": [
      [ <String, spec key>, <String, spec value>]
     ]
}

Fields that are not self-explanatory:

  • specs:
    • an array of key pair values. It allows for the Customer UI to present present product specifications in a structure mode. It also allows comparison between products
    • eg: [["operating_system", "Android 12.0"], ["screen_size", "6.4 inches"], ["connector_type", "USB Type C"]]

Open: better to move spec in the tags section of the event?

Event Tags:

  "tags": [
       ["d", <String, id of product],
       ["t", <String (optional), product category],
       ["t", <String (optional), product category],
       ...
    ]
  • the d tag is required by NIP33. Its value MUST be the same as the product id.
  • the t tag is as searchable tag (NIP12). It represents different categories that the product can be part of (food, fruits). Multiple t tags can be present.

Checkout events

All checkout events are sent as JSON strings using (NIP04).

The merchant and the customer can exchange JSON messages that represent different actions. Each JSON message MUST have a type field indicating the what the JSON represents. Possible types:

Message Type Sent By Description
0 Customer New Order
1 Merchant Payment Request
2 Merchant Order Status Update

Step 1: customer order (event)

The below json goes in content of NIP04.

{
    "id": <String, UUID generated by the customer>,
    "type": 0,
    "name": <String (optional), ???>,
    "address": <String (optional), for physical goods an address should be provided>
    "message": "<String (optional), message for merchant>,
    "contact": {
        "nostr": <32-bytes hex of a pubkey>,
        "phone": <String (optional), if the customer wants to be contacted by phone>,
        "email": <String (optional), if the customer wants to be contacted by email>,
    },
    "items": [
        {
            "product_id": <String, UUID of the product>,
            "quantity": <int, how many products the customer is ordering>
        }
    ],
    "shipping_id": <String, UUID of the shipping zone>
}

Open: is contact.nostr required?

Step 2: merchant request payment (event)

Sent back from the merchant for payment. Any payment option is valid that the merchant can check.

The below json goes in content of NIP04.

payment_options/type include:

  • url URL to a payment page, stripe, paypal, btcpayserver, etc
  • btc onchain bitcoin address
  • ln bitcoin lightning invoice
  • lnurl bitcoin lnurl-pay
{
    "id": <String, UUID of the order>,
    "type": 1,
    "message": <String, message to customer, optional>,
    "payment_options": [
        {
            "type": <String, option type>,
            "link": <String, url, btc address, ln invoice, etc>
        },
        {
            "type": <String, option type>,
            "link": <String, url, btc address, ln invoice, etc>
        },
                {
            "type": <String, option type>,
            "link": <String, url, btc address, ln invoice, etc>
        }
    ]
}

Step 3: merchant verify payment/shipped (event)

Once payment has been received and processed.

The below json goes in content of NIP04.

{
    "id": <String, UUID of the order>,
    "type": 2,
    "message": <String, message to customer>,
    "paid": <Bool, true/false has received payment>,
    "shipped": <Bool, true/false has been shipped>,
}

Customer support events

Customer support is handled over whatever communication method was specified. If communicating via nostr, NIP-04 is used https://github.com/nostr-protocol/nips/blob/master/04.md.

Additional

Standard data models can be found here here