From 4e7ec7681775a8e0d6179b1d635307a0b2d4e726 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 28 May 2026 15:06:06 -0400 Subject: [PATCH] Lightning tips: pay in-app via NWC when a wallet is connected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adam pointed out the tip flow still showed the bolt11 / QR view even when the user had a NIP-47 wallet set up. That re-introduces the exact wallet-handoff friction the NWC integration is meant to solve. Changes to the existing LNURL tip flow: - openTipModal sets the action button to "⚡ Send tip" when nwc.isConnected(), and to the original "Get invoice" otherwise. - getTipInvoiceFlow, after fetching the bolt11, tries nwc.payInvoice() FIRST when a wallet is connected. On success: toast "Sent N sats ⚡" + close modal. On failure (preimage missing, wallet error, budget exhausted, etc.): falls through to the existing showTipInvoiceView so the user can still pay the bolt11 externally with the visible error inline. - Button label restoration uses the same dynamic source so post- error retry shows the correct (state-appropriate) label. Pre-flight gates (amount validation, LNURL meta fetch, zap path construction) are unchanged — NIP-57 zap tagging still wraps the LNURL request so the recipient still sees a tracked kind-9735 receipt; the only difference is who pays the bolt11. Verified via CDP: button label flips between "Get invoice" and "⚡ Send tip" with nwc.isConnected() state. Co-Authored-By: Claude Opus 4.7 --- src/index.html | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/index.html b/src/index.html index b4db7a1..c72acfd 100644 --- a/src/index.html +++ b/src/index.html @@ -18806,7 +18806,12 @@ $('tipCustomInput').style.display = 'none'; $('tipCustomInput').value = ''; const getBtn = $('tipGetInvoiceBtn'); - if (getBtn) { getBtn.disabled = true; getBtn.textContent = 'Get invoice'; } + if (getBtn) { + getBtn.disabled = true; + // When a wallet is connected, the button pays in-app directly. When + // not, it falls back to the original LNURL → bolt11 QR flow. + getBtn.textContent = nwc.isConnected() ? '⚡ Send tip' : 'Get invoice'; + } // Clear any active selection $('tipAmounts').querySelectorAll('button').forEach(b => b.classList.remove('active')); @@ -18857,6 +18862,7 @@ if (!_tipTarget) return; let sats = _tipSelectedSats; if (sats <= 0) { if (errEl) errEl.textContent = 'Pick an amount first.'; return; } + const wasOriginalLabel = nwc.isConnected() ? '⚡ Send tip' : 'Get invoice'; try { if (getBtn) { getBtn.disabled = true; getBtn.textContent = 'Fetching…'; } @@ -18866,7 +18872,7 @@ const maxSats = Math.floor((meta.maxSendable || 100000000) / 1000); if (sats < minSats || sats > maxSats) { if (errEl) errEl.textContent = `Enter between ${minSats} and ${maxSats.toLocaleString()} sats.`; - if (getBtn) { getBtn.disabled = false; getBtn.textContent = 'Get invoice'; } + if (getBtn) { getBtn.disabled = false; getBtn.textContent = wasOriginalLabel; } return; } const msats = sats * 1000; @@ -18875,6 +18881,27 @@ const issuedAt = Math.floor(Date.now() / 1000); const bolt11 = await fetchLnurlInvoice(meta.callback, msats, zap ? { nostr: zap.encoded } : {}); + // NWC-first: if a wallet is connected, try to pay the invoice in-app. + // Falls back to the bolt11/QR view on any failure so the user still has + // a way to settle the tip externally. + if (nwc.isConnected()) { + if (getBtn) getBtn.textContent = 'Paying…'; + try { + const result = await nwc.payInvoice(bolt11); + if (result?.preimage) { + const tracked = !!zap; + toastSuccess(`Sent ${sats.toLocaleString()} sats${tracked ? ' ⚡' : ''}`); + closeTipModal(); + return; + } + // No preimage returned — treat as failure and surface the QR. + } catch (err) { + // Show the failure inline + still display the bolt11 so the user + // can pay externally. + if (errEl) errEl.textContent = `Wallet error: ${err?.message || err}. Falling back to invoice.`; + } + } + // If we took the zap path, watch for the kind-9735 receipt and auto-close on success. let watch = null; if (zap && _tipTarget?.targetEvent?.authorPubkey) { @@ -18891,7 +18918,7 @@ showTipInvoiceView(bolt11, sats, !!zap, watch); } catch (err) { if (errEl) errEl.textContent = String(err?.message || err); - if (getBtn) { getBtn.disabled = false; getBtn.textContent = 'Get invoice'; } + if (getBtn) { getBtn.disabled = false; getBtn.textContent = wasOriginalLabel; } } }