From 8469af5f4eb3e8ee6af4b93a510b442af12553da Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 5 Aug 2026 23:41:35 -0400 Subject: [PATCH] fix(wallet): surface LND sweep refusals as readable errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A sweep of 92 unconfirmed/dust sats failed with LND's debug-flavored "insufficient input to create sweep tx: input_sum=0 BTC, output_sum= 0.00000092 BTC" — and the RPC sanitizer then masked even that behind "Operation failed. Check server logs." (framework-pt, 2026-08-06). The sweep mechanics are untouched (balance minus fee, as always) — this only makes the refusal say WHY in plain language. - lnd.sendcoins translates the sweep refusal: balance below Bitcoin's dust minimum or not yet confirmed, so no transaction can be built (LND's original message kept in parens). - "Failed to send" joins the sanitizer's user-facing allowlist — the same lesson as "Insufficient balance"/"Payment failed" before it. Co-Authored-By: Claude Fable 5 --- core/archipelago/src/api/rpc/lnd/wallet.rs | 13 +++++++++++++ core/archipelago/src/api/rpc/middleware.rs | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/core/archipelago/src/api/rpc/lnd/wallet.rs b/core/archipelago/src/api/rpc/lnd/wallet.rs index d679de7e..8997c998 100644 --- a/core/archipelago/src/api/rpc/lnd/wallet.rs +++ b/core/archipelago/src/api/rpc/lnd/wallet.rs @@ -192,6 +192,19 @@ impl RpcHandler { .get("message") .and_then(|v| v.as_str()) .unwrap_or("Unknown error"); + // LND's sweep refusal reads like a debug dump ("insufficient + // input to create sweep tx: input_sum=0 BTC, output_sum=…"). + // input_sum=0 with a tiny output means the wallet's coins are + // unconfirmed or below Bitcoin's dust minimum — say that + // (framework-pt sweep of 92 sats, 2026-08-06). + if msg.contains("insufficient input to create sweep tx") { + return Err(anyhow::anyhow!( + "Failed to send: your on-chain balance is too small or still \ + unconfirmed to sweep. Bitcoin cannot build a transaction from \ + coins below the dust minimum (~546 sats) or from funds that \ + have not confirmed yet. (LND: {msg})" + )); + } return Err(anyhow::anyhow!("Failed to send: {}", msg)); } diff --git a/core/archipelago/src/api/rpc/middleware.rs b/core/archipelago/src/api/rpc/middleware.rs index a20a184f..bba77598 100644 --- a/core/archipelago/src/api/rpc/middleware.rs +++ b/core/archipelago/src/api/rpc/middleware.rs @@ -79,6 +79,11 @@ pub(super) fn sanitize_error_message(msg: &str) -> String { // them in the first place (ecash send, 2026-07-22). "Insufficient balance", "Insufficient funds", + // On-chain send/sweep refusals from LND ("Failed to send: your + // on-chain balance is too small or still unconfirmed to sweep…"). + // Masking sent the operator to journalctl again (framework-pt + // sweep, 2026-08-06) — same lesson as the two above. + "Failed to send", // Lightning payment failures carry LND's reason ("invoice expired. // Valid until …", "no route", …) — the user can act on every one of // them, and masking sent the operator to journalctl (invoice-expired