Chapter 69 - Commerce
69
Nostr Implementation Possibility - Hex № 69

Peer-to-peer Order events

One shared order book for every peer-to-peer Bitcoin trading platform, built from kind 38383 events.

1 Overview

A peer-to-peer (P2P) platform matches people who want to trade Bitcoin for fiat money without a central custodian. Many such platforms exist. Each platform keeps its own private list of open orders. This splits the liquidity: a trader on one platform cannot see the orders on another. Sometimes there are not enough matching orders to trade.

This NIP defines one simple event format for a P2P order. Every participating platform publishes its orders as Nostr events. Every platform reads the orders of all the others from relays. The result is one big shared liquidity pool. A pool of many platforms can compete with centralized exchanges, where a single authority controls the liquidity.

An order event is an NIP-01 addressable event with kind 38383. All order data lives in tags. The content field stays empty. A platform updates an order by publishing a new event with the same d tag; the relay keeps only the latest version.

Hint!

You do not trade through this event. The event only advertises the order. The actual trade happens on the platform named in the y tag, often reached through the URL in the source tag.

Platform A Platform B Platform C Relays shared pool Client one order book kind 38383 REQ
Fig. 69-1 — The shared liquidity pool. Each platform publishes orders as kind 38383 events. Any client reads them all as one order book.

2 The Order Event

An order event is an addressable event, as defined in NIP-01. It uses kind 38383. The relay keeps only the latest event for each pubkey + kind + d value. A complete order event looks like this:

Wire format - kind 38383 order event
{
  "id": "84fad0d29cb3529d789faeff2033e88fe157a48e071c6a5d1619928289420e31",
  "pubkey": "dbe0b1be7aafd3cfba92d7463edbd4e33b2969f61bd554d37ac56f032e13355a",
  "created_at": 1702548701,
  "kind": 38383,
  "tags": [
    ["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
    ["k", "sell"],
    ["f", "VES"],
    ["s", "pending"],
    ["amt", "0"],
    ["fa", "100"],
    ["pm", "face to face", "bank transfer"],
    ["premium", "1"],
    ["rating", "{\"total_reviews\":1,\"total_rating\":3.0,\"last_rating\":3,\"max_rate\":5,\"min_rate\":1}"],
    ["source", "https://t.me/p2plightning/xxxxxxx"],
    ["network", "mainnet"],
    ["layer", "lightning"],
    ["name", "Nakamoto"],
    ["g", <geohash>],
    ["bond", "0"],
    ["expires_at", "1719391096"],
    ["expiration", "1719995896"],
    ["y", "lnp2pbot"],
    ["z", "order"]
  ],
  "content": "",
  "sig": "7e8fe1eb644f33ff51d8805c02a0e1a6d034e6234eac50ef7a7e0dac68a0414f…"
}

3 Tag Reference

The source spec marks each tag as mandatory or optional. A mandatory tag must appear in every order event. An optional tag may be omitted.

Mandatory tags
TagMeaningRule
dOrder IDA unique identifier for the order. Also the addressable-event key.
kOrder typesell or buy.
fCurrencyThe asset being traded, as an ISO 4217 currency code.
sStatuspending, canceled, in-progress, success, or expired. See §4.
amtAmountThe amount of Bitcoin to trade, in satoshis. 0 means: get the satoshi amount from a public price API after the taker accepts the order.
faFiat amountThe fiat amount being traded. A range order carries two values: the minimum and the maximum amount.
pmPayment methodThe payment method for the trade. For multiple payment methods, the spec text says: separate them with a comma. See the Tech Note below.
premiumPremiumThe percentage of premium the maker is willing to pay.
networkNetworkThe network for the trade: mainnet, testnet, signet, etc.
layerLayerThe layer for the trade: onchain, lightning, liquid, etc.
expires_atExpires atUnix timestamp. The expiration date of the order while in pending status. After this time, the status SHOULD change to expired.
expirationExpirationUnix timestamp. The expiration date of the event itself. After this time, the relay SHOULD delete the event, per NIP-40.
yPlatformThe platform that created the order.
zDocumentAlways order.
Optional tags
TagMeaningRule
sourceSourceThe source of the order. It can be a URL that redirects to the order.
ratingRatingThe rating of the maker. This NIP does not define how the rating is calculated; each platform defines its own method.
nameNameThe name of the maker.
gGeohashThe geohash of the operation. Useful for a face-to-face trade.
bondBondThe bond amount. The bond is a security deposit that both parties must pay.
Tech Note

The spec text and its own example disagree on pm. The text says: separate multiple payment methods with a comma. The example event instead lists them as separate tag elements: ["pm", "face to face", "bank transfer"]. Implementations in the wild follow the example. Expect both shapes when parsing.

Warning!

Do not confuse the two expiry tags. expires_at expires the order: the status SHOULD flip to expired. expiration expires the event: the relay SHOULD delete it (NIP-40). Set expiration later than expires_at, or the order vanishes before it can expire.

4 Order Status

The s tag carries the order status. The spec defines five values. A platform replaces the order event to change the status. Because the event is addressable, the relay keeps only the newest version.

  • pending — the order is open and waits for a taker.
  • in-progress — a taker accepted the order; the trade is running.
  • success — the trade completed.
  • canceled — the maker or the platform canceled the order.
  • expired — the expires_at time passed while the order was still pending.
pending in-progress success canceled expired taker accepts trade done cancel expires_at passes
Fig. 69-2 — Status lifecycle. Every change is a new kind 38383 event with the same d value and a new s tag.
Tech Note

The spec lists the five status values but defines no transition rules between them. The transitions in Fig. 69-2 are the common practice of the listed implementations, not normative text. The only normative rule is: after expires_at, a pending order SHOULD become expired.

5 Worked Example

A trader wants to browse open sell orders for Venezuelan bolivar (VES) over Lightning. The client filters kind 38383 by the indexed single-letter tags from NIP-01. Orange lines go to the relay. Blue lines come from the relay.

Session transcript - wss://relay.mostro.network
-- browse open sell orders in VES --
→ ["REQ", "book", {"kinds":[38383], "#f":["VES"], "#s":["pending"], "#k":["sell"]}]
← ["EVENT", "book", {"kind":38383, "pubkey":"dbe0b1be…", "tags":[
     ["d","ede61c96-4c13-4519-bf3a-dcf7f1e9d842"], ["k","sell"], ["f","VES"],
     ["s","pending"], ["amt","0"], ["fa","100"], ["pm","face to face","bank transfer"],
     ["premium","1"], ["network","mainnet"], ["layer","lightning"],
     ["expires_at","1719391096"], ["expiration","1719995896"],
     ["y","lnp2pbot"], ["z","order"]], "content":"", …}]
← ["EOSE", "book"]

-- a taker accepts; the platform replaces the order --
→ ["EVENT", {"kind":38383, "pubkey":"dbe0b1be…", "tags":[
     ["d","ede61c96-4c13-4519-bf3a-dcf7f1e9d842"], ["s","in-progress"],
     ["k","sell"], ["f","VES"], …same order, new status…], "content":"", …}]
← ["OK", "9c31aa02…", true, ""]
-- the relay keeps only this latest version for d=ede61c96-… --

The relay treats both events as one address: 38383:dbe0b1be…:ede61c96-4c13-4519-bf3a-dcf7f1e9d842. Old versions may be discarded. Subscribers see the status change live.

6 Implementations

The spec lists four platforms that publish kind 38383 order events:

  • Mostro — a Lightning P2P daemon; see the Mostro protocol specification.
  • @lnp2pBot — a Telegram bot for Lightning P2P trades.
  • Robosats — added NIP-69 order publishing in a dedicated pull request.
  • Peach Bitcoin — via a Nostr announcer bot.

The spec also references two earlier designs: the messages specification of the original peer-to-peer NIP proposal (a draft 31001.md file, linked at a fixed old commit) and the n3xB exchange protocol.

7 Version History

The proposal started in June 2024 and received its NIP number in November 2024. Notable changes, newest first:

  • 20252025-11Added order expiration support: the expires_at and expiration tags.
  • 20252025-07Added Peach Bitcoin to the implementations list.
  • 20252025-02 / 2025-04Repository-wide fixes to references, titles, and descriptions.
  • 20242024-11The proposal received the number NIP-69 and entered the README index.
  • 20242024-07Added more optional tags and small fixes.
  • 20242024-06Added the source tag.
  • Origin2024-06-27Francisco Calderón proposed "a very simple spec for p2p events".

Original Proposal - Credits

AuthorFrancisco Calderón
First commit2024-06-27 — "Proposal of a very simple spec for p2p events" (numbered NIP-69 on 2024-11-04)
Statusdraft - optional
Source69.md on GitHub - full change history

8 See Also

  • NIP-01 — addressable events, tag indexing, and the REQ filters used to query the order book.
  • NIP-40 — the expiration tag; relays delete the event after the given timestamp.
  • NIP-15 — the marketplace spec for fixed-price product listings; NIP-69 covers P2P currency trades instead.
  • NIP-99 — classified listings; a related but more general buy/sell format.