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 take-buy
order message:
{
"order": {
"version": 1,
"id": "<Order Id>",
"request_id": "123456",
"trade_index": 1,
"action": "take-buy",
"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 (trade key)>",
"kind": 1,
"content": [
{
"order": {
"version": 1,
"id": "<Order Id>",
"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 (trade key)>",
"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": "<Order Id>",
// "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",
"trade_index": 1,
"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": "<Order id>",
"action": "new-order",
"payload": {
"order": {
"id": "<Order id>",
"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", "<Order id>"],
["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",
"trade_index": 1,
"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": "<Order id>",
"action": "new-order",
"payload": {
"order": {
"id": "<Order id>",
"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", "<Order id>"],
["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",
"trade_index": 1,
"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": "<Order id>",
"action": "new-order",
"payload": {
"order": {
"id": "<Order id>",
"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", "<Order id>"],
["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",
"trade_index": 1,
"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": "<Order Id>",
"action": "new-order",
"payload": {
"order": {
"id": "<Order Id>",
"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", "<Order Id>"],
["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", "<Order Id>"],
["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": "<Order Id>",
"action": "take-sell",
"trade_index": 1,
"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": "<Order Id>",
"action": "add-invoice",
"payload": {
"order": {
"id": "<Order Id>",
"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 <Order Id>
to change the status to waiting-buyer-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"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": "<Order Id>",
"action": "waiting-seller-to-pay",
"payload": null
}
}
Mostro updates the addressable event with d
tag <Order Id>
to change the status to waiting-payment
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"action": "take-sell",
"trade_index": 1,
"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": "<Order Id>",
"action": "waiting-seller-to-pay",
"payload": null
}
}
Mostro updates the addressable event with d
tag <Order Id>
to change the status to waiting-payment
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"action": "take-sell",
"trade_index": 1,
"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": "<Order Id>",
"action": "add-invoice",
"payload": {
"order": {
"id": "<Order Id>",
"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 <Order Id>
to change the status to waiting-buyer-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"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": "<Order Id>",
"action": "take-buy",
"trade_index": 1,
"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": "<Order Id>",
"action": "pay-invoice",
"payload": {
"payment_request": [
{
"id": "<Order Id>",
"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 <Order Id>
to change the status to WaitingPayment
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"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": "<Order Id>",
"action": "waiting-buyer-invoice",
"payload": null
}
}
Mostro updates the addressable event with d
tag <Order Id>
to change the status to waiting-buyer-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"action": "add-invoice",
"payload": {
"order": {
"id": "<Order Id>",
"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": "<Order Id>",
"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 <Order Id>
to change the status to active
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"action": "take-buy",
"trade_index": 1,
"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": "<Order Id>",
"action": "pay-invoice",
"payload": {
"payment_request": [
{
"id": "<Order Id>",
"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": "<Order Id>",
"action": "buyer-took-order",
"payload": {
"order": {
"id": "<Order Id>",
"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": "<Order Id>",
"action": "hold-invoice-payment-accepted",
"payload": {
"order": {
"id": "<Order Id>",
"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 <Order Id>
to change the status to active
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"action": "waiting-buyer-invoice",
"payload": null
}
}
And this message to the buyer:
{
"order": {
"version": 1,
"id": "<Order Id>",
"action": "add-invoice",
"payload": {
"order": {
"id": "<Order Id>",
"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 <Order Id>
to change the status to waiting-buyer-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"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, message in the first element of the rumor's content would look like this:
{
"order": {
"version": 1,
"id": "<Order Id>",
"action": "fiat-sent",
"payload": null
}
}
When the maker is the buyer on a range order
In most of the cases after complete a range order, a child order needs to be created, the client is rotating keys favoring privacy so Mostro can't know which would be the next trade pubkey
of the maker, to solve this the client needs to send trade pubkey
and trade index
of the child order on the fiat-sent
message, the message looks like this:
{
"order": {
"version": 1,
"id": "<Order Id>",
"action": "fiat-sent",
"payload": {
"next_trade": ["<trade pubkey>", <trade index>]
}
}
}
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": "<Order Id>",
"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": "<Order Id>",
"pubkey": "<Seller's trade pubkey>",
"action": "fiat-sent-ok",
"payload": {
"Peer": {
"pubkey": "<Buyer's trade pubkey>"
}
}
}
}
Mostro updates the addressable event with d
tag <Order Id>
to change the status to fiat-sent
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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 message inside rumor's content will look like this:
{
"order": {
"version": 1,
"id": "<Order Id>",
"request_id": "123456",
"action": "release",
"payload": null
}
}
Mostro response
Here an example of the Mostro response to the seller:
{
"order": {
"version": 1,
"id": "<Order Id>",
"request_id": "123456",
"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": "<Order Id>",
"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": "<Order Id>",
"action": "purchase-completed",
"payload": null
}
}
Mostro updates the addressable event with d
tag <Order Id>
to change the status to settled-hold-invoice
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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 <Order Id>
to change the status to success
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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>"
}
]
Release a range order
If the order is a range order probably after release a child order would need to be created, Mostro can't know which would be the next trade pubkey
, so the client of the maker must send this information, here how the message must look like:
{
"order": {
"version": 1,
"id": "4fd93fc9-e909-4fc9-acef-9976122b5dfa",
"action": "release",
"payload": {
"next_trade": ["<trade pubkey>", <trade index>]
}
}
}
Mostro will send to the maker the newly child order created with the same trade_index
received in the payload, if the maker is the buyer the trade_index
would be the one sent in the payload of the fiat-sent
message by the buyer, the trade_index
will be used by the client to get the next key, the message will look like this:
{
"order": {
"version": 1,
"id": "4fd93fc9-e909-4fc9-acef-9976122b5dfa",
"action": "new-order",
"trade_index": <trade index>,
"request_id": "123456",
"payload": {
"order": {
"id": "4fd93fc9-e909-4fc9-acef-9976122b5dfa",
"kind": "sell",
"status": "pending",
"amount": 0,
"fiat_code": "VES",
"min_amount": <min amount>,
"max_amount": <max amount>,
"fiat_amount": 0,
"payment_method": "face to face",
"premium": 1,
"created_at": 123456789,
"expires_at": 123456789,
"buyer_token": null,
"seller_token": null
}
}
}
}
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": "<Order Id>",
"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": "<Order Id>",
"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": "<Order Id>",
"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": "<Order Id>",
"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": "<Order Id>",
"action": "canceled",
"payload": null
}
}
Mostro updates the parameterized replaceable event with d
tag <Order Id>
to change the status to canceled
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"action": "cancel",
"payload": null
}
}
Mostro will send this message to the seller:
{
"order": {
"version": 1,
"id": "<Order Id>",
"action": "cooperative-cancel-initiated-by-you",
"payload": null
}
}
And this message to the buyer:
{
"order": {
"version": 1,
"id": "<Order Id>",
"action": "cooperative-cancel-initiated-by-peer",
"payload": null
}
}
Mostro updates the parameterized replaceable event with d
tag <Order Id>
to change the status to cooperatively-canceled
:
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1702549437,
"kind": 38383,
"tags": [
["d", "<Order Id>"],
["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": "<Order Id>",
"action": "cancel",
"payload": null
}
}
And Mostro will send this message to both parties:
{
"order": {
"version": 1,
"id": "<Order Id>",
"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": "<Order Id>",
"action": "dispute",
"payload": null
}
}
Mostro response
Mostro will send this message to the seller:
{
"order": {
"version": 1,
"id": "<Order Id>",
"action": "dispute-initiated-by-you",
"payload": {
"dispute": "<Dispute Id>"
}
}
}
And here is the message to the buyer:
{
"order": {
"version": 1,
"id": "<Order Id>",
"action": "dispute-initiated-by-peer",
"payload": {
"dispute": "<Dispute Id>"
}
}
}
Mostro will not update the addressable event with d
tag <Order Id>
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", "<Dispute Id>"],
["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, here how should look the message sent by the admin:
{
"dispute": {
"version": 1,
"id": "<Dispute Id>",
"action": "admin-take-dispute",
"payload": null
}
}
Mostro will send a confirmation message to the admin with the order details:
{
"dispute": {
"version": 1,
"id": "<Dispute Id>",
"action": "admin-took-dispute",
"payload": {
"order": {
"id": "<Order Id>",
"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", "<Dispute Id>"],
["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", "<Dispute Id>"],
["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": "<Order Id>",
"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": "<Order Id>",
"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", "<Order Id>"],
["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", "<Order Id>"],
["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": "<Order Id>",
"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": "<Order Id>",
"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", "<Order Id>"],
["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 Actions
Below are suggestions for messages that clients can show to users when receiving specific actions. These messages can be customized, translated, enhanced with emojis, or modified to provide a better user experience. Clients should replace placeholders in monospace
format with the corresponding values.
Actions
-
new-order:
Your offer has been published! Please wait until another user picks your order. It will be available forexpiration_hours
hours. You can cancel this order before another user picks it up by executing:cancel
. -
canceled:
You have canceled the order ID:id
. -
pay-invoice:
Please pay this hold invoice ofamount
Sats forfiat_code
fiat_amount
to start the operation. If you do not pay it withinexpiration_seconds
, the trade will be canceled. -
add-invoice:
Please send me an invoice foramount
satoshis equivalent tofiat_code
fiat_amount
. This is where I will send the funds upon trade completion. If you don’t provide the invoice withinexpiration_seconds
, the trade will be canceled. -
waiting-seller-to-pay:
Please wait. I’ve sent a payment request to the seller to send the Sats for the order ID:id
. If the seller doesn’t complete the payment withinexpiration_seconds
, the trade will be canceled. -
waiting-buyer-invoice:
Payment received! Your Sats are now "held" in your wallet. I’ve requested the buyer to provide an invoice. If they don’t do so withinexpiration_seconds
, your Sats will return to your wallet, and the trade will be canceled. -
buyer-invoice-accepted:
The invoice has been successfully saved. -
hold-invoice-payment-accepted:
Contact the seller atseller-npub
to arrange how to sendfiat_code
fiat_amount
usingpayment_method
. Once you send the fiat money, notify me withfiat-sent
. -
buyer-took-order:
Contact the buyer atbuyer-npub
to inform them how to sendfiat_code
fiat_amount
throughpayment_method
. You’ll be notified when the buyer confirms the fiat payment. 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
seller-npub
that you sent the fiat money. If the seller confirms receipt, they will release the funds. If they refuse, you can open a dispute. - To the seller:
buyer-npub
has informed you that they sent the fiat money. Once you confirm receipt, 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
-
released:
seller-npub
has released the Sats! Expect your invoice to be paid shortly. Ensure your wallet is online to receive via Lightning Network. -
purchase-completed:
Your purchase of Bitcoin has been completed successfully. Your invoice has been paid. Enjoy sound money! -
hold-invoice-payment-settled:
Your sale of Bitcoin has been completed after confirming the payment frombuyer-npub
. -
rate:
Please rate your counterparty. -
rate-received:
The rating has been successfully saved. -
cooperative-cancel-initiated-by-you:
You’ve initiated the cancellation of order ID:id
. Your counterparty must agree. 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
. Sendcancel-order-message
to confirm. 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:id
has been successfully canceled. -
dispute-initiated-by-you:
You’ve initiated a dispute for order ID:id
. A solver will be assigned 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 initiated a dispute for order ID:id
. A solver will be assigned 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:
- Admin: Here are the details of the dispute:
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. - Users: 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.
- Admin: Here are the details of the dispute:
-
admin-canceled:
- Admin: You have canceled order ID:
id
. - Users: The admin has canceled order ID:
id
.
- Admin: You have canceled order ID:
-
admin-settled:
- Admin: You have completed order ID:
id
. - Users: The admin has completed order ID:
id
.
- Admin: You have completed order ID:
-
payment-failed:
I couldn’t send the Sats. I’ll retrypayment_attempts
times inpayment_retries_interval
minutes. Please ensure your node/wallet is online. -
invoice-updated:
The invoice has been successfully updated. -
hold-invoice-payment-canceled:
The invoice was canceled. Your Sats are available in your wallet again. -
admin-add-solver:
Solvernpub
has been successfully added. -
cant-do:
You are not allowed to perform the action:action
.
Cant Do Reasons
Mostro also handles messages with the CantDo
action for various reasons. The details of the failure are included in the payload section of the event, providing a structured explanation of the issue. Below are suggested texts that clients can display to users based on the CantDo
reason received:
-
invalid-trade-index:
The provided trade index is invalid. Please ensure your client is synchronized and try again. -
invalid-amount:
The provided amount is invalid. Please verify it and try again. -
invalid-invoice:
The provided Lightning invoice is invalid. Please check the invoice details and try again. -
invalid-peer:
You are not authorized to perform this action. -
invalid-order-status:
The action cannot be completed due to the current order status. -
invalid-parameters:
The action cannot be completed due to invalid parameters. Please review the provided values and try again. -
invalid-pubkey:
The action cannot be completed because the public key is invalid. -
order-already-canceled:
The action cannot be completed because the order has already been canceled. -
cant-create-user:
The action cannot be completed because the user could not be created. -
is-not-your-dispute:
This dispute is not assigned to you. -
not-found:
The requested dispute could not be found. -
invalid-signature:
The action cannot be completed because the signature is invalid. -
is-not-your-order:
This order does not belong to you. -
not-allowed-by-status:
The action cannot be completed because order Idid
status isorder-status
. -
out-of-range-fiat-amount:
The requested fiat amount is outside the acceptable range (min_amount
–max_amount
). -
out-of-range-sats-amount:
The allowed Sats amount for this Mostro is between minmin_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", "<Order Id>"],
["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 d
label.
[
"EVENT",
"RAND",
{
"id": "<Event id>",
"pubkey": "<Mostro's pubkey>",
"kind": 38383
"tags": [
[
"d",
"<Mostro's pubkey>"
],
[
"mostro_version",
"0.12.8"
],
[
"mostro_commit_hash",
"1aac442058720c05954850bcffca6bcdfc87d150"
],
[
"max_order_amount",
"1000000"
],
[
"min_order_amount",
"100"
],
[
"expiration_hours",
"1"
],
[
"expiration_seconds",
"900"
],
[
"fee",
"0.006"
],
[
"pow",
"0"
],
[
"hold_invoice_expiration_window",
"120"
],
[
"hold_invoice_cltv_delta",
"144"
],
[
"invoice_expiration_window",
"120"
],
[
"lnd_version",
"0.18.4-beta commit=v0.18.4-beta"
],
[
"lnd_node_pubkey",
"0220e4558a8d9af4988ef6c8def0e73b05403819e49b7fb2db79d322ac3be1547e"
],
[
"lnd_commit_hash",
"ddeb8351684a611f6c27f16f09be75d5c039f08c"
],
[
"lnd_node_alias",
"alice"
],
[
"lnd_chains",
"bitcoin"
],
[
"lnd_networks",
"regtest"
],
[
"lnd_uris",
"0220e4558a8d9af4988ef6c8def0e73b05403819e49b7fb2db79d322ac3be1547e@172.26.0.2:9735"
],
[
"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_hash
: 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.lnd_version
: The version of the LND daemon running on the instance.lnd_node_pubkey
: The pubkey of the LND node running on the instance.lnd_commit_hash
: The ID of the last commit used by the LND node.lnd_node_alias
: The alias of the LND node.lnd_chains
: The chains supported by the LND node.lnd_networks
: The networks supported by the LND node.lnd_uris
: The URIs of the LND node.y
: The platform which is publishing its events.z
: The type of event.
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",
{
"id": "<Event id>",
"kind": 10002,
"tags": [
["r", "wss://relay.mostro.network/"],
["r", "wss://nostr.bilthon.dev/"]
],
"content": "",
"sig": "<Mostro's signature>",
"pubkey": "<Mostro's pubkey>",
"created_at": 1731680102
}
]
The r
label indicates the relays through which the Mostro instance is publishing its events.