diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9379a75..d7eda60 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/cancel.md b/src/cancel.md index fb3a361..d303836 100644 --- a/src/cancel.md +++ b/src/cancel.md @@ -66,6 +66,10 @@ Mostro updates the addressable event with `d` tag `` 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: diff --git a/src/message_suggestions_for_actions.md b/src/message_suggestions_for_actions.md index efe6608..d710b83 100644 --- a/src/message_suggestions_for_actions.md +++ b/src/message_suggestions_for_actions.md @@ -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. diff --git a/src/order_event.md b/src/order_event.md index 92ddfc8..15e363f 100644 --- a/src/order_event.md +++ b/src/order_event.md @@ -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. diff --git a/src/other_events.md b/src/other_events.md index 9bedc04..613e250 100644 --- a/src/other_events.md +++ b/src/other_events.md @@ -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. + ## 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. diff --git a/src/pay_bond_invoice.md b/src/pay_bond_invoice.md new file mode 100644 index 0000000..f670ed7 --- /dev/null +++ b/src/pay_bond_invoice.md @@ -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": "", + "action": "pay-bond-invoice", + "payload": { + "payment_request": [ + { + "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). diff --git a/src/take_buy.md b/src/take_buy.md index 85a3f58..e301082 100644 --- a/src/take_buy.md +++ b/src/take_buy.md @@ -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: diff --git a/src/take_sell.md b/src/take_sell.md index 724791b..7cb432d 100644 --- a/src/take_sell.md +++ b/src/take_sell.md @@ -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: