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' import { finalizeEvent } from 'nostr-tools' import { hexToBytes } from 'nostr-tools/utils' import type { Purchase, ReviewTip, Sponsoring } from '@/types/nostr' import { writeOrchestrator } from './writeOrchestrator' import { getPublishRelays } from './relaySelection' /** * 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 { const category = mapPaymentCategory(params.category) const payload = await buildPurchaseNotePayload({ ...params, category }) return publishPaymentNoteToRelays({ payerPrivateKey: params.payerPrivateKey, objectType: 'purchase', hash: payload.hashId, eventTemplate: payload.eventTemplate, parsed: payload.parsedPurchase, version: 0, hidden: false, index: 0, }) } /** * 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 { const category = mapPaymentCategory(params.category) const payload = await buildReviewTipNotePayload({ ...params, category }) return publishPaymentNoteToRelays({ payerPrivateKey: params.payerPrivateKey, objectType: 'review_tip', hash: payload.hashId, eventTemplate: payload.eventTemplate, parsed: payload.parsedReviewTip, version: 0, hidden: false, index: 0, }) } /** * 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 { const category = mapPaymentCategory(params.category) const payload = await buildSponsoringNotePayload({ ...params, category }) return publishPaymentNoteToRelays({ payerPrivateKey: params.payerPrivateKey, objectType: 'sponsoring', hash: payload.hashId, eventTemplate: payload.eventTemplate, parsed: payload.parsedSponsoring, version: 0, hidden: false, index: 0, }) } function mapPaymentCategory( category: 'science-fiction' | 'scientific-research' | undefined ): 'sciencefiction' | 'research' { if (category === 'scientific-research') { return 'research' } return 'sciencefiction' } async function buildPurchaseNotePayload(params: { articleId: string authorPubkey: string payerPubkey: string amount: number paymentHash: string zapReceiptId?: string category: 'sciencefiction' | 'research' seriesId?: string }): Promise<{ hashId: string; eventTemplate: EventTemplate; parsedPurchase: Purchase }> { const purchaseData = { payerPubkey: params.payerPubkey, articleId: params.articleId, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, ...(params.seriesId ? { seriesId: params.seriesId } : {}), } const hashId = await generatePurchaseHashId(purchaseData) const id = buildObjectId(hashId, 0, 0) const tags = buildPurchaseNoteTags({ ...params, hashId }) tags.push(['json', JSON.stringify({ type: 'purchase', id, hash: hashId, version: 0, index: 0, ...purchaseData })]) const parsedPurchase = buildParsedPurchase({ ...params, id, hashId }) return { hashId, eventTemplate: buildPaymentNoteTemplate(tags, `Purchase confirmed: ${params.amount} sats for article ${params.articleId}`), parsedPurchase } } function buildPurchaseNoteTags(params: { articleId: string authorPubkey: string payerPubkey: string amount: number paymentHash: string zapReceiptId?: string category: 'sciencefiction' | 'research' seriesId?: string hashId: string }): string[][] { return buildTags({ type: 'payment', category: params.category, id: params.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 } : {}), }) } function buildParsedPurchase(params: { articleId: string authorPubkey: string payerPubkey: string amount: number paymentHash: string id: string hashId: string seriesId?: string }): Purchase { return { id: params.id, hash: params.hashId, version: 0, index: 0, payerPubkey: params.payerPubkey, articleId: params.articleId, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, createdAt: Math.floor(Date.now() / 1000), ...(params.seriesId ? { seriesId: params.seriesId } : {}), kindType: 'purchase', } } async function buildReviewTipNotePayload(params: { articleId: string reviewId: string authorPubkey: string reviewerPubkey: string payerPubkey: string amount: number paymentHash: string zapReceiptId?: string category: 'sciencefiction' | 'research' seriesId?: string text?: string }): Promise<{ hashId: string; eventTemplate: EventTemplate; parsedReviewTip: ReviewTip }> { const tipData = { payerPubkey: params.payerPubkey, articleId: params.articleId, reviewId: params.reviewId, reviewerPubkey: params.reviewerPubkey, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, ...(params.seriesId ? { seriesId: params.seriesId } : {}), ...(params.text ? { text: params.text } : {}), } const hashId = await generateReviewTipHashId(tipData) const id = buildObjectId(hashId, 0, 0) const tags = buildReviewTipNoteTags({ ...params, hashId }) tags.push(['json', JSON.stringify({ type: 'review_tip', id, hash: hashId, version: 0, index: 0, ...tipData })]) const parsedReviewTip = buildParsedReviewTip({ ...params, id, hashId }) return { hashId, eventTemplate: buildPaymentNoteTemplate(tags, buildReviewTipNoteContent(params)), parsedReviewTip } } function buildReviewTipNoteTags(params: { articleId: string reviewId: string authorPubkey: string reviewerPubkey: string payerPubkey: string amount: number paymentHash: string zapReceiptId?: string category: 'sciencefiction' | 'research' seriesId?: string text?: string hashId: string }): string[][] { return buildTags({ type: 'payment', category: params.category, id: params.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 } : {}), }) } function buildReviewTipNoteContent(params: { amount: number; reviewId: string; text?: string }): string { const prefix = `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}` return params.text ? `${prefix}\n\n${params.text}` : prefix } function buildParsedReviewTip(params: { articleId: string reviewId: string authorPubkey: string reviewerPubkey: string payerPubkey: string amount: number paymentHash: string seriesId?: string text?: string id: string hashId: string }): ReviewTip { return { id: params.id, hash: params.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.seriesId ? { seriesId: params.seriesId } : {}), ...(params.text ? { text: params.text } : {}), kindType: 'review_tip', } } function buildPaymentNoteTemplate(tags: string[][], content: string): EventTemplate { return { kind: 1, created_at: Math.floor(Date.now() / 1000), tags, content, } } async function buildSponsoringNotePayload(params: { authorPubkey: string payerPubkey: string amount: number paymentHash: string category: 'sciencefiction' | 'research' seriesId?: string articleId?: string text?: string transactionId?: string }): Promise<{ hashId: string eventTemplate: EventTemplate parsedSponsoring: Sponsoring }> { const sponsoringData = buildSponsoringHashInput(params) const hashId = await generateSponsoringHashId(sponsoringData) const id = buildObjectId(hashId, 0, 0) const tags = buildSponsoringNoteTags({ ...params, hashId }) tags.push(['json', buildSponsoringPaymentJson({ ...params, sponsoringData, id, hashId })]) const parsedSponsoring = buildParsedSponsoring({ ...params, id, hashId }) const eventTemplate = buildSponsoringEventTemplate({ tags, content: buildSponsoringNoteContent(params) }) return { hashId, eventTemplate, parsedSponsoring } } function buildSponsoringHashInput(params: { payerPubkey: string authorPubkey: string amount: number paymentHash: string seriesId?: string articleId?: string }): Parameters[0] { return { payerPubkey: params.payerPubkey, authorPubkey: params.authorPubkey, amount: params.amount, paymentHash: params.paymentHash, ...(params.seriesId ? { seriesId: params.seriesId } : {}), ...(params.articleId ? { articleId: params.articleId } : {}), } } function buildParsedSponsoring(params: { authorPubkey: string payerPubkey: string amount: number paymentHash: string seriesId?: string articleId?: string text?: string id: string hashId: string }): Sponsoring { return { id: params.id, hash: params.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', } } function buildSponsoringEventTemplate(params: { tags: string[][]; content: string }): EventTemplate { return { kind: 1, created_at: Math.floor(Date.now() / 1000), tags: params.tags, content: params.content } } function buildSponsoringNoteContent(params: { authorPubkey: string amount: number text?: string }): string { const prefix = `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...` return params.text ? `${prefix}\n\n${params.text}` : prefix } function buildSponsoringNoteTags(params: { authorPubkey: string payerPubkey: string amount: number paymentHash: string category: 'sciencefiction' | 'research' seriesId?: string articleId?: string text?: string transactionId?: string hashId: string }): string[][] { const tags = buildTags({ type: 'payment', category: params.category, id: params.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 } : {}), }) if (params.transactionId) { tags.push(['transaction_id', params.transactionId]) } return tags } function buildSponsoringPaymentJson(params: { id: string hashId: string sponsoringData: Record text?: string transactionId?: string }): string { return JSON.stringify({ type: 'sponsoring', id: params.id, hash: params.hashId, version: 0, index: 0, ...params.sponsoringData, ...(params.text ? { text: params.text } : {}), ...(params.transactionId ? { transactionId: params.transactionId } : {}), }) } async function publishPaymentNoteToRelays(params: { payerPrivateKey: string objectType: 'purchase' | 'review_tip' | 'sponsoring' hash: string eventTemplate: EventTemplate parsed: Purchase | ReviewTip | Sponsoring version: number hidden: boolean index: number }): Promise { nostrService.setPrivateKey(params.payerPrivateKey) writeOrchestrator.setPrivateKey(params.payerPrivateKey) const secretKey = hexToBytes(params.payerPrivateKey) const event = finalizeEvent(params.eventTemplate, secretKey) const relays = await getPublishRelays() const result = await writeOrchestrator.writeAndPublish( { objectType: params.objectType, hash: params.hash, event, parsed: params.parsed, version: params.version, hidden: params.hidden, index: params.index, }, relays ) if (!result.success) { return null } return event }