aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/zaps.js10
-rw-r--r--tools/lnurl-withdraw-test.js9
2 files changed, 17 insertions, 2 deletions
diff --git a/src/features/zaps.js b/src/features/zaps.js
index 8ac78fb..8e29409 100644
--- a/src/features/zaps.js
+++ b/src/features/zaps.js
@@ -533,7 +533,15 @@ export function zapsFeature(ctx) {
// a login challenge needs no balance, only the wallet's key
const auth = parseLnurlAuth(text);
if (auth) { beginLogin(auth); return true; }
- if (!canPay() || !ui.send || ui.send.recipients.length !== 1) return false;
+ if (!ui.send || ui.send.recipients.length !== 1) return false;
+ // An LNURL may be a withdraw — it pays US, so it needs no spendable
+ // balance, and right after a refresh (Spending still connecting) the
+ // check below turned a scanned withdraw link into a "Zap LNURL…"
+ // recipient (Adam, 2026-11-08). Only the GET can tell: take it now,
+ // and a pay one meets the pay checks where it's paid.
+ const early = parseZapTarget(text);
+ if (early && early.kind === 'lnurl' && !typed) { begin(early, zapDisplay(early, text), true); return true; }
+ if (!canPay()) return false;
const tap = !typed; // scanned/pasted (a tap), not a keystroke
const nof = maybeNoffer(text);
if (nof) {
diff --git a/tools/lnurl-withdraw-test.js b/tools/lnurl-withdraw-test.js
index cc1b152..0241f31 100644
--- a/tools/lnurl-withdraw-test.js
+++ b/tools/lnurl-withdraw-test.js
@@ -22,7 +22,7 @@ const ctx = {
parseAmount: (v) => { const n = Number(String(v).replace(/,/g, '')); return isFinite(n) ? Math.round(n) : null; }, getUnit: () => 'sats',
amountField: (sat) => String(sat),
hook(name, ...a) {
- if (name === 'canLnPay') return true;
+ if (name === 'canLnPay') return !window.noPay;
if (name === 'arkLnInvoice') { invoices++; return Promise.resolve({ id: 'ln' + invoices, invoice: 'lnbc' + a[0] + 'n1testinvoice' + invoices, amountSat: a[0] }); }
if (name === 'arkLnWatch') { watch = { id: a[0], cb: a[1] }; return true; }
if (name === 'arkLnUnwatch') { if (watch && watch.id === a[0]) watch = null; return true; }
@@ -94,6 +94,13 @@ try {
await click('Back'); await pause(50);
check('Back before it is paid stops watching', (await page.evaluate(() => test.watch)) === null && !!(await page.$('#closed')));
+ console.log('[scanned while Spending is still connecting]');
+ await page.evaluate(() => { window.noPay = true; });
+ check('a withdraw lnurl is still taken (it pays us; nothing to spend)', await page.evaluate((s) => test.paste(s), lnurl('/withdraw')));
+ check('…and opens as a withdraw, not a zap', await waitText(/Withdraw over Lightning/) && !/Zap/.test(await text()), await text());
+ await click('Back'); await pause(50);
+ await page.evaluate(() => { window.noPay = false; });
+
console.log('[a site that refuses]');
await page.evaluate((s) => test.paste(s), lnurl('/withdraw-spent'));
await waitText(/will pay you 1 sats/); await click('Withdraw');