Pay bond invoice

When the receiving Mostro node has the anti-abuse bond feature enabled, makers and/or 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.

The bond_apply_to tag in the Mostro info event (kind 38385) tells clients which sides must post a bond: "take", "make", or "both" — see Other events published by Mostro.

Taker bond

Direction and trigger

  • Direction: Mostro → user (the taker).
  • Trigger: Sent immediately after a successful take-buy / take-sell when the operator has bonds enabled for takers.
  • 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). 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 message's content has the same shape as pay-invoice; only the action discriminator differs:

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

Note: the SmallOrder echo carries "status": "pending" — the NIP-69 wire bucket the order is advertised in while the bond is outstanding. The daemon-internal state waiting-taker-bond (which Mostro uses to route subsequent messages) is tracked only in the daemon's database and is never emitted in the DM payload. Clients should dispatch on the pay-bond-invoice action, not on a status field.

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 (internal only)

Mostro tags the order's internal state as waiting-taker-bond so it can route subsequent messages correctly while one or more taker bonds are outstanding. This status lives only in the daemon's database — it is neither published on the NIP-33 order event (whose s tag stays pending) nor echoed in any DM payload (the pay-bond-invoice SmallOrder carries pending, as shown above). It is documented here only to describe the daemon's lifecycle; clients never observe the literal waiting-taker-bond value on the wire.

Internal transitions (not visible to clients):

  • 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 (after hold_invoice_expiration_window seconds, see Failure modes), or the taker cancels before locking, and no other taker's bond is still outstanding. Otherwise the order stays in waiting-taker-bond while the remaining bonds race. 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 hold_invoice_expiration_window seconds after it was issued, the value the node advertises in its info event. From then on the Lightning node refuses any payment on it. Mostro releases the bond and, if no other taker's bond is still outstanding, returns the order to pending and republishes its event with the same tags (a range order keeps amt "0" and fa [min, max]). The order's NIP-33 status was pending throughout, so the rollback only undoes the daemon-internal take state, and the order remains takeable.

    The taker receives no message when this happens. A client should treat the bolt11's expiry as the end of the take and let the user take the order again.

    If the daemon misses the Lightning node's cancel (for example across a restart), a periodic check releases a bond left unpaid past hold_invoice_expiration_window within a few minutes. Nodes that predate the expiry issued taker bond invoices with the Lightning node's default expiry (24 hours on LND); on those, the invoice may stay payable long after the window.

  • 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. The solver directs slashing via the bond_resolution payload documented under Admin Settle order and Admin Cancel order, and the non-slashed counterparty is then asked for their share of the bond via Bond payout invoice. When a timeout slash fires, the slashed party first receives a bond-slashed notification.


Maker bond

When apply_to is "make" or "both", the maker must lock a bond before the order is published to Nostr. The same pay-bond-invoice action is used, but the trigger and the order lifecycle differ from the taker case.

Direction and trigger

  • Direction: Mostro → user (the maker).
  • Trigger: Sent in response to a new-order message when the operator has bonds enabled for makers, before the order is published.
  • Order visibility: The order is not published to Nostr while the maker bond is outstanding. External observers see nothing — no pending order event, no order in the book. This differs from the taker bond, where the order remains visible and re-takeable throughout. The daemon tracks this internally as waiting-maker-bond, a status kept in its database only — it is never emitted on the wire (the SmallOrder echo carries pending, as shown below).

Mostro message to the maker

The action and wire shape are identical to the taker case:

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

Note: the SmallOrder echo always carries "status": "pending" — this is the NIP-69 wire bucket the order will appear in once the bond locks and the order is published. The daemon-internal state waiting-maker-bond is tracked only in the daemon's database and is never emitted in DM payloads. No NIP-33 order event has been emitted yet at this point.

For range sell orders, the bond is sized against max_amount (not the individual fiat_amount). The SmallOrder echo will reflect the range order fields (min_amount, max_amount).

Follow-up after bond locks

Once the maker's bond HTLC is Accepted:

  1. Mostro publishes the order to Nostr with status pending for the first time.
  2. Mostro sends the maker the new-order confirmation message (same as in the no-bond flow — see Creating a new sell order and Creating a new buy order).

Deadline to pay

The maker bond hold invoice expires after the node's maker-bond payment timeout (operator setting maker_bond_payment_timeout_seconds, 900 seconds by default). The node does not advertise this timeout in its info event; a client reads it from the bolt11's expiry and can show the maker a countdown.

When the deadline passes, the Lightning node refuses any payment on the invoice, and Mostro closes the order as expired (in its database only), releases the bond and sends the maker canceled:

[
  {
    "order": {
      "version": 2,
      "id": "<Order Id>",
      "action": "canceled",
      "payload": null
    }
  },
  null,
  null
]

The message answers no request of the maker's, so it carries no request_id. A client should end the bond-payment screen when it arrives, or at the bolt11's expiry if it never does. Nodes that predate the deadline issued maker bond invoices with the Lightning node's default expiry (24 hours on LND), kept the order until its expires_at, and sent the maker nothing.

Daemon status waiting-maker-bond (internal only)

Like waiting-taker-bond, this status lives only in the daemon's database — it is never published on a NIP-33 event (none exists yet) nor echoed in any DM payload (the pay-bond-invoice SmallOrder carries pending). It is documented here only to describe the daemon's lifecycle.

Internal transitions (not visible to clients):

  • On new-order receipt → waiting-maker-bond, before any NIP-33 event is emitted.
  • waiting-maker-bond → pending (and order published), once the bond HTLC is Accepted.
  • waiting-maker-bond → expired, if the maker does not pay before the deadline, or the order reaches its expires_at first.
  • waiting-maker-bond → canceled, if the maker sends cancel before paying.
  • waiting-maker-bond → canceled-by-admin, if the operator cancels it.

None of the last three emits a NIP-33 event: the order was never published, so there is nothing to replace. Each releases the bond, which cancels its hold invoice. A bond that already locked wins over all three: its order is published as pending, and the maker then handles it as a published order.

Failure modes

  • Maker never pays → at the deadline the invoice expires, the order closes as expired and the maker receives canceled. No NIP-33 event was ever emitted, so the order book is unaffected.
  • Maker changes their mind before paying → the maker sends cancel; the bond invoice is canceled and the order closes as canceled, again without any NIP-33 event.
  • Maker pays the bond, order publishes as pending, then maker cancels → bond HTLC released, funds return to maker.
  • A waiting-state timeout slash (when slash_on_waiting_timeout = true) settles the maker's bond HTLC instead of releasing it. The maker first receives a bond-slashed notification, then the order is canceled. The winning counterparty (the taker) then receives add-bond-invoice for their share.

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 operation 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 Mostro info event carries bond-related tags so clients can detect bond-enabled nodes ahead of a take or create — see Other events published by Mostro.