Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [Take range sell order](./take_sell_range_order.md)
- [Take buy order](./take_buy.md)
- [Take range buy order](./take_buy_range_order.md)
- [Pay bond invoice](./pay_bond_invoice.md)
- [Seller pays hold invoice](./seller_pay_hold_invoice.md)
- [Fiat sent](./fiatsent.md)
- [Release](./release.md)
Expand Down
4 changes: 4 additions & 0 deletions src/cancel.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ Mostro updates the addressable event with `d` tag `<Order Id>` to change the sta
]
```

## Bond race during take

A client that sent `take-buy` / `take-sell` and is waiting for [`pay-bond-invoice`](./pay_bond_invoice.md) may receive `Action::Canceled` instead — meaning another user paid their bond first on the same order (whichever bond locks first wins; see [pay bond invoice](./pay_bond_invoice.md)). Surface this clearly to the user, e.g. *"Order was taken by another user before you locked the bond."* Do not silently retry the take — the order may not be available anymore, and the supersede mechanism on the daemon side has already discarded the prior bond request.

## 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:
Expand Down
3 changes: 3 additions & 0 deletions src/message_suggestions_for_actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Below are suggestions for messages that clients can show to users when receiving
- **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.

- **pay-bond-invoice:**
Please pay this **bond** hold invoice of `amount` Sats as a security deposit before the trade begins. The bond is separate from the trade escrow and is released when the trade completes normally. If you do not pay it within `expiration_seconds`, the take will be canceled — the order remains visible on the order book as `pending` and may be taken again by anyone.

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

Expand Down
2 changes: 2 additions & 0 deletions src/order_event.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Events are [addressable events](https://github.com/nostr-protocol/nips/blob/mast
- `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](https://en.wikipedia.org/wiki/ISO_4217) standard.
- `s` < Status >: `pending`, `canceled`, `in-progress`, `success`, `expired`.

An order with `s = pending` may already be matched to a taker who is in the middle of paying their anti-abuse bond. It remains takeable in this window — another user may attempt the take, and whichever bond locks first wins (the prior taker is notified with `Action::Canceled`, see [Cancel](./cancel.md)). The internal daemon state that tracks this (`waiting-taker-bond`, visible only in DM payload echoes) is not part of NIP-69's four-bucket wire model. Clients **must not** gray out or hide a `pending` order from the local order-book view just because their user has initiated a take.
- `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.
Expand Down
6 changes: 6 additions & 0 deletions src/other_events.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ Below is an explanation of the meaning of some of the labels in this event, all
- `y`: Platform identifier tag values. Mostro publishes `"mostro"` and MAY include a second value with the Mostro instance name from settings.
- `z`: The type of event.

### Upcoming: anti-abuse bond tags

A future Mostro release will publish bond-related tags in this event so clients can detect bond-enabled nodes ahead of a take. The tag set is still being finalized; at minimum it will include a `bond` tag with the value `enabled` or `disabled`. Additional tags describing the bond size policy (percentage, floor) may be added.

Clients that take orders on a node advertising `bond` `enabled` should be prepared to handle the [`pay-bond-invoice`](./pay_bond_invoice.md) action and the `waiting-taker-bond` order status. Until this tag is finalized and shipped, clients should treat the absence of bond tags as "behaviour unknown" and surface a clear error if the node sends `pay-bond-invoice` to a take they cannot handle.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify waiting-taker-bond as daemon-internal, not public wire status.

Line 206 currently reads like waiting-taker-bond is a regular order status. To avoid client parser mistakes, call it out explicitly as DM-payload/internal state (e.g., SmallOrder.status echo), while public NIP-69 stays pending.

Suggested wording
-Clients that take orders on a node advertising `bond` `enabled` should be prepared to handle the [`pay-bond-invoice`](./pay_bond_invoice.md) action and the `waiting-taker-bond` order status. Until this tag is finalized and shipped, clients should treat the absence of bond tags as "behaviour unknown" and surface a clear error if the node sends `pay-bond-invoice` to a take they cannot handle.
+Clients that take orders on a node advertising `bond` `enabled` should be prepared to handle the [`pay-bond-invoice`](./pay_bond_invoice.md) action and the daemon-internal `waiting-taker-bond` state echoed in DM payloads (the public NIP-69 status remains `pending`). Until this tag is finalized and shipped, clients should treat the absence of bond tags as "behaviour unknown" and surface a clear error if the node sends `pay-bond-invoice` to a take they cannot handle.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Clients that take orders on a node advertising `bond` `enabled` should be prepared to handle the [`pay-bond-invoice`](./pay_bond_invoice.md) action and the `waiting-taker-bond` order status. Until this tag is finalized and shipped, clients should treat the absence of bond tags as "behaviour unknown" and surface a clear error if the node sends `pay-bond-invoice` to a take they cannot handle.
Clients that take orders on a node advertising `bond` `enabled` should be prepared to handle the [`pay-bond-invoice`](./pay_bond_invoice.md) action and the daemon-internal `waiting-taker-bond` state echoed in DM payloads (the public NIP-69 status remains `pending`). Until this tag is finalized and shipped, clients should treat the absence of bond tags as "behaviour unknown" and surface a clear error if the node sends `pay-bond-invoice` to a take they cannot handle.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/other_events.md` at line 206, The documentation currently presents
`waiting-taker-bond` as a public order status; change the text to state
explicitly that `waiting-taker-bond` is a daemon-internal/DM-payload state
(e.g., echoed in SmallOrder.status for internal use) and not part of the public
NIP-69 wire status, which should remain `pending`; also clarify that
`pay-bond-invoice` is an internal action tied to that DM payload and that
clients must still treat missing bond tags as "behaviour unknown" and surface a
clear error if they receive a `pay-bond-invoice` for a take they cannot handle.


## 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](https://github.com/nostr-protocol/nips/blob/master/65.md), which are published by the Mostro instances.
Expand Down
86 changes: 86 additions & 0 deletions src/pay_bond_invoice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Pay bond invoice

When the receiving Mostro node has the anti-abuse bond feature enabled, takers must lock a small Lightning hold invoice as a security deposit before the trade flow proceeds. Mostro asks for this payment with the `pay-bond-invoice` action.

The bond is **independent from the trade escrow**: it is a second hold invoice with its own payment hash, and it is **released on every normal exit path** — completion, cancel before timeout, or dispute resolution where the solver does not direct otherwise. Bonds are typically around **1% of the trade amount** (with an operator-configured floor), much smaller than the trade hold invoice that may follow.

## Direction and trigger

- **Direction:** Mostro → user (the taker).
- **Trigger:** Sent immediately after a successful `take-buy` / `take-sell` when the operator has bonds enabled.
- **Order status:** The published NIP-33 order event keeps the `s` tag at `pending` while the bond is outstanding (per NIP-69's four-bucket model — see [Peer-to-peer Order events. NIP-69](./order_event.md)). The DM payload's embedded `SmallOrder` echo carries the daemon-internal status `waiting-taker-bond` so the recipient client can render the bond-payment phase distinctly, but external observers (other potential takers, order-book aggregators) continue to see the order as `pending` and may still attempt to take it. This is deliberate: a taker who never pays the bond cannot park the order off the book.

## Mostro message to the taker

The rumor's content has the same shape as `pay-invoice`; only the action discriminator differs:

```json
[
{
"order": {
"version": 1,
"id": "<Order Id>",
"action": "pay-bond-invoice",
"payload": {
"payment_request": [
{
"id": "<Order Id>",
"kind": "sell",
"status": "waiting-taker-bond",
"amount": 7851,
"fiat_code": "VES",
"fiat_amount": 100,
"payment_method": "face to face",
"premium": 1,
"created_at": 1698937797
},
"lnbcrt780n1pj59wmepp5..."
]
}
}
},
null
]
```

> Note: the `status` value `"waiting-taker-bond"` here is the daemon-internal state echoed in the DM payload. The corresponding NIP-33 addressable order event's `s` tag is still `pending`.

## Expected client behaviour

- Decode the bolt11 and surface it to the user **explicitly as a bond**, not as the trade escrow. Do not reuse the same wording or UI step that you use for `pay-invoice`.
- Pay it. The HTLC enters `Accepted` state — the sats are locked in the taker's wallet, not captured.
- Do not collapse this into the same UI step as any subsequent `pay-invoice` on the same order. They are independent HTLCs (different payment hashes), and the user must explicitly approve each.

## Follow-up flow

Once the bond HTLC is `Accepted`, Mostro proceeds with the normal trade flow:

- **Sell order taken (taker = buyer):** order status moves to `waiting-buyer-invoice`; the taker next receives `add-invoice` to provide a payout invoice.
- **Buy order taken (taker = seller):** order status moves to `waiting-payment`; the taker next receives `pay-invoice` for the **trade hold invoice**.

> **Important — buy order taken (seller-as-taker):** this is the only flow on which a single user pays **two hold invoices in sequence on the same order** — first the bond (`pay-bond-invoice`), then the trade escrow (`pay-invoice`). They arrive as distinct actions and must be presented to the user as separate steps. Do not auto-pay either, do not coalesce them, and make the distinction obvious in the UI; this is the most error-prone path for client developers.

## Daemon status `waiting-taker-bond` (DM payloads only)

In addition to the NIP-69 wire status (`pending` while the bond is outstanding), Mostro tags the order's internal state as `waiting-taker-bond` so it can route subsequent messages correctly. Clients see this value in the `SmallOrder` echo embedded in `pay-bond-invoice` payloads and may use it to drive UI ("Waiting for bond payment"). It does **not** appear on the addressable NIP-33 order event — that one continues to advertise the order as `pending`.

Internal transitions (visible only in DM payload echoes):

- From `pending` → `waiting-taker-bond`, after a successful `take-buy` / `take-sell` when bonds are enabled.
- From `waiting-taker-bond` → `waiting-payment` (buy order taken) or → `waiting-buyer-invoice` (sell order taken), once the bond HTLC is `Accepted`.
- From `waiting-taker-bond` → `pending`, if the bond bolt11 is never paid and expires, or the taker cancels before locking. The published `s` tag was `pending` throughout — observers see only that the take attempt left no trace.

## Failure modes

- The user never pays the bond bolt11 → the invoice expires; the order's NIP-33 status was `pending` throughout, so the rollback only undoes the daemon-internal take state. The order remains takeable.
- The user pays the bond and then cancels before trade completion → the bond HTLC is cancelled and the funds return to the taker.
- Slashing conditions (solver-directed dispute resolution, or timeout while in a waiting state) can settle the bond rather than release it. These paths are documented under [Admin Settle order](./admin_settle_order.md) and [Admin Cancel order](./admin_cancel_order.md), and in the Mostro daemon's anti-abuse bond specification.

## Backwards compatibility

Clients running an older `mostro-core` version that does not yet know `pay-bond-invoice` will fail to deserialize the message and silently drop it; from the user's perspective the take stalls and eventually times out without surfacing a useful error. Operators are responsible for not enabling bonds in production until clients in the wild have adopted the `mostro-core` release that ships `Action::PayBondInvoice`. Clients should:

- Recognise the action explicitly and surface it to the user.
- If unable to handle it (e.g. an older build talking to a bond-enabled node), present a clear error rather than silently retrying the take.

The Mostro info event will gain bond-related tags so clients can detect bond-enabled nodes ahead of a take — see [Other events published by Mostro](./other_events.md#mostro-instance-status).
6 changes: 6 additions & 0 deletions src/take_buy.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ The event to send to Mostro would look like this:
}
```

## Optional: anti-abuse bond step

When the receiving Mostro node has bonds enabled, the seller (taker) first receives a [`pay-bond-invoice`](./pay_bond_invoice.md) message; the published NIP-33 order event remains `pending` (re-takeability is preserved — see [order event](./order_event.md)) and the DM payload echoes the internal status `waiting-taker-bond`. Only after the bond HTLC is `Accepted` does Mostro send the `pay-invoice` message described below.

This is the **only flow on which a single user pays two hold invoices in sequence on the same order**: the bond first, then the trade hold invoice. They arrive as distinct actions (`pay-bond-invoice` then `pay-invoice`) and clients **must** present them as separate steps. Clients that do not recognise `pay-bond-invoice` should expect the take to time out and surface a clear error to the user — do not silently retry the take.

## Mostro response

Mostro respond to the seller with a message with the following content:
Expand Down
4 changes: 4 additions & 0 deletions src/take_sell.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ If the order amount is `0` the buyer doesn't know the exact amount to create the
]
```

## Optional: anti-abuse bond step

When the receiving Mostro node has bonds enabled, the buyer (taker) first receives a [`pay-bond-invoice`](./pay_bond_invoice.md) message; the published NIP-33 order event remains `pending` (re-takeability is preserved — see [order event](./order_event.md)) and the DM payload echoes the internal status `waiting-taker-bond`. Only after the bond HTLC is `Accepted` does Mostro send the `add-invoice` message described below. Clients that do not recognise `pay-bond-invoice` should expect the take to time out and surface a clear error to the user — do not silently retry the take.

## 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:
Expand Down