Overview
In order to have a shared order's book, Mostro daemon send Addressable Events with 38383
as event kind
, you can find more details about that specific event here
Communication between users and Mostro
All messages from/to Mostro should be Gift wrap Nostr events, the content
of the rumor
event should be a nip44 encrypted JSON-serialized string (with no white space or line breaks) of the following structure:
- Wrapper: Wrapper of the message
version
: Version of the protocol, currently1
id
: (optional) Wrapper Idrequest_id
: (optional) Mostro daemon should send back this same id in the responsetrade_index
: (optional) This field is used by users who wants to maintain reputation, it should be the index of the trade in the user's trade history- action: Action to be performed by Mostro daemon
- payload (optional): Payload of the message, this field is optional and depends on the action
These fields are relative to the wrapper, here an example of a fiat-sent
Order message, in this case id
is the Order Id:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"request_id": "12345",
"trade_index": 1,
"action": "fiat-sent",
"payload": null
}
}
Key management
It is required to read NIP-59 (gift wrap) and NIP-06 to fully understand this document
Mostro clients should implement nip59 which creates newly fresh keys on each message to Mostro, but the key management is a bit more complex, here we will explain how to manage keys in Mostro clients.
Objectives:
- Facilitate portability by using a deterministic key generation mechanism based on NIP-06.
- Prevent users from mistakenly entering key material already used in other Nostr social media apps.
- Rotate keys for every trade.
When a user started a Mostro client for first time, the client should create a new mnemonic seed phrase which is the only information users will need to share with other client to have the same Mostro session
. Mostro clients should use the derivation path m/44'/1237'/38383'/0/0
.
Clients will always use the first key (zero) m/44'/1237'/38383'/0/0
to identify itself with mostrod, users who wants to maintain reputation can send an event to Mostro signed with the zero
key, the identity key, used to update their rating, for every new order created or taken the client will start deriving new keys from m/44'/1237'/38383'/0/1
, users who don't want to maintain reputation simply don't send the identity key to mostrod, let's see it in more detail with an example:
-
Alice starts a Mostro client for first time, at that moment the client creates a new mnemonic seed phrase and derive two keys, the identity key (index
0
) and the next trade key with index1
m/44'/1237'/38383'/0/1
, we will use identiy key to sign the gift wrap seal event and the trade key to sign the first element of the content of the rumor event. -
Alice wants to buy some bitcoin and take a sell order, the client send a message in a Gift wrap Nostr event to mostrod with the seal signed with index
0
key and in the rumor we should demostrate we own the trade key (index1
), let's see atake-sell
example in an unencrypted gift wrap event:
// external wrap layer
{
"id": "<id>",
"kind": 1059,
"pubkey": "<Buyer's ephemeral pubkey>",
"content": {
// seal
"id": "<seal's id>",
"pubkey": "<index 0 pubkey (identity key)>",
"content": {
// rumor
"id": "<rumor's id>",
"pubkey": "<Index 1 pubkey>",
"kind": 1,
"content": [
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"trade_index": 1,
"action": "take-sell",
"payload": null
}
},
"<index 1 signature of the sha256 hash of the serialized first element of content>"
],
"created_at": 1691518405,
"tags": []
},
"kind": 13,
"created_at": 1686840217,
"tags": [],
"sig": "<index 0 pubkey (identity key) signature>"
},
"tags": [["p", "<Mostro's pubkey>"]],
"created_at": 1234567890,
"sig": "<Buyer's ephemeral pubkey signature>"
}
- After finish the deal the rate each other.
Then Alice wants to create a new buy order:
- The client derives the next key, new key is index
2
(m/44'/1237'/38383'/0/2
) and send a message in a Gift wrap Nostr event to mostrod with the seal signed with index0
key, but let's see the complete example with a full unencrypted gift wrap:
// external wrap layer
{
"id": "<id>",
"kind": 1059,
"pubkey": "<Buyer's ephemeral pubkey>",
"content": {
// seal
"id": "<seal's id>",
"pubkey": "<index 0 pubkey (identity key)>",
"content": {
// rumor
"id": "<rumor's id>",
"pubkey": "<Index 2 pubkey>",
"kind": 1,
"content": [
{
"order": {
"version": 1,
"trade_index": 2,
"action": "new-order",
"payload": {
"order": {
"kind": "buy",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": 1691518405
}
}
}
},
"<index 2 signature of the sha256 hash of the serialized first element of content>"
],
"created_at": 1691518405,
"tags": []
},
"kind": 13,
"created_at": 1686840217,
"tags": [],
"sig": "<index 0 pubkey (identity key) signature>"
},
"tags": [["p", "<Mostro's pubkey>"]],
"created_at": 1234567890,
"sig": "<Buyer's ephemeral pubkey signature>"
}
Now Alice waits for some seller to take her order, mostrod will show Alice's reputation but not Alice pubkey.
Full privacy mode
Clients must offer a more private version where the client never send the identity key to mostrod, in that case mostrod can't link orders to users, the tradeoff is that users who choose this option cannot have a reputation, let's see a take-sell
example in an unencrypted gift wrap event:
// external wrap layer
{
"id": "<id>",
"kind": 1059,
"pubkey": "<Buyer's ephemeral pubkey>",
"content": {
// seal
"id": "<seal's id>",
"pubkey": "<index N pubkey (trade key)>",
"content": {
// rumor
"id": "<rumor's id>",
"pubkey": "<index N pubkey (trade key)>",
"kind": 1,
"content": [
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
// "trade_index": 1, // not needed
"action": "take-sell",
"payload": null
}
},
null
],
"created_at": 1691518405,
"tags": []
},
"kind": 13,
"created_at": 1686840217,
"tags": [],
"sig": "<index N pubkey (trade key) signature>"
},
"tags": [["p", "<Mostro's pubkey>"]],
"created_at": 1234567890,
"sig": "<Buyer's ephemeral pubkey signature>"
}
Creating a new sell order
To create a new sell order the user should send a Gift wrap Nostr event to Mostro, the rumor event should have the following rumor's content:
{
"order": {
"version": 1,
"action": "new-order",
"payload": {
"order": {
"kind": "sell",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"min_amount": null,
"max_amount": null,
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": 0
}
}
}
}
Let's explain some of the fields:
- kind:
sell
orbuy
- status: Is always
pending
when creating a new order - amount: 0 for when we want to sell with at market price, otherwise the amount in satoshis
- created_at: No need to send the correct unix timestamp, Mostro will replace it with the current time
The event to send to Mostro would look like this:
{
"id": "<Event id>",
"kind": 1059,
"pubkey": "<Seller's ephemeral pubkey>",
"content": "<sealed-rumor-content>",
"tags": [["p", "Mostro's pubkey"]],
"created_at": 1234567890,
"sig": "<Signature of ephemeral pubkey>"
}
Confirmation message
Mostro will send back a nip59 event as a confirmation message to the user like the following (unencrypted rumor's content example):
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "new-order",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "sell",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": 1698870173
}
}
}
}
Mostro publishes this order as an event kind 38383
with status pending
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"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"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Creating a new sell range order
To create a new range order the user should send a Gift wrap Nostr event to Mostro with the following rumor's content:
{
"order": {
"version": 1,
"action": "new-order",
"payload": {
"order": {
"kind": "sell",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"min_amount": 10,
"max_amount": 20,
"fiat_amount": 0,
"payment_method": "face to face",
"premium": 1,
"created_at": 0
}
}
}
}
Here we have two new fields, min_amount
and max_amount
, to define the range of the order. The fiat_amount
field is set to 0 to indicate that the order is for a range of amounts.
When a taker takes the order, the amount will be set on the message.
Confirmation message
Mostro will send back a nip59 event as a confirmation message to the user like the following:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "new-order",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "sell",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"min_amount": 10,
"max_amount": 20,
"fiat_amount": 0,
"payment_method": "face to face",
"premium": 1,
"created_at": 1698870173
}
}
}
}
Mostro publishes this order as an event kind 38383
with status pending
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702548701,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "pending"],
["amt", "0"],
["fa", "10", "20"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Creating a new buy order
To create a new buy order the user should send a Gift wrap Nostr event to Mostro with the following rumor's content:
{
"order": {
"version": 1,
"action": "new-order",
"payload": {
"order": {
"kind": "buy",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": 0
}
}
}
}
The nostr event will look like this:
{
"id": "<Event id>",
"kind": 1059,
"pubkey": "<Buyer's ephemeral pubkey>",
"content": "<sealed-rumor-content>",
"tags": [["p", "Mostro's pubkey"]],
"created_at": 1234567890,
"sig": "<Signature of ephemeral pubkey>"
}
Confirmation message
Mostro will send back a nip59 event as a confirmation message to the user like the following:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "new-order",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "buy",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"master_buyer_pubkey": null,
"master_seller_pubkey": null,
"buyer_invoice": null,
"created_at": 1698870173
}
}
}
}
Mostro publishes this order as an event kind 38383
with status pending
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702548701,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "buy"],
["f", "VES"],
["s", "pending"],
["amt", "0"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
After a seller takes this order Mostro will request an invoice from the buyer, Mostro will pay the buyer's invoice when the seller releases the funds.
Creating the order with a lightning invoice
There are two ways where the buyer can create the order adding the invoice:
-
If the buyer already knows the amount, e.g. buyer wants to buy 15000 sats with 10 euros, in this case the buyer knows from the beginning that the invoice should have amount 15000 - Mostro's fee, instead of the user doing the calculation, the client must do it and in some cases create the invoice with the right amount.
-
If the buyer don't know the amount, e.g. buyer wants to buy sats with 10 euros, in this case the buyer can add an amountless invoice and Mostro will pay it with the market price amount - Mostro's fee automatically.
Creating a new order
Creating buy order with a lightning address would make the process way faster and easy going, to acomplish the buyer should send a Gift wrap Nostr event to Mostro with the following rumor's content:
{
"order": {
"version": 1,
"action": "new-order",
"payload": {
"order": {
"kind": "buy",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"buyer_invoice": "mostro_p2p@ln.tips",
"created_at": 0
}
}
}
}
The nostr event will look like this:
{
"id": "<Event id>",
"kind": 1059,
"pubkey": "<Buyer's ephemeral pubkey>",
"content": "<sealed-rumor-content>",
"tags": [["p", "Mostro's pubkey"]],
"created_at": 1234567890,
"sig": "<Signature of ephemeral pubkey>"
}
Confirmation message
Mostro will send back a nip59 event as a confirmation message to the user like the following:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "new-order",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "buy",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"master_buyer_pubkey": null,
"master_seller_pubkey": null,
"buyer_invoice": "mostro_p2p@ln.tips",
"created_at": 1698870173
}
}
}
}
Mostro publishes this order as an event kind 38383
with status pending
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702548701,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "buy"],
["f", "VES"],
["s", "pending"],
["amt", "0"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
After a seller takes this order Mostro will not ask for an invoice to the buyer, Mostro will get the buyer's invoice and paid it when the seller releases the funds.
Listing Orders
Mostro publishes new orders with event kind 38383
and status pending
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"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"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Clients can query this events by nostr event kind 38383
, nostr event author, order status (s
), order kind (k
), order currency (f
), type (z
)
Taking a sell order
If the order amount is 0
the buyer don't know the exact amount to create the invoice, buyer will send a message in a Gift wrap Nostr event to Mostro with the following rumor's content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "take-sell",
"payload": null
}
}
The event to send to Mostro would look like this:
{
"id": "<Event id>",
"kind": 1059,
"pubkey": "<Ephemeral pubkey>",
"content": "<sealed-rumor-content>",
"tags": [["p", "Mostro's pubkey"]],
"created_at": 1234567890,
"sig": "<Signature of ephemeral pubkey>"
}
Mostro response
In order to continue the buyer needs to send a lightning network invoice to Mostro, in this case the amount of the order is 0
, so Mostro will need to calculate the amount of sats for this order, then Mostro will send back a message asking for a LN invoice indicating the correct amount of sats that the invoice should have, here the rumor's content of the message:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "add-invoice",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"buyer_pubkey": null,
"seller_pubkey": null
}
}
}
}
Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to waiting-buyer-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "waiting-buyer-invoice"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Buyer sends LN invoice
The buyer sends a Gift wrap Nostr event to Mostro with the lightning invoice, the action should be the same the buyer just received in the last message from Mostro (add-invoice
), here the rumor's content of the event for an invoice with no amount:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "add-invoice",
"payload": {
"payment_request": [
null,
"lnbcrt1pn9dvx0pp5935mskms2uf8wx90m8dlr60ytwn5vxy0e65ls42h7y7exweyvekqdqqcqzzsxqyz5vqsp5xjmllv4ta7jkuc5nfgqp8qjc3amzfewmlycpkkggr7q2y5mjfldq9qyyssqncpf3vm8hwujutqc99f0vy45zh8es54mn6u99q9t6rwm0q80dxszskzrp24y46lxqkc7ly9p80t6lalc8x8xhsn49yhy70a7wqyygugpv7chqs",
3922
]
}
}
}
If the invoice includes an amount, the last element of the payment_request
array should be set to null
.
Mostro response
Mostro send a Gift wrap Nostr event to the buyer with a wrapped order
in the rumor's content, it would look like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "waiting-seller-to-pay",
"payload": null
}
}
Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to waiting-payment
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "waiting-payment"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Taking a sell order with a lightning address
The buyer can use a lightning address to receive funds and avoid to manually create and send lightning invoices on each trade, to acomplish this the buyer will send a message in a Gift wrap Nostr event to Mostro with the following rumor's content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "take-sell",
"payload": {
"payment_request": [null, "mostro_p2p@ln.tips"]
}
}
}
The event to send to Mostro would look like this:
{
"id": "<Event id>",
"kind": 1059,
"pubkey": "<Ephemeral pubkey>",
"content": "<sealed-rumor-content>",
"tags": [["p", "Mostro's pubkey"]],
"created_at": 1234567890,
"sig": "<Signature of ephemeral pubkey>"
}
Mostro response
Mostro send a Gift wrap Nostr event to the buyer with a wrapped order
in the rumor's content, it would look like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "waiting-seller-to-pay",
"payload": null
}
}
Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to waiting-payment
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "waiting-payment"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Taking a sell range order
If the order fiat amount is a range like 10-20
the buyer must indicate a fiat amount to take the order, buyer will send a message in a Gift wrap Nostr event to Mostro with the following rumor's content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "take-sell",
"payload": {
"amount": 15
}
}
}
Mostro response
In order to continue the buyer needs to send a lightning network invoice to Mostro, in this case the amount of the order is 0
, so Mostro will need to calculate the amount of sats for this order, then Mostro will send back a message asking for a LN invoice indicating the correct amount of sats that the invoice should have, here the rumor's content of the message:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "add-invoice",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"amount": 7851,
"fiat_code": "VES",
"min_amount": 10,
"max_amount": 20,
"fiat_amount": 15,
"payment_method": "face to face",
"premium": 1,
"master_buyer_pubkey": null,
"master_seller_pubkey": null,
"buyer_invoice": null,
"created_at": null,
"expires_at": null
}
}
}
}
Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to waiting-buyer-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "waiting-buyer-invoice"],
["amt", "7851"],
["fa", "15"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Using a lightning address
The buyer can use a lightning address to receive funds and avoid to create and send lightning invoices on each trade, with a range order we set the fiat amount as the third element of the payment_request
array, to acomplish this the buyer will send a message in a Gift wrap Nostr event to Mostro with the following rumor's content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "take-sell",
"payload": {
"payment_request": [null, "mostro_p2p@ln.tips", 15]
}
}
}
Taking a buy order
To take an order the seller will send to Mostro a message with the following rumor's content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "take-buy",
"payload": null
}
}
The event to send to Mostro would look like this:
{
"id": "<Event id>",
"kind": 1059,
"pubkey": "<Seller's ephemeral pubkey>",
"content": "<sealed-rumor-content>",
"tags": [["p", "Mostro's pubkey"]],
"created_at": 1234567890,
"sig": "<Signature of ephemeral pubkey>"
}
Mostro response
Mostro respond to the seller with a message with the following content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "pay-invoice",
"payload": {
"payment_request": [
{
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "buy",
"status": "waiting-payment",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": 1698957793
},
"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e"
]
}
}
}
Mostro updates the event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to WaitingPayment
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "waiting-payment"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
And send a message to the buyer with the following content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "waiting-seller-to-pay",
"payload": null
}
}
Seller pays LN invoice
After seller pays the hold invoice Mostro send a message to the seller with the following content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "waiting-buyer-invoice",
"payload": null
}
}
Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to waiting-buyer-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "waiting-buyer-invoice"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
And sends a message to the buyer with the following content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "add-invoice",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"status": "waiting-buyer-invoice",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": null
}
}
}
}
Buyer sends LN invoice
Buyer sends the LN invoice to Mostro.
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "add-invoice",
"payload": {
"payment_request": [
null,
"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e"
]
}
}
}
Now both parties have an active
order and they can keep going with the trade.
Finally Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to active
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "active"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Taking a buy range order
If the order fiat amount is a range like 10-20
the seller must indicate a fiat amount to take the order, seller will send a message in a Gift wrap Nostr event to Mostro with the following rumor's content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "take-buy",
"payload": {
"amount": 15
}
}
}
Mostro response
Response is the same as we explained in the Taking a buy order section.
Seller pays hold invoice
When the seller is the maker and the order was taken by a buyer, Mostro will send to the seller a message asking to pay the hold invoice, the rumor's content of the message will look like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "pay-invoice",
"payload": {
"payment_request": [
{
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "sell",
"status": "waiting-payment",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": 1698937797
},
"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e"
]
}
}
}
After the hold invoice is paid and the buyer already sent the invoice to receive the sats, Mostro will send a new message to seller with the following rumor's content:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "buyer-took-order",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "sell",
"status": "active",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"master_buyer_pubkey": "<Buyer's trade pubkey>",
"master_seller_pubkey": "<Seller's trade pubkey>",
"buyer_invoice": null,
"created_at": 1698937797
}
}
}
}
Mostro also send a message to the buyer, this way they can both write to each other in private, this message would look like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "hold-invoice-payment-accepted",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "sell",
"status": "active",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"master_buyer_pubkey": "<Buyer's trade pubkey>",
"master_seller_pubkey": "<Seller's trade pubkey>",
"buyer_invoice": null,
"created_at": 1698937797
}
}
}
}
Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to active
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "active"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
If the buyer didn't sent the invoice yet
Mostro send this message to the seller:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "waiting-buyer-invoice",
"payload": null
}
}
And this message to the buyer:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "add-invoice",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "sell",
"status": "waiting-buyer-invoice",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": 1698937797
}
}
}
}
And updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to waiting-buyer-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "waiting-buyer-invoice"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Now buyer sends the invoice to Mostro:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "add-invoice",
"payload": {
"payment_request": [
null,
"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e"
]
}
}
}
And both parties receives each other pubkeys to start a direct conversation
Fiat sent
After the buyer sends the fiat money to the seller, the buyer should send a message in a Gift wrap Nostr event to Mostro indicating that the fiat money was sent, the rumor's content would look like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "fiat-sent",
"payload": null
}
}
The event to send to Mostro would look like this:
{
"id": "<Event id>",
"kind": 1059,
"pubkey": "<Ephemeral pubkey>",
"content": "<sealed-rumor-content>",
"tags": [["p", "Mostro's pubkey"]],
"created_at": 1234567890,
"sig": "<Signature of ephemeral pubkey>"
}
Mostro response
Mostro send messages to both parties confirming fiat-sent
action and sending again the counterpart pubkey, here an example of the message to the buyer:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "fiat-sent-ok",
"payload": {
"Peer": {
"pubkey": "<Seller's trade pubkey>"
}
}
}
}
And here an example of the message from Mostro to the seller:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"pubkey": "<Seller's trade pubkey>",
"action": "fiat-sent-ok",
"payload": {
"Peer": {
"pubkey": "<Buyer's trade pubkey>"
}
}
}
}
Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to fiat-sent
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "fiat-sent"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
release
After confirming the buyer sent the fiat money, the seller should send a message to Mostro indicating that sats should be delivered to the buyer, the rumor's content of the message will look like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "release",
"payload": null
}
}
Mostro response
Here an example of the Mostro response to the seller:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "hold-invoice-payment-settled",
"payload": null
}
}
And a message to the buyer to let him know that the sats were released:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "released",
"payload": null
}
}
Buyer receives sats
Right after seller release sats Mostro will try to pay the buyer's lightning invoice, if the payment is successful Mostro will send a message to the buyer indicating that the purchase was completed:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "purchase-completed",
"payload": null
}
}
Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to settled-hold-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "settled-hold-invoice"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Mostro will then attempt to pay the buyer's invoice, if the payment successds Mostro updates the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to success
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "success"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
User rating
After a successful trade Mostro send a Gift wrap Nostr event to both parties to let them know they can rate each other, here an example how the message look like:
{
"order": {
"version": 1,
"id": "7e44aa5d-855a-4b17-865e-8ca3834a91a3",
"action": "rate",
"payload": null
}
}
After a Mostro client receive this message, the user can rate the other party, the rating is a number between 1 and 5, to rate the client must receive user's input and create a new Gift wrap Nostr event to send to Mostro with this content:
{
"order": {
"version": 1,
"id": "7e44aa5d-855a-4b17-865e-8ca3834a91a3",
"action": "rate-user",
"payload": {
"rating_user": 5 // User input
}
}
}
Confirmation message
If Mostro received the correct message, it will send back a confirmation message to the user with the action rate-received
:
{
"order": {
"version": 1,
"id": "7e44aa5d-855a-4b17-865e-8ca3834a91a3",
"action": "rate-received",
"payload": {
"rating_user": 5
}
}
}
Mostro updates the addressable rating event, in this event the d
tag will be the user pubkey <Seller's trade pubkey>
and looks like this:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702637077,
"kind": 38383,
"tags": [
["d", "<Seller's trade pubkey>"],
["total_reviews", "1"],
["total_rating", "2"],
["last_rating", "1"],
["max_rate", "2"],
["min_rate", "5"],
["z", "rating"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Cancel Order
A user can cancel an order created by himself and with status pending
sending action cancel
, the rumor's content of the message will look like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "cancel",
"payload": null
}
}
Mostro response
Mostro will send a message with action cancel
confirming the order was canceled, here an example of rumor's content of the message:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "canceled",
"payload": null
}
}
Mostro updates the parameterized replaceable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to canceled
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "canceled"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["network", "mainnet"],
["layer", "lightning"],
["expiration", "1719391096"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Cancel cooperatively
A user can cancel an active
order, but will need the counterparty to agree, let's look at an example where the seller initiates a cooperative cancellation:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "cancel",
"payload": null
}
}
Mostro will send this message to the seller:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "cooperative-cancel-initiated-by-you",
"payload": null
}
}
And this message to the buyer:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "cooperative-cancel-initiated-by-peer",
"payload": null
}
}
Mostro updates the parameterized replaceable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to cooperatively-canceled
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "cooperatively-canceled"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
The buyer can accept the cooperative cancellation sending this message:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "cancel",
"payload": null
}
}
And Mostro will send this message to both parties:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "cooperative-cancel-accepted",
"payload": null
}
}
Dispute
A use can start a dispute in an order with status active
or fiat-sent
sending action dispute
, here is an example where the seller initiates a dispute:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "dispute",
"payload": null
}
}
Mostro response
Mostro will send this message to the seller:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "dispute-initiated-by-you",
"payload": {
"dispute": "efc75871-2568-40b9-a6ee-c382d4d6de01"
}
}
}
And here is the message to the buyer:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "dispute-initiated-by-peer",
"payload": {
"dispute": "efc75871-2568-40b9-a6ee-c382d4d6de01"
}
}
}
Mostro will not update the addressable event with d
tag ede61c96-4c13-4519-bf3a-dcf7f1e9d842
to change the status to dispute
, this is because the order is still active, the dispute is just a way to let the admins and the other party know that there is a problem with the order.
Mostro send a addressable event to show the dispute
Here is an example of the event sent by Mostro:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1703016565,
"kind": 38383,
"tags": [
["d", "efc75871-2568-40b9-a6ee-c382d4d6de01"],
["s", "initiated"],
["y", "mostrop2p"],
["z", "dispute"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Mostro admin will see the dispute and can take it using the dispute Id
from d
tag, in this case efc75871-2568-40b9-a6ee-c382d4d6de01
.
{
"dispute": {
"version": 1,
"id": "efc75871-2568-40b9-a6ee-c382d4d6de01",
"action": "admin-take-dispute",
"payload": null
}
}
Mostro will send a confirmation message to the admin with the order details:
{
"dispute": {
"version": 1,
"id": "efc75871-2568-40b9-a6ee-c382d4d6de01",
"action": "admin-took-dispute",
"payload": {
"order": {
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"kind": "sell",
"status": "active",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"master_buyer_pubkey": "<Buyer's trade pubkey>",
"master_seller_pubkey": "<Seller's trade pubkey>",
"buyer_invoice": "lnbcrt11020n1pjcypj3pp58m3d9gcu4cc8l3jgkpfn7zhqv2jfw7p3t6z3tq2nmk9cjqam2c3sdqqcqzzsxqyz5vqsp5mew44wzjs0a58d9sfpkrdpyrytswna6gftlfrv8xghkc6fexu6sq9qyyssqnwfkqdxm66lxjv8z68ysaf0fmm50ztvv773jzuyf8a5tat3lnhks6468ngpv3lk5m7yr7vsg97jh6artva5qhd95vafqhxupyuawmrcqnthl9y",
"created_at": 1698870173
}
}
}
}
Also Mostro will broadcast a new addressable dispute event to update the dispute status
to in-progress
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1703020540,
"kind": 38383,
"tags": [
["d", "efc75871-2568-40b9-a6ee-c382d4d6de01"],
["s", "in-progress"],
["y", "mostrop2p"],
["z", "dispute"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Listing Disputes
Mostro publishes new disputes with event kind 38383
and status initiated
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1703016565,
"kind": 38383,
"tags": [
["d", "efc75871-2568-40b9-a6ee-c382d4d6de01"],
["s", "initiated"],
["y", "mostrop2p"],
["z", "dispute"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Clients can query this events by nostr event kind 38383
, nostr event author, dispute status (s
), type (z
)
Settle order
An admin can settle an order, most of the time this is done when admin is solving a dispute, for this the admin will need to send an order
message to Mostro with action admin-settle
with the id
of the order like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "admin-settle",
"payload": null
}
}
Mostro response
Mostro will send this message to the both parties buyer/seller and to the admin:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "admin-settled",
"payload": null
}
}
Mostro updates addressable events
Mostro will publish two addressable events, one for the order to update the status to settled-by-admin
, this means that the hold invoice paid by the seller was settled:
[
"EVENT",
"RAND",
{
"id": "3d74ce3f10096d163603aa82beb5778bd1686226fdfcfba5d4c3a2c3137929ea",
"pubkey": "<Mostro's pubkey>",
"created_at": 1703260182,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "settled-by-admin"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
And updates addressable dispute event with status settled
:
[
"EVENT",
"RAND",
{
"id": "098e8622eae022a79bc793984fccbc5ea3f6641bdcdffaa031c00d3bd33ca5a0",
"pubkey": "<Mostro's pubkey>",
"created_at": 1703274022,
"kind": 38383,
"tags": [
["d", "efc75871-2568-40b9-a6ee-c382d4d6de01"],
["s", "settled"],
["y", "mostrop2p"],
["z", "dispute"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Payment of the buyer's invoice
At this point Mostro is trying to pay the buyer's invoice, right after complete the payment Mostro will update the status of the order addressable event to success
:
[
"EVENT",
"RAND",
{
"id": "6170892aca6a73906142e58a9c29734d49b399a3811f6216ce553b4a77a8a11e",
"pubkey": "<Mostro's pubkey>",
"created_at": 1703274032,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "success"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Cancel order
An admin can cancel an order, most of the time this is done when admin is solving a dispute, for this the admin will need to send an order
message to Mostro with action admin-cancel
with the id
of the order like this:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "admin-cancel",
"payload": null
}
}
Mostro response
Mostro will send this message to the both parties buyer/seller and to the admin:
{
"order": {
"version": 1,
"id": "ede61c96-4c13-4519-bf3a-dcf7f1e9d842",
"action": "admin-canceled",
"payload": null
}
}
Mostro updates addressable events
Mostro will publish two addressable events, one for the order to update the status to canceled-by-admin
, this means that the hold invoice was canceled and the seller's funds were returned:
[
"EVENT",
"RAND",
{
"id": "3d74ce3f10096d163603aa82beb5778bd1686226fdfcfba5d4c3a2c3137929ea",
"pubkey": "<Mostro's pubkey>",
"created_at": 1703260182,
"kind": 38383,
"tags": [
["d", "ede61c96-4c13-4519-bf3a-dcf7f1e9d842"],
["k", "sell"],
["f", "VES"],
["s", "canceled-by-admin"],
["amt", "7851"],
["fa", "100"],
["pm", "face to face"],
["premium", "1"],
["y", "mostrop2p"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
And updates addressable dispute event with status seller-refunded
:
[
"EVENT",
"RAND",
{
"id": "098e8622eae022a79bc793984fccbc5ea3f6641bdcdffaa031c00d3bd33ca5a0",
"pubkey": "<Mostro's pubkey>",
"created_at": 1703274022,
"kind": 38383,
"tags": [
["d", "efc75871-2568-40b9-a6ee-c382d4d6de01"],
["s", "seller-refunded"],
["y", "mostrop2p"],
["z", "dispute"]
],
"content": "",
"sig": "<Mostro's signature>"
}
]
Admin add solver
Solvers are users appointed by the Mostro administrator and are responsible for resolving disputes.
The administrator can add or remove them at any time.
The administrator can also solve disputes.
To add a solver the admin will need to send an order
message to Mostro with action admin-add-solver
:
{
"order": {
"version": 1,
"action": "admin-add-solver",
"payload": {
"text_message": "npub1qqq884wtp2jn96lqhqlnarl4kk3rmvrc9z2nmrvqujx3m4l2ea5qd5d0fq"
}
}
}
Mostro response
Mostro will send this message to the admin:
{
"order": {
"version": 1,
"action": "admin-add-solver",
"payload": null
}
}
Actions
mostro_core::Action
Action is used to identify each message between Mostro and users
You can see details in mostro core documentation
Message suggestions for some actions
Here are suggestions for messages that clients could show to users when they receive certain actions. Clients can customize these messages to their convenience, as well as translate them, add emojis or any other modifications they consider necessary to provide a good user experience. Clients must complete the information that is in monospace
format.
-
new-order: Your offer has been published! Please wait until another user picks your order. It will be available for
expiration_hours
hours. You can cancel this order before another user picks it up by executing:cancel
. -
canceled: You have cancelled the order ID:
id
! -
pay-invoice: Please pay this hold invoice of
amount
Sats forfiat_code
fiat_amount
to start the operation. If you do not pay it withinexpiration_seconds
the trade will be cancelled. -
add-invoice: Please send me an invoice for
amount
satoshis equivalent tofiat_code
fiat_amount
. This is where I’ll send the funds upon completion of the trade. If you don't provide the invoice withinexpiration_seconds
this trade will be cancelled. -
waiting-seller-to-pay: Please wait a bit. I've sent a payment request to the seller to sends the Sats for the order ID
id
. Once the payment is made, I'll connect you both. If the seller doesn’t complete the payment withinexpiration_seconds
minutes the trade will be cancelled. -
waiting-buyer-invoice: Payment received! Your Sats are now "held" in your own wallet. Please wait a bit. I've requested the buyer to provide an invoice. Once they do, I 'll connect you both. If they does not do so within
expiration_seconds
your Sats will be available at your wallet again and the trade will be cancelled. -
buyer-invoice-accepted: Invoice has been successfully saved!
-
hold-invoice-payment-accepted: Get in touch with the seller, this is their npub
seller-npub
to get the details on how to send the fiat money for the orderid
, you must sendfiat_code
fiat_amount
usingpayment_method
. Once you send the fiat money, please let me know withfiat-sent
. -
buyer-took-order: Get in touch with the buyer, this is their npub
buyer-npub
to inform them how to send youfiat_code
fiat_amount
throughpayment_method
. I will notify you once the buyer indicates the fiat money has been sent. Afterward, you should verify if it has arrived. If the buyer does not respond, you can initiate a cancellation or a dispute. Remember, an administrator will NEVER contact you to resolve your order unless you open a dispute first. -
fiat-sent-ok:
- To the buyer: I have informed to
seller-npub
that you have sent the fiat money. When the seller confirms they have received your fiat money, they should release the funds. If they refuse, you can open a dispute. - To the seller:
buyer-npub
has informed that they have sent you the fiat money. Once you confirm receipt, please release the funds. After releasing, the money will go to the buyer and there will be no turning back, so only proceed if you are sure. If you want to release the Sats to the buyer, send merelease-order-message
.
- To the buyer: I have informed to
-
released:
seller-npub
has already released the Sats! Expect your invoice to be paid any time. Remember your wallet needs to be online to receive through the Lightning Network. -
purchase-completed: Your satoshis purchase has been completed successfully. I have paid your invoice, enjoy sound money!
-
hold-invoice-payment-settled: Your Sats sale has been completed after confirming the payment from
buyer-npub
. -
rate: Please qualify your counterparty
-
rate-received: Rating successfully saved!
-
cooperative-cancel-initiated-by-you: You have initiated the cancellation of the order ID:
id
. Your counterparty must agree to the cancellation too. If they do not respond, you can open a dispute. Note that no administrator will contact you regarding this cancellation unless you open a dispute first. -
cooperative-cancel-initiated-by-peer: Your counterparty wants to cancel order ID:
id
. Note that no administrator will contact you regarding this cancellation unless you open a dispute first. If you agree on such cancellation, please send mecancel-order-message
. -
cooperative-cancel-accepted: Order
id
has been successfully cancelled! -
dispute-initiated-by-you: You have initiated a dispute for order Id:
id
. A solver will be assigned to your dispute soon. Once assigned, I will share their npub with you, and only they will be able to assist you. You may contact the solver directly, but if they reach out first, please ask them to provide the token for your dispute. Your dispute token is:user-token
. -
dispute-initiated-by-peer: Your counterparty has initiated a dispute for order Id:
${orderId}.
A solver will be assigned to your dispute soon. Once assigned, I will share their npub with you, and only they will be able to assist you. You may contact the solver directly, but if they reach out first, please ask them to provide the token for your dispute. Your dispute token is:user-token
. -
admin-took-dispute:
- To the admin: Here are the details of the dispute order you have taken:
details
. You need to determine which user is correct and decide whether to cancel or complete the order. Please note that your decision will be final and cannot be reversed. - To the users: The solver
admin-npub
will handle your dispute. You can contact them directly, but if they reach out to you first, make sure to ask them for your dispute token.
- To the admin: Here are the details of the dispute order you have taken:
-
admin-canceled:
- To the admin: You have cancelled the order ID:
id
! - To the users: Admin has cancelled the order ID:
id
!
- To the admin: You have cancelled the order ID:
-
admin-settled:
- To the admin: You have completed the order ID:
id
! - To the users: Admin has completed the order ID:
id
!
- To the admin: You have completed the order ID:
-
is-not-your-dispute: This dispute was not assigned to you!
-
not-found: Dispute not found.
-
payment-failed: I tried to send you the Sats but the payment of your invoice failed, I will try
payment_attempts
more times inpayment_retries_interval
minutes window. Please ensure your node/wallet is online. -
invoice-updated: Invoice has been successfully updated!
-
hold-invoice-payment-canceled: The invoice was cancelled, your Sats will be available at your wallet again.
-
cant-do: You are not allowed to
action
for this order! -
admin-add-solver: You have successfully added to the solver
npub
. -
is-not-your-order: You did not create this order and are not authorized to
action
it. -
not-allowed-by-status: You are not allowed to
action
because order Idid
status isorder-status
. -
out-of-range-fiat-amount: The requested amount is incorrect and may be outside the acceptable range. The minimum is
min_amount
and the maximum ismax_amount
. -
incorrect-invoice-amount:
- If the buyer previously had sent the
new-order
action:
An invoice with non-zero amount was receive for the new order. Please send an invoice with a zero amount or no invoice at all. - If the buyer previously sent the
add-invoice
action:
The amount stated in the invoice is incorrect. Please send an invoice with an amount ofamount
satoshis, an invoice without an amount, or a lightning address.
- If the buyer previously had sent the
-
invalid-sats-amount: That specified Sats amount is invalid.
-
out-of-range-sats-amount: The allowed Sats amount for this Mostro is between min
min_order_amount
and maxmax_order_amount
. Please enter an amount within this range.
Peer-to-peer Order events. NIP-69
Abstract
Peer-to-peer (P2P) platforms have seen an upturn in recent years, while having more and more options is positive, in the specific case of p2p, having several options contributes to the liquidity split, meaning sometimes there's not enough assets available for trading. If we combine all these individual solutions into one big pool of orders, it will make them much more competitive compared to centralized systems, where a single authority controls the liquidity.
This NIP defines a simple standard for peer-to-peer order events, which enables the creation of a big liquidity pool for all p2p platforms participating.
The event
Events are addressable events and use 38383
as event kind, a p2p event look like this:
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"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"],
["expiration", "1719391096"],
["y", "lnp2pbot"],
["z", "order"]
],
"content": "",
"sig": "<Mostro's signature>"
}
Tags
d
< Order ID >: A unique identifier for the order.k
< Order type >:sell
orbuy
. This specifies the type of transaction in terms of bitcoin. "sell" means selling bitcoin, while "buy" indicates buying bitcoin.f
< Currency >: The fiat asset being traded, using the ISO 4217 standard.s
< Status >:pending
,canceled
,in-progress
,success
.amt
< Amount >: The amount of Bitcoin to be traded, the amount is defined in satoshis, if0
means that the amount of satoshis will be obtained from a public API after the taker accepts the order.fa
< Fiat amount >: The fiat amount being traded, for range orders two values are expected, the minimum and maximum amount.pm
< Payment method >: The payment method used for the trade, if the order has multiple payment methods, they should be separated by a comma.premium
< Premium >: The percentage of the premium the maker is willing to pay.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 document does not define how the rating is calculated, it's up to the platform to define it.network
< Network >: The network used for the trade, it can bemainnet
,testnet
,signet
, etc.layer
< Layer >: The layer used for the trade, it can beonchain
,lightning
,liquid
, etc.name
[Name]: The name of the maker.g
[Geohash]: The geohash of the operation, it can be useful in a face to face trade.bond
[Bond]: The bond amount, the bond is a security deposit that both parties must pay.expiration
< Expiration>: The expiration date of the order (NIP-40).y
< Platform >: The platform that created the order.z
< Document >:order
.
Mandatory tags are enclosed with <tag>
, optional tags are enclosed with [tag]
.
Implementations
Currently implemented on the following platforms:
This document is inspired on
Other events published by Mostro
Each Mostro instance periodically publishes events with relevant information about its status, such as the code version it is using, the latest commit, the fees it charges, allowed exchange limits, the relays it publishes to, and much more. Below, we provide details on these events.
Mostro Instance Status
This event contains specific data about a Mostro instance. The instance is identified by the label mostro_pubkey
.
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"kind": 38383
"tags": [
[
"d",
"info-<Mostro's pubkey>"
],
[
"mostro_pubkey",
"<Mostro's pubkey>"
],
["mostro_version", "0.12.8"],
["mostro_commit_id", "69052b2adba6c006e6929afda27f041a427f58f8"],
["max_order_amount", "20000"],
["min_order_amount", "100"],
["expiration_hours", "24"],
["expiration_seconds", "900"],
["fee", "0.006"],
["pow", "0"],
["hold_invoice_expiration_window", "900"],
["hold_invoice_cltv_delta", "298"],
["invoice_expiration_window", "900"],
["y", "mostrop2p"],
["z", "info"]
],
"content": "",
"sig": "<Mostro's signature>",
"created_at": 1731701441,
}
]
Below is an explanation of the meaning of some of the labels in this event, all of which can be modified by anyone running a Mostro instance.
mostro_version
: The version of the Mostro daemon running on the instance.mostro_commit_id
: The ID of the last commit used by the instance.max_order_amount
: The maximum amount of Satoshis allowed for exchange.min_order_amount
: The minimum amount of Satoshis allowed for exchange.expiration_hours
: The maximum time, in hours, that an order can remain inpending
status before it expires.expiration_seconds
: The maximum time, in seconds, that an order can remain inwaiting-payment
orwaiting-buyer-invoice
status before being canceled or reverted topending
status.fee
: The fee percentage charged by the instance. For example, "0.006" means a 0.6% fee.pow
: The Proof of Work required of incoming events.hold_invoice_expiration_window
: The maximum time, in seconds, for the hold invoice issued by Mostro to be paid by the seller.hold_invoice_cltv_delta
: The number of blocks in which the Mostro hold invoice will expire.invoice_expiration_window
: The maximum time, in seconds, for a buyer to submit an invoice to Mostro.
Information about the Relays Where Events Are Published
The operator of a Mostro instance decides which relays the events from that instance are published to. This information can be accessed in events kind 10002, which are published by the Mostro instances.
[
"EVENT",
"RAND",
{
"tags": [
["r", "wss://relay.mostro.network/"],
["r", "wss://nostr.bilthon.dev/"]
],
"content": "",
"sig": "<Mostro's signature>",
"id": "7a31879cbb4f32b86ca535912ba568722c52845e1517468249b66f9af6eff05c",
"pubkey": "<Mostro's pubkey>",
"created_at": 1731680102,
"kind": 10002
}
]
The r
label indicates the relays through which the Mostro instance is publishing its events.