From c327473b5231b60f25fb9c145c6f43d4d59d5851 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 5 Sep 2026 00:20:59 -0700 Subject: LNURL payments name the merchant: after the pay-params fetch, the metadata's text/identifier (the lightning address) replaces the bech32 blob as the payee's name and address on the amount screen, with the text/plain description as fallback. And when the address is a coinos name — whether it arrived as an lnurl's metadata, a typed name@coinos.io, or an intent — the registrar is asked who owns it and the screen wears their AVATAR via the same profileChip the npub zap flow already uses; the pubkey also lets a NIP-57 zap request attribute to the right person. Lookup is fire-and-forget so the amount screen never waits on it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz --- src/features/zaps.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'src') diff --git a/src/features/zaps.js b/src/features/zaps.js index 7148df1..68ae2dc 100644 --- a/src/features/zaps.js +++ b/src/features/zaps.js @@ -121,6 +121,30 @@ export function zapsFeature(ctx) { } const params = await fetchPayParams(target.url); if (ui.zap !== z) return; // user navigated away + // Name the payee like a human: LNURL metadata usually carries the + // lightning address (text/identifier) — show that instead of a bech32 + // blob, falling back to the text/plain description. And when the + // address is a coinos name, ask the registrar who owns it: with the + // pubkey in hand the screen wears their avatar (the profileChip the + // npub flow already uses), and a zap request attributes properly. + try { + const entries = JSON.parse(params.metadata || '[]'); + const ident = (entries.find((e) => Array.isArray(e) && e[0] === 'text/identifier') || [])[1]; + const plain = (entries.find((e) => Array.isArray(e) && e[0] === 'text/plain') || [])[1]; + if (ident && !z.address) { z.address = String(ident); z.name = String(ident); } + else if (!z.address && plain) z.name = String(plain).slice(0, 64); + const m = /^([a-z0-9._-]{1,30})@coinos\.io$/i.exec(String(z.address || ident || '')); + if (m && !z.target.pk) { + fetch(`https://names.coinos.io/name/${encodeURIComponent(m[1].toLowerCase())}`) + .then((r) => r.json()) + .then((j) => { + if (ui.zap === z && j && j.taken && /^[0-9a-f]{64}$/.test(j.pubkey || '')) { + z.target.pk = j.pubkey; + render(); + } + }).catch(() => {}); + } + } catch {} // A fixed-price LNURL (min == max — a bolt-card top-up, a paywall, a // static charge) has no amount to ask for: fill it and lock the field, // exactly like a fixed-amount BOLT 12 offer. -- cgit v1.2.3