Use nip57 module for zaps

This commit is contained in:
Jonathan Staab 2023-06-29 15:01:04 -07:00
parent e14aaf99a9
commit 75f8b7fec4
7 changed files with 76 additions and 108 deletions

View File

@ -62,10 +62,6 @@ const Meta = {
stats.subsCount += 1
stats.activeSubsCount += 1
stats.lastRequest = Date.now()
if (stats.activeSubsCount > 10) {
warn(`Relay ${url} has ${stats.activeSubsCount} active subscriptions`)
}
})
},
onSubscriptionEnd(urls) {

View File

@ -1,17 +1,7 @@
import {partition, is, uniq, reject, nth, objOf, pick, identity} from "ramda"
import {nip05} from "nostr-tools"
import {noop, ensurePlural, chunk} from "hurdak/lib/hurdak"
import {
hexToBech32,
tryFunc,
bech32ToHex,
tryFetch,
now,
sleep,
tryJson,
timedelta,
hash,
} from "src/util/misc"
import {now, sleep, tryJson, timedelta, hash} from "src/util/misc"
import {Tags, roomAttrs, isRelay, isShareableRelay, normalizeRelayUrl} from "src/util/nostr"
import {topics, people, userEvents, relays, rooms, routes} from "src/agent/db"
import {uniqByUrl} from "src/agent/relays"
@ -80,37 +70,6 @@ const verifyNip05 = (pubkey, alias) =>
}
}, noop)
const verifyZapper = async (pubkey, address) => {
let url
// Try to parse it as a lud06 LNURL or as a lud16 address
if (address.startsWith("lnurl1")) {
url = tryFunc(() => bech32ToHex(address))
} else if (address.includes("@")) {
const [name, domain] = address.split("@")
if (domain && name) {
url = `https://${domain}/.well-known/lnurlp/${name}`
}
}
if (!url) {
return
}
const res = await tryFetch(() => fetch(url))
const zapper = await tryJson(() => res?.json())
const lnurl = hexToBech32("lnurl", url)
if (zapper?.allowsNostr && zapper?.nostrPubkey) {
updatePerson(pubkey, {
lnurl,
// Trim zapper so we don't have so much metadata filling up memory
zapper: pick(["callback", "maxSendable", "minSendable", "nostrPubkey"], zapper),
})
}
}
addHandler(0, e => {
tryJson(() => {
const kind0 = JSON.parse(e.content)
@ -125,12 +84,6 @@ addHandler(0, e => {
verifyNip05(e.pubkey, kind0.nip05)
}
const address = kind0.lud16 || kind0.lud06
if (address) {
verifyZapper(e.pubkey, address.toLowerCase())
}
updatePerson(e.pubkey, {
kind0: {...person?.kind0, ...kind0},
kind0_updated_at: e.created_at,

View File

@ -12,7 +12,6 @@ const profile = synced("agent/user/profile", {
pubkey: null,
kind0: null,
lnurl: null,
zapper: null,
rooms_joined: [],
last_checked: {},
relays: pool.defaultRelays,

View File

@ -4,7 +4,7 @@
import {tweened} from "svelte/motion"
import {find, reject, identity, propEq, pathEq, sum, pluck, sortBy} from "ramda"
import {stringToHue, formatSats, hsl} from "src/util/misc"
import {displayRelay, isLike, processZaps} from "src/util/nostr"
import {displayRelay, isLike} from "src/util/nostr"
import {quantify, first} from "hurdak/lib/hurdak"
import {modal} from "src/partials/state"
import Popover from "src/partials/Popover.svelte"
@ -14,7 +14,7 @@
import CopyValue from "src/partials/CopyValue.svelte"
import PersonBadge from "src/app/shared/PersonBadge.svelte"
import RelayCard from "src/app/shared/RelayCard.svelte"
import {ENABLE_ZAPS, keys} from "src/system"
import {ENABLE_ZAPS, keys, nip57} from "src/system"
import {getEventPublishRelays} from "src/agent/relays"
import {getPersonWithFallback} from "src/agent/db"
import pool from "src/agent/pool"
@ -29,6 +29,7 @@
export let setFeedRelay
const {canSign} = keys
const zapper = nip57.zappers.get(note.pubkey)
const bech32Note = nip19.noteEncode(note.id)
const nevent = nip19.neventEncode({id: note.id, relays: [note.seen_on]})
const interpolate = (a, b) => t => a + Math.round((b - a) * t)
@ -70,12 +71,14 @@
$: allLikes = like ? likes.filter(n => n.id !== like?.id).concat(like) : likes
$: $likesCount = allLikes.length
$: zaps = processZaps(note.zaps, $author)
$: zaps = nip57.processZaps(note.zaps, note.pubkey)
$: zap = zap || find(pathEq(["request", "pubkey"], user.getPubkey()), zaps)
$: allZaps = zap ? zaps.filter(n => n.id !== zap?.id).concat(processZaps([zap], $author)) : zaps
$: allZaps = zap
? zaps.filter(n => n.id !== zap?.id).concat(nip57.processZaps([zap], note.pubkey))
: zaps
$: $zapsTotal = sum(pluck("invoiceAmount", allZaps)) / 1000
$: canZap = $author?.zapper && $author?.pubkey !== user.getPubkey()
$: canZap = zapper && $author?.pubkey !== user.getPubkey()
$: $repliesCount = note.replies.length
$: {
@ -131,8 +134,7 @@
{#if ENABLE_ZAPS}
<button
class={cx("w-20 text-left", {
"pointer-events-none opacity-50":
disableActions || $author.pubkey === user.getPubkey() || !$author.zapper,
"pointer-events-none opacity-50": disableActions || !canZap,
"text-accent": zap,
})}
on:click={startZap}>

View File

@ -13,7 +13,7 @@
import {getEventPublishRelays} from "src/agent/relays"
import {getPersonWithFallback} from "src/agent/db"
import network from "src/agent/network"
import {keys, settings} from "src/system"
import {keys, settings, nip57} from "src/system"
import cmd from "src/agent/cmd"
export let note
@ -33,13 +33,22 @@
const loadZapInvoice = async () => {
zap.loading = true
const {zapper, lnurl} = author
const amount = zap.amount * 1000
const zapper = nip57.zappers.get(note.pubkey)
const relays = getEventPublishRelays(note)
const urls = pluck("url", relays)
const publishable = cmd.requestZap(urls, zap.message, note.pubkey, note.id, amount, lnurl)
const publishable = cmd.requestZap(
urls,
zap.message,
note.pubkey,
note.id,
amount,
zapper.lnurl
)
const event = encodeURI(JSON.stringify(await keys.sign(publishable.event)))
const res = await fetchJson(`${zapper.callback}?amount=${amount}&nostr=${event}&lnurl=${lnurl}`)
const res = await fetchJson(
`${zapper.callback}?amount=${amount}&nostr=${event}&lnurl=${zapper.lnurl}`
)
// If they closed the dialog before fetch resolved, we're done
if (!zap) {

View File

@ -1,4 +1,6 @@
import {fetchJson, tryFunc, tryJson, hexToBech32, bech32ToHex} from "src/util/misc"
import {invoiceAmount} from "src/util/lightning"
import {Tags} from "src/util/nostr"
import {Table} from "src/agent/db"
const getLnUrl = address => {
@ -18,7 +20,7 @@ const getLnUrl = address => {
}
export default ({sync, sortByGraph}) => {
const zappers = new Table("nip57/zappers", "pubkey", {max: 5000, sort: sortByGraph})
const zappers = new Table("zappers", "pubkey", {max: 5000, sort: sortByGraph})
sync.addHandler(0, e => {
tryJson(async () => {
@ -54,7 +56,57 @@ export default ({sync, sortByGraph}) => {
})
})
const processZaps = (zaps, pubkey) => {
const zapper = zappers.get(pubkey)
if (!zapper) {
return []
}
return zaps
.map(zap => {
const zapMeta = Tags.from(zap).asMeta()
return tryJson(() => ({
...zap,
invoiceAmount: invoiceAmount(zapMeta.bolt11),
request: JSON.parse(zapMeta.description),
}))
})
.filter(zap => {
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()
// 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
})
}
return {
zappers,
processZaps,
}
}

View File

@ -3,7 +3,6 @@ import {is, fromPairs, mergeLeft, last, identity, objOf, prop, flatten, uniq} fr
import {nip19} from "nostr-tools"
import {ensurePlural, ellipsize, first} from "hurdak/lib/hurdak"
import {tryJson, avg} from "src/util/misc"
import {invoiceAmount} from "src/util/lightning"
export const noteKinds = [1, 1985, 30023, 1063, 9802]
export const personKinds = [0, 2, 3, 10001, 10002]
@ -198,48 +197,6 @@ export const toHex = (data: string): string | null => {
export const mergeFilter = (filter, extra) =>
is(Array, filter) ? filter.map(mergeLeft(extra)) : {...filter, ...extra}
export const processZaps = (zaps, author) =>
zaps
.map(zap => {
const zapMeta = Tags.from(zap).asMeta()
return tryJson(() => ({
...zap,
invoiceAmount: invoiceAmount(zapMeta.bolt11),
request: JSON.parse(zapMeta.description),
}))
})
.filter(zap => {
if (!zap) {
return false
}
// Don't count zaps that the user sent himself
if (zap.request.pubkey === author.pubkey) {
return false
}
const {invoiceAmount, request} = zap
const reqMeta = Tags.from(request).asMeta()
// 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 !== author.lnurl) {
return false
}
// Verify that the zap note actually came from the recipient's zapper
if (author.zapper?.nostrPubkey !== zap.pubkey) {
return false
}
return true
})
export const fromNostrURI = s => s.replace(/^[\w\+]+:\/?\/?/, "")
export const toNostrURI = s => `web+nostr://${s}`