Skip to content

feat(ui): PayBondInvoice popup for Mostro Phase 1.5 anti-abuse bond#68

Merged
arkanoider merged 2 commits into
mainfrom
feat/pay-bond-invoice-popup
May 12, 2026
Merged

feat(ui): PayBondInvoice popup for Mostro Phase 1.5 anti-abuse bond#68
arkanoider merged 2 commits into
mainfrom
feat/pay-bond-invoice-popup

Conversation

@arkanoider
Copy link
Copy Markdown
Collaborator

@arkanoider arkanoider commented May 12, 2026

Summary

mostro-core 0.11.0 introduces Action::PayBondInvoice and Status::WaitingTakerBond so takers can be asked to lock an optional anti-abuse bond before the trade starts. This PR wires that new action end-to-end through the TUI.

  • Dedicated 🛡️ Anti-abuse Bond Invoice popup (render_pay_bond_invoice in src/ui/message_notification.rs) mirroring PayInvoice chrome but visually distinct: shield title, "Bond invoice to pay (… sats)" label, and a yellow "Locked, not spent — refunded on normal completion" disclaimer. Primary button is Acknowledge (payment happens in the user's wallet); Cancel Order still sends Action::Cancel.
  • Popup is gated on order_status ∈ { WaitingTakerBond, None } (invoice_popup_allowed_for_order_status in src/ui/orders.rs).
  • Both routing paths now carry the originating Action:
    • Sync after take (src/util/order_utils/take_order.rs) — branches on inner_message.action and forwards through OperationResult::PaymentRequestRequired { action, … }.
    • DM listener (src/util/dm_utils/mod.rs, order_ch_mng.rs, notifications_ch_mng.rs) — added PayBondInvoice to upsert_order_from_trade_dm, invoice/sat_amount extraction, actionability gate, and the auto-popup arm. The non-overwrite guard at L243 of order_ch_mng.rs now includes PayBondInvoice.
  • Status inference: inferred_status_from_trade_action maps PayBondInvoice → WaitingTakerBond; null request_id allowlist extended to cover bond DMs.
  • Timeline stepper: listing_step_from_status handles WaitingTakerBond for both Kind::Buy and Kind::Sell so the column doesn't jump while the taker is reviewing the bond popup.
  • Key handlers: scroll, copy-on-C, action-selection cycling, clear-copied-indicator, and Enter dispatch all widened to include PayBondInvoice.

No-bond safety (important)

Mostro bonds are configurable in mostrod and not mandatory. All changes are additive:

  • Daemons not running Phase 1.5 keep using PayInvoice for hold invoices.
  • Nothing in the existing flow has been gated behind bond presence.
  • The bond popup is only opened when the daemon explicitly sends Action::PayBondInvoice (or status reaches WaitingTakerBond).

Docs

Updated docs/README.md, docs/MESSAGE_FLOW_AND_PROTOCOL.md, docs/TUI_INTERFACE.md, docs/DM_LISTENER_FLOW.md, docs/buy order flow.md, docs/sell order flow.md to describe:

  • the new popup, the Action::PayBondInvoice / Status::WaitingTakerBond semantics, and the configurable nature of bonds in mostrod;
  • a new phase 0 ("Pay anti-abuse bond") on the taker happy paths (taker = seller for buy listings, taker = buyer for sell listings);
  • the wire-level distinction with PayInvoice (pay-bond-invoice discriminator from mostro-core 0.11.0, replaces the Phase 1 hack of reusing PayInvoice for bonds).

Summary by CodeRabbit

  • New Features

    • Added an optional anti-abuse bond phase with a dedicated "Bond Invoice" popup and an “Acknowledge” primary action; bond funds are locked and refunded on normal completion.
    • Invoice notifications now only become actionable when an actual invoice exists; Enter/arrow keys and clipboard copy behave consistently for bond invoices.
  • Documentation

    • Updated flow, protocol, README and TUI docs to describe bond invoice semantics, statuses, gating, and UI text.
  • Chores

    • Bumped core dependency to v0.11.0.

Review Change Stack

mostro-core 0.11.0 introduces `Action::PayBondInvoice` and
`Status::WaitingTakerBond` so takers can be asked to lock an optional
anti-abuse bond before the trade starts.

Adds a dedicated bond popup mirroring PayInvoice but visually distinct
(shield title, "Locked, not spent — refunded on normal completion"
disclaimer) and gated on `WaitingTakerBond | None`. The take-order
sync path and the DM listener both route `PayBondInvoice` through
`OperationResult::PaymentRequestRequired`, which now carries the
originating `Action` so `order_chat_static` covers either flavor.

Additive only: daemons without bonds keep using `PayInvoice` and the
no-bond flow is unchanged.

Also updates docs (README, MESSAGE_FLOW_AND_PROTOCOL, TUI_INTERFACE,
DM_LISTENER_FLOW, buy/sell order flow) to describe the new popup,
status, and the Phase 1.5 taker phase 0.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: edec5e40-d452-4654-a22a-8b660da6fac0

📥 Commits

Reviewing files that changed from the base of the PR and between fe0af9c and 01bbd80.

📒 Files selected for processing (3)
  • src/ui/orders.rs
  • src/util/dm_utils/mod.rs
  • src/util/order_utils/take_order.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/util/order_utils/take_order.rs
  • src/util/dm_utils/mod.rs
  • src/ui/orders.rs

Walkthrough

Adds PayBondInvoice and WaitingTakerBond support: bumps mostro-core to 0.11.0, threads action through PaymentRequest result, updates DM upsert/extraction and notification gating, adds a bond invoice popup renderer and labels, extends key handlers, and updates protocol/docs.

Changes

Anti-Abuse Bond Invoice Integration

Layer / File(s) Summary
Dependency & Core Type Updates
Cargo.toml, src/ui/orders.rs
Bumped mostro-core to 0.11.0; OperationResult::PaymentRequestRequired gains an action field to distinguish PayBondInvoice from PayInvoice.
Protocol & UI Documentation
docs/DM_LISTENER_FLOW.md, docs/MESSAGE_FLOW_AND_PROTOCOL.md, docs/README.md, docs/TUI_INTERFACE.md, docs/buy order flow.md, docs/sell order flow.md
Docs updated to document the anti-abuse bond phase, PayBondInvoice/WaitingTakerBond semantics, popup gating, clipboard behavior, and timeline mapping.
Bond Invoice Popup UI & Labels
src/ui/message_notification.rs, src/ui/orders.rs, tests
Adds render_pay_bond_invoice, routes PayBondInvoice to dedicated renderer with new title/size and disclaimer; maps labels to "Bond Invoice"; allows popup when status is WaitingTakerBond; adjusts timeline step resolution and adds tests.
Key & Enter Handlers, AppState Docs
src/ui/key_handler/*, src/ui/app_state.rs
Enter/key handlers route and handle PayBondInvoice (acknowledge/cancel/stale detection), enable scrolling/copy/selection for bond popups; AppState doc comments clarified.
DM Reception, Upsert & Extraction
src/util/dm_utils/mod.rs
is_pre_active_status includes WaitingTakerBond; upsert_order_from_trade_dm persists embedded order for PayBondInvoice; extraction derives sat_amount and invoice from PaymentRequest for bond invoices.
Notification Creation & Gating
src/util/dm_utils/notifications_ch_mng.rs, src/util/dm_utils/order_ch_mng.rs
handle_message_notification and handle_operation_result include PayBondInvoice in popup creation, use notification/result action when building UiMode::NewMessageNotification, and avoid overwriting existing bond popups; actionable popups require a non-empty invoice.
Status Inference & Take-Order Handling
src/util/order_utils/helper.rs, src/util/order_utils/take_order.rs
inferred_status_from_trade_action maps PayBondInvoice -> WaitingTakerBond; handle_mostro_response allows null request_id for bond responses; take_order selects popup_action (bond vs invoice), logs it, computes sat_amount fallback, and sets the result action.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • grunch
  • Catrya
  • mostronatorcoder

Poem

🐰 A tiny hop, a bond to pay,
The DM rings, the popup says: “🛡️” — hooray!
Acknowledge, cancel, the keys do their part,
From mostro-core to UI, the flow is art.
— a rabbit, clipboard in paw 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature addition: a PayBondInvoice popup implementation for anti-abuse bond handling in Mostro Phase 1.5, which aligns with the comprehensive changes across UI, DM handling, routing, and documentation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pay-bond-invoice-popup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/ui/orders.rs (1)

596-598: ⚡ Quick win

Consider adding test coverage for WaitingTakerBond status.

The timeline step logic correctly treats Status::WaitingTakerBond like Pending (pre-trade state), but the existing test suite (lines 812-924) doesn't include test cases for this new status. While the logic is straightforward, adding tests would improve confidence and serve as documentation for the Phase 1.5+ bond flow.

📋 Example test to add
#[test]
fn buy_taker_waiting_taker_bond_maps_to_seller_payment_step() {
    let m = sample_order_message(
        Action::PayBondInvoice,
        Some(mostro_core::order::Kind::Buy),
        Some(false),
        Some(mostro_core::order::Status::WaitingTakerBond),
    );
    assert_eq!(
        message_trade_timeline_step(&m),
        FlowStep::BuyFlowStep(StepLabelsBuy::StepSellerPayment)
    );
}

#[test]
fn sell_taker_waiting_taker_bond_maps_to_seller_payment_step() {
    let m = sample_order_message(
        Action::PayBondInvoice,
        Some(mostro_core::order::Kind::Sell),
        Some(false),
        Some(mostro_core::order::Status::WaitingTakerBond),
    );
    assert_eq!(
        message_trade_timeline_step(&m),
        FlowStep::SellFlowStep(StepLabelsSell::StepSellerPayment)
    );
}

Also applies to: 620-622

🤖 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/ui/orders.rs` around lines 596 - 598, Add unit tests that cover
Status::WaitingTakerBond mapping in message_trade_timeline_step: create two
tests using sample_order_message with Action::PayBondInvoice, Kind::Buy and
Kind::Sell respectively, and Status::WaitingTakerBond, then assert the returned
FlowStep equals FlowStep::BuyFlowStep(StepLabelsBuy::StepSellerPayment) for the
buy case and FlowStep::SellFlowStep(StepLabelsSell::StepSellerPayment) for the
sell case; place them alongside the existing tests that cover other statuses so
the Phase 1.5+ bond flow is exercised.
🤖 Prompt for all review comments with 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.

Nitpick comments:
In `@src/ui/orders.rs`:
- Around line 596-598: Add unit tests that cover Status::WaitingTakerBond
mapping in message_trade_timeline_step: create two tests using
sample_order_message with Action::PayBondInvoice, Kind::Buy and Kind::Sell
respectively, and Status::WaitingTakerBond, then assert the returned FlowStep
equals FlowStep::BuyFlowStep(StepLabelsBuy::StepSellerPayment) for the buy case
and FlowStep::SellFlowStep(StepLabelsSell::StepSellerPayment) for the sell case;
place them alongside the existing tests that cover other statuses so the Phase
1.5+ bond flow is exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b40990f-4611-4b90-bc82-830ee845de17

📥 Commits

Reviewing files that changed from the base of the PR and between c496f0c and fe0af9c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • Cargo.toml
  • docs/DM_LISTENER_FLOW.md
  • docs/MESSAGE_FLOW_AND_PROTOCOL.md
  • docs/README.md
  • docs/TUI_INTERFACE.md
  • docs/buy order flow.md
  • docs/sell order flow.md
  • src/ui/app_state.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/key_handler/message_handlers.rs
  • src/ui/key_handler/mod.rs
  • src/ui/message_notification.rs
  • src/ui/orders.rs
  • src/util/dm_utils/mod.rs
  • src/util/dm_utils/notifications_ch_mng.rs
  • src/util/dm_utils/order_ch_mng.rs
  • src/util/order_utils/helper.rs
  • src/util/order_utils/take_order.rs

Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

I reviewed this strictly and I found a blocker in the PayBondInvoice handling.

Blocker

In the DM listener path, the popup amount shown for PayBondInvoice / PayInvoice is derived from the embedded order amount instead of from the payment-request amount.

Specifically, in src/util/dm_utils/mod.rs:

Action::PayInvoice | Action::PayBondInvoice => match &inner_kind.payload {
    Some(Payload::PaymentRequest(opt_order, invoice, _)) => {
        (opt_order.as_ref().map(|o| o.amount), Some(invoice.clone()))
    }
    _ => (None, None),
},

That uses opt_order.amount, which is the trade/order amount, not the invoice amount carried by Payload::PaymentRequest.

This is especially dangerous for PayBondInvoice, because the anti-abuse bond amount is not the same thing as the order amount. So the UI can end up showing the user a wrong sats figure in the bond popup even though the actual invoice string is different.

The take-order sync path already forwards sat_amount from the payment-request payload correctly (*opt_amount in src/util/order_utils/take_order.rs), but the DM listener path does not mirror that behavior.

Since this is a payment-facing popup and can display a misleading amount, I am marking this as request changes.

I think the fix is to source sat_amount from the PaymentRequest amount field in the DM path as well, and keep the two code paths consistent.

Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

Re-reviewed the latest commit.

The blocker I raised is resolved now.

The DM listener path now prefers the explicit PaymentRequest amount override for PayBondInvoice, and only falls back to the embedded order amount when needed for legacy PayInvoice behavior. The synchronous take_order path was updated consistently as well.

That was the important correctness fix here, because the payment-facing popup must not show a misleading sats amount for the anti-abuse bond flow.

I also checked the small follow-up timeline tests added in src/ui/orders.rs, and I do not see a new blocker in the updated version.

@arkanoider arkanoider merged commit 4006ca1 into main May 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant