From 30761039ea5480ac25b3b92a7f893344f748a5c1 Mon Sep 17 00:00:00 2001 From: Kayos Date: Mon, 4 May 2026 21:22:28 -0700 Subject: [PATCH] AUDIT4-G2 fix: client-side min_utxo guard on ada-only wallet_send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wallet_send now rejects sub-min-utxo (1 ADA) ada-only sends with a clear local error before any koios round-trip. Asset-bearing sends still go through to chain so the dynamic per-asset min computation is what surfaces in the error — no static guard would be right there. Saves the chain round-trip + the bewildering "tx submitted... wait 30 seconds... actually it failed" UX. Surfaced 2026-05-04 audit-4 phase G2 against the deployed container. --- crates/aldabra-mcp/src/tools.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/aldabra-mcp/src/tools.rs b/crates/aldabra-mcp/src/tools.rs index 256bb6b..7928d07 100644 --- a/crates/aldabra-mcp/src/tools.rs +++ b/crates/aldabra-mcp/src/tools.rs @@ -417,6 +417,20 @@ impl WalletService { if lovelace == 0 { return Err("lovelace must be > 0".into()); } + // AUDIT4-G2 fix: catch sub-min-utxo ada-only sends client-side + // before the chain rejects them (saves a koios round-trip + the + // user's mental model of "tx submitted" → "tx failed minutes later"). + // Asset-bearing sends have a dynamic min driven by asset count + + // name lengths — let those reach chain so the real number is in + // the error. + let default_min = ProtocolParams::default().min_utxo_lovelace; + if assets.is_empty() && lovelace < default_min { + return Err(format!( + "lovelace {lovelace} below min-utxo {default_min}; \ + the chain would reject this output. Send ≥ {default_min} \ + (1 ADA), or pass assets to use the dynamic asset-aware min." + )); + } self.enforce_value_cap(lovelace, force)?; let utxos = self