Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NIP-15 Nostr marketplace #330

Merged
merged 32 commits into from
Apr 13, 2023
Merged
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
604f6e3
NIP45 Nostr marketplace
arcbtc Mar 6, 2023
5fa3da0
Added correct header
arcbtc Mar 6, 2023
b3adbc6
Added fiatjaf as author, for his comments in the previous repo we fol…
arcbtc Mar 6, 2023
8e03a42
doc: add events table
motorina0 Mar 23, 2023
1e46b3f
doc: add data structures
motorina0 Mar 23, 2023
a73d709
doc: update links
motorina0 Mar 23, 2023
c9f9601
doc: shipping zones
motorina0 Mar 23, 2023
750f257
doc: clean-up
motorina0 Mar 23, 2023
a5b4833
doc: product fields
motorina0 Mar 23, 2023
98448e8
doc: update Step 1: `customer` order (event)
motorina0 Mar 23, 2023
0f705f5
doc: stuff
motorina0 Mar 23, 2023
f7a7552
doc: replace hard-coded values with spec
motorina0 Mar 23, 2023
8edc357
doc: replace concrete values with placeholders
motorina0 Mar 24, 2023
7ae38f6
doc: add message type table
motorina0 Mar 24, 2023
0605320
doc: add type to messages
motorina0 Mar 24, 2023
9b20e0a
doc: clean-up
motorina0 Mar 24, 2023
752a2c4
doc: punctuation
motorina0 Mar 24, 2023
e0ac743
doc: fix UUID
motorina0 Mar 24, 2023
14c7e81
doc: add code type
motorina0 Mar 24, 2023
cebcecc
doc: fix JSON
motorina0 Mar 24, 2023
ed6e3ae
doc: add tags
motorina0 Mar 24, 2023
2cb6587
doc: update titles
motorina0 Mar 24, 2023
55b7d08
doc: clean-up
motorina0 Mar 24, 2023
0928419
Merge pull request #1 from arcbtc/nip_45_draft
motorina0 Mar 24, 2023
545e017
doc: add __Open__ topic
motorina0 Mar 24, 2023
f55a9c2
doc: re-order
motorina0 Mar 24, 2023
9af7652
doc: add `shipping_id`
motorina0 Mar 30, 2023
cf1d1c6
fix: typo
motorina0 Apr 3, 2023
a8076e9
fix: typo
motorina0 Apr 3, 2023
1e3ec27
Renamed to 15
arcbtc Apr 12, 2023
ebe6786
Merge remote-tracking branch 'origin/master' into patch-1
arcbtc Apr 12, 2023
e660b25
Added link to implementation
arcbtc Apr 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions 45.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
NIP-45
======

Nostr Marketplace (for resilient marketplaces)
-----------------------------------

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

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

## 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 ](https://github.com/nostr-protocol/nips/blob/master/01.md) |
| `30017` | `set_stall` | Create or update a stall. | [NIP33](https://github.com/nostr-protocol/nips/blob/master/33.md) (Parameterized Replaceable Event) |
| `30018` | `set_product` | Create or update a product. | [NIP33](https://github.com/nostr-protocol/nips/blob/master/33.md) (Parameterized Replaceable Event) |
| `4 ` | `direct_message` | Communicate with the customer. The messages can be plain-text or JSON. | [NIP09](https://github.com/nostr-protocol/nips/blob/master/09.md) |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use the kind 4, use another number so that it doesn't get mixed with regular DM's in clients

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • this is the only kind that its encrypted
  • it has the (privacy) advantage that it "looses in the crowd". Otherwise it would be much easier to identify orders.
  • the key (npub) that a customer uses to buy goods might be different than the one used for social media
  • some social media clients (like hamstr) have nice way of displaying JSON (in the form of tables)

Copy link
Member

@v0l v0l Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Use the same encryption scheme
  • Stores and clients might use different keys anyway so this doesnt really add any noise to hide in
  • Yes
  • Most don't and won't parse json messages in DM's, nor should they ever expect JSON, normies will be extremely confused

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stores and clients might use different keys anyway so this doesnt really add any noise to hide in

The noise is network-wide not key-specific. Although since the other kinds determine the pubkey of the marketplace, you are right, it is obvious the comms are for marketplace actions.

@motorina0
The only way to make this work is to do what I did in lnurl/luds#203 and have an extra interaction round so that the stall tells the customer which pubkey to communicate with.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kukks

| `5 ` | `delete` | Delete a product or a stall. | [NIP05](https://github.com/nostr-protocol/nips/blob/master/05.md) |

### Event `30017`: Create or update a stall.

**Event Content**:
```json
{
"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": [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels very specific and inflexible. What if the items require a more complex shipping cost structure, such as a weight/country matrix, cart subtotal quota, etc? Realistically, the current shipping option does not work for most sellers.

I would suggest shipping be optional, a simple string value describing the shipping costs.

Then, I would add a checkoutData array similar in function to https://github.com/lnurl/luds/blob/luds/18.md
For example:

    "checkoutData": {
        "name": { mandatory: boolean, description: string },
        "address": { mandatory: boolean, description: string },
        "country": { mandatory: boolean, description: string }
    }

And then, in the customer order event, add the following to the payload:

   "checkoutData": { "name" : "Kukks", "address": "17, Chocolate Street", "country": "Russia"}

This would also omit the shipping_id and address from customer order event

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the items require a more complex shipping cost structure, such as a weight/country matrix

  • I agree it does not cover complex shipping scenarios (was not intended to)
  • the solution (as far as this NIP goes) is for the merchant to send over DM to the customer any relevant information (like extra shipping cost for higher weights)
  • use-cases for : gift cards, promotions, discounts (over a certain quantity), etc are not included in this NIP (but can be part of future NIPs)

I would suggest shipping be optional, a simple string value describing the shipping costs.

  • the shipping cost can vary depending on the shipping zone (so more costs are possible)
  • the shipping zone id is required, as the merchant cannot rely on the value (cost) sent by the client

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realistically, the current shipping option does not work for most sellers.

  • why not?
  • left: amazon, right: alibaba

image

{
"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 examle) 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**:
```json
"tags": [["d", <String, id of stall]]
```
- the `d` tag is required by [NIP33](https://github.com/nostr-protocol/nips/blob/master/33.md). Its value MUST be the same as the stall `id`.

### Event `30018`: Create or update a product

**Event Content**:
```json
{
"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**:
```json
"tags": [
["d", <String, id of product],
["t", <String (optional), product category],
["t", <String (optional), product category],
...
]
```

- the `d` tag is required by [NIP33](https://github.com/nostr-protocol/nips/blob/master/33.md). Its value MUST be the same as the product `id`.
- the `t` tag is as searchable tag ([NIP12](https://github.com/nostr-protocol/nips/blob/master/12.md)). 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](https://github.com/nostr-protocol/nips/blob/master/04.md)).

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](https://github.com/nostr-protocol/nips/blob/master/04.md).

```json
{
"id": <String, UUID generated by the customer>,
"type": 0,
"name": <String (optional), ???>,
"address": <String (optional), for phisical goods an address should be provided>
motorina0 marked this conversation as resolved.
Show resolved Hide resolved
"message": "<String (optional), message for merchant>,
"contact": {
"nostr": <32-bytes hex of a pubkey>,
"phone": <String (optional), if the customer whats to be contacted by phone>,
"email": <String (optional), if the customer whats to be contacted by email>,
},
"items": [
{
"product_id": <String, UUID of the product>,
"quantity": <int, how many products have been ordered>
}
],
"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](https://github.com/nostr-protocol/nips/blob/master/04.md).

`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

```json
{
"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](https://github.com/nostr-protocol/nips/blob/master/04.md).

```json
{
"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 handle over whatever communication method was specified. If communicationg via nostr, NIP-04 is used https://github.com/nostr-protocol/nips/blob/master/04.md.
motorina0 marked this conversation as resolved.
Show resolved Hide resolved

## Additional

Standard data models can be found here <a href="https://raw.githubusercontent.com/lnbits/nostrmarket/main/models.py">here</a>