You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: document anti-abuse bond implementation (Phases 1–7)
Cover all protocol changes introduced by the anti-abuse bond PRs
(#712–#777) that were not yet reflected in the protocol docs:
### New page
- `bond_slashed.md`: documents the `bond-slashed` action (Phase 4/7).
Sent to the bonded party when a waiting-state timeout elapses and
`slash_on_waiting_timeout = true`. Distinguishes timeout-slash from
dispute-slash path; explains order cancel vs. republish depending on
which party was responsible; notes that cancel-before-timeout never
triggers this.
### Extended pages
- `pay_bond_invoice.md`: restructured to cover both taker bond (Phase 1.5,
existing) and maker bond (Phase 5/6, new). Maker bond: trigger is
`new-order` response; order not published until bond HTLC accepts;
`waiting-maker-bond` DM status; range orders sized against `max_amount`.
- `add_bond_invoice.md`: adds Phase 3.5 payout confirmations —
`bond-invoice-accepted` (Mostro acknowledges receipt of payout bolt11)
and `bond-payout-completed` (payment settled). Notes re-prompt on
`send_payment` exhaustion and claim window.
- `cancel.md`: fixes incorrect "supersede mechanism" wording (replaced by
concurrent bonds / first-to-lock-wins model since Phase 1.5). Adds two
new sections — taker self-cancel during `waiting-taker-bond` (releases
only sender's bond; order stays in `waiting-taker-bond` if others
remain) and maker cancel during `waiting-taker-bond` (releases all
concurrent bonds).
### Order creation pages
- `new_sell_order.md`, `new_buy_order.md`: "Optional: anti-abuse maker bond"
section explaining that `pay-bond-invoice` precedes `new-order`
confirmation when `apply_to ∈ {make, both}`.
- `new_sell_range_order.md`: same section, noting bond sized against
`max_amount` and proportional per-slice accounting.
### Message suggestions
- `message_suggestions_for_actions.md`: adds `bond-invoice-accepted`,
`bond-payout-completed`, and `bond-slashed` suggested texts.
### Navigation
- `SUMMARY.md`: adds `bond_slashed.md` link after `add_bond_invoice.md`.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: src/add_bond_invoice.md
+67Lines changed: 67 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -99,11 +99,78 @@ The recipient of the request message is the non-slashed counterparty of the trad
99
99
-**Treat re-deliveries as idempotent reminders.** The same outstanding request is being repeated; do not re-prompt the user for a fresh invoice on every retry. `slashed_at` is re-emitted unchanged, so the deadline the client shows must not shift across retries.
100
100
-**Sign the reply with the non-slashed side's trade key.** See [Keys management](./key_management.md).
101
101
102
+
## Payout confirmations (Phase 3.5)
103
+
104
+
After the counterparty submits their payout invoice, Mostro acknowledges receipt and later confirms payment with two dedicated actions.
105
+
106
+
### `bond-invoice-accepted`
107
+
108
+
Sent by Mostro to the counterparty immediately after successfully receiving and validating their payout bolt11. This confirms Mostro has the invoice on file and is attempting the Lightning payment.
109
+
110
+
```json
111
+
[
112
+
{
113
+
"order": {
114
+
"version": 1,
115
+
"id": "<Order Id>",
116
+
"action": "bond-invoice-accepted",
117
+
"payload": {
118
+
"order": {
119
+
"id": "<Order Id>",
120
+
"kind": "sell",
121
+
"amount": 500,
122
+
"fiat_code": "VES",
123
+
"fiat_amount": 100,
124
+
"payment_method": "face to face",
125
+
"premium": 1
126
+
}
127
+
}
128
+
}
129
+
},
130
+
null
131
+
]
132
+
```
133
+
134
+
Clients should surface this as a reassurance: *"Your bond payout invoice has been received and the payment is being processed."*
135
+
136
+
### `bond-payout-completed`
137
+
138
+
Sent by Mostro to the counterparty when the Lightning payment to their invoice has been confirmed as settled. The `amount` in the payload reflects the amount paid.
139
+
140
+
```json
141
+
[
142
+
{
143
+
"order": {
144
+
"version": 1,
145
+
"id": "<Order Id>",
146
+
"action": "bond-payout-completed",
147
+
"payload": {
148
+
"order": {
149
+
"id": "<Order Id>",
150
+
"kind": "sell",
151
+
"amount": 500,
152
+
"fiat_code": "VES",
153
+
"fiat_amount": 100,
154
+
"payment_method": "face to face",
155
+
"premium": 1
156
+
}
157
+
}
158
+
}
159
+
},
160
+
null
161
+
]
162
+
```
163
+
164
+
Clients should surface this as a terminal success: *"Your bond payout of `amount` Sats has been completed!"*
165
+
166
+
Both actions carry `Payload::Order` (the `SmallOrder` context) and are serde-additive: a client that does not recognise them ignores the message and falls back to the `add-bond-invoice` reply it already handles — no funds at risk.
167
+
102
168
## Failure modes
103
169
104
170
- The counterparty never replies before the deadline → no Lightning payment arrives and the share is forfeited; the slashed funds remain with the node.
105
171
- A reply arrives after the deadline, or from a sender other than the resolved recipient, or after another reply already won the race → Mostro responds with a `cant-do` action carrying reason `not-allowed-by-status`.
106
172
- The bolt11 principal does not match the requested counterparty share, or the invoice is otherwise undecodable / expired → Mostro responds with `cant-do` reason `invalid-invoice`.
107
173
- On a node where the operator retains 100% of slashed bonds, **no `add-bond-invoice` message is emitted at all**. Clients should not surface a phantom payout request.
174
+
- If `send_payment` retries are exhausted (see `payout_max_retries` in the info event) the counterparty is re-prompted with a fresh `add-bond-invoice` request, provided the claim window has not yet elapsed. If the window has passed, the share is forfeited and no further messages are sent.
108
175
109
176
On success, the counterparty receives their share as a Lightning payment from the node's wallet. The routing fee is paid separately from that wallet and is not deducted from the principal.
The `bond-slashed` action is a notification Mostro sends to a bonded party when their anti-abuse bond has been **settled due to a waiting-state timeout**. It is a forfeiture notice: the bond HTLC has already been claimed into Mostro's wallet by the time this message is sent.
4
+
5
+
> **Scope.** This action is only emitted on the **timeout slash** path (scheduler-driven, gated by `slash_on_waiting_timeout = true` in the Mostro info event). It is **not** sent on the dispute-slash path — when a solver slashes a bond via [`admin-settle`](./admin_settle_order.md) or [`admin-cancel`](./admin_cancel_order.md), the slashed party receives the `admin-settled` / `admin-canceled` confirmation instead.
6
+
7
+
## Direction and trigger
8
+
9
+
-**Direction:** Mostro → the party whose bond was slashed.
10
+
-**Trigger:** The waiting-state timeout elapsed while the responsible party had not performed their expected trade action (e.g. the seller never paid the hold invoice while in `waiting-payment`, or the buyer never submitted an invoice in `waiting-buyer-invoice`), and the operator has configured `slash_on_waiting_timeout = true`.
11
+
- The `amount` in the payload is the **slashed bond amount in satoshis** — not the trade amount.
12
+
13
+
## Wire format
14
+
15
+
```json
16
+
[
17
+
{
18
+
"order": {
19
+
"version": 1,
20
+
"id": "<Order Id>",
21
+
"action": "bond-slashed",
22
+
"payload": {
23
+
"order": {
24
+
"id": "<Order Id>",
25
+
"kind": "sell",
26
+
"status": "canceled",
27
+
"amount": 785,
28
+
"fiat_code": "VES",
29
+
"fiat_amount": 100,
30
+
"payment_method": "face to face",
31
+
"premium": 1,
32
+
"created_at": 1698937797
33
+
}
34
+
}
35
+
}
36
+
},
37
+
null
38
+
]
39
+
```
40
+
41
+
The `amount` field in the embedded `SmallOrder` is the **slashed bond amount** (not the original trade amount). For a range-order maker bond, this is the proportional slice amount for the taken sub-order.
42
+
43
+
## What happens next
44
+
45
+
After `bond-slashed` is sent to the responsible party, Mostro also:
46
+
47
+
1.**Cancels or republishes the order** depending on which party was responsible:
48
+
-**Maker responsible** (e.g. maker-as-seller never paid the hold invoice): the order is **cancelled** (since the maker cannot be trusted to fulfil it). The maker receives the `bond-slashed` notice followed by a `canceled` confirmation.
49
+
-**Taker responsible** (e.g. taker-as-buyer never submitted their invoice): the order is **republished** to the book as `pending` so the maker can be matched again. The taker receives `bond-slashed` followed by `canceled`. The maker's bond (if any) remains `Locked`.
50
+
51
+
2.**Asks the winning counterparty for a payout invoice** by sending them an [`add-bond-invoice`](./add_bond_invoice.md) message for their share of the slashed bond (governed by `bond_slash_node_share_pct` — the node retains its share, the rest goes to the counterparty).
52
+
53
+
## Expected client behaviour
54
+
55
+
- Surface `bond-slashed` to the user as an explicit forfeiture notice, not as a generic cancellation. Suggested wording: *"Your bond of `amount` Sats has been forfeited due to a waiting-state timeout."*
56
+
- This action is distinct from `canceled` — clients should not suppress it or merge it into the cancel flow.
57
+
- A `canceled` message will typically follow for the order itself; clients should handle both.
58
+
59
+
## Note on cancels vs. slashes
60
+
61
+
A cancel sent by either party **before** the timeout elapses never triggers `bond-slashed`. Bonds are always released (never slashed) on user-initiated cancels. Only the automated scheduler-driven timeout slash path emits this action, and only when `slash_on_waiting_timeout = true` is set by the operator.
Copy file name to clipboardExpand all lines: src/cancel.md
+20-1Lines changed: 20 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -68,7 +68,26 @@ Mostro updates the addressable event with `d` tag `<Order Id>` to change the sta
68
68
69
69
## Bond race during take
70
70
71
-
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.
71
+
Multiple takers may simultaneously attempt to take the same order: each creates their own bond hold invoice (a `Requested` bond row) and whichever HTLC is accepted first by LND wins. When a bond locks, all other concurrent `Requested` bonds on the same order are released and their takers each receive `Action::Canceled`.
72
+
73
+
A client that sent `take-buy` / `take-sell` and is waiting for [`pay-bond-invoice`](./pay_bond_invoice.md) may therefore receive `canceled` instead — meaning another taker's bond locked first. 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 no longer be available.
74
+
75
+
## Cancel during `waiting-taker-bond`
76
+
77
+
An order remains in the daemon-internal `waiting-taker-bond` state (NIP-33 `s` tag still `pending`) while one or more taker bonds are outstanding. Cancels during this window are routed differently from cancels on `pending` orders.
78
+
79
+
### Taker self-cancel
80
+
81
+
A taker who has a `Requested` (unpaid) bond on an order may cancel it by sending `cancel`. Mostro releases **only that taker's bond** (LND invoice cancelled, funds returned). Other concurrent takers' bonds are unaffected and continue racing.
82
+
83
+
- If the cancelling taker was the only prospective taker, the order drops back to `pending` and is visible on the order book again.
84
+
- If other takers' bonds are still `Requested`, the order remains in `waiting-taker-bond` with those bonds still racing.
85
+
86
+
The maker is not notified; the NIP-33 order event stays `pending` throughout.
87
+
88
+
### Maker cancel
89
+
90
+
The maker can cancel the order at any point before trade flow starts (i.e. while in `pending` or `waiting-taker-bond`). Sending `cancel` releases **all** concurrent taker bonds on the order, notifies each prospective taker with `canceled`, transitions the order to `canceled`, and publishes the updated NIP-33 event.
Copy file name to clipboardExpand all lines: src/message_suggestions_for_actions.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,6 +19,15 @@ Below are suggestions for messages that clients can show to users when receiving
19
19
-**add-bond-invoice:**
20
20
Please send me a Lightning invoice for `amount` Sats — this is your share of a slashed bond on order `id`. You have until `deadline` to submit it, or your share will be forfeited and the entire bond will be retained by the node.
21
21
22
+
-**bond-invoice-accepted:**
23
+
Your bond payout invoice has been received. The payment is being processed — you'll be notified once it completes.
24
+
25
+
-**bond-payout-completed:**
26
+
Your bond payout of `amount` Sats has been sent successfully. The funds should arrive in your Lightning wallet shortly.
27
+
28
+
-**bond-slashed:**
29
+
Your anti-abuse bond of `amount` Sats has been forfeited. This happened because the waiting-state timeout elapsed before you completed your required action on order `id`. The bond has been settled into the node's wallet.
30
+
22
31
-**add-invoice:**
23
32
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.
Copy file name to clipboardExpand all lines: src/new_buy_order.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,6 +38,16 @@ The nostr event will look like this:
38
38
}
39
39
```
40
40
41
+
## Optional: anti-abuse maker bond
42
+
43
+
When the Mostro node has bonds enabled and `apply_to` is `"make"` or `"both"`, the maker must lock a bond **before** the order is published. Instead of a `new-order` confirmation, Mostro first responds with a [`pay-bond-invoice`](./pay_bond_invoice.md#maker-bond) message asking the maker to pay a small hold invoice (typically ~1% of the trade amount). The order is **not visible on Nostr** until the bond HTLC is accepted.
44
+
45
+
Only after the maker pays the bond does Mostro:
46
+
1. Publish the order to Nostr with status `pending`.
47
+
2. Send the `new-order` confirmation (shown below).
48
+
49
+
If the maker never pays the bond invoice it expires and no order is created. See [Pay bond invoice — Maker bond](./pay_bond_invoice.md#maker-bond) for details.
50
+
41
51
## Confirmation message
42
52
43
53
Mostro will send back a nip59 event as a confirmation message, the message in the rumor looks like the following:
Copy file name to clipboardExpand all lines: src/new_sell_order.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -47,6 +47,16 @@ The event to send to Mostro would look like this:
47
47
}
48
48
```
49
49
50
+
## Optional: anti-abuse maker bond
51
+
52
+
When the Mostro node has bonds enabled and `apply_to` is `"make"` or `"both"`, the maker must lock a bond **before** the order is published. Instead of a `new-order` confirmation, Mostro first responds with a [`pay-bond-invoice`](./pay_bond_invoice.md#maker-bond) message asking the maker to pay a small hold invoice (typically ~1% of the trade amount). The order is **not visible on Nostr** until the bond HTLC is accepted.
53
+
54
+
Only after the maker pays the bond does Mostro:
55
+
1. Publish the order to Nostr with status `pending`.
56
+
2. Send the `new-order` confirmation (shown below).
57
+
58
+
If the maker never pays the bond invoice it expires and no order is created. See [Pay bond invoice — Maker bond](./pay_bond_invoice.md#maker-bond) for details.
59
+
50
60
## Confirmation message
51
61
52
62
Mostro will send back a nip59 event as a confirmation message to the user like the following (unencrypted rumor's content example):
Copy file name to clipboardExpand all lines: src/new_sell_range_order.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,6 +30,14 @@ Here we have two new fields, `min_amount` and `max_amount`, to define the range
30
30
31
31
When a taker takes the order, the amount will be set on the message.
32
32
33
+
## Optional: anti-abuse maker bond
34
+
35
+
When the Mostro node has bonds enabled and `apply_to` is `"make"` or `"both"`, the maker of a range order must lock a bond **before** the order is published — same as non-range orders, with one key difference: **the bond is sized against `max_amount`**, not the minimum.
36
+
37
+
Mostro responds to the `new-order` with a [`pay-bond-invoice`](./pay_bond_invoice.md#maker-bond) instead of the confirmation below. The order is not published to Nostr until the bond HTLC is accepted.
38
+
39
+
When a taker takes a slice of the range order, the bond obligation is reduced proportionally (the slice's fiat amount relative to `max_amount`). The bond hold invoice remains `Locked` for the lifetime of the range order and is settled once in full when the range closes; partial slashes are tracked as child bond rows rather than early partial settlements. See [Pay bond invoice — Maker bond](./pay_bond_invoice.md#maker-bond) for details.
40
+
33
41
## Confirmation message
34
42
35
43
Mostro will send back a nip59 event as a confirmation message to the user like the following:
0 commit comments