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,
+ },
+ ],
+ })
+}