From 671d2a96474f59ca9234c876ce6604d03de95727 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 14 Sep 2026 09:06:02 -0700 Subject: One device pays a zap, whichever of them heard it first. Every awake device of a wallet hears a wallet-connect pay request within the same second, and the per-tab lock only reaches one browser profile: two profiles on one laptop each locked an HTLC for the same 21-sat zap. The push notifier now hands each pay request to the first device that claims it; a device that loses the claim stands down silently, and a device that cannot reach the notifier answers as before so nobody goes quiet over a hiccup. The worker path claims the same way. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR --- src/nwc-respond.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src/nwc-respond.js') diff --git a/src/nwc-respond.js b/src/nwc-respond.js index 9fdfcff..bab25b0 100644 --- a/src/nwc-respond.js +++ b/src/nwc-respond.js @@ -183,6 +183,19 @@ export async function respondFromBg(data, { return !!(await r.json()).answered; } catch { return false; } }; + // ...and hands each pay request to the first device that claims it, so + // two awake devices don't each lock an HTLC for one zap. Unreachable → + // proceed: a lone worker must not go silent over a notifier hiccup. + const claim = async () => { + try { + const r = await fetchFn(`${notifier}/claim`, { + method: 'POST', headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ event: ev.id, by: `sw-${walletKey}`.slice(0, 64) }), + }); + if (!r.ok) return true; + return (await r.json()).ok !== false; + } catch { return true; } + }; if (method === 'get_info') { await publish({ @@ -268,6 +281,7 @@ export async function respondFromBg(data, { } return true; }; + if (!(await claim())) { log('another device claimed this request — standing down'); return true; } const deadline = Date.now() + PAY_WAIT_MS; let a = null; for (let attempt = 0; ; attempt++) { -- cgit v1.2.3