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, currently 1
    • id: (optional) Wrapper Id
    • request_id: (optional) Mostro daemon should send back this same id in the response
    • trade_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 index 1 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 (index 1), 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 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 index 0 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 or buy
  • 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:

  1. 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.

  2. 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 for expiration_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 of amount Sats for fiat_code fiat_amount to start the operation. If you do not pay it within expiration_seconds, the trade will be canceled.

  • add-invoice:
    Please send me an invoice for amount satoshis equivalent to fiat_code fiat_amount. This is where I will send the funds upon trade completion. If you don’t provide the invoice within expiration_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 within expiration_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 within expiration_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 at seller-npub to arrange how to send fiat_code fiat_amount using payment_method. Once you send the fiat money, notify me with fiat-sent.

  • buyer-took-order:
    Contact the buyer at buyer-npub to inform them how to send fiat_code fiat_amount through payment_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 me release-order-message.
  • 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 from buyer-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. Send cancel-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 me cancel-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-canceled:

    • Admin: You have canceled order ID: id.
    • Users: The admin has canceled order ID: id.
  • admin-settled:

    • Admin: You have completed order ID: id.
    • Users: The admin has completed order ID: id.
  • payment-failed:
    I couldn’t send the Sats. I’ll retry payment_attempts times in payment_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:
    Solver npub 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 Id id status is order-status.

  • out-of-range-fiat-amount:
    The requested fiat amount is outside the acceptable range (min_amountmax_amount).

  • out-of-range-sats-amount:
    The allowed Sats amount for this Mostro is between min min_order_amount and max max_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 or buy. 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, if 0 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 be mainnet, testnet, signet, etc.
  • layer < Layer >: The layer used for the trade, it can be onchain, 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 in pending status before it expires.
  • expiration_seconds: The maximum time, in seconds, that an order can remain in waiting-payment or waiting-buyer-invoice status before being canceled or reverted to pending 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.