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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ When `user_mode = "admin"` and `admin_privkey` is set in `settings.toml`, Mostri
- For each `(dispute, party)` pair, a shared key is derived between the admin key and the party’s trade pubkey and stored as hex in the local DB.
- Admin and party chat via NIP‑59 gift-wrap events addressed to the shared key’s public key, providing restart‑safe, per‑dispute conversations.
- Use **Tab** to switch chat view, **Shift+I** to enable/disable chat input, **PageUp** / **PageDown** to scroll, **End** to jump to latest. Press **Ctrl+S** to save the selected attachment to `~/.mostrix/downloads/`. Press **Shift+F** to open the finalization popup.
- **Finalization**: **Shift+F** opens one popup: **💰 Pay buyer** / **↩️ Refund seller** / **Bond** (inline slash picker overlay; confirm shows bond recap). Wire payload via [`BondSlashChoice`](src/util/order_utils/bond_resolution.rs). **Esc** exits (no Exit button). Instance `bond_enabled` gating still pending — see [docs/FINALIZE_DISPUTES.md](docs/FINALIZE_DISPUTES.md). Finalized disputes cannot be settled/canceled again.
- **Finalization**: **Shift+F** opens one popup: **💰 Pay buyer** / **↩️ Refund seller** / **Bond** (only when instance info has `bond_enabled: true` on kind 38385). Inline slash overlay; confirm shows bond recap when bonds are on. Wire payload via [`BondSlashChoice`](src/util/order_utils/bond_resolution.rs). **Esc** exits. Post-slash traders may get **AddBondInvoice** payout popups — see [docs/FINALIZE_DISPUTES.md](docs/FINALIZE_DISPUTES.md). Finalized disputes cannot be settled/canceled again.
- **Settings (admin)**: **Add Dispute Solver** (add another solver by `npub`), **Change Admin Key** (update `admin_privkey`).

For detailed flows and UI, see [docs/ADMIN_DISPUTES.md](docs/ADMIN_DISPUTES.md), [docs/FINALIZE_DISPUTES.md](docs/FINALIZE_DISPUTES.md), and [docs/TUI_INTERFACE.md](docs/TUI_INTERFACE.md).
Expand Down
4 changes: 2 additions & 2 deletions docs/ADMIN_DISPUTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The interface is divided into three main sections:
- **Party switching**: Tab key toggles between buyer and seller
- **Message history**: Per-dispute chat storage with scrolling
- **Dynamic input**: Input box grows from 1 to 10 lines
- **Finalization**: Press **Shift+F** for the finalize popup (💰 pay buyer / ↩️ refund seller / bond slash with overlay submenu; **Esc** to close). Confirm step shows bond recap. Execute: `execute_finalize_dispute(dispute_id, bond, …)` → `execute_admin_settle` / `execute_admin_cancel` with `bond.to_optional_payload()`. **Pending:** hide bond UI when instance `bond_enabled` is false (kind 38385). See [FINALIZE_DISPUTES.md](FINALIZE_DISPUTES.md).
- **Finalization**: Press **Shift+F** for the finalize popup (💰 pay buyer / ↩️ refund seller bodies show **Admin settle** / **Admin cancel**; optional bond slash when instance `bond_enabled` is true on kind 38385; **Esc** to close). Confirm shows bond recap when bonds are enabled. Execute: `execute_finalize_dispute` → `execute_admin_settle` / `execute_admin_cancel` (`request_id`, `wait_for_dm`, `handle_mostro_response` for `CantDo`); success toast via `BondSlashChoice::finalize_success_message`. See [FINALIZE_DISPUTES.md](FINALIZE_DISPUTES.md).
- **Visual indicators**: Focus states, colors, and icons for clarity

#### Keyboard Navigation
Expand Down Expand Up @@ -416,7 +416,7 @@ This comprehensive information allows admins to:
Once a dispute is finalized (status: `Settled`, `SellerRefunded`, or `Released`), the AdminSettle and AdminCancel actions are blocked at multiple levels:

- **Model Layer**: `AdminDispute::is_finalized()` returns `true` for finalized disputes. The helper methods `can_settle()` and `can_cancel()` return `false` when finalized.
- **UI Layer**: The finalization popup disables and grays out the "Pay Buyer" and "Refund Seller" buttons, showing "N/A" instead of the action names.
- **UI Layer**: The finalization popup disables and grays out the pay/refund buttons (inner body `—`).
- **Handler Layer**: `execute_finalize_dispute()` checks the dispute state before executing any action. If the dispute is already finalized, it returns an error: "Cannot execute [action]: dispute is already finalized".
- **Key Handler Layer**: When pressing Enter on a disabled action button, an error message is displayed: "Cannot finalize: dispute is already finalized".

Expand Down
85 changes: 59 additions & 26 deletions docs/FINALIZE_DISPUTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ This document describes how admins finalize disputes in Mostrix after reviewing
|-------|--------|--------|
| **`mostro-core` 0.11.3** | Done | `BondResolution`, `Payload::BondResolution`, `CantDoReason::InvalidPayload` |
| **`BondSlashChoice`** | Done | [`src/util/order_utils/bond_resolution.rs`](../src/util/order_utils/bond_resolution.rs) — wire mapping + unit tests |
| **Execute layer** (`execute_admin_settle` / `cancel`) | Done | Accepts `BondSlashChoice`; uses `to_optional_payload()` on the wire |
| **Execute layer** (`execute_admin_settle` / `cancel`) | Done | `request_id` + `wait_for_dm` + `handle_mostro_response`; expects `AdminSettled` / `AdminCanceled`; `CantDo` before DB update |
| **Success / error popup** | Done | `BondSlashChoice::finalize_success_message`; word-wrapped `OperationResult::Info` in `operation_result.rs` |
| **TUI** (slash picker + confirm summary) | Done | Inline bond button + overlay; confirm shows `bond.label()` recap |
| **`bond_enabled` gating** (kind 38385) | Pending | Skip slash UI when instance bonds are off |
| **`bond_enabled` gating** (kind 38385) | Done | Parse tag in [`mostro_info.rs`](../src/util/mostro_info.rs); hide Bond button when not `"true"` |
| **Trader `AddBondInvoice`** | Done | Payout popup + `execute_add_bond_invoice`; `wait_for_dm` + follow-up via `OpenInvoicePopup` / `PaymentRequestRequired` ([MESSAGE_FLOW_AND_PROTOCOL.md](MESSAGE_FLOW_AND_PROTOCOL.md)) |

Protocol references: [Admin Settle](https://mostro.network/protocol/admin_settle_order.html), [Admin Cancel](https://mostro.network/protocol/admin_cancel_order.html). Daemon bond payout (`Action::AddBondInvoice`, Mostro PR [#738](https://github.com/MostroP2P/mostro/pull/738)) is documented under trade flows, not admin finalization.
Protocol references: [Admin Settle](https://mostro.network/protocol/admin_settle_order.html), [Admin Cancel](https://mostro.network/protocol/admin_cancel_order.html).

## User Flow

Expand All @@ -31,12 +33,13 @@ Protocol references: [Admin Settle](https://mostro.network/protocol/admin_settle
- Visual scrollbar on the right shows position in chat history
5. **Open Finalization**: Press Shift+F to open finalization popup
6. **Review Full Details**: Popup shows complete dispute information
7. **Choose actions on one popup** (Left/Right): **💰 Pay buyer** (`AdminSettle`), **↩️ Refund seller** (`AdminCancel`), or **Bond** (body shows [`BondSlashChoice::label()`](../src/util/order_utils/bond_resolution.rs), default 🔓 *No bond slash*). **Esc** closes the popup (no separate Exit button).
7. **Choose actions on one popup** (Left/Right): **💰 Pay buyer** (`AdminSettle`), **↩️ Refund seller** (`AdminCancel`), and **Bond** when the instance advertises `bond_enabled: true` on kind **38385** (otherwise a two-button layout only). Button **titles** use the outcome labels; the **body** shows protocol names **`Admin settle`** / **`Admin cancel`** (and `bond.label()` on Bond). **Esc** closes the popup (no separate Exit button).
8. **Bond slash submenu** (optional): With **Bond** focused, **Enter** opens overlay **⚔️ Bond resolution**; ↑/↓ among four labeled choices; **Enter** applies; **Esc** closes submenu only.
9. **Confirm**: **Enter** on pay/refund opens Yes/No — title e.g. `⚠️ Confirm 💰 Pay buyer`, description + **Bond:** recap (`bond.label()`).
10. **Execute**: **Enter** on Yes — sends encrypted DM to Mostro.
10. **Execute**: **Enter** on Yes — UI enters `AdminMode::WaitingDisputeFinalization`, sends encrypted DM to Mostro, waits for reply.
11. **Result**: Success → multi-line **Operation Successful** popup; failure (timeout, `CantDo`, wrong action) → **Operation Failed** with daemon text. **Esc** / **Enter** closes; disputes list refreshes on success (`"Dispute finalized"` marker in `main.rs`).

**Current UI:** single finalize popup (7–8) → confirm with bond recap (9) → execute (10). `bond_enabled` gating (hide bond button) still pending.
**Current UI:** single finalize popup (7–8) → confirm with bond recap when bonds enabled (9) → wait + result (10–11).

## Finalization Actions

Expand Down Expand Up @@ -74,7 +77,11 @@ Mostrix maps these via [`BondSlashChoice`](../src/util/order_utils/bond_resoluti

If the daemon rejects a slash (e.g. side has no bond row), Mostro may reply with `CantDo(InvalidPayload)` — surfaced as *"Invalid payload - check bond slash choices or message format"* ([`get_cant_do_description`](../src/util/types.rs)).

After a slash, the non-slashed party may receive `Action::AddBondInvoice` to claim their share of the bond (see Mostro anti-abuse bond spec / PR #738); that is handled on the **trader** path, not in the admin finalization popup.
After a slash, the non-slashed party may receive `Action::AddBondInvoice` (`Payload::BondPayoutRequest`) to claim their counterparty share; Mostrix opens the bond payout invoice popup (not the admin finalization popup). When they submit their bolt11, `execute_add_bond_invoice` waits for Mostro’s next DM and chains into the normal trade flow—for example `waiting-buyer-invoice` on a sell take opens the **Add Invoice** popup for the buyer/taker. See the bond payout submit table in [MESSAGE_FLOW_AND_PROTOCOL.md](MESSAGE_FLOW_AND_PROTOCOL.md).

### Instance `bond_enabled` (kind 38385)

Mostro always emits a `bond_enabled` tag (`"true"` / `"false"`). Mostrix reads it via [`instance_bonds_enabled()`](../src/util/mostro_info.rs): only `"true"` shows the Bond button and confirm bond recap. Fetch instance info from the **Mostro Info** tab (Enter) so gating reflects the connected daemon.

## UI Components

Expand Down Expand Up @@ -110,13 +117,13 @@ The popup displays comprehensive dispute information:

**Action Buttons** (three columns, Left/Right focus):

| Button | When selected | Enter |
|--------|---------------|-------|
| **💰 Pay buyer** | Green highlight | Open confirm (settle) |
| **↩️ Refund seller** | Red highlight | Open confirm (cancel) |
| **Bond** | Primary highlight; body = `bond.label()` | Open bond overlay submenu |
| Button (title bar) | Inner body (active) | When selected | Enter |
|--------------------|---------------------|---------------|-------|
| **💰 Pay buyer** | `Admin settle` | Green highlight | Open confirm (settle) |
| **↩️ Refund seller** | `Admin cancel` | Red highlight | Open confirm (cancel) |
| **Bond** | `bond.label()` | Primary highlight | Open bond overlay submenu |

Finalized disputes: pay/refund buttons are dimmed (body `—`); use **Esc** to leave. Bond focus may remain on index 2 for display.
Finalized disputes: pay/refund buttons are dimmed (inner body `—`); use **Esc** to leave. Bond focus may remain on index 2 for display.

### Keyboard Navigation

Expand Down Expand Up @@ -197,9 +204,26 @@ Call chain from the TUI (today):

1. [`execute_finalize_dispute_action`](../src/ui/key_handler/admin_handlers.rs) — spawns async task with `bond` from `ConfirmFinalizeDispute` (chosen on the finalize popup / overlay).
2. [`execute_finalize_dispute`](../src/util/order_utils/execute_finalize_dispute.rs) — DB guards, then dispatches settle or cancel with the same `bond`.
3. [`execute_admin_settle`](../src/util/order_utils/execute_admin_settle.rs) / [`execute_admin_cancel`](../src/util/order_utils/execute_admin_cancel.rs) — `Message::new_dispute(..., bond.to_optional_payload())`, logs include `bond.log_context()`.
3. [`execute_admin_settle`](../src/util/order_utils/execute_admin_settle.rs) / [`execute_admin_cancel`](../src/util/order_utils/execute_admin_cancel.rs) — `Message::new_dispute(..., bond.to_optional_payload())` with `request_id`, `wait_for_dm`, and `handle_mostro_response` (surfaces `CantDo` before DB update). Success requires `AdminSettled` / `AdminCanceled` from Mostro.

Success popup (`OperationResult::Info`) is built by [`BondSlashChoice::finalize_success_message`](../src/util/order_utils/bond_resolution.rs) and rendered with newline-aware, word-boundary wrapping in [`operation_result.rs`](../src/ui/operation_result.rs) (dynamic popup height).

Example layout:

```text
Dispute finalized

Success toasts use the same bond phrase (e.g. `settled (buyer paid) (no bond slash)`).
Outcome:
Admin cancel — seller refunded

Bond:
⚔️ Slash buyer bond

Dispute ID:
8397bc78-7c98-4b4f-bb49-40c7101391b0
```

(Settle uses `Admin settle — buyer paid`.)

### UI state (`AdminMode`)

Expand All @@ -223,11 +247,14 @@ Rendered by [`dispute_finalization_confirm.rs`](../src/ui/dispute_finalization_c

### Expected Responses

After sending a finalization action, Mostro should respond with:
After sending a finalization action, Mostro replies over the same admin DM channel:

| Request | Success action | Failure |
|---------|----------------|---------|
| `AdminSettle` | `AdminSettled` | `CantDo` (e.g. `InvalidPayload` for bad bond slash) |
| `AdminCancel` | `AdminCanceled` | same |

- Success confirmation
- Updated dispute status
- Transaction details
Mostrix waits with `wait_for_dm` and validates via `handle_mostro_response` before updating `admin_disputes`.

## Database Updates

Expand All @@ -241,14 +268,16 @@ After successful finalization:

Possible error scenarios:

- Mostro daemon unresponsive
- Mostro daemon unresponsive or **`wait_for_dm` timeout** (15s)
- Invalid admin credentials
- Dispute already finalized
- Dispute already finalized (blocked before send)
- Network/relay issues
- Dispute not found (e.g., dispute was removed or ID is invalid)
- **`CantDo` from Mostro** (e.g. `InvalidPayload` for impossible bond slash) — surfaced via `handle_mostro_response` / [`get_cant_do_description`](../src/util/types.rs); **local DB is not updated**
- Unexpected response action (not `AdminSettled` / `AdminCanceled`)
- **Data integrity error**: Missing required fields (buyer_pubkey or seller_pubkey)

All errors are displayed in a result popup with appropriate error messages. The finalization popup includes robust error handling:
Errors use the same word-wrapped **Operation Failed** popup as other flows (`operation_result.rs`). The finalization popup includes robust error handling:

- **Dispute Not Found**: If a dispute ID is invalid or the dispute is no longer available, a clear error popup is displayed with the dispute ID and instructions to close it (Press ESC or ENTER).
- **Data Integrity Error**: If a dispute is missing required fields (`buyer_pubkey` or `seller_pubkey`), a dedicated error popup is displayed explaining that the database entry is incomplete and the dispute cannot be finalized. This validation happens both when taking a dispute (prevents saving incomplete data) and when viewing the finalization popup.
Expand Down Expand Up @@ -335,13 +364,17 @@ Tab: Switch Party | Shift+F: Finalize | ↑↓: Select Dispute | PgUp/PgDn: Scro

## Related Files

- `src/util/order_utils/bond_resolution.rs` - `BondSlashChoice`, `Payload::BondResolution` mapping, wire tests
- `src/ui/dispute_finalization_popup.rs` - Finalize popup (💰 pay / ↩️ refund / Bond + overlay trigger)
- `src/util/order_utils/bond_resolution.rs` - `BondSlashChoice`, wire mapping, `finalize_success_message`
- `src/ui/dispute_finalization_popup.rs` - Finalize popup (titles + inner `Admin settle` / `Admin cancel` / bond label)
- `src/ui/operation_result.rs` - Success/error popups (word wrap, dynamic height for `Info`)
- `src/ui/key_handler/admin_handlers.rs` - `execute_finalize_dispute_action`, waiting mode, result channel
- `src/ui/dispute_bond_slash_popup.rs` - `render_bond_slash_overlay` on finalize popup
- `src/ui/dispute_finalization_confirm.rs` - Yes/No confirm with bond recap
- `src/util/order_utils/execute_admin_settle.rs` - AdminSettle + `BondSlashChoice` payload
- `src/util/order_utils/execute_admin_cancel.rs` - AdminCancel + `BondSlashChoice` payload
- `src/util/order_utils/execute_admin_settle.rs` - AdminSettle; waits for `AdminSettled`
- `src/util/order_utils/execute_admin_cancel.rs` - AdminCancel; waits for `AdminCanceled`
- `src/util/order_utils/execute_finalize_dispute.rs` - DB checks + dispatches settle/cancel
- `src/util/order_utils/execute_add_invoice.rs` - `execute_add_invoice`, `execute_add_bond_invoice` / `execute_bond_payment_request_reply`
- `src/util/dm_utils/notifications_ch_mng.rs` - `apply_open_invoice_popup_from_execute`, `present_add_invoice_popup`
- `src/ui/disputes_in_progress_tab.rs` - Main disputes UI with chat interface
- `src/ui/key_handler/enter_handlers.rs` - Enter key handling and chat message sending
- `src/ui/key_handler/mod.rs` - Chat input handling and clipboard operations
Expand Down
Loading
Loading