diff options
Diffstat (limited to 'routes')
| -rw-r--r-- | routes/nostr.ts | 69 | ||||
| -rw-r--r-- | routes/payments.ts | 40 |
2 files changed, 74 insertions, 35 deletions
diff --git a/routes/nostr.ts b/routes/nostr.ts index 0eb95ff6..4a7ce069 100644 --- a/routes/nostr.ts +++ b/routes/nostr.ts @@ -21,6 +21,36 @@ import { decode } from "nostr-tools/nip19"; import type { ProfilePointer } from "nostr-tools/nip19"; import { getZapEndpoint, makeZapRequest } from "nostr-tools/nip57"; +// Amount and payer of a zap receipt. NIP-57 kind 9735: amount comes from +// decoding the paid bolt11, payer from the embedded 9734 zap request in the +// description tag. NIP-177 kind 9736 (BOLT12 zaps): amount is an explicit +// msat tag, and the receipt is payer-published with the signed 9737 intent in +// the description tag. +const parseZap = async (ev) => { + const { kind, tags, pubkey } = ev; + const description = tags.find((t) => t[0] === "description")?.[1]; + + let payer; + try { + payer = JSON.parse(description).pubkey; + } catch (e) {} + payer ||= pubkey; + + let amount = 0; + if (kind === 9736) { + const msat = Number.parseInt(tags.find((t) => t[0] === "amount")?.[1]); + if (msat > 0) amount = Math.round(msat / 1000); + } else { + try { + const bolt11 = tags.find((t) => t[0] === "bolt11")?.[1]; + const { amount_msat } = await ln.decode(bolt11); + if (amount_msat) amount = Math.round(amount_msat / 1000); + } catch (e) {} + } + + return { amount, pubkey: payer }; +}; + export default { async event(req, res) { try { @@ -46,23 +76,14 @@ export default { ) .map(({ value }) => value.pubkey); - const zapEvents = await scan({ kinds: [9735], "#e": [id] }); + const zapEvents = await scan({ kinds: [9735, 9736], "#e": [id] }); const zaps = []; - for (const { tags } of zapEvents) { - const bolt11 = tags.find((t) => t[0] === "bolt11")?.[1]; - const description = tags.find((t) => t[0] === "description")?.[1]; - const { pubkey } = JSON.parse(description); - let amount = 0; - try { - const { amount_msat } = await ln.decode(bolt11); - if (amount_msat) amount = Math.round(amount_msat / 1000); - } catch (e) {} - - zaps.push({ amount, pubkey }); + for (const zapEvent of zapEvents) { + zaps.push(await parseZap(zapEvent)); } pubkeys.push(event.pubkey); - pubkeys.push(...zaps.map((z) => z.pubkey)); + pubkeys.push(...zaps.map((z) => z.pubkey).filter((p) => p)); pubkeys = [...new Set(pubkeys)]; const profiles = await scan({ kinds: [0], authors: pubkeys }); const found = profiles.map((p) => p.pubkey); @@ -167,24 +188,16 @@ export default { let { id } = req.params; if (id.startsWith("nevent")) id = decode(id).data.id; if (id.startsWith("note")) id = decode(id).data; - const filter = { kinds: [9735], "#e": [id] }; - let events = await scan(filter); - if (!events.length) { - await sync("wss://relay.primal.net", filter); - events = await scan(filter); - } + // q() scans the local strfry and falls back to primal (the old code + // called a `sync` helper that never existed and threw ReferenceError + // whenever the local relay had no receipts) + const filter = { kinds: [9735, 9736], "#e": [id] }; + const events = await q(filter); if (!events.length) return res.send([]); const zaps = []; - for (const { tags } of events) { - const bolt11 = tags.find((t) => t[0] === "bolt11")?.[1]; - const description = tags.find((t) => t[0] === "description")?.[1]; - const { pubkey } = JSON.parse(description); - let amount = 0; - try { - const { amount_msat } = await ln.decode(bolt11); - if (amount_msat) amount = Math.round(amount_msat / 1000); - } catch (e) {} + for (const zapEvent of events) { + const { amount, pubkey } = await parseZap(zapEvent); let user; if (pubkey) { diff --git a/routes/payments.ts b/routes/payments.ts index 9c62d2bb..4239a409 100644 --- a/routes/payments.ts +++ b/routes/payments.ts @@ -2,7 +2,7 @@ import config from "$config"; import api from "$lib/api"; import { requirePin } from "$lib/auth"; import { archive, db, g, gf, s, sa } from "$lib/db"; -import { generate } from "$lib/invoices"; +import { generate, getUserOffer } from "$lib/invoices"; import { replay } from "$lib/lightning"; import ln from "$lib/ln"; import { err, l, warn } from "$lib/logging"; @@ -219,21 +219,30 @@ export default { let amount_msat; let payee; - if (decoded.type.includes("bolt12")) { + if (decoded.type === "bolt12 offer") { + ({ offer_amount_msat: amount_msat } = decoded); + payee = decoded.offer_issuer_id || decoded.offer_node_id; + } else if (decoded.type.includes("bolt12")) { ({ invoice_amount_msat: amount_msat, invoice_node_id: payee } = decoded); } else ({ amount_msat, payee } = decoded); const node = nodes.find((n) => n.nodeid === payee); - const alias = node ? node.alias : payee.substr(0, 12); + const alias = node ? node.alias : (payee || "").substr(0, 12); - const amount = Math.round(amount_msat / 1000); + const amount = Math.round((amount_msat || 0) / 1000); let ourfee = Math.round(amount * config.fee[PaymentType.lightning]); const credit = await g(`credit:lightning:${user.id}`); const covered = Math.min(credit, ourfee) || 0; ourfee -= covered; - res.send({ alias, amount, ourfee }); + res.send({ + alias, + amount, + ourfee, + type: decoded.type, + description: decoded.offer_description || decoded.description, + }); } catch (e) { err("problem parsing", e.message); bail(res, e.message); @@ -716,9 +725,26 @@ export default { res.send(await ln.decode(bolt11)); }, + // The user's standing bolt12 offer (lno1...) — reusable receive code they + // can publish (e.g. in a nostr kind 10058 list for bolt12 zaps) + async offer(req, res) { + try { + res.send(await getUserOffer(req.user)); + } catch (e) { + bail(res, e.message); + } + }, + async fetchinvoice(req, res) { - const { amount, offer } = req.body; - res.send(await ln.fetchinvoice(offer, amount ? amount * 1000 : null)); + const { amount, offer, payer_note } = req.body; + res.send( + await ln.fetchinvoice({ + offer, + amount_msat: amount ? amount * 1000 : undefined, + payer_note, + timeout: 60, + }), + ); }, async auth(req, res) { |
