From 6713f8d4e55db8d5344684b08b062cc9129ef6b0 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 30 Jul 2026 08:43:47 -0700 Subject: Add NIP-57 Lightning zaps (npub / lightning address / lnurl) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Send a Lightning zap to a nostr npub, a lightning address (name@domain), or an lnurl. New src/lnurl.js does the resolution half — target → LNURL-pay params → signed kind-9734 zap request → callback → bolt11 — and the new zaps feature (src/features/zaps.js) drives the amount/comment UI, then hands the invoice to the existing Boltz submarine-swap flow via a new swaps.startLnPay hook, so there's one tested pay/review/confirm/success path. Routing: a bolt11 is still caught by swaps; an npub still prefers an instant free Ark zap when Ark can serve it, and only falls through to a Lightning zap otherwise — ark's "no Ark address" screen also offers an explicit "Zap over Lightning" fallback (gated on the zaps feature's canLnZap). A real zap receipt is attributed only when the server advertises allowsNostr and we know the recipient's pubkey; otherwise it degrades to a plain LNURL-pay. Nostr seam gains nostrSign (finalize without publishing), nostrProfile (kind-0 with lud16/lud06), and nostrRelays. Lightning addresses are debounced in matchSendText since their regex matches mid-typing. Verified: unit tests for parse/encode/zap-request; a live resolve→signed 9734→payable mainnet bolt11 against coinos; and an in-browser DOM smoke (zap form + LNURL params over CORS + allowsNostr hint + handoff). Co-Authored-By: Claude Opus 4.8 --- src/features/sync.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'src/features/sync.js') diff --git a/src/features/sync.js b/src/features/sync.js index 715c789..1e2db41 100644 --- a/src/features/sync.js +++ b/src/features/sync.js @@ -3,7 +3,7 @@ // (used e.g. to DM locked-gift claim codes). Installed onto the core wallet; // a build without sync ships none of it and never talks to a relay. -import { NostrSync, getSyncConfig, setSyncConfig, npubOf, syncDtag, deviceDtag, isOurDtag } from '../nostr.js'; +import { NostrSync, getSyncConfig, setSyncConfig, npubOf, syncDtag, deviceDtag, isOurDtag, fetchNostrProfile, PROFILE_RELAYS } from '../nostr.js'; import { t } from '../i18n.js'; // Keep a published snapshot under the relay's event-size cap. relay.coinos.io @@ -62,6 +62,24 @@ export function installSyncWallet(wallet) { return this.nostr.subscribeEvents(filter, onEvent); }, + // Sign an event with our nostr key without publishing (NIP-57 zap request). + nostrSign(partial) { return this.nostr ? this.nostr.signEvent(partial) : null; }, + + // Fetch a pubkey's profile (name/picture + lud16/lud06 for zaps) from the + // broad profile relays — independent of whether cross-device sync is on. + async nostrProfile(pkHex) { + if (this.offline) return null; + return fetchNostrProfile(pkHex); + }, + + // Relays to advertise in a zap request so we can later find the receipt: + // our sync relays plus the broad profile relays. + nostrRelays() { + const sync = getSyncConfig(); + const base = sync.enabled ? sync.relays : []; + return [...new Set([...base, ...PROFILE_RELAYS])].slice(0, 8); + }, + // Pull the latest state from Nostr; apply it if it's newer than what we have. // Returns true if state was applied (so the caller can skip a full scan). async syncFromNostr() { -- cgit v1.2.3