import type { Event } from 'nostr-tools' import { nostrService } from './nostr' import type { SimplePoolWithSub } from '@/types/nostr-tools-extended' const DEFAULT_RELAY = process.env.NEXT_PUBLIC_NOSTR_RELAY_URL ?? 'wss://relay.damus.io' interface ZapAggregationFilter { authorPubkey: string seriesId?: string articleId?: string kindType: 'sponsoring' | 'purchase' | 'review_tip' reviewerPubkey?: string timeoutMs?: number } function buildFilters(params: ZapAggregationFilter) { const filters = [ { kinds: [9735], '#p': [params.authorPubkey], '#kind_type': [params.kindType], ...(params.seriesId ? { '#series': [params.seriesId] } : {}), ...(params.articleId ? { '#article': [params.articleId] } : {}), ...(params.reviewerPubkey ? { '#reviewer': [params.reviewerPubkey] } : {}), }, ] return filters } function millisatsToSats(value: string | undefined): number { if (!value) { return 0 } const parsed = parseInt(value, 10) if (Number.isNaN(parsed)) { return 0 } return Math.floor(parsed / 1000) } export function aggregateZapSats(params: ZapAggregationFilter): Promise { const pool = nostrService.getPool() if (!pool) { throw new Error('Nostr pool not initialized') } const poolWithSub = pool as SimplePoolWithSub const filters = buildFilters(params) const relay = DEFAULT_RELAY const timeout = params.timeoutMs ?? 5000 const sub = poolWithSub.sub([relay], filters) return collectZap(sub, timeout) } function collectZap( sub: ReturnType, timeout: number ): Promise { return new Promise((resolve, reject) => { let total = 0 let finished = false const done = () => { if (finished) { return } finished = true sub.unsub() resolve(total) } const onError = (err: unknown) => { if (finished) { return } finished = true sub.unsub() reject(err instanceof Error ? err : new Error('Unknown zap aggregation error')) } sub.on('event', (event: Event) => { const amountTag = event.tags.find((tag) => tag[0] === 'amount') total += millisatsToSats(amountTag?.[1]) }) sub.on('eose', () => done()) setTimeout(() => done(), timeout).unref?.() if (typeof (sub as unknown as { on?: unknown }).on === 'function') { ;(sub as any).on('error', onError) } }) }