/** * Featured ("Pro") ranking verification. * * SECURITY: a `["featured", ]` tag is SELF-ASSERTED — anyone can add it * and get top placement, and `useFeaturedVerification` only downgrades the badge, * never the ordering. This helper resolves which featured coordinates are backed * by a **provider-authored NIP-57 zap receipt** so list hooks can rank only those. * * Availability over strictness: if the provider pubkey can't be resolved or the * receipt query fails, it returns `null` and the caller keeps the self-asserted * ordering (so a provider outage never blanks paid placement). */ import { PRO_LIGHTNING_ADDRESS } from '@/lib/sasquatchPro'; import { getZapProviderPubkey } from '@/lib/lightningAddress'; import { getSatoshisFromBolt11 } from '@/lib/bolt11'; import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions'; import type { NostrEvent, NostrFilter } from '@nostrify/nostrify'; /** Minimum paid amount for a "featured" receipt to count (closes the 1-sat self-zap hole). */ const MIN_FEATURED_SATS = 1000; let providerPromise: Promise | null = null; function cachedProvider(): Promise { if (!providerPromise) { providerPromise = getZapProviderPubkey(PRO_LIGHTNING_ADDRESS).catch(() => null); } return providerPromise; } /** * @returns the set of featured coords with a verified provider receipt, or * `null` when verification is unavailable (caller should not demote). */ export async function verifiedFeaturedCoords( coords: string[], query: (filter: NostrFilter) => Promise, ): Promise | null> { if (coords.length === 0) return null; const provider = await cachedProvider(); if (!provider) return null; try { const receipts = await query({ kinds: [9735], authors: [provider], '#a': coords, '#p': PLATFORM_OWNER_PUBKEYS, limit: coords.length * 4, }); const set = new Set(); for (const r of receipts) { // Require a real payment — a receipt's mere presence let anyone self-zap // 1 sat and buy a "Verified" badge. (The exact tier also depends on the // expiry; a floor is enough to close the hole.) const invoice = r.tags.find(([n]) => n === 'bolt11')?.[1]; const sats = invoice ? getSatoshisFromBolt11(invoice) : 0; if (!Number.isFinite(sats) || sats < MIN_FEATURED_SATS) continue; for (const [n, v] of r.tags) if (n === 'a' && v) set.add(v); } return set; } catch { return null; } }