This document describes how admins finalize disputes in Mostrix after reviewing the case and communicating with the buyer and seller.
| Layer | Status | Notes |
|---|---|---|
mostro-core 0.13.0 |
Done | BondResolution, Payload::BondResolution, CantDoReason::InvalidPayload, Status::WaitingMakerBond, Transport |
BondSlashChoice |
Done | src/util/order_utils/bond_resolution.rs — wire mapping + unit tests |
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) |
Done | Parse tag in 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) |
Protocol references: Admin Settle, Admin Cancel.
- Navigate to Disputes: Admin opens the "Disputes in Progress" tab
- Select Dispute: Use Up/Down arrows to select a dispute from the left sidebar
- Review Details: View dispute information in the header (parties, amounts, ratings, privacy)
- Chat with Parties:
- Use Tab to switch between buyer and seller chat views
- Press Shift+I to enable/disable chat input (prevents accidental typing)
- Type messages directly in the input box (when input enabled)
- Press Enter to send messages
- Use PageUp/PageDown to scroll through chat history
- Press End to jump to bottom of chat (latest messages)
- Visual scrollbar on the right shows position in chat history
- Open Finalization: Press Shift+F to open finalization popup
- Review Full Details: Popup shows complete dispute information
- Choose actions on one popup (Left/Right): 💰 Pay buyer (
AdminSettle), ↩️ Refund seller (AdminCancel), and Bond when the instance advertisesbond_enabled: trueon kind 38385 (otherwise a two-button layout only). Button titles use the outcome labels; the body shows protocol namesAdmin settle/Admin cancel(andbond.label()on Bond). Esc closes the popup (no separate Exit button). - Bond slash submenu (optional): With Bond focused, Enter opens overlay ⚔️ Bond resolution; ↑/↓ among four labeled choices; Enter applies; Esc closes submenu only.
- Confirm: Enter on pay/refund opens Yes/No — title e.g.
⚠️ Confirm 💰 Pay buyer, description + Bond: recap (bond.label()). - Execute: Enter on Yes — UI enters
AdminMode::WaitingDisputeFinalization, sends encrypted DM to Mostro, waits for reply. - 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 inmain.rs).
Current UI: single finalize popup (7–8) → confirm with bond recap when bonds enabled (9) → wait + result (10–11).
- Protocol Action:
Action::AdminSettle - Effect: Settles the dispute in favor of the buyer
- Result: Buyer receives the full amount from escrow
- Use Case: When buyer's claim is valid (e.g., seller didn't deliver, scam attempt)
- Protocol Action:
Action::AdminCancel - Effect: Cancels the order and refunds the seller
- Result: Seller receives the full amount back from escrow
- Use Case: When seller's position is valid (e.g., buyer false claim, buyer unresponsive)
- Effect: Press Esc on the finalize popup to return to dispute management without taking action
- Use Case: Need more information, want to continue chatting with parties
Independent of settle vs cancel: the admin chooses whether to slash posted anti-abuse bonds. Valid on both admin-settle and admin-cancel only.
| Choice | TUI label (label()) |
slash_seller |
slash_buyer |
When to use |
|---|---|---|---|---|
| No bond slash | 🔓 No bond slash | false | false | Release bonds; no penalty |
| Slash buyer bond | ⚔️ Slash buyer bond | false | true | Buyer at fault (e.g. false claim on sell order) |
| Slash seller bond | ⚔️ Slash seller bond | true | false | Seller at fault |
| Slash both bonds | ⚔️ Slash both bonds | true | true | Both parties violated rules |
Mostrix maps these via BondSlashChoice: to_optional_payload() sends payload: null for no slash and Payload::BondResolution only when a side is slashed. Use to_payload() if you need an explicit {false, false} object (same server semantics as null).
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).
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.
Mostro always emits a bond_enabled tag ("true" / "false"). Mostrix reads it via instance_bonds_enabled(): only "true" shows the Bond button and confirm bond recap. The same instance event may include protocol_version ("1" / "2") for wire transport discovery — see MESSAGE_FLOW_AND_PROTOCOL.md. Fetch instance info from the Mostro Info tab (Enter) so gating reflects the connected daemon.
The popup displays comprehensive dispute information:
Header Section:
- Order ID (full UUID) - the order associated with this dispute
- Dispute ID (full UUID) - the unique dispute identifier
- Dispute type and status
- Creation and Taken timestamps
Note: The UI displays both Order ID and Dispute ID. Previous documentation only mentioned "Dispute ID (full UUID)" which was incomplete. ✅ Resolved in this PR.
Parties Section:
- Buyer information: pubkey (truncated), role indicator (🟢 BUYER), privacy status ("Privacy: Yes/No"), rating with operating days
- Seller information: pubkey (truncated), role indicator (🔴 SELLER), privacy status ("Privacy: Yes/No"), rating with operating days
- Initiator indicator (shows "(Initiator)" suffix on the party who started the dispute)
Note: Privacy status is displayed as text labels "Yes" or "No" (not emoji indicators). The emojis (🟢/🔴) are used for role indicators (BUYER/SELLER), not privacy. Previous documentation described "privacy status (🟢 info available / 🔴 private)" which was incorrect. ✅ Resolved in this PR.
Financial Section:
- Amount in satoshis
- Fiat amount with currency code (e.g., "1000 USD")
- Premium percentage
- Payment method (if available)
Note: The fiat currency code IS displayed alongside the amount. Previous documentation listed "Fiat amount and currency" but did not clarify the format. ✅ Confirmed working.
Action Buttons (three columns, Left/Right focus):
| 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 (inner body —); use Esc to leave. Bond focus may remain on index 2 for display.
In Dispute List:
- Up/Down: Select dispute in sidebar
- Tab: Switch between buyer/seller chat party
- Shift+I: Toggle chat input enabled/disabled
- Type: Start typing message in input box (when input enabled)
- Enter: Send message
- Shift+F: Open finalization popup
- PageUp/PageDown: Scroll through chat history
- End: Jump to bottom of chat (latest messages)
- Backspace: Delete characters from input (when input enabled)
In Finalization Popup:
- Left/Right: Navigate 💰 Pay buyer | ↩️ Refund seller | Bond
- Enter on Pay/Refund: Open confirmation
- Enter on Bond: Open bond submenu overlay
- Esc: Close popup (or close submenu first if open)
In Bond Slash Submenu (overlay):
- Up/Down: Highlight choice (no slash, slash buyer, slash seller, slash both)
- Enter: Apply choice and return to main finalize popup
- Esc: Close submenu without applying
Both finalization actions use Message::new_dispute with the order UUID (from admin_disputes.id), not the dispute UUID:
use crate::util::order_utils::BondSlashChoice;
let bond = BondSlashChoice::SlashBuyer; // example
Message::new_dispute(
Some(order_id),
None,
None,
Action::AdminSettle, // or AdminCancel
bond.to_optional_payload(), // None → null; slash variants → BondResolution
)Example JSON (settle + slash buyer), per protocol:
{
"dispute": {
"version": 1,
"id": "<order-uuid>",
"action": "admin-settle",
"payload": {
"bond_resolution": {
"slash_seller": false,
"slash_buyer": true
}
}
}
}Note: Mostrix serializes
Message::Dispute(not theorderwrapper shown in some protocol examples);mostro-coreaccepts both shapes on decode. Theidfield is always the order id.
Internally, Mostrix:
- Looks up the dispute in the local
admin_disputestable by its dispute_id. - Reads the corresponding order ID from the
idcolumn. - Uses that order ID as the first parameter of
Message::new_dispute, matching what Mostro expects for finalization actions.
Call chain from the TUI (today):
execute_finalize_dispute_action— spawns async task withbondfromConfirmFinalizeDispute(chosen on the finalize popup / overlay).execute_finalize_dispute— DB guards, then dispatches settle or cancel with the samebond.execute_admin_settle/execute_admin_cancel—Message::new_dispute(..., bond.to_optional_payload())withrequest_id,wait_for_dm, andhandle_mostro_response(surfacesCantDobefore DB update). Success requiresAdminSettled/AdminCanceledfrom Mostro.
Success popup (OperationResult::Info) is built by BondSlashChoice::finalize_success_message and rendered with newline-aware, word-boundary wrapping in operation_result.rs (dynamic popup height).
Example layout:
Dispute finalized
Outcome:
Admin cancel — seller refunded
Bond:
⚔️ Slash buyer bond
Dispute ID:
8397bc78-7c98-4b4f-bb49-40c7101391b0
(Settle uses Admin settle — buyer paid.)
Finalize flow uses a single ReviewingDisputeForFinalization mode (no separate full-screen bond step):
dispute_id,selected_button_index(0=pay, 1=refund, 2=bond)bond: BondSlashChoice(defaultNone)slash_submenu_open,slash_submenu_index— overlay while picking bond
Confirm: ConfirmFinalizeDispute carries is_settle, bond, selected_button (Yes/No).
Rendered by dispute_finalization_confirm.rs: outcome title with emoji, short description, Bond: line with bond.label(), Yes/No. Esc or No returns to finalize popup preserving bond.
- Uses admin private key from settings
- Sent via encrypted DM to Mostro daemon
- Admin keys must be configured in
settings.toml
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 |
Mostrix waits with wait_for_dm and validates via handle_mostro_response before updating admin_disputes.
After successful finalization:
- Dispute status updated in local database
- Dispute may be moved to "resolved" list
- Local dispute cache refreshed
Possible error scenarios:
- Mostro daemon unresponsive or
wait_for_dmtimeout (15s) - Invalid admin credentials
- Dispute already finalized (blocked before send)
- Network/relay issues
- Dispute not found (e.g., dispute was removed or ID is invalid)
CantDofrom Mostro (e.g.InvalidPayloadfor impossible bond slash) — surfaced viahandle_mostro_response/get_cant_do_description; local DB is not updated- Unexpected response action (not
AdminSettled/AdminCanceled) - Data integrity error: Missing required fields (buyer_pubkey or seller_pubkey)
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_pubkeyorseller_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. - User-Friendly Messages: All error messages are descriptive and help users understand what went wrong.
- Safe Display: Dispute IDs and other data are safely truncated to prevent display issues with unexpected data lengths.
Source: src/ui/dispute_finalization_popup.rs:22, src/models.rs (AdminDispute::new validation)
The chat interface provides real-time communication with dispute parties:
Visual Design:
- Color-coded senders: Each message displays a header in the format "Sender - date - time" where the sender name is color-coded:
- Cyan: Admin messages
- Green: Buyer messages
- Red: Seller messages
- Dynamic input box: Automatically grows from 1 to 10 lines based on message length
- Focus indicators: Bold yellow border when typing, gray when inactive
- Chat history: Scrollable message history per dispute
Message Management:
- Per-dispute storage: Each dispute has its own chat history (stored in
admin_dispute_chats) - Party filtering: Messages are filtered by the active chat party:
- Admin messages: Only shown in the chat view of the party they were sent to (tracked via
target_partyfield) - Buyer messages: Only shown when viewing the Buyer chat
- Seller messages: Only shown when viewing the Seller chat
- Admin messages: Only shown in the chat view of the party they were sent to (tracked via
- Scroll control:
- PageUp/PageDown to navigate history
- End key to jump to bottom (latest messages)
- Visual scrollbar on the right shows position (↑/↓/│/█ symbols)
- Auto-scrolls to newest after sending
- Empty state: Shows "No messages yet" when starting a new conversation
Input Handling:
- Input toggle: Press Shift+I to enable/disable chat input
- When disabled, prevents accidental typing while navigating
- Visual indicator in input title shows enabled/disabled state
- Input is enabled by default when entering dispute management
- Text wrapping: Input wraps at word boundaries, respects available width
- Character limit: Grows up to 10 lines, with visual feedback
- Send behavior: Enter sends message, Shift+F opens finalization popup
- Clear on send: Input automatically clears after sending
The footer shows context-sensitive shortcuts:
When typing (input enabled):
Tab: Switch Party | Enter: Send | Shift+I: Disable | Shift+F: Finalize | PgUp/PgDn: Scroll | End: Bottom | ↑↓: Select Dispute
When typing (input disabled):
Tab: Switch Party | Shift+I: Enable | Shift+F: Finalize | PgUp/PgDn: Scroll | ↑↓: Navigate Chat | End: Bottom | ↑↓: Select Dispute
When not typing:
Tab: Switch Party | Shift+F: Finalize | ↑↓: Select Dispute | PgUp/PgDn: Scroll Chat | End: Bottom
- Always chat first: Communicate with both parties before finalizing
- Review all evidence: Check chat history, payment proofs, timestamps
- Consider reputation: Factor in user ratings and operating days (shown in header)
- Document reasoning: All chat messages are stored per dispute for review
- Be impartial: Base decisions on facts, not party behavior alone
- Check privacy: Privacy labels ("Yes" = private mode / "No" = public mode) indicate whether user info may be limited
- Switch parties: Use Tab to alternate between buyer and seller chats
- Scroll history: Use PageUp/PageDown to review full conversation history, or End to jump to latest
- Toggle input: Use Shift+I to disable input when navigating to prevent accidental typing
- Monitor scrollbar: Visual scrollbar on the right shows your position in the chat history
src/util/order_utils/bond_resolution.rs-BondSlashChoice, wire mapping,finalize_success_messagesrc/ui/dispute_finalization_popup.rs- Finalize popup (titles + innerAdmin settle/Admin cancel/ bond label)src/ui/operation_result.rs- Success/error popups (word wrap, dynamic height forInfo)src/ui/key_handler/admin_handlers.rs-execute_finalize_dispute_action, waiting mode, result channelsrc/ui/dispute_bond_slash_popup.rs-render_bond_slash_overlayon finalize popupsrc/ui/dispute_finalization_confirm.rs- Yes/No confirm with bond recapsrc/util/order_utils/execute_admin_settle.rs- AdminSettle; waits forAdminSettledsrc/util/order_utils/execute_admin_cancel.rs- AdminCancel; waits forAdminCanceledsrc/util/order_utils/execute_finalize_dispute.rs- DB checks + dispatches settle/cancelsrc/util/order_utils/execute_add_invoice.rs-execute_add_invoice,execute_add_bond_invoice/execute_bond_payment_request_replysrc/util/dm_utils/notifications_ch_mng.rs-apply_open_invoice_popup_from_execute,present_add_invoice_popupsrc/ui/disputes_in_progress_tab.rs- Main disputes UI with chat interfacesrc/ui/key_handler/enter_handlers.rs- Enter key handling and chat message sendingsrc/ui/key_handler/mod.rs- Chat input handling and clipboard operationssrc/ui/mod.rs- AppState with chat storage (DisputeChatMessage, ChatSender)src/models.rs- AdminDispute data model
- ADMIN_DISPUTES.md - Admin dispute management overview
- MESSAGE_FLOW_AND_PROTOCOL.md - Mostro protocol details
- TUI_INTERFACE.md - General UI navigation