From 138f9905bf2a15dcfeecf8ca20d7bec63ebf18fb Mon Sep 17 00:00:00 2001 From: Padreug Date: Mon, 15 Jun 2026 22:05:09 +0200 Subject: [PATCH] fix(wallet): accept uppercase QR-scanned BOLT11 invoices on send The send path detected and decoded uppercase invoices correctly (SendDialog's isBolt11 lowercases first) but WalletService.sendPayment gated the bolt11 branch on a case-sensitive startsWith('ln'), so QR-scanned invoices (uppercase bech32, e.g. LNBC...) fell through to the else and threw "Invalid payment destination format" despite the UI showing a valid decode. Detect BOLT11 case-insensitively by its HRP (lnbc/lntb/lntbs/lnbcrt) and send the lowercase canonical form. The bare 'ln' prefix also incidentally matched bech32 LNURLs (lnurl1...) and misrouted them to the bolt11 endpoint; matching the full HRP closes that gap too. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/modules/wallet/services/WalletService.ts | 23 +++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/modules/wallet/services/WalletService.ts b/src/modules/wallet/services/WalletService.ts index a18b625..7607b99 100644 --- a/src/modules/wallet/services/WalletService.ts +++ b/src/modules/wallet/services/WalletService.ts @@ -240,21 +240,28 @@ export default class WalletService extends BaseService { let endpoint = '' let body: any = {} - // Determine payment type and prepare request - if (request.destination.startsWith('ln')) { - // Lightning invoice + // Determine payment type and prepare request. + // QR-scanned invoices are uppercase bech32, so detection must be + // case-insensitive. Match BOLT11 by its HRP (lnbc / lntb / lntbs / + // lnbcrt) rather than a bare "ln" prefix, which would also swallow + // bech32 LNURLs and misroute them to the bolt11 endpoint. + const dest = request.destination.trim() + const lower = dest.toLowerCase() + + if (lower.startsWith('lnbc') || lower.startsWith('lntb') || lower.startsWith('lntbs') || lower.startsWith('lnbcrt')) { + // Lightning invoice (BOLT11) — send the lowercase canonical form endpoint = `${config.api.baseUrl}/api/v1/payments` body = { out: true, - bolt11: request.destination + bolt11: lower } - } else if (request.destination.includes('@') || request.destination.toLowerCase().startsWith('lnurl')) { + } else if (dest.includes('@') || lower.startsWith('lnurl')) { // Lightning address or LNURL endpoint = `${config.api.baseUrl}/api/v1/payments/lnurl` body = { - lnurl: request.destination.includes('@') - ? `https://${request.destination.split('@')[1]}/.well-known/lnurlp/${request.destination.split('@')[0]}` - : request.destination, + lnurl: dest.includes('@') + ? `https://${dest.split('@')[1]}/.well-known/lnurlp/${dest.split('@')[0]}` + : dest, amount: request.amount * 1000, // Convert to millisats comment: request.comment || '' }