story-research-zapwall/lib/paymentNotes.ts
2026-01-07 02:20:33 +01:00

398 lines
11 KiB
TypeScript

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'
/**
* 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<Event | null> {
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])
// Build parsed Purchase object
const parsedPurchase: Purchase = {
id,
hash: 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),
kindType: 'purchase',
}
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)
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: 'purchase',
hash: hashId,
event,
parsed: parsedPurchase,
version: 0,
hidden: false,
index: 0,
},
relays
)
if (!result.success) {
return null
}
return event
}
/**
* 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<Event | null> {
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<Event | null> {
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
}