-
Notifications
You must be signed in to change notification settings - Fork 597
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
Changes from 27 commits
604f6e3
5fa3da0
b3adbc6
8e03a42
1e46b3f
a73d709
c9f9601
750f257
a5b4833
98448e8
0f705f5
f7a7552
8edc357
7ae38f6
0605320
9b20e0a
752a2c4
e0ac743
14c7e81
cebcecc
ed6e3ae
2cb6587
55b7d08
0928419
545e017
f55a9c2
9af7652
cf1d1c6
a8076e9
1e3ec27
ebe6786
e660b25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | | ||
| `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": [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Then, I would add a "checkoutData": {
"name": { mandatory: boolean, description: string },
"address": { mandatory: boolean, description: string },
"country": { mandatory: boolean, description: string }
} And then, in the "checkoutData": { "name" : "Kukks", "address": "17, Chocolate Street", "country": "Russia"} This would also omit the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
"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> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kind
that its encryptednpub
) that a customer uses to buy goods might be different than the one used for social mediahamstr
) have nice way of displaying JSON (in the form of tables)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kukks
kind
for "typed/structured" DMs is also an option. See addcontent-type
tag for Encrypted Direct Messages #394 (comment)