Skip to content

feat(ui): confirm saved Lightning address before AddInvoice - step 3#61

Merged
arkanoider merged 2 commits into
mainfrom
feat/buyer-invoice-saved-ln-confirm
May 5, 2026
Merged

feat(ui): confirm saved Lightning address before AddInvoice - step 3#61
arkanoider merged 2 commits into
mainfrom
feat/buyer-invoice-saved-ln-confirm

Conversation

@arkanoider
Copy link
Copy Markdown
Collaborator

@arkanoider arkanoider commented May 5, 2026

Summary

When the user has a saved buyer ln_address in settings and receives an AddInvoice notification (sell-side taker flow), Mostrix now asks whether to use that saved address as the invoice target before opening the manual invoice popup. Choosing no keeps the existing BOLT11 / Lightning-address flow.

Motivation

Avoid silently assuming the saved address matches what the user wants for this trade, while still making the happy path one confirmation away when settings already hold a verified address.

Changes

  • BuyerInvoicePreference keyed by order_id in AppState (UseSavedLnAddress vs ManualInvoice).
  • New UiMode::ConfirmSavedLnAddressForInvoice with YES/NO handling wired through enter / esc / navigation / confirmation paths.
  • present_add_invoice_popup / apply_saved_ln_address_invoice_choice in notifications_ch_mng (narrow HashMap borrow to avoid conflicting with Messages-tab mutex use).
  • Preference cleared when canceling from the invoice popup and when tearing down closed trades (order_ch_mng).
  • Confirm UI shows the actual saved address (settings loaded at draw time; render_saved_ln_address_invoice_confirm in admin_key_confirm).
  • Docs updated: SETTINGS_ANALYSIS, MESSAGE_FLOW_AND_PROTOCOL, TUI_INTERFACE, buy/sell flow specs, docs/README index line.

Summary by CodeRabbit

  • New Features

    • Added a confirmation prompt when adding invoices if a saved Lightning address is configured. Users can opt to use the saved address or manually enter an invoice.
    • Invoice preference choices are now cached per order to avoid repeated prompts until the order is canceled.
  • Documentation

    • Updated flow documentation across multiple guides to describe the new saved Lightning address confirmation behavior in invoice workflows.

- Track BuyerInvoicePreference per order_id; UiMode::ConfirmSavedLnAddressForInvoice
- Route AddInvoice through present_add_invoice_popup / apply_saved_ln_address_invoice_choice
- Clear preference on invoice-popup cancel and closed-trade cleanup (order_ch_mng)
- Draw saved-address confirm with wrapped body (admin_key_confirm, load_settings_from_disk)
- Docs: settings analysis, message flow, TUI, buy/sell flow specs

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

coderabbitai Bot commented May 5, 2026

Warning

Rate limit exceeded

@arkanoider has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 6 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cc1c475-a06f-4bbf-9be9-ddb1c7900949

📥 Commits

Reviewing files that changed from the base of the PR and between 6753ae4 and 052511a.

📒 Files selected for processing (1)
  • src/ui/admin_key_confirm.rs

Walkthrough

This PR implements an optional pre-confirmation flow for invoice entry when a saved Lightning address is configured in settings. Users see a confirmation popup asking whether to use the saved address or enter manually; the choice is cached per order and cleared when trades are canceled or removed.

Changes

Saved Lightning Address Invoice Confirmation Flow

Layer / File(s) Summary
Data Types & State
src/ui/orders.rs, src/ui/app_state.rs
New BuyerInvoicePreference enum (UseSavedLnAddress, ManualInvoice) and UiMode::ConfirmSavedLnAddressForInvoice(MessageNotification, bool) variant added; AppState field buyer_invoice_preference: HashMap<Uuid, BuyerInvoicePreference> initialized to track per-order choices.
UI Rendering
src/ui/admin_key_confirm.rs, src/ui/draw.rs
New render_saved_ln_address_invoice_confirm() function renders a centered confirmation popup with wrapped body text and YES/NO buttons; ui_draw loads saved settings, formats a confirmation message, and invokes the renderer.
Core Preference Logic
src/util/dm_utils/notifications_ch_mng.rs
present_add_invoice_popup() checks saved-address preference cache and either opens AddInvoice UI directly (with prefilled or empty address) or returns a confirmation mode when preference is unset. apply_saved_ln_address_invoice_choice() applies user selection (YES/NO) to set preference and switch to appropriate AddInvoice UI with correct invoice input.
Confirmation & Entry Handling
src/ui/key_handler/confirmation.rs, src/ui/key_handler/enter_handlers.rs, src/ui/key_handler/esc_handlers.rs
Confirm/Cancel/ESC key handlers recognize ConfirmSavedLnAddressForInvoice and apply the user's choice or defer to pending result. Enter handler on AddInvoice action calls present_add_invoice_popup() instead of direct mode switch.
Navigation & Button Toggle
src/ui/key_handler/mod.rs, src/ui/key_handler/navigation.rs
Left/Right keys toggle YES/NO button selection; Up/Down navigation is disabled for this mode.
Cache Cleanup & Integration
src/ui/key_handler/message_handlers.rs, src/util/dm_utils/order_ch_mng.rs, src/util/dm_utils/mod.rs, src/ui/mod.rs, src/ui/state.rs
Order cancellation clears the preference entry; trade/order removal removes stale preferences; confirmed operation results are deferred when popup is active; new functions are re-exported from dm_utils.
Documentation
docs/*.md
Flow descriptions in MESSAGE_FLOW_AND_PROTOCOL, SETTINGS_ANALYSIS, TUI_INTERFACE, buy/sell order flow specs updated to explain the optional LN-address confirmation step and per-order preference caching behavior.

Sequence Diagram

sequenceDiagram
    participant User
    participant UIKeyHandler as UI Key Handler
    participant NotificationMgr as Notification Manager
    participant AppState
    participant SettingsDisk as Settings Disk
    participant UIRender as UI Renderer

    User->>UIKeyHandler: Press Enter on AddInvoice
    UIKeyHandler->>NotificationMgr: present_add_invoice_popup()
    NotificationMgr->>SettingsDisk: load_settings_from_disk()
    SettingsDisk-->>NotificationMgr: ln_address (trimmed)
    NotificationMgr->>AppState: Check buyer_invoice_preference[order_id]
    
    alt Preference cached
        NotificationMgr->>AppState: Set mode to NewMessageNotification<br/>(with prefilled or empty address)
        AppState-->>UIKeyHandler: UiMode updated
    else Preference not cached & address exists
        NotificationMgr-->>UIKeyHandler: Return ConfirmSavedLnAddressForInvoice mode
        UIKeyHandler->>AppState: Set mode
        AppState-->>UIRender: Render confirmation popup
        UIRender-->>User: Show "Use saved Lightning address?" popup
        
        User->>UIKeyHandler: Press Y or N
        UIKeyHandler->>NotificationMgr: apply_saved_ln_address_invoice_choice()
        NotificationMgr->>AppState: Update buyer_invoice_preference[order_id]<br/>Set mode to NewMessageNotification<br/>(with appropriate invoice input)
        AppState-->>UIRender: Render AddInvoice popup
        UIRender-->>User: Show invoice input popup
    else No saved address
        NotificationMgr->>AppState: Set mode to NewMessageNotification<br/>(empty invoice input)
        AppState-->>UIRender: Render AddInvoice popup
        UIRender-->>User: Show invoice input popup
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

  • MostroP2P/mostrix#47: Integrates with DM/operation-result routing and pending_post_take_operation_result handshake that defers confirmations during active popups.
  • MostroP2P/mostrix#22: Modifies AddInvoice/PayInvoice message notification UI modes and popup handling logic with overlapping notification flow changes.
  • MostroP2P/mostrix#54: Related through invoice/notification popup flow updates affecting UI modes, key handlers, and notifications_ch_mng functions.

Suggested Reviewers

  • AndreaDiazCorreia
  • Catrya
  • mostronatorcoder

🐰 A confirmation quest, so bright and new,
"Use the saved address?" Yes or No, it's true!
Per-order cache remembers your choice,
Till cancel clears the voice,
Now invoices flow smoother—hooray for you!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main feature: confirming saved Lightning address before the AddInvoice flow, marking this as the third step in the feature implementation.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/buyer-invoice-saved-ln-confirm

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ui/key_handler/mod.rs (1)

912-937: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Left/Right should set explicit YES/NO, not toggle.

In this branch, both Left and Right currently flip selected_button, so pressing Left can switch from YES to NO. That conflicts with the deterministic behavior implemented elsewhere and makes selection unpredictable for this new confirmation mode too.

Suggested fix
-                | UiMode::ConfirmExit(ref mut selected_button) => {
-                    *selected_button = !*selected_button; // Toggle between YES and NO
+                | UiMode::ConfirmExit(ref mut selected_button) => {
+                    *selected_button = code == KeyCode::Left; // Left=YES, Right=NO
                     return Some(true);
                 }
🤖 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/key_handler/mod.rs` around lines 912 - 937, The Left/Right branch
currently toggles selected_button for many confirmation UiMode variants, making
Left sometimes switch YES->NO; change the handler for KeyCode::Left |
KeyCode::Right to set selected_button explicitly based on which key was pressed
(e.g., KeyCode::Left => *selected_button = false, KeyCode::Right =>
*selected_button = true) instead of flipping it, keeping the AddSolver
permission toggle branch unchanged; apply this change to the match arm that
touches UiMode::AdminMode(.. ConfirmAddSolver ..) and all the listed
confirmation variants that bind ref mut selected_button and still return
Some(true).
🧹 Nitpick comments (3)
src/ui/admin_key_confirm.rs (1)

274-367: ⚡ Quick win

Button + help-text rendering (~90 lines) is duplicated from render_admin_key_confirm_with_message.

The YES/NO button layout (style computation, Block/yes_inner/no_inner splits, centred Paragraph) and the help-text Paragraph at the bottom are character-for-character copies of lines ~114–211 in the same file. Any future change to key hints, colours, or button sizing must be made in two places.

Consider extracting private helpers, e.g.:

fn render_yes_no_buttons(f: &mut ratatui::Frame, button_area: Rect, selected: bool) {}
fn render_confirmation_help_line(f: &mut ratatui::Frame, area: Rect) {}

Both render_admin_key_confirm_with_message and render_saved_ln_address_invoice_confirm can then delegate to them.

🤖 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/admin_key_confirm.rs` around lines 274 - 367, The YES/NO button
rendering and bottom help-text are duplicated; extract them into two private
helper functions and call them from both render_admin_key_confirm_with_message
and render_saved_ln_address_invoice_confirm: implement fn
render_yes_no_buttons(f: &mut ratatui::Frame, button_area: Rect, selected: bool)
to contain the yes_style/no_style logic, Block creation, yes_inner/no_inner
splits and centered Paragraphs that use selected_button (rename parameter to
selected), and implement fn render_confirmation_help_line(f: &mut
ratatui::Frame, area: Rect) to render the final help Paragraph (the long Span
chain using PRIMARY_COLOR); replace the duplicated code in the original
functions with calls to these helpers so styling, sizing and key hints are
maintained in one place.
src/ui/draw.rs (1)

279-284: 🏗️ Heavy lift

Avoid disk reads in the render loop for this popup.

load_settings_from_disk() in ui_draw runs every frame while the modal is open. Consider resolving/caching ln_address when entering UiMode::ConfirmSavedLnAddressForInvoice and render the cached value here.

🤖 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/draw.rs` around lines 279 - 284, The render loop is calling
load_settings_from_disk() inside the UiMode::ConfirmSavedLnAddressForInvoice
branch which causes a disk read every frame; instead capture and cache the
ln_address when you transition into UiMode::ConfirmSavedLnAddressForInvoice and
render that cached value. Update the code that sets app.mode to pass the
resolved ln_address (or populate a new App field like cached_ln_address) so
UiMode::ConfirmSavedLnAddressForInvoice carries the string (or reads from
app.cached_ln_address), remove load_settings_from_disk() from draw.rs, and have
the draw code use the cached ln_address (referencing
UiMode::ConfirmSavedLnAddressForInvoice, load_settings_from_disk, ln_address,
and app.mode to locate the changes).
src/util/dm_utils/notifications_ch_mng.rs (1)

176-190: ⚡ Quick win

Extract a shared PayInvoice state builder to avoid drift across files.

Line 180 rebuilds the PayInvoice InvoiceInputState literal that is also built in src/ui/key_handler/enter_handlers.rs (Lines 941-948). A shared helper will keep defaults (focused, scroll_y, selection) synchronized.

♻️ Proposed refactor
+fn invoice_state_for_pay_invoice() -> InvoiceInputState {
+    InvoiceInputState {
+        invoice_input: String::new(),
+        focused: false,
+        just_pasted: false,
+        copied_to_clipboard: false,
+        scroll_y: 0,
+        action_selection: InvoiceNotificationActionSelection::Primary,
+    }
+}
...
-                let invoice_state = InvoiceInputState {
-                    invoice_input: String::new(),
-                    focused: false,
-                    just_pasted: false,
-                    copied_to_clipboard: false,
-                    scroll_y: 0,
-                    action_selection: InvoiceNotificationActionSelection::Primary,
-                };
+                let invoice_state = invoice_state_for_pay_invoice();
🤖 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/util/dm_utils/notifications_ch_mng.rs` around lines 176 - 190, Extract a
shared constructor for the PayInvoice InvoiceInputState to avoid duplicated
literals: add a helper function (e.g., new_pay_invoice_state or
build_default_invoice_input_state) that returns InvoiceInputState with
invoice_input: String::new(), focused: false, just_pasted: false,
copied_to_clipboard: false, scroll_y: 0, and action_selection:
InvoiceNotificationActionSelection::Primary; replace the inline literal in
notifications_ch_mng.rs where UiMode::NewMessageNotification(...) is created and
the literal in src/ui/key_handler/enter_handlers.rs with calls to this helper so
both places use the same centralized builder (keep the helper visible to both
modules or move it into a shared util module).
🤖 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.

Inline comments:
In `@src/ui/admin_key_confirm.rs`:
- Around line 231-241: The popup layout currently uses Constraint::Min(7) in the
Layout::new constraint array which expands to fill all popup rows and causes
chunk[4] (help text) to overwrite the bottom border; change that
Constraint::Min(7) to Constraint::Length(9) in the Layout::new call so the
constraints sum to less than the popup height and leave the bottom border row
unallocated (this is analogous to how render_admin_key_confirm_with_message uses
fixed Length constraints).

---

Outside diff comments:
In `@src/ui/key_handler/mod.rs`:
- Around line 912-937: The Left/Right branch currently toggles selected_button
for many confirmation UiMode variants, making Left sometimes switch YES->NO;
change the handler for KeyCode::Left | KeyCode::Right to set selected_button
explicitly based on which key was pressed (e.g., KeyCode::Left =>
*selected_button = false, KeyCode::Right => *selected_button = true) instead of
flipping it, keeping the AddSolver permission toggle branch unchanged; apply
this change to the match arm that touches UiMode::AdminMode(.. ConfirmAddSolver
..) and all the listed confirmation variants that bind ref mut selected_button
and still return Some(true).

---

Nitpick comments:
In `@src/ui/admin_key_confirm.rs`:
- Around line 274-367: The YES/NO button rendering and bottom help-text are
duplicated; extract them into two private helper functions and call them from
both render_admin_key_confirm_with_message and
render_saved_ln_address_invoice_confirm: implement fn render_yes_no_buttons(f:
&mut ratatui::Frame, button_area: Rect, selected: bool) to contain the
yes_style/no_style logic, Block creation, yes_inner/no_inner splits and centered
Paragraphs that use selected_button (rename parameter to selected), and
implement fn render_confirmation_help_line(f: &mut ratatui::Frame, area: Rect)
to render the final help Paragraph (the long Span chain using PRIMARY_COLOR);
replace the duplicated code in the original functions with calls to these
helpers so styling, sizing and key hints are maintained in one place.

In `@src/ui/draw.rs`:
- Around line 279-284: The render loop is calling load_settings_from_disk()
inside the UiMode::ConfirmSavedLnAddressForInvoice branch which causes a disk
read every frame; instead capture and cache the ln_address when you transition
into UiMode::ConfirmSavedLnAddressForInvoice and render that cached value.
Update the code that sets app.mode to pass the resolved ln_address (or populate
a new App field like cached_ln_address) so
UiMode::ConfirmSavedLnAddressForInvoice carries the string (or reads from
app.cached_ln_address), remove load_settings_from_disk() from draw.rs, and have
the draw code use the cached ln_address (referencing
UiMode::ConfirmSavedLnAddressForInvoice, load_settings_from_disk, ln_address,
and app.mode to locate the changes).

In `@src/util/dm_utils/notifications_ch_mng.rs`:
- Around line 176-190: Extract a shared constructor for the PayInvoice
InvoiceInputState to avoid duplicated literals: add a helper function (e.g.,
new_pay_invoice_state or build_default_invoice_input_state) that returns
InvoiceInputState with invoice_input: String::new(), focused: false,
just_pasted: false, copied_to_clipboard: false, scroll_y: 0, and
action_selection: InvoiceNotificationActionSelection::Primary; replace the
inline literal in notifications_ch_mng.rs where
UiMode::NewMessageNotification(...) is created and the literal in
src/ui/key_handler/enter_handlers.rs with calls to this helper so both places
use the same centralized builder (keep the helper visible to both modules or
move it into a shared util module).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e43c1f3d-e46d-44d6-95d6-02f4bd4e5dda

📥 Commits

Reviewing files that changed from the base of the PR and between ad2ef05 and 6753ae4.

📒 Files selected for processing (21)
  • docs/MESSAGE_FLOW_AND_PROTOCOL.md
  • docs/README.md
  • docs/SETTINGS_ANALYSIS.md
  • docs/TUI_INTERFACE.md
  • docs/buy order flow.md
  • docs/sell order flow.md
  • src/ui/admin_key_confirm.rs
  • src/ui/app_state.rs
  • src/ui/draw.rs
  • src/ui/key_handler/confirmation.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/key_handler/esc_handlers.rs
  • src/ui/key_handler/message_handlers.rs
  • src/ui/key_handler/mod.rs
  • src/ui/key_handler/navigation.rs
  • src/ui/mod.rs
  • src/ui/orders.rs
  • src/ui/state.rs
  • src/util/dm_utils/mod.rs
  • src/util/dm_utils/notifications_ch_mng.rs
  • src/util/dm_utils/order_ch_mng.rs

Comment thread src/ui/admin_key_confirm.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 carefully and I do not see a blocker in the current diff.

What I checked:

  • the new confirmation step before AddInvoice when a saved ln_address exists
  • the per-order in-memory preference (UseSavedLnAddress vs ManualInvoice) and where it gets cleared
  • Enter / y / Esc / Left-Right handling for the new confirm modal
  • the AddInvoice notification path, both when auto-opened from an incoming DM and when reopened from the Messages tab
  • the actual AddInvoice execution path, to make sure it still verifies Lightning addresses before sending the DM

The behavior looks coherent to me:

  • we no longer silently assume the saved address should be used
  • saying No preserves the existing manual BOLT11 / Lightning-address path
  • saying Yes pre-fills from Settings, but verification still happens later in execute_add_invoice, so the network-dependent validation stays in the send path
  • the preference is scoped per order and gets dropped on cancel / trade teardown, which avoids stale carry-over into later trades

Checks are green too, so this looks good from my side. Nice UX step, and it keeps the safety properties added in the previous LN-address work.

@arkanoider arkanoider merged commit 9b825ba into main May 5, 2026
11 checks passed
@arkanoider arkanoider deleted the feat/buyer-invoice-saved-ln-confirm branch May 5, 2026 20:37
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