Small fixes, rework zaps

This commit is contained in:
Jon Staab
2026-06-17 09:10:33 -07:00
parent bc728c680e
commit 28219eb64f
11 changed files with 220 additions and 134 deletions
+47
View File
@@ -0,0 +1,47 @@
import {fetchJson, last} from "@welshman/lib"
import type {Maybe} from "@welshman/lib"
/**
* NIP-05: mapping nostr public keys to DNS-based internet identifiers (e.g.
* `name@example.com`), resolved via each domain's `/.well-known/nostr.json`.
*/
export type Handle = {
nip05: string
pubkey?: string
nip46?: string[]
relays?: string[]
}
export async function queryProfile(nip05: string): Promise<Maybe<Handle>> {
const parts = nip05.split("@")
const name = parts.length > 1 ? parts[0] : "_"
const domain = last(parts)
try {
const {
names,
relays = {},
nip46 = {},
} = await fetchJson(`https://${domain}/.well-known/nostr.json?name=${name}`)
const pubkey = names[name]
if (!pubkey) {
return undefined
}
return {
nip05,
pubkey,
nip46: nip46[pubkey],
relays: relays[pubkey],
}
} catch (_e) {
return undefined
}
}
export const displayNip05 = (nip05: string) =>
nip05?.startsWith("_@") ? last(nip05.split("@")) : nip05
export const displayHandle = (handle: Handle) => displayNip05(handle.nip05)
+1 -1
View File
@@ -123,7 +123,7 @@ export const ROOM_JOIN = 9021
export const ROOM_LEAVE = 9022
export const ZAP_GOAL = 9041
export const ZAP_REQUEST = 9734
export const ZAP_RESPONSE = 9735
export const ZAP_RECEIPT = 9735
export const HIGHLIGHT = 9802
export const MUTES = 10000
export const PINS = 10001
+76 -3
View File
@@ -1,5 +1,5 @@
import {now, tryCatch, fetchJson, hexToBech32, fromPairs} from "@welshman/lib"
import {ZAP_RESPONSE, ZAP_REQUEST} from "./Kinds.js"
import {now, tryCatch, fetchJson, hexToBech32, fromPairs, sum, allPass, nthEq, nth} from "@welshman/lib"
import {ZAP_RECEIPT, ZAP_REQUEST} from "./Kinds.js"
import {getTagValue} from "./Tags.js"
import type {Filter} from "./Filters.js"
import type {TrustedEvent, SignedEvent} from "./Events.js"
@@ -138,6 +138,79 @@ export const zapFromEvent = (response: TrustedEvent, zapper: Zapper | undefined)
return zap
}
/**
* A single recipient of an event's zaps, parsed from a NIP-57 Appendix G `zap`
* tag of the form `["zap", <pubkey>, <relay hint>, <weight>]`.
*/
export type ZapSplit = {
pubkey: string
relay?: string
weight: number
}
export type ZapSplitAmount = ZapSplit & {amount: number}
const parseWeight = (weight: string | undefined) => {
const n = weight === undefined ? NaN : parseFloat(weight)
return Number.isFinite(n) && n > 0 ? n : 0
}
/**
* Resolve an event's zap-split recipients per NIP-57 Appendix G:
*
* - With no `zap` tags the whole zap goes to the event's author.
* - If no recipient carries a weight, the zap is split equally (weight 1 each).
* - If weights are only partially present, unweighted recipients drop to weight
* 0 (i.e. they should not be zapped).
*
* Weight-0 recipients are still returned so callers have the full recipient set;
* they simply receive 0 from `splitZapAmount`.
*/
export const getZapSplits = (event: TrustedEvent): ZapSplit[] => {
const zapTags = event.tags.filter(allPass(nthEq(0, "zap"), nth(1)))
if (zapTags.length === 0) {
return [{pubkey: event.pubkey, weight: 1}]
}
const anyWeighted = zapTags.some(nth(3))
return zapTags.map(([, pubkey, relay, weight]) => ({
pubkey,
relay: relay || undefined,
weight: anyWeighted ? parseWeight(weight) : 1,
}))
}
/**
* Divide `total` (in any integer unit, e.g. millisats) across an event's
* zap-split recipients proportionally to their weights. Any rounding remainder
* is handed to the highest-weighted recipient so the parts sum back to exactly
* `total`. If every weight is 0, nobody is zapped.
*/
export const splitZapAmount = (event: TrustedEvent, total: number): ZapSplitAmount[] => {
const splits = getZapSplits(event)
const totalWeight = sum(splits.map(split => split.weight))
if (totalWeight === 0) {
return splits.map(split => ({...split, amount: 0}))
}
const amounts = splits.map(split => Math.floor((total * split.weight) / totalWeight))
let maxIndex = 0
splits.forEach((split, i) => {
if (split.weight > splits[maxIndex].weight) {
maxIndex = i
}
})
amounts[maxIndex] += total - sum(amounts)
return splits.map((split, i) => ({...split, amount: amounts[i]}))
}
export type ZapRequestParams = {
msats: number
zapper: Zapper
@@ -201,7 +274,7 @@ export const getZapResponseFilter = ({zapper, pubkey, eventId}: ZapResponseFilte
}
const filter: Filter = {
kinds: [ZAP_RESPONSE],
kinds: [ZAP_RECEIPT],
authors: [zapper.nostrPubkey],
since: now() - 30,
"#p": [pubkey],
+1
View File
@@ -4,6 +4,7 @@ export * from "./Encryptable.js"
export * from "./Events.js"
export * from "./Filters.js"
export * from "./Handler.js"
export * from "./Handles.js"
export * from "./Keys.js"
export * from "./Kinds.js"
export * from "./Links.js"