Skip to content

Feature: Anti-Abuse Bond — Phased Implementation for Taker and Maker Flows #711

@mostronatorcoder

Description

@mostronatorcoder

Summary

Add an optional, node-level anti-abuse bond to Mostro that creates real financial cost for order abandonment and abuse. The bond is a Lightning hold invoice locked by the party (taker or maker) when they enter a trade, forfeited only in clearly defined dispute resolution outcomes, and released on normal completion.

This feature is intentionally opt-in per node, disabled by default, and implemented in three phased releases to allow safe testing of each abuse vector independently.


Motivation

Mostro has no economic friction for actors that repeatedly:

  • Take orders and abandon them: creates noise in the order book, wastes maker time, triggers unnecessary dispute workflows
  • Create spam orders: floods the book, degrades the marketplace for legitimate users

Without cost, an attacker scales abuse for free. A bond makes every action deliberate: if a party cannot afford to lock a bond, they cannot trade on a node that enforces it. If they lock it and behave badly, they lose it.

The bond mechanic has been discussed in #366. This proposal supersedes and formalizes that discussion.


Proposed Configuration

[anti_abuse_bond]
enabled = false

# Bond = max(amount_sats * order_amount_sats, base_amount_sats)
# 0.01 = 1% of the order amount in sats
amount_sats = 0.01
# Floor: percentage below this value is replaced by the floor
base_amount_sats = 1000

# Which flow(s) require the bond
apply_to = "both"  # "create" | "take" | "both"

# Slash conditions
slash_on_lost_dispute = true       # bond forfeited if this party loses the dispute
slash_on_waiting_timeout = false   # bond forfeited if party goes silent in waiting-buyer-invoice / waiting-payment

Key design choices:

  • enabled = false by default: nodes not using this feature are unaffected.
  • Percentage bond with floor: scales with order size (fair for large trades), guaranteed minimum (prevents trivial-cost abuse on many small orders).
  • Two independent slash conditions: node operator configures both separately.
  • Phases enable apply_to one flow at a time for independent testing.

Bond Computation

bond_amount = max(amount_sats * order_amount_sats, base_amount_sats)

Example — 100,000 sats order with amount_sats = 0.01, base_amount_sats = 1000:

  • 0.01 * 100000 = 1000 sats
  • max(1000, 1000) = 1000 sats

Example — 10,000,000 sats order:

  • 0.01 * 10000000 = 100000 sats
  • max(100000, 1000) = 100000 sats

The amount must be computed before the hold invoice is requested, so both parties know the required bond upfront.


Phased Implementation Plan

Three phases. Each phase covers one abuse vector and one slash condition. Phases are tested and deployed independently.


Phase 1 — Taker Bond: apply_to = "take", slash_on_lost_dispute = true

Abuse vector: Party that repeatedly takes orders and abandons them.

Flow:

  1. Taker initiates take on an order
  2. Mostro computes the required bond amount
  3. Mostro requests a hold invoice from the taker for the bond amount
  4. Taker provides the hold invoice; Mostro accepts it and records the bond state (BondState::Locked)
  5. Trade proceeds normally (active state)
  6. On normal completion: hold invoice is released (Cancel)
  7. On dispute: bond stays locked until resolution
    • Taker loses the dispute + slash_on_lost_dispute = true: bond is settled (taker loses it)
    • Taker wins the dispute: bond is released
  8. On any cancellation (mutual, unilateral, or admin): bond is released regardless of who cancelled or when. A cancellation that occurs before the timeout has elapsed is never a slash condition.

What is NOT in scope: create flow bond, slash_on_waiting_timeout behavior.

If the taker cannot provide a hold invoice: take flow is rejected — same as insufficient liquidity today. No special error needed.

If the hold invoice expires before being accepted: take flow fails; order returns to the book. No bond is outstanding.


Phase 2 — Taker Timeout Slash: apply_to = "take", slash_on_waiting_timeout = true

Prerequisite: Phase 1 merged and stable.

Abuse vector: Taker that takes an order and goes completely silent in waiting-buyer-invoice or waiting-payment, refusing to send an invoice (buyer) or confirm payment (seller).

Critical invariant — bond is slashed ONLY on actual timeout:

The bond for timeout MUST be slashed only when the configured waiting-state timeout actually elapses without the required action. A cancellation — mutual or unilateral — that occurs before the timeout elapses MUST NOT trigger the bond slash. The order ending in canceled state is not sufficient; the slash requires that the timeout clock ran to zero.

Concrete example: Maker (A) creates an order with a 15-minute timeout. Taker (B) takes the order and locks a bond. At minute 5, A cancels the order. B's bond must be released in full — the 15-minute timeout never elapsed, so no slash condition was triggered. This invariant prevents a malicious maker from exploiting the bond system: A could otherwise create and cancel orders repeatedly at minute 5 to steal takers' bonds.

Additional flow behavior:

When the configured timeout elapses without progress in a waiting state:

  • If slash_on_waiting_timeout = true: the bond is settled as a penalty, and the order returns to Pending — the maker does not need to re-post the order. This mirrors existing Mostro behavior, without going to dispute or cancellation.
  • A BondSlashReason::Timeout is recorded for audit purposes
  • The taker cannot re-take the same order without posting a new bond

When slash_on_waiting_timeout = false (default): No bond slash occurs for timeout. The order returns to Pending as it does today. The taker is not penalized financially.

Cancellation before timeout: If the order is cancelled (by either party or by mutual agreement) before the timeout elapses, the bond is released regardless of slash_on_waiting_timeout setting. The bond is a cost for inaction after the timer started, not for the order ending in any particular state.

Why this is off by default: Legitimate users in regions with unreliable power or internet can be harmed by timeout slashing. Node operators who enable this flag are making an explicit policy decision for their community.

UI expectation: When the bond is slashed for timeout, the affected user receives a message explaining the forfeiture, with the slash_on_waiting_timeout flag value shown in the node public config so users can check before trading.


Phase 3 — Maker Bond: apply_to = "create", both slash conditions

Prerequisite: Phases 1 and 2 merged and stable.

Abuse vector: Actor that floods the order book with spam orders or creates orders with no intention of completing them.

Flow: Mirrors Phase 1 for the maker. Bond is computed and locked when the order is published. Released on normal completion. Slashed if the maker loses a dispute (or on timeout if slash_on_waiting_timeout = true).

Critical invariant applies symmetrically: The maker bond is slashed on timeout ONLY if the waiting-state timeout actually elapses. If the order is cancelled before the timeout, the maker bond is released. Same attack scenario as Phase 2 but in the opposite direction.

Order book visibility: An order with a required maker bond must NOT appear in the public order book until the bond is confirmed. This prevents an order from being visible and taken by others before the maker bond is in place.

With both bonds active: Both parties in a trade have economic skin in the game. Dispute resolution has symmetric consequences. A maker who creates an order and abandons it loses their bond just as a taker would.


Technical Considerations

Hold Invoice Mechanics

Hold invoices (HTLC with payment hold) are the natural primitive. When a hold invoice is presented:

  • The payment is reserved but not settled
  • The holder can release it (Cancel) or claim it (Settle) based on outcome
  • Mostro needs to be able to claim the hold invoice if the bond is slashed

Preimage custody: Mostro should hold the preimage. This makes the bond mechanically enforceable without depending on the bonded party to cooperate in the slashing step.

State Storage

A new bonds table (or extension) tracking:

  • bond_id, order_id, pubkey, role (maker/taker), amount_sats
  • state: locked | released | slashed | pending_payout
  • slashed_reason: null unless state is slashed (LostDispute, Timeout)
  • locked_at, released_at timestamps

Range Orders

When an order is published with a range (e.g., 50,000–500,000 sats), the bond mechanics differ:

  • Maker bond: Computed once at order creation based on the maximum amount in the range. The maker posts a single hold invoice for this full-range bond at publication time.

  • Taker bond: Computed at take time based on the exact amount the taker selects within the range. Each taker posts a bond proportional to their specific trade amount.

  • Slash proportionality for the maker: If a child order (a specific take within a range order) results in a dispute loss or timeout, the maker does not forfeit the entire range bond. Only the proportional share corresponding to that child order is slashed.

    Example: range bond = 5,000 sats (computed on the 500,000 sat maximum). Taker takes 100,000 sats → child order represents 20% of the range maximum. If the maker loses the dispute on that child order, 1,000 sats (20%) are slashed. The remaining 4,000 sats stay locked for the rest of the range order.

  • Full range bond release: When the range order expires, is fully exhausted, or is cancelled with no outstanding child disputes, the full remaining maker bond is released.

Bond Payment Flow

When a bond is slashed, the forfeited amount is paid to the winning counterparty. The flow is:

  1. Mostro sends add-invoice action to the winner. The winning party (e.g., the buyer if the seller lost the dispute) receives a message requesting a Lightning invoice for the bond payout.
  2. Invoice amount = bond amount minus estimated routing fee. Before sending add-invoice, Mostro estimates the routing fee to reach the winner's node. The invoice requested must be for bond_amount - estimated_routing_fee. This ensures the Mostro node does not absorb routing losses from bond payouts.
  3. Bond payment is non-blocking. The completion of the underlying trade (escrow settlement, invoice submission, rating) MUST NOT wait for the bond payout to settle. Bond payout runs as an independent flow once the slash condition is confirmed.
  4. add-invoice failure handling. If the winner does not respond with a valid invoice within a configurable window, Mostro retries up to a configurable number of attempts. If no invoice is received, the bond transitions to pending_payout state and is retried on the next activity from the winning party.

Interaction with Existing Flows

  • Mutual cancel: bond is released, not slashed.
  • Admin cancel: bond is generally released unless abuse is clearly established.
  • Escrow: the bond is separate from the trade escrow. It is a separate hold invoice, not deducted from escrow.
  • Full privacy mode: bonds bound any spam that privacy mode might enable. No change needed to privacy mode behavior.

No Operator Revenue in v1

The slashed bond is paid to the counterparty who won the dispute via the add-invoice flow described above. There is no revenue share to the node operator in the initial implementation. Operator revenue introduces a conflict of interest (operator as judge and beneficiary) and should be a separate design decision if ever considered.


Open Questions for Implementer

  1. Hold invoice library support: Does Mostro use a Lightning library that supports hold invoices natively? If not, what is the path to adding it?
  2. Preimage custody: Mostro should hold the preimage — confirm this is feasible with the current LN stack.
  3. Order book visibility: Orders with a required maker bond should not appear publicly until bond is confirmed. Confirm this approach.
  4. Bond rollover on re-take/re-create: Each take or create is independent. A new taker on the same order must post a new bond.
  5. Dispute state machine: Does Mostro track which party is responsible for a loss when resolving disputes? Required to trigger slash_on_lost_dispute correctly.
  6. Routing fee estimation: What Lightning library primitive is available for estimating routing fees to a given destination before requesting the add-invoice? Confirm the estimation approach and fallback behavior if estimation fails.
  7. Range order partial slash tracking: How are child orders tracked against the parent range order bond? Confirm the data model required to compute and record proportional slashes without affecting the remaining locked amount.

References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions