import { Event, validateEvent, verifySignature } from 'nostr-tools' /** * Service for verifying zap receipts and their signatures */ export class ZapVerificationService { /** * Verify a zap receipt signature */ verifyZapReceiptSignature(event: Event): boolean { try { return validateEvent(event) && verifySignature(event) } catch (error) { console.error('Error verifying zap receipt signature:', error) return false } } /** * Check if a zap receipt is valid for a specific article and user */ verifyZapReceiptForArticle( zapReceipt: Event, articleId: string, articlePubkey: string, _userPubkey: string, expectedAmount: number ): boolean { if (!this.verifyZapReceiptSignature(zapReceipt)) { console.warn('Zap receipt signature verification failed') return false } return ( this.isRecipientValid(zapReceipt, articlePubkey) && this.isArticleReferenced(zapReceipt, articleId) && this.isAmountValid(zapReceipt, expectedAmount) && this.isZapKind(zapReceipt) ) } /** * Extract payment information from a zap receipt */ extractPaymentInfo(zapReceipt: Event): { amount: number // in satoshis recipient: string articleId: string | null payer: string } | null { try { const amountTag = zapReceipt.tags.find((tag) => tag[0] === 'amount') const recipientTag = zapReceipt.tags.find((tag) => tag[0] === 'p') const eventTag = zapReceipt.tags.find((tag) => tag[0] === 'e') if (!amountTag || !recipientTag) { return null } const amountInMillisats = parseInt(amountTag[1] ?? '0') const amountInSats = Math.floor(amountInMillisats / 1000) return { amount: amountInSats, recipient: recipientTag?.[1] ?? '', articleId: eventTag?.[1] ?? null, payer: zapReceipt.pubkey, } } catch (error) { console.error('Error extracting payment info:', error) return null } } private isRecipientValid(zapReceipt: Event, articlePubkey: string): boolean { const recipient = zapReceipt.tags.find((tag) => tag[0] === 'p')?.[1] if (recipient !== articlePubkey) { console.warn('Zap receipt recipient does not match article author') return false } return true } private isArticleReferenced(zapReceipt: Event, articleId: string): boolean { const eventIdTag = zapReceipt.tags.find((tag) => tag[0] === 'e')?.[1] if (eventIdTag !== articleId) { console.warn('Zap receipt does not reference the correct article') return false } return true } private isAmountValid(zapReceipt: Event, expectedAmount: number): boolean { const amountTag = zapReceipt.tags.find((tag) => tag[0] === 'amount')?.[1] if (!amountTag) { console.warn('Zap receipt does not contain amount tag') return false } const amountInMillisats = parseInt(amountTag ?? '0') const expectedAmountInMillisats = expectedAmount * 1000 if (amountInMillisats < expectedAmountInMillisats) { console.warn(`Zap amount ${amountInMillisats} is less than expected ${expectedAmountInMillisats}`) return false } return true } private isZapKind(zapReceipt: Event): boolean { if (zapReceipt.kind !== 9735) { console.warn('Event is not a zap receipt (kind 9735)') return false } return true } } export const zapVerificationService = new ZapVerificationService()