From 75a031125d046c3c2f4ecdb7e68118adeae6b339 Mon Sep 17 00:00:00 2001 From: grunch Date: Wed, 13 May 2026 15:57:59 -0300 Subject: [PATCH 1/2] feat(admin): expose BondResolution payload on adm-settle / adm-cancel Add --slash-seller and --slash-buyer flags to the AdmSettle and AdmCancel subcommands so QA / solvers can exercise the anti-abuse-bond Phase 2 flow shipped in MostroP2P/mostro#737. When either flag is set, the dispute message now carries a Payload::BondResolution; with no flags the payload stays None to preserve Phase 1 behaviour. The slash decisions are also surfaced in the printed action table. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/cli.rs | 24 +++++++++++++++++++-- src/cli/take_dispute.rs | 48 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index ba2fe2c..5b5a701 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -266,12 +266,24 @@ pub enum Commands { /// Order id #[arg(short, long)] order_id: Uuid, + /// Slash the seller's bond (anti-abuse-bond Phase 2) + #[arg(long, default_value_t = false)] + slash_seller: bool, + /// Slash the buyer's bond (anti-abuse-bond Phase 2) + #[arg(long, default_value_t = false)] + slash_buyer: bool, }, /// Settle a seller's hold invoice (only admin) AdmSettle { /// Order id #[arg(short, long)] order_id: Uuid, + /// Slash the seller's bond (anti-abuse-bond Phase 2) + #[arg(long, default_value_t = false)] + slash_seller: bool, + /// Slash the buyer's bond (anti-abuse-bond Phase 2) + #[arg(long, default_value_t = false)] + slash_buyer: bool, }, /// Requests open disputes from Mostro pubkey ListDisputes {}, @@ -583,8 +595,16 @@ impl Commands { // Admin commands Commands::ListDisputes {} => execute_list_disputes(ctx).await, Commands::AdmAddSolver { npubkey } => execute_admin_add_solver(npubkey, ctx).await, - Commands::AdmSettle { order_id } => execute_admin_settle_dispute(order_id, ctx).await, - Commands::AdmCancel { order_id } => execute_admin_cancel_dispute(order_id, ctx).await, + Commands::AdmSettle { + order_id, + slash_seller, + slash_buyer, + } => execute_admin_settle_dispute(order_id, *slash_seller, *slash_buyer, ctx).await, + Commands::AdmCancel { + order_id, + slash_seller, + slash_buyer, + } => execute_admin_cancel_dispute(order_id, *slash_seller, *slash_buyer, ctx).await, Commands::AdmTakeDispute { dispute_id } => execute_take_dispute(dispute_id, ctx).await, // Simple commands diff --git a/src/cli/take_dispute.rs b/src/cli/take_dispute.rs index a8e2a85..ad33a6a 100644 --- a/src/cli/take_dispute.rs +++ b/src/cli/take_dispute.rs @@ -44,7 +44,12 @@ pub async fn execute_admin_add_solver(npubkey: &str, ctx: &Context) -> Result<() Ok(()) } -pub async fn execute_admin_cancel_dispute(dispute_id: &Uuid, ctx: &Context) -> Result<()> { +pub async fn execute_admin_cancel_dispute( + dispute_id: &Uuid, + slash_seller: bool, + slash_buyer: bool, + ctx: &Context, +) -> Result<()> { println!("👑 Admin Cancel Dispute"); println!("═══════════════════════════════════════"); let mut table = create_standard_table(); @@ -54,6 +59,12 @@ pub async fn execute_admin_cancel_dispute(dispute_id: &Uuid, ctx: &Context) -> R "Dispute ID", &dispute_id.to_string(), )); + if slash_seller { + table.add_row(create_emoji_field_row("⚔️ ", "Slash", "seller bond")); + } + if slash_buyer { + table.add_row(create_emoji_field_row("⚔️ ", "Slash", "buyer bond")); + } table.add_row(create_emoji_field_row( "🎯 ", "Mostro PubKey", @@ -64,9 +75,18 @@ pub async fn execute_admin_cancel_dispute(dispute_id: &Uuid, ctx: &Context) -> R let _admin_keys = get_admin_keys(ctx)?; + let payload = if slash_seller || slash_buyer { + Some(Payload::BondResolution(BondResolution { + slash_seller, + slash_buyer, + })) + } else { + None + }; + // Build admin dispute message let take_dispute_message = - Message::new_dispute(Some(*dispute_id), None, None, Action::AdminCancel, None) + Message::new_dispute(Some(*dispute_id), None, None, Action::AdminCancel, payload) .as_json() .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; @@ -77,7 +97,12 @@ pub async fn execute_admin_cancel_dispute(dispute_id: &Uuid, ctx: &Context) -> R Ok(()) } -pub async fn execute_admin_settle_dispute(dispute_id: &Uuid, ctx: &Context) -> Result<()> { +pub async fn execute_admin_settle_dispute( + dispute_id: &Uuid, + slash_seller: bool, + slash_buyer: bool, + ctx: &Context, +) -> Result<()> { println!("👑 Admin Settle Dispute"); println!("═══════════════════════════════════════"); let mut table = create_standard_table(); @@ -87,6 +112,12 @@ pub async fn execute_admin_settle_dispute(dispute_id: &Uuid, ctx: &Context) -> R "Dispute ID", &dispute_id.to_string(), )); + if slash_seller { + table.add_row(create_emoji_field_row("⚔️ ", "Slash", "seller bond")); + } + if slash_buyer { + table.add_row(create_emoji_field_row("⚔️ ", "Slash", "buyer bond")); + } table.add_row(create_emoji_field_row( "🎯 ", "Mostro PubKey", @@ -97,9 +128,18 @@ pub async fn execute_admin_settle_dispute(dispute_id: &Uuid, ctx: &Context) -> R let _admin_keys = get_admin_keys(ctx)?; + let payload = if slash_seller || slash_buyer { + Some(Payload::BondResolution(BondResolution { + slash_seller, + slash_buyer, + })) + } else { + None + }; + // Build admin dispute message let take_dispute_message = - Message::new_dispute(Some(*dispute_id), None, None, Action::AdminSettle, None) + Message::new_dispute(Some(*dispute_id), None, None, Action::AdminSettle, payload) .as_json() .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; admin_send_dm(ctx, take_dispute_message).await?; From fe69fa864c6a303af9b32baa99e405695eabcaeb Mon Sep 17 00:00:00 2001 From: grunch Date: Wed, 13 May 2026 16:05:58 -0300 Subject: [PATCH 2/2] bumps mostro-core version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c360fa0..ccc6ddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1550,9 +1550,9 @@ dependencies = [ [[package]] name = "mostro-core" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f73dc932127909d84e64a3dd1a5cd0e9b6549fdef3eb6ec02a1de26a71472f9" +checksum = "0cd16bab24c530f7bc026014ce4810e6d427abf5f4d5a5977c46ef97bd420ef4" dependencies = [ "bitcoin", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 9e02fc2..d059c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ reqwest = { version = "0.12.23", default-features = false, features = [ "json", "rustls-tls", ] } -mostro-core = "0.11.0" +mostro-core = "0.11.1" lnurl-rs = { version = "0.9.0", default-features = false, features = ["ureq"] } pretty_env_logger = "0.5.0" sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio-rustls"] }