diff --git a/src/app/ModalRoutes.svelte b/src/app/ModalRoutes.svelte index 5df5757b..d89e8ddf 100644 --- a/src/app/ModalRoutes.svelte +++ b/src/app/ModalRoutes.svelte @@ -42,7 +42,7 @@ {:else if m.type === "note/create"} {:else if m.type === "zap/create"} - + {:else if m.type === "notification/info"} {:else if m.type === "thread/detail"} diff --git a/src/app/engine.ts b/src/app/engine.ts index b35ee0c6..9afd6b50 100644 --- a/src/app/engine.ts +++ b/src/app/engine.ts @@ -57,7 +57,6 @@ export const Nip05 = engine.Nip05 export const Nip24 = engine.Nip24 export const Nip28 = engine.Nip28 export const Nip44 = engine.Nip44 -export const Nip57 = engine.Nip57 export const Nip59 = engine.Nip59 export const Nip65 = engine.Nip65 export const Outbox = engine.Outbox diff --git a/src/app/shared/NoteActions.svelte b/src/app/shared/NoteActions.svelte index 6f5bbfd9..e63b1910 100644 --- a/src/app/shared/NoteActions.svelte +++ b/src/app/shared/NoteActions.svelte @@ -15,8 +15,8 @@ import PersonBadge from "src/app/shared/PersonBadge.svelte" import RelayCard from "src/app/shared/RelayCard.svelte" import {toastProgress} from "src/app/state" - import {getUserRelayUrls} from "src/engine2" - import {Env, Nip57, Builder, Nip65, Keys, Outbox, user} from "src/app/engine" + import {zappers, getUserRelayUrls, processZaps} from "src/engine2" + import {Env, Builder, Nip65, Keys, Outbox, user} from "src/app/engine" export let note export let reply @@ -24,7 +24,7 @@ export let showEntire export let setFeedRelay - const zapper = Nip57.zappers.key(note.pubkey) + const zapper = zappers.key(note.pubkey) const nevent = nip19.neventEncode({id: note.id, relays: [note.seen_on]}) const interpolate = (a, b) => t => a + Math.round((b - a) * t) const likesCount = tweened(0, {interpolate}) @@ -82,16 +82,14 @@ $: allLikes = like ? likes.filter(n => n.id !== like?.id).concat(like) : likes $: $likesCount = allLikes.length - $: zaps = Nip57.processZaps(note.zaps, note.pubkey) + $: zaps = processZaps(note.zaps, note.pubkey) $: zap = zap || find(z => z.request.pubkey === Keys.pubkey.get(), zaps) $: $zapsTotal = sum( pluck( "invoiceAmount", - zap - ? zaps.filter(n => n.id !== zap?.id).concat(Nip57.processZaps([zap], note.pubkey)) - : zaps + zap ? zaps.filter(n => n.id !== zap?.id).concat(processZaps([zap], note.pubkey)) : zaps ) ) / 1000 diff --git a/src/app/views/About.svelte b/src/app/views/About.svelte index c3102865..0aa4dbea 100644 --- a/src/app/views/About.svelte +++ b/src/app/views/About.svelte @@ -1,37 +1,37 @@ diff --git a/src/app/views/ZapModal.svelte b/src/app/views/ZapModal.svelte index 5e9e2567..1571c2da 100644 --- a/src/app/views/ZapModal.svelte +++ b/src/app/views/ZapModal.svelte @@ -7,13 +7,11 @@ import Anchor from "src/partials/Anchor.svelte" import Input from "src/partials/Input.svelte" import Textarea from "src/partials/Textarea.svelte" - import {getSetting, getUserRelayUrls} from "src/engine2" - import {Directory, Nip65, Outbox, Network, Builder, Nip57} from "src/app/engine" + import {getSetting, requestZap, collectInvoice, loadZapResponse} from "src/engine2" + import {Directory} from "src/app/engine" export let pubkey export let note = null - export let zapper = null - export let author = null let sub let zap = { @@ -25,27 +23,15 @@ confirmed: false, } - const _zapper = zapper || Nip57.zappers.key(pubkey).get() - const _author = author || Directory.getProfile(pubkey) - const loadZapInvoice = async () => { zap.loading = true - const amount = zap.amount * 1000 - const relayLimit = getSetting("relay_limit") - const relays = note - ? Nip65.getPublishHints(relayLimit, note, getUserRelayUrls("write")) - : Nip65.getPubkeyHints(relayLimit, pubkey, "read") - const rawEvent = Builder.requestZap( - relays, - zap.message, + const {invoice, relays} = await requestZap({ pubkey, - note?.id, - amount, - _zapper.lnurl - ) - const signedEvent = await Outbox.prep(rawEvent) - const invoice = await Nip57.fetchInvoice(_zapper, signedEvent, amount) + event: note, + amount: zap.amount, + content: zap.message, + }) // If they closed the dialog before fetch resolved, we're done if (!zap) { @@ -55,21 +41,14 @@ zap.invoice = invoice zap.loading = false - await Nip57.collectInvoice(invoice) + await collectInvoice(invoice) // Listen for the zap confirmation - sub = Network.subscribe({ - relays, - filter: { - kinds: [9735], - authors: [_zapper.nostrPubkey], - "#p": [pubkey], - since: zap.startedAt - 10, - }, - onEvent: event => { - zap.confirmed = true - setTimeout(() => modal.pop(), 1000) - }, + sub = loadZapResponse({relays, pubkey}) + + sub.on("event", event => { + zap.confirmed = true + setTimeout(() => modal.pop(), 1000) }) } @@ -81,7 +60,7 @@

Send a zap

-

to {Directory.displayProfile(_author)}

+

to {Directory.displayPubkey(pubkey)}

{#if zap.confirmed}
diff --git a/src/engine/Engine.ts b/src/engine/Engine.ts index f350e4a8..99610e2f 100644 --- a/src/engine/Engine.ts +++ b/src/engine/Engine.ts @@ -12,7 +12,6 @@ import {Nip05} from "./components/Nip05" import {Nip24} from "./components/Nip24" import {Nip28} from "./components/Nip28" import {Nip44} from "./components/Nip44" -import {Nip57} from "./components/Nip57" import {Nip59} from "./components/Nip59" import {Nip65} from "./components/Nip65" import {Outbox} from "./components/Outbox" @@ -33,7 +32,6 @@ export class Engine { Nip24 = new Nip24() Nip28 = new Nip28() Nip44 = new Nip44() - Nip57 = new Nip57() Nip59 = new Nip59() Nip65 = new Nip65() Outbox = new Outbox() diff --git a/src/engine/components/Nip57.ts b/src/engine/components/Nip57.ts deleted file mode 100644 index 7d81b300..00000000 --- a/src/engine/components/Nip57.ts +++ /dev/null @@ -1,147 +0,0 @@ -import {Fetch, tryFunc} from "hurdak" -import {now, tryJson, hexToBech32, bech32ToHex} from "src/util/misc" -import {invoiceAmount} from "src/util/lightning" -import {warn} from "src/util/logger" -import {Tags} from "src/util/nostr" -import type {Engine} from "src/engine/Engine" -import type {Zapper, ZapEvent, Event} from "src/engine/types" -import {collection} from "src/engine/util/store" - -export class Nip57 { - zappers = collection("pubkey") - - processZaps = (zaps: Event[], pubkey: string) => { - const zapper = this.zappers.key(pubkey).get() - - if (!zapper) { - return [] - } - - return zaps - .map((zap: Event) => { - const zapMeta = Tags.from(zap).asMeta() as { - bolt11: string - description: string - } - - return tryJson(() => ({ - ...zap, - invoiceAmount: invoiceAmount(zapMeta.bolt11), - request: JSON.parse(zapMeta.description), - })) as ZapEvent - }) - .filter((zap: ZapEvent) => { - if (!zap) { - return false - } - - // Don't count zaps that the user sent himself - if (zap.request.pubkey === pubkey) { - return false - } - - const {invoiceAmount, request} = zap - const reqMeta = Tags.from(request).asMeta() as { - amount?: string - lnurl?: string - } - - // Verify that the zapper actually sent the requested amount (if it was supplied) - if (reqMeta.amount && parseInt(reqMeta.amount) !== invoiceAmount) { - return false - } - - // If the sending client provided an lnurl tag, verify that too - if (reqMeta.lnurl && reqMeta.lnurl !== zapper.lnurl) { - return false - } - - // Verify that the zap note actually came from the recipient's zapper - if (zapper.nostrPubkey !== zap.pubkey) { - return false - } - - return true - }) - } - - getLnUrl(address: string): string { - // Try to parse it as a lud06 LNURL - if (address.startsWith("lnurl1")) { - return tryFunc(() => bech32ToHex(address)) as string - } - - // Try to parse it as a lud16 address - if (address.includes("@")) { - const [name, domain] = address.split("@") - - if (domain && name) { - return `https://${domain}/.well-known/lnurlp/${name}` - } - } - } - - async fetchInvoice(zapper, event, amount) { - const {callback, lnurl} = zapper - const s = encodeURI(JSON.stringify(event)) - const res = await Fetch.fetchJson(`${callback}?amount=${amount}&nostr=${s}&lnurl=${lnurl}`) - - if (!res.pr) { - warn(JSON.stringify(res)) - } - - return res?.pr - } - - async collectInvoice(invoice) { - const {webln} = window as {webln?: any} - - if (webln) { - await webln.enable() - - try { - webln.sendPayment(invoice) - } catch (e) { - warn(e) - } - } - } - - initialize(engine: Engine) { - engine.Events.addHandler(0, (e: Event) => { - tryJson(async () => { - const kind0 = JSON.parse(e.content) - const zapper = this.zappers.key(e.pubkey) - const address = (kind0.lud16 || kind0.lud06 || "").toLowerCase() - - if (!address || e.created_at < zapper.get()?.created_at) { - return - } - - const lnurl = this.getLnUrl(address) - - if (!lnurl) { - return - } - - const url = engine.Settings.dufflepud("zapper/info") - const result = (await tryFunc(() => Fetch.postJson(url, {lnurl}))) as any - - if (!result?.allowsNostr || !result?.nostrPubkey) { - return - } - - zapper.set({ - pubkey: e.pubkey, - lnurl: hexToBech32("lnurl", lnurl), - callback: result.callback, - minSendable: result.minSendable, - maxSendable: result.maxSendable, - nostrPubkey: result.nostrPubkey, - created_at: e.created_at, - updated_at: now(), - }) - }) - }) - } -} diff --git a/src/engine/index.ts b/src/engine/index.ts index d716a210..fb98b370 100644 --- a/src/engine/index.ts +++ b/src/engine/index.ts @@ -13,7 +13,6 @@ export {Nip04} from "./components/Nip04" export {Nip05} from "./components/Nip05" export {Nip28} from "./components/Nip28" export {Nip44} from "./components/Nip44" -export {Nip57} from "./components/Nip57" export {Nip65} from "./components/Nip65" export {User} from "./util/User" diff --git a/src/engine2/commands/index.ts b/src/engine2/commands/index.ts index e0cc6a47..ccbb74a7 100644 --- a/src/engine2/commands/index.ts +++ b/src/engine2/commands/index.ts @@ -4,4 +4,5 @@ export * from "./nip04" export * from "./nip24" export * from "./nip28" export * from "./nip32" +export * from "./nip57" export * from "./nip95" diff --git a/src/engine2/commands/nip57.ts b/src/engine2/commands/nip57.ts new file mode 100644 index 00000000..da78621b --- /dev/null +++ b/src/engine2/commands/nip57.ts @@ -0,0 +1,56 @@ +import {Fetch} from "hurdak" +import {warn} from "src/util/logger" +import {people} from "src/engine2/state" +import {signer, getSetting, getPubkeyHints, getPublishHints} from "src/engine2/queries" +import {buildEvent} from "./util" + +export async function requestZap({content, amount, pubkey, event}) { + const person = people.key(pubkey).get() + + if (!person?.zapper) { + throw new Error("Can't zap a person without a zapper") + } + + const {callback, lnurl} = person.zapper + const msats = amount * 1000 + const relays = event + ? getPublishHints(getSetting("relay_limit"), event) + : getPubkeyHints(getSetting("relay_limit"), pubkey, "read") + + const tags = [ + ["relays", ...relays], + ["amount", msats.toString()], + ["lnurl", lnurl], + ["p", pubkey], + ] + + if (event) { + tags.push(["e", event.id]) + } + + const zap = signer.get().prepAsUser(buildEvent(9734, {content, tags})) + const zapString = encodeURI(JSON.stringify(zap)) + const res = await Fetch.fetchJson( + `${callback}?amount=${amount}&nostr=${zapString}&lnurl=${lnurl}` + ) + + if (!res.pr) { + warn(JSON.stringify(res)) + } + + return {relays, invoice: res?.pr} +} + +export async function collectInvoice(invoice) { + const {webln} = window as {webln?: any} + + if (webln) { + await webln.enable() + + try { + webln.sendPayment(invoice) + } catch (e) { + warn(e) + } + } +} diff --git a/src/engine2/queries/nip57.ts b/src/engine2/queries/nip57.ts index 1b02d5d4..dba71ce1 100644 --- a/src/engine2/queries/nip57.ts +++ b/src/engine2/queries/nip57.ts @@ -1,7 +1,5 @@ -import {Fetch} from "hurdak" import {tryJson} from "src/util/misc" import {invoiceAmount} from "src/util/lightning" -import {warn} from "src/util/logger" import {Tags} from "src/util/nostr" import type {Event, ZapEvent} from "src/engine2/model" import {people} from "src/engine2/state" @@ -60,29 +58,3 @@ export function processZaps(zaps: Event[], pubkey: string) { return true }) } - -export async function fetchInvoice(zapper, event, amount) { - const {callback, lnurl} = zapper - const s = encodeURI(JSON.stringify(event)) - const res = await Fetch.fetchJson(`${callback}?amount=${amount}&nostr=${s}&lnurl=${lnurl}`) - - if (!res.pr) { - warn(JSON.stringify(res)) - } - - return res?.pr -} - -export async function collectInvoice(invoice) { - const {webln} = window as {webln?: any} - - if (webln) { - await webln.enable() - - try { - webln.sendPayment(invoice) - } catch (e) { - warn(e) - } - } -} diff --git a/src/engine2/queries/nip65.ts b/src/engine2/queries/nip65.ts index 216636e4..7ee6eafd 100644 --- a/src/engine2/queries/nip65.ts +++ b/src/engine2/queries/nip65.ts @@ -125,12 +125,12 @@ export const getRootHints = hintSelector(function* (event) { // anyone else who is tagged in the original event or the reply. Get everyone's read // relays. Limit how many per pubkey we publish to though. We also want to advertise // our content to our followers, so publish to our write relays as well. -export const getPublishHints = (limit: number, event: Event, extraRelays: string[] = []) => { +export const getPublishHints = (limit: number, event: Event) => { const pubkeys = Tags.from(event).type("p").values().all() const hintGroups = pubkeys.map(pubkey => getPubkeyRelayUrls(pubkey, RelayMode.Read)) const authorRelays = getPubkeyRelayUrls(event.pubkey, RelayMode.Write) - return mergeHints(limit, hintGroups.concat([extraRelays, authorRelays])) + return mergeHints(limit, [...hintGroups, authorRelays, getUserRelayUrls(RelayMode.Write)]) } export const getInboxHints = (limit: number, pubkeys: string[]) => diff --git a/src/engine2/requests/index.ts b/src/engine2/requests/index.ts index f8eec69e..8156bc35 100644 --- a/src/engine2/requests/index.ts +++ b/src/engine2/requests/index.ts @@ -5,4 +5,5 @@ export * from "./pubkeys" export * from "./subscription" export * from "./thread" export * from "./nip04" +export * from "./nip57" export * from "./nip59" diff --git a/src/engine2/requests/nip57.ts b/src/engine2/requests/nip57.ts new file mode 100644 index 00000000..d269f795 --- /dev/null +++ b/src/engine2/requests/nip57.ts @@ -0,0 +1,19 @@ +import {now} from "src/util/misc" +import {people} from "src/engine2/state" +import {Subscription} from "./subscription" + +export function loadZapResponse({pubkey, relays}) { + const {zapper} = people.key(pubkey).get() + + return new Subscription({ + relays, + filters: [ + { + kinds: [9735], + authors: [zapper.nostrPubkey], + "#p": [pubkey], + since: now() - 10, + }, + ], + }) +}