From 78044cc8b6a1a560498f940f5137de3ea41933c7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 28 May 2024 16:54:57 -0500 Subject: [PATCH] Remove NWC, return a Ln-Invoice header on the zap endpoint --- src/controllers/api/statuses.ts | 13 +++++--- src/pipeline.ts | 56 ++------------------------------- src/utils/lnurl.ts | 31 +++++++++++++++++- 3 files changed, 41 insertions(+), 59 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index c81b587..147e6e4 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -15,7 +15,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; import { Storages } from '@/storages.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { createEvent, paginationSchema, parseBody, updateListEvent } from '@/utils/api.ts'; -import { getLnurl } from '@/utils/lnurl.ts'; +import { getInvoice, getLnurl } from '@/utils/lnurl.ts'; import { lookupPubkey } from '@/utils/lookup.ts'; import { addTag, deleteTag } from '@/utils/tags.ts'; import { asyncReplaceAll } from '@/utils/text.ts'; @@ -450,15 +450,16 @@ const zapController: AppController = async (c) => { const author = target?.author; const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content); const lnurl = getLnurl(meta); + const amount = params.data.amount; if (target && lnurl) { - await createEvent({ + const nostr = await createEvent({ kind: 9734, content: params.data.comment ?? '', tags: [ ['e', target.id], ['p', target.pubkey], - ['amount', params.data.amount.toString()], + ['amount', amount.toString()], ['relays', Conf.relay], ['lnurl', lnurl], ], @@ -467,7 +468,11 @@ const zapController: AppController = async (c) => { const status = await renderStatus(target, { viewerPubkey: await c.get('signer')?.getPublicKey() }); status.zapped = true; - return c.json(status); + return c.json(status, { + headers: { + 'Ln-Invoice': await getInvoice({ amount, nostr, lnurl }, signal), + }, + }); } else { return c.json({ error: 'Event not found.' }, 404); } diff --git a/src/pipeline.ts b/src/pipeline.ts index 0db46c9..e60ce6c 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -1,5 +1,4 @@ import { NKinds, NostrEvent, NPolicy, NSchema as n } from '@nostrify/nostrify'; -import { LNURL } from '@nostrify/nostrify/ln'; import { PipePolicy } from '@nostrify/nostrify/policies'; import Debug from '@soapbox/stickynotes/debug'; import { sql } from 'kysely'; @@ -12,15 +11,12 @@ import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { DVM } from '@/pipeline/DVM.ts'; import { MuteListPolicy } from '@/policies/MuteListPolicy.ts'; import { RelayError } from '@/RelayError.ts'; -import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; import { Storages } from '@/storages.ts'; -import { eventAge, nostrDate, nostrNow, parseNip05, Time } from '@/utils.ts'; -import { fetchWorker } from '@/workers/fetch.ts'; +import { eventAge, nostrDate, parseNip05, Time } from '@/utils.ts'; import { policyWorker } from '@/workers/policy.ts'; import { TrendsWorker } from '@/workers/trends.ts'; import { verifyEventWorker } from '@/workers/verify.ts'; -import { AdminSigner } from '@/signers/AdminSigner.ts'; -import { lnurlCache } from '@/utils/lnurl.ts'; import { nip05Cache } from '@/utils/nip05.ts'; import { updateStats } from '@/utils/stats.ts'; import { getTagSet } from '@/utils/tags.ts'; @@ -48,7 +44,6 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise name === 'lnurl')?.[1]; - const amount = Number(event.tags.find(([name]) => name === 'amount')?.[1]); - - if (!lnurl || !amount) return; - - try { - const details = await lnurlCache.fetch(lnurl, { signal }); - - if (details.tag !== 'payRequest' || !details.allowsNostr || !details.nostrPubkey) { - throw new Error('invalid lnurl'); - } - - if (amount > details.maxSendable || amount < details.minSendable) { - throw new Error('amount out of range'); - } - - const { pr } = await LNURL.callback( - details.callback, - { amount, nostr: purifyEvent(event), lnurl }, - { fetch: fetchWorker, signal }, - ); - - const signer = new AdminSigner(); - - const nwcRequestEvent = await signer.signEvent({ - kind: 23194, - content: await signer.nip04.encrypt( - event.pubkey, - JSON.stringify({ method: 'pay_invoice', params: { invoice: pr } }), - ), - created_at: nostrNow(), - tags: [ - ['p', event.pubkey], - ['e', event.id], - ], - }); - - await handleEvent(nwcRequestEvent, signal); - } catch (e) { - debug('lnurl error:', e); - } -} - /** Determine if the event is being received in a timely manner. */ function isFresh(event: NostrEvent): boolean { return eventAge(event) < Time.seconds(10); diff --git a/src/utils/lnurl.ts b/src/utils/lnurl.ts index af344f2..ca7e125 100644 --- a/src/utils/lnurl.ts +++ b/src/utils/lnurl.ts @@ -4,6 +4,7 @@ import Debug from '@soapbox/stickynotes/debug'; import { SimpleLRU } from '@/utils/SimpleLRU.ts'; import { Time } from '@/utils/time.ts'; import { fetchWorker } from '@/workers/fetch.ts'; +import { NostrEvent } from '@nostrify/nostrify'; const debug = Debug('ditto:lnurl'); @@ -38,4 +39,32 @@ function getLnurl({ lud06, lud16 }: { lud06?: string; lud16?: string }, limit?: } } -export { getLnurl, lnurlCache }; +interface CallbackParams { + amount: number; + nostr: NostrEvent; + lnurl: string; +} + +async function getInvoice(params: CallbackParams, signal?: AbortSignal): Promise { + const { amount, lnurl } = params; + + const details = await lnurlCache.fetch(lnurl, { signal }); + + if (details.tag !== 'payRequest' || !details.allowsNostr || !details.nostrPubkey) { + throw new Error('invalid lnurl'); + } + + if (amount > details.maxSendable || amount < details.minSendable) { + throw new Error('amount out of range'); + } + + const { pr } = await LNURL.callback( + details.callback, + params, + { fetch: fetchWorker, signal }, + ); + + return pr; +} + +export { getInvoice, getLnurl, lnurlCache };