Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description = "Mostro TUI client"
[dependencies]
ratatui = "0.30.0"
crossterm = { version = "0.29.0", features = ["event-stream"] }
mostro-core = "0.10.1"
mostro-core = "0.11.0"
nostr-sdk = { version = "0.44.1", features = ["nip06", "nip44", "nip59"] }
bip39 = { version = "2.1.0", features = ["rand"] }
sqlx = { version = "0.8.5", features = ["sqlite", "runtime-tokio-rustls"] }
Expand Down
10 changes: 5 additions & 5 deletions docs/DM_LISTENER_FLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ This function is where `OrderMessage` is created/updated and pushed into `messag
Key behaviors:

- **DB refresh/upsert for certain actions**
For `add-invoice` and `pay-invoice` where the payload embeds an order, the listener persists/upserts the order row (including request id when available).
For `add-invoice`, `pay-invoice`, and **`pay-bond-invoice`** (Mostro Phase 1.5+ anti-abuse bond) where the payload embeds an order, the listener persists/upserts the order row (including request id when available). `pay-bond-invoice` is additionally allow-listed for null `request_id` DMs (`src/util/order_utils/helper.rs`) since Mostro emits these unsolicited after a take.

- **Status persistence**
Updates the order status in SQLite via `update_order_status` using:
Expand Down Expand Up @@ -191,7 +191,7 @@ So the “message list” is really a **per-order summary row list**, not a chat

If the update is both:

- **actionable** (e.g. `pay-invoice` only when an actual invoice exists), and
- **actionable** (e.g. `pay-invoice` / `pay-bond-invoice` only when an actual invoice exists in the `PaymentRequest` payload — same gate in `src/util/dm_utils/mod.rs`), and
- **new** (per the logic above),

then the listener:
Expand All @@ -202,13 +202,13 @@ then the listener:
## Action vs Status vs Database (how to think about them)

- **`Action`** (`mostro_core::Action`)
The *event type* of a protocol step (e.g. `PayInvoice`, `AddInvoice`, `FiatSent`, `Release`, `Canceled`, …). This is always present in the decoded `MessageKind`.
The *event type* of a protocol step (e.g. `PayInvoice`, **`PayBondInvoice`**, `AddInvoice`, `FiatSent`, `Release`, `Canceled`, …). This is always present in the decoded `MessageKind`. `PayBondInvoice` (wire discriminator `pay-bond-invoice`) was introduced in `mostro-core` 0.11.0 and replaces the Phase 1 hack of reusing `PayInvoice` for anti-abuse bonds.

- **`Status`** (`mostro_core::order::Status`)
The order’s *state machine position* (e.g. `waiting-payment`, `active`, `fiat-sent`, `success`, …). This may come from:
The order’s *state machine position* (e.g. `waiting-payment`, **`waiting-taker-bond`** (Phase 1.5+), `active`, `fiat-sent`, `success`, …). This may come from:
- an embedded order payload (`Payload::Order` or `PaymentRequest(Some(order), ...)`)
- the local DB (previously persisted)
- inference from certain action-only messages
- inference from certain action-only messages (e.g. `PayBondInvoice` → `WaitingTakerBond` in `inferred_status_from_trade_action`)

- **Database (`sqlite`)**
Used to persist “critical truth” for recovery and UI:
Expand Down
8 changes: 5 additions & 3 deletions docs/MESSAGE_FLOW_AND_PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ In addition to relay-driven trade DMs, Mostrix keeps a lightweight local transcr
- **Incremental merge**: `apply_user_order_chat_updates` deduplicates by `(timestamp, content)`, persists new entries, and advances per-order cursors.
- **Compatibility parsing**: legacy sender labels from older files (`Admin`, `Admin to Buyer`, `Admin to Seller`, `Buyer`, `Seller`) are mapped to `You/Peer` when loading.
- **UI selection safety**: the "My Trades" sidebar and Enter/send handlers resolve the active order list from the same shared projection (`helpers::build_active_order_chat_list`), ensuring `selected_order_chat_idx` cannot target a different order than the highlighted row.
- **My Trades static header (`order_chat_static`)**: in-memory map `AppState.order_chat_static` (see `src/ui/orders.rs` — `OrderChatStaticHeader`) is written by `handle_operation_result` in `src/util/dm_utils/order_ch_mng.rs` on `OperationResult::Success` and `PaymentRequestRequired` (after take / PayInvoice path), and populated from the local `orders` table during `sync_user_order_history_messages_from_db` in `src/ui/helpers/startup.rs`. It is cleared for removed trades when `TradeClosed` / `OrderHistoryDeleted` are handled. It supplies stable header fields (order id, kind, created time, trade index, initiator) so the UI does not depend on folding those out of the DM stream.
- **My Trades static header (`order_chat_static`)**: in-memory map `AppState.order_chat_static` (see `src/ui/orders.rs` — `OrderChatStaticHeader`) is written by `handle_operation_result` in `src/util/dm_utils/order_ch_mng.rs` on `OperationResult::Success` and `PaymentRequestRequired` (after take / PayInvoice / PayBondInvoice path — the variant now carries the originating `Action` so the same write covers anti-abuse bond responses), and populated from the local `orders` table during `sync_user_order_history_messages_from_db` in `src/ui/helpers/startup.rs`. It is cleared for removed trades when `TradeClosed` / `OrderHistoryDeleted` are handled. It supplies stable header fields (order id, kind, created time, trade index, initiator) so the UI does not depend on folding those out of the DM stream.
- **Live fields from DMs**: the projection over `AppState.messages` per order merges `Payload::Order` (first economic snapshot, buyer/seller trade pubkeys) with `Payload::Peer` so counterparty `UserInfo` can populate buyer/seller rating, and `order_status` updates status for the header and for `resolve_selected_mytrades_order_status` in `src/ui/key_handler/chat_helpers.rs`.

**Source**: `src/ui/helpers/startup.rs`, `src/ui/helpers/chat_storage.rs`, `src/ui/helpers/order_chat_projection.rs`, `src/util/dm_utils/order_ch_mng.rs`, `src/util/chat_utils.rs`
Expand Down Expand Up @@ -469,14 +469,16 @@ Otherwise:
- Action mapping:
- `AddInvoice` and `WaitingBuyerInvoice` -> AddInvoice popup mode.
- `PayInvoice` and `WaitingSellerToPay` -> PayInvoice popup mode.
- **`PayBondInvoice`** (Mostro Phase 1.5+) -> dedicated **anti-abuse bond** popup mode (`render_pay_bond_invoice` in `src/ui/message_notification.rs`). Same `Payload::PaymentRequest` shape as `PayInvoice`, but distinguished visually (shield emoji title, "Bond invoice to pay (… sats)" amount label, and a yellow "Locked, not spent — refunded on normal completion" disclaimer). The popup is gated on `order_status` ∈ {`WaitingTakerBond`, `None`} (`invoice_popup_allowed_for_order_status` in `src/ui/orders.rs`) and is **additive**: daemons not running Phase 1.5 keep using `PayInvoice` for hold invoices, so no-bond flows are unchanged.
- Popup selection:
- Left/Right toggles between **Primary** and **Cancel Order**.
- Enter confirms the selected action.
- For **`PayBondInvoice`** the **Primary** button is labelled **Acknowledge** (closes the popup) since the actual payment happens in the user's wallet; cancel still sends `Action::Cancel`.
- Cancel path:
- Selecting **Cancel Order** sends `Action::Cancel` through `execute_send_msg`, reusing the existing async order-result channel flow.
- Selecting **Cancel Order** sends `Action::Cancel` through `execute_send_msg`, reusing the existing async order-result channel flow. This is valid during `WaitingTakerBond` per the Mostro Phase 1.5+ spec.
- Paste/copy details:
- AddInvoice supports bracketed paste plus key/mouse fallbacks where terminals do not emit `Event::Paste`.
- PayInvoice keeps copy (`C`) + scroll behavior while supporting cancel selection.
- PayInvoice and PayBondInvoice keep copy (`C`) + scroll behavior while supporting cancel selection.
- **Lightning address as invoice**: If the input is a Lightning address (`user@domain.com`), Mostrix still sends `AddInvoice` with a `PaymentRequest` payload, but first verifies the LNURL metadata endpoint returns `tag: payRequest` (`util::ln_address::ln_address_pay_request_reachable`) so unreachable addresses fail before hitting Mostro.

### Rating the counterparty (`RateUser`)
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Index of architecture and feature guides for the Mostrix TUI client. The [root R

- **TUI Interface**: [TUI_INTERFACE.md](TUI_INTERFACE.md) — Navigation, modes, state; My Trades (static `order_chat_static` header vs `build_active_order_chat_list` live fields)
- **UI constants** (`src/ui/constants.rs`): Shared copy (footers, help, **`StepLabel`** for the Messages tab buy/sell timeline)
- **Buy order flow (spec)**: [buy order flow.md](buy%20order%20flow.md)
- **Sell order flow (spec)**: [sell order flow.md](sell%20order%20flow.md)
- **Buy order flow (spec)**: [buy order flow.md](buy%20order%20flow.md) — Phase 1.5+ optional **anti-abuse bond** (`PayBondInvoice` / `WaitingTakerBond`) included as phase 0 for the taker
- **Sell order flow (spec)**: [sell order flow.md](sell%20order%20flow.md) — Phase 1.5+ optional **anti-abuse bond** for the taker (buyer)
- **Range Orders**: [RANGE_ORDERS.md](RANGE_ORDERS.md) — Variable amount orders and NextTrade payload

## Admin
Expand Down
9 changes: 5 additions & 4 deletions docs/TUI_INTERFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ The `handle_key_event` function dispatches keys based on the current `UiMode`.
- Text wrapping with word boundary detection
- **Input toggle**: Press **Shift+I** to enable/disable chat input (prevents accidental typing)
- **Visual feedback**: Input title shows enabled/disabled state
- **Copy to Clipboard**: Pressing `C` in a `PayInvoice` notification uses the `arboard` crate to copy the invoice. On Linux, it uses the `SetExtLinux::wait()` method to properly wait until the clipboard is overwritten, ensuring reliable clipboard handling without arbitrary delays.
- **Copy to Clipboard**: Pressing `C` in a `PayInvoice` or `PayBondInvoice` notification uses the `arboard` crate to copy the invoice. On Linux, it uses the `SetExtLinux::wait()` method to properly wait until the clipboard is overwritten, ensuring reliable clipboard handling without arbitrary delays.
- **Exit Confirmation**: Pressing `Q` or selecting the Exit tab shows a confirmation popup before exiting the application. Use Left/Right to select Yes/No, Enter to confirm, or Esc to cancel.
- **Help popup**: Press **Ctrl+H** (in normal or managing-dispute mode) to open a centered overlay with all keyboard shortcuts for the current tab. Press Esc, Enter, or Ctrl+H to close.

Expand All @@ -233,8 +233,9 @@ Displays a list of direct messages related to the user's trades. Messages are tr
- **Invoice popups (`NewMessageNotification`)**:
- `AddInvoice` / `WaitingBuyerInvoice` map to invoice-submit popup mode. If **`settings.toml`** **`ln_address`** is non-empty, **`UiMode::ConfirmSavedLnAddressForInvoice`** may appear first (**YES** / **NO**), listing the saved address; **Left/Right** moves the highlight; **YES + Enter** auto-submits **`AddInvoice`** via **`submit_add_invoice`** (`message_handlers.rs`) without opening the invoice input popup (**`UseSavedLnAddress`** is saved only after the send succeeds — **`OperationResult::InvoiceSubmitted`** in **`order_ch_mng.rs`**). Choose **NO** and press **Enter** to open the manual invoice UI (**`ManualInvoice`**). **Esc** closes the confirm popup without committing YES or NO (**`handle_esc_key`** in `esc_handlers.rs`). **`BuyerInvoicePreference`** per **`order_id`** (`src/ui/app_state.rs`, `src/ui/orders.rs`) remembers the choice for that trade until **Cancel Order** from the popup removes it (`message_handlers.rs`) or the trade row is torn down (`order_ch_mng.rs`).
- `PayInvoice` / `WaitingSellerToPay` map to payment popup mode.
- Both popups provide two actions (`Primary` + `Cancel Order`) via Left/Right selection; Enter confirms the selected action.
- `PayInvoice` keeps copy (`C`) and scroll (`Up/Down`, `PageUp/PageDown`) behavior while adding cancel selection.
- **`PayBondInvoice`** / `WaitingTakerBond` (Mostro **Phase 1.5+** anti-abuse bond) maps to a dedicated bond popup mode (`render_pay_bond_invoice` in `src/ui/message_notification.rs`). It mirrors the PayInvoice layout but uses a **🛡️** title (`Anti-abuse Bond Invoice`), a "Bond invoice to pay (… sats)" label, and a yellow "Locked, not spent — refunded on normal completion" disclaimer. Primary button is **Acknowledge** (closes the popup; payment happens in the user's wallet); **Cancel Order** is still wired to `Action::Cancel`. The popup is gated on `order_status ∈ {WaitingTakerBond, None}` (`invoice_popup_allowed_for_order_status`). Bonds are **configurable in mostrod** — when not enabled, the daemon sends regular `PayInvoice` and this popup is never opened.
- All three popups provide two actions (`Primary` + `Cancel Order`) via Left/Right selection; Enter confirms the selected action.
- `PayInvoice` and `PayBondInvoice` keep copy (`C`) and scroll (`Up/Down`, `PageUp/PageDown`) behavior while adding cancel selection.

**`ViewingMessage` (trade confirmations)** — `render_message_view` in `src/ui/tabs/tab_content.rs`:

Expand All @@ -255,7 +256,7 @@ A stateful form for creating new orders. It supports both fixed amounts and fiat
The My Trades workspace (`src/ui/tabs/order_in_progress_tab.rs`) now shows richer order context and cleaner chat readability:

- **Header metadata (two layers)**:
- **Stable (one-time)**: order id, kind, created-at, trade index, and initiator (role: Maker/Taker + truncated trade pubkey) come from `OrderChatStaticHeader` in `AppState.order_chat_static`. The map is filled when the user **creates** an order (maker) or **takes** an order (taker, including the `PaymentRequestRequired` / PayInvoice path), and from `sync_user_order_history_messages_from_db` in `src/ui/helpers/startup.rs` (parses local `orders` rows so restarts do not lose the header). Entries are removed when a trade is closed or history cleanup deletes the row (`handle_operation_result` in `src/util/dm_utils/order_ch_mng.rs`).
- **Stable (one-time)**: order id, kind, created-at, trade index, and initiator (role: Maker/Taker + truncated trade pubkey) come from `OrderChatStaticHeader` in `AppState.order_chat_static`. The map is filled when the user **creates** an order (maker) or **takes** an order (taker, including the `PaymentRequestRequired` path for both `PayInvoice` and `PayBondInvoice` responses — the variant carries the originating `Action`), and from `sync_user_order_history_messages_from_db` in `src/ui/helpers/startup.rs` (parses local `orders` rows so restarts do not lose the header). Entries are removed when a trade is closed or history cleanup deletes the row (`handle_operation_result` in `src/util/dm_utils/order_ch_mng.rs`).
- **Live (from DMs)**: **status**, **amount / fiats**, **payment method**, **premium**, and **buyer/seller rating** (when present) still come from the message projection (see below).
- **Privacy / ratings**: there is no placeholder row for **`Privacy:`** / **`Buyer -`** / **`Seller -`** until trade privacy can be sourced from the same context as disputes (DM `SmallOrder` does not carry those flags). **Buyer Rating:** / **Seller Rating:** lines are shown only when reputation exists: `helpers::build_active_order_chat_list` merges `Payload::Peer` with `UserInfo` when `peer.pubkey` matches `buyer_trade_pubkey` / `seller_trade_pubkey` from `Payload::Order`, and the header uses `helpers::format_user_rating` for display.
- **Chat rendering**: user/peer messages are wrapped to fit pane width (including splitting overlong tokens by **Unicode character** count so lines do not overflow); peer messages are right-aligned for better sender separation.
Expand Down
Loading
Loading