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.
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.
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:
{
"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.
| Tag | Meaning | Rule |
|---|---|---|
| d | Order ID | A unique identifier for the order. Also the addressable-event key. |
| k | Order type | sell or buy. |
| f | Currency | The asset being traded, as an ISO 4217 currency code. |
| s | Status | pending, canceled, in-progress, success, or expired. See §4. |
| amt | Amount | The amount of Bitcoin to trade, in satoshis. 0 means: get the satoshi amount from a public price API after the taker accepts the order. |
| fa | Fiat amount | The fiat amount being traded. A range order carries two values: the minimum and the maximum amount. |
| pm | Payment method | The payment method for the trade. For multiple payment methods, the spec text says: separate them with a comma. See the Tech Note below. |
| premium | Premium | The percentage of premium the maker is willing to pay. |
| network | Network | The network for the trade: mainnet, testnet, signet, etc. |
| layer | Layer | The layer for the trade: onchain, lightning, liquid, etc. |
| expires_at | Expires at | Unix timestamp. The expiration date of the order while in pending status. After this time, the status SHOULD change to expired. |
| expiration | Expiration | Unix timestamp. The expiration date of the event itself. After this time, the relay SHOULD delete the event, per NIP-40. |
| y | Platform | The platform that created the order. |
| z | Document | Always order. |
| Tag | Meaning | Rule |
|---|---|---|
| source | Source | The source of the order. It can be a URL that redirects to the order. |
| rating | Rating | The rating of the maker. This NIP does not define how the rating is calculated; each platform defines its own method. |
| name | Name | The name of the maker. |
| g | Geohash | The geohash of the operation. Useful for a face-to-face trade. |
| bond | Bond | The bond amount. The bond is a security deposit that both parties must pay. |
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.
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_attime passed while the order was stillpending.
d value and a new s tag.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.
-- 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_atandexpirationtags. - 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
sourcetag. - Origin2024-06-27Francisco Calderón proposed "a very simple spec for p2p events".
Original Proposal - Credits
| Author | Francisco Calderón |
| First commit | 2024-06-27 — "Proposal of a very simple spec for p2p events" (numbered NIP-69 on 2024-11-04) |
| Status | draft - optional |
| Source | 69.md on GitHub - full change history |
8 See Also
- NIP-01 — addressable events, tag indexing, and the
REQfilters used to query the order book. - NIP-40 — the
expirationtag; 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.