import type { Event } from 'nostr-tools' import { nostrService } from './nostr' import type { Purchase } from '@/types/nostr' import { parsePurchaseFromEvent } from './nostrEventParsing' import { objectCache } from './objectCache' import { getPrimaryRelaySync } from './config' import { MIN_EVENT_DATE } from './platformConfig' import { parseObjectId } from './urlGenerator' function buildPurchaseFilters(articleId?: string, payerPubkey?: string, authorPubkey?: string): Array<{ kinds: number[] since?: number authors?: string[] '#p'?: string[] '#e'?: string[] '#kind_type'?: string[] }> { const filters: Array<{ kinds: number[] since?: number authors?: string[] '#p'?: string[] '#e'?: string[] '#kind_type'?: string[] }> = [] const baseFilter: { kinds: number[] since: number '#kind_type': string[] authors?: string[] '#p'?: string[] '#e'?: string[] } = { kinds: [9735], // Zap receipt since: MIN_EVENT_DATE, '#kind_type': ['purchase'], } if (payerPubkey) { baseFilter.authors = [payerPubkey] } if (authorPubkey) { baseFilter['#p'] = [authorPubkey] } if (articleId) { baseFilter['#e'] = [articleId] } filters.push(baseFilter) return filters } export async function getPurchaseById(purchaseId: string, timeoutMs: number = 5000): Promise { const parsed = parseObjectId(purchaseId) const hash = parsed.hash ?? purchaseId // Check cache first const cached = await objectCache.get('purchase', hash) if (cached) { return cached as Purchase } const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const filters = buildPurchaseFilters() const { createSubscription } = require('@/types/nostr-tools-extended') return new Promise((resolve) => { const relayUrl = getPrimaryRelaySync() const sub = createSubscription(pool, [relayUrl], filters) let finished = false const done = async (value: Purchase | null): Promise => { if (finished) { return } finished = true sub.unsub() resolve(value) } sub.on('event', async (event: Event): Promise => { const parsed = await parsePurchaseFromEvent(event) if (parsed?.id === purchaseId) { // Cache the parsed purchase if (parsed.hash) { await objectCache.set('purchase', parsed.hash, event, parsed, 0, false, parsed.index) } done(parsed) } }) sub.on('eose', (): void => { void done(null) }) setTimeout(() => done(null), timeoutMs).unref?.() }) } export function getPurchasesForArticle(articleId: string, timeoutMs: number = 5000): Promise { const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const filters = buildPurchaseFilters(articleId) const { createSubscription } = require('@/types/nostr-tools-extended') return new Promise((resolve) => { const results: Purchase[] = [] const relayUrl = getPrimaryRelaySync() const sub = createSubscription(pool, [relayUrl], filters) let finished = false const done = async (): Promise => { if (finished) { return } finished = true sub.unsub() resolve(results) } sub.on('event', async (event: Event): Promise => { const parsed = await parsePurchaseFromEvent(event) if (parsed?.articleId === articleId) { // Cache the parsed purchase if (parsed.hash) { await objectCache.set('purchase', parsed.hash, event, parsed, 0, false, parsed.index) } results.push(parsed) } }) sub.on('eose', () => done()) setTimeout(() => done(), timeoutMs).unref?.() }) } export function getPurchasesByPayer(payerPubkey: string, timeoutMs: number = 5000): Promise { const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const filters = buildPurchaseFilters(undefined, payerPubkey) const { createSubscription } = require('@/types/nostr-tools-extended') return new Promise((resolve) => { const results: Purchase[] = [] const relayUrl = getPrimaryRelaySync() const sub = createSubscription(pool, [relayUrl], filters) let finished = false const done = async (): Promise => { if (finished) { return } finished = true sub.unsub() resolve(results) } sub.on('event', async (event: Event): Promise => { const parsed = await parsePurchaseFromEvent(event) if (parsed) { // Cache the parsed purchase if (parsed.hash) { await objectCache.set('purchase', parsed.hash, event, parsed, 0, false, parsed.index) } results.push(parsed) } }) sub.on('eose', (): void => { void done() }) setTimeout(() => done(), timeoutMs).unref?.() }) }