import { nostrService } from './nostr' import { buildTags } from './nostrTagSystem' import { PLATFORM_SERVICE } from './platformConfig' import { generatePurchaseHashId, generateReviewTipHashId, generateSponsoringHashId } from './hashIdGenerator' import { buildObjectId } from './urlGenerator' import type { Event, EventTemplate } from 'nostr-tools' /** * Publish an explicit payment note (kind 1) for a purchase * This note is published in addition to the zap receipt (kind 9735) */ export async function publishPurchaseNote(params: { articleId: string authorPubkey: string payerPubkey: string amount: number paymentHash: string zapReceiptId?: string category?: 'science-fiction' | 'scientific-research' seriesId?: string payerPrivateKey: string }): Promise { let category: 'sciencefiction' | 'research' = 'sciencefiction' if (params.category === 'science-fiction') { category = 'sciencefiction' } else if (params.category === 'scientific-research') { category = 'research' } const purchaseData = { payerPubkey: params.payerPubkey, articleId: params.articleId, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, } const hashId = await generatePurchaseHashId(purchaseData) const id = buildObjectId(hashId, 0, 0) const tags = buildTags({ type: 'payment', category, id: hashId, service: PLATFORM_SERVICE, version: 0, hidden: false, payment: true, paymentType: 'purchase', amount: params.amount, payerPubkey: params.payerPubkey, recipientPubkey: params.authorPubkey, paymentHash: params.paymentHash, articleId: params.articleId, ...(params.zapReceiptId ? { zapReceiptId: params.zapReceiptId } : {}), ...(params.seriesId ? { seriesId: params.seriesId } : {}), }) // Build JSON metadata const paymentJson = JSON.stringify({ type: 'purchase', id, hash: hashId, version: 0, index: 0, ...purchaseData, }) tags.push(['json', paymentJson]) const eventTemplate: EventTemplate = { kind: 1, created_at: Math.floor(Date.now() / 1000), tags, content: `Purchase confirmed: ${params.amount} sats for article ${params.articleId}`, } nostrService.setPrivateKey(params.payerPrivateKey) return nostrService.publishEvent(eventTemplate) } /** * Publish an explicit payment note (kind 1) for a review tip * This note is published in addition to the zap receipt (kind 9735) */ export async function publishReviewTipNote(params: { articleId: string reviewId: string authorPubkey: string reviewerPubkey: string payerPubkey: string amount: number paymentHash: string zapReceiptId?: string category?: 'science-fiction' | 'scientific-research' seriesId?: string text?: string payerPrivateKey: string }): Promise { let category: 'sciencefiction' | 'research' = 'sciencefiction' if (params.category === 'science-fiction') { category = 'sciencefiction' } else if (params.category === 'scientific-research') { category = 'research' } const tipData = { payerPubkey: params.payerPubkey, articleId: params.articleId, reviewId: params.reviewId, reviewerPubkey: params.reviewerPubkey, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, } const hashId = await generateReviewTipHashId(tipData) const id = buildObjectId(hashId, 0, 0) const tags = buildTags({ type: 'payment', category, id: hashId, service: PLATFORM_SERVICE, version: 0, hidden: false, payment: true, paymentType: 'review_tip', amount: params.amount, payerPubkey: params.payerPubkey, recipientPubkey: params.reviewerPubkey, paymentHash: params.paymentHash, articleId: params.articleId, reviewId: params.reviewId, ...(params.zapReceiptId ? { zapReceiptId: params.zapReceiptId } : {}), ...(params.seriesId ? { seriesId: params.seriesId } : {}), ...(params.text ? { text: params.text } : {}), }) // Build JSON metadata const paymentJson = JSON.stringify({ type: 'review_tip', id, hash: hashId, version: 0, index: 0, ...tipData, ...(params.text ? { text: params.text } : {}), }) tags.push(['json', paymentJson]) // Build parsed ReviewTip object const parsedReviewTip: ReviewTip = { id, hash: hashId, version: 0, index: 0, payerPubkey: params.payerPubkey, articleId: params.articleId, reviewId: params.reviewId, reviewerPubkey: params.reviewerPubkey, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, createdAt: Math.floor(Date.now() / 1000), ...(params.text ? { text: params.text } : {}), kindType: 'review_tip', } const content = params.text ? `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}\n\n${params.text}` : `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}` const eventTemplate: EventTemplate = { kind: 1, created_at: Math.floor(Date.now() / 1000), tags, content, } nostrService.setPrivateKey(params.payerPrivateKey) writeOrchestrator.setPrivateKey(params.payerPrivateKey) // Finalize event const secretKey = hexToBytes(params.payerPrivateKey) const event = finalizeEvent(eventTemplate, secretKey) // Get active relays const { relaySessionManager } = await import('./relaySessionManager') const activeRelays = await relaySessionManager.getActiveRelays() const { getPrimaryRelay } = await import('./config') const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()] // Publish via writeOrchestrator (parallel network + local write) const result = await writeOrchestrator.writeAndPublish( { objectType: 'review_tip', hash: hashId, event, parsed: parsedReviewTip, version: 0, hidden: false, index: 0, }, relays ) if (!result.success) { return null } return event } /** * Publish an explicit payment note (kind 1) for a sponsoring * This note is published in addition to the zap receipt (kind 9735) if applicable */ export async function publishSponsoringNote(params: { authorPubkey: string payerPubkey: string amount: number paymentHash: string category?: 'science-fiction' | 'scientific-research' seriesId?: string articleId?: string text?: string transactionId?: string // Bitcoin transaction ID for mainnet payments payerPrivateKey: string }): Promise { let category: 'sciencefiction' | 'research' = 'sciencefiction' if (params.category === 'science-fiction') { category = 'sciencefiction' } else if (params.category === 'scientific-research') { category = 'research' } const sponsoringData = { payerPubkey: params.payerPubkey, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, ...(params.seriesId ? { seriesId: params.seriesId } : {}), ...(params.articleId ? { articleId: params.articleId } : {}), } const hashId = await generateSponsoringHashId(sponsoringData) const id = buildObjectId(hashId, 0, 0) const tags = buildTags({ type: 'payment', category, id: hashId, service: PLATFORM_SERVICE, version: 0, hidden: false, payment: true, paymentType: 'sponsoring', amount: params.amount, payerPubkey: params.payerPubkey, recipientPubkey: params.authorPubkey, paymentHash: params.paymentHash, ...(params.seriesId ? { seriesId: params.seriesId } : {}), ...(params.articleId ? { articleId: params.articleId } : {}), ...(params.text ? { text: params.text } : {}), }) // Add transaction ID if provided (for Bitcoin mainnet payments) if (params.transactionId) { tags.push(['transaction_id', params.transactionId]) } // Build JSON metadata const paymentJson = JSON.stringify({ type: 'sponsoring', id, hash: hashId, version: 0, index: 0, ...sponsoringData, ...(params.text ? { text: params.text } : {}), ...(params.transactionId ? { transactionId: params.transactionId } : {}), }) tags.push(['json', paymentJson]) // Build parsed Sponsoring object const parsedSponsoring: Sponsoring = { id, hash: hashId, version: 0, index: 0, payerPubkey: params.payerPubkey, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, createdAt: Math.floor(Date.now() / 1000), ...(params.seriesId ? { seriesId: params.seriesId } : {}), ...(params.articleId ? { articleId: params.articleId } : {}), ...(params.text ? { text: params.text } : {}), kindType: 'sponsoring', } const content = params.text ? `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...\n\n${params.text}` : `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...` const eventTemplate: EventTemplate = { kind: 1, created_at: Math.floor(Date.now() / 1000), tags, content, } nostrService.setPrivateKey(params.payerPrivateKey) writeOrchestrator.setPrivateKey(params.payerPrivateKey) // Finalize event const secretKey = hexToBytes(params.payerPrivateKey) const event = finalizeEvent(eventTemplate, secretKey) // Get active relays const { relaySessionManager } = await import('./relaySessionManager') const activeRelays = await relaySessionManager.getActiveRelays() const { getPrimaryRelay } = await import('./config') const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()] // Publish via writeOrchestrator (parallel network + local write) const result = await writeOrchestrator.writeAndPublish( { objectType: 'sponsoring', hash: hashId, event, parsed: parsedSponsoring, version: 0, hidden: false, index: 0, }, relays ) if (!result.success) { return null } return event }