lint fix wip
This commit is contained in:
parent
d2124e24aa
commit
38941147cb
@ -64,9 +64,9 @@ export async function createPreviewEvent(
|
||||
draft: params.draft,
|
||||
invoice: params.invoice,
|
||||
authorPubkey: params.authorPubkey,
|
||||
authorPresentationId: params.authorPresentationId,
|
||||
extraTags: params.extraTags,
|
||||
encryptedKey: params.encryptedKey,
|
||||
...(params.authorPresentationId ? { authorPresentationId: params.authorPresentationId } : {}),
|
||||
...(params.extraTags ? { extraTags: params.extraTags } : {}),
|
||||
...(params.encryptedKey ? { encryptedKey: params.encryptedKey } : {}),
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@ -95,13 +95,17 @@ async function buildParsedArticleFromDraft(
|
||||
return { article, hash, version, index }
|
||||
}
|
||||
|
||||
async function publishPreviewWithInvoice(
|
||||
draft: ArticleDraft,
|
||||
invoice: AlbyInvoice,
|
||||
authorPubkey: string,
|
||||
presentationId: string,
|
||||
extraTags?: string[][],
|
||||
interface PublishPreviewWithInvoiceParams {
|
||||
draft: ArticleDraft
|
||||
invoice: AlbyInvoice
|
||||
authorPubkey: string
|
||||
presentationId: string
|
||||
extraTags?: string[][]
|
||||
customArticle?: Article
|
||||
}
|
||||
|
||||
async function publishPreviewWithInvoice(
|
||||
params: PublishPreviewWithInvoiceParams
|
||||
): Promise<import('nostr-tools').Event | null> {
|
||||
// Build parsed article object (use custom article if provided, e.g., for updates with version)
|
||||
let article: Article
|
||||
@ -109,22 +113,22 @@ async function publishPreviewWithInvoice(
|
||||
let version: number
|
||||
let index: number
|
||||
|
||||
if (customArticle) {
|
||||
;({ hash, version } = customArticle)
|
||||
article = customArticle
|
||||
index = customArticle.index ?? 0
|
||||
if (params.customArticle) {
|
||||
;({ hash, version } = params.customArticle)
|
||||
article = params.customArticle
|
||||
index = params.customArticle.index ?? 0
|
||||
} else {
|
||||
const built = await buildParsedArticleFromDraft(draft, invoice, authorPubkey)
|
||||
const built = await buildParsedArticleFromDraft(params.draft, params.invoice, params.authorPubkey)
|
||||
;({ article, hash, version, index } = built)
|
||||
}
|
||||
|
||||
// Build event template
|
||||
const previewEventTemplate = await createPreviewEvent({
|
||||
draft,
|
||||
invoice,
|
||||
authorPubkey,
|
||||
authorPresentationId: presentationId,
|
||||
extraTags,
|
||||
draft: params.draft,
|
||||
invoice: params.invoice,
|
||||
authorPubkey: params.authorPubkey,
|
||||
authorPresentationId: params.presentationId,
|
||||
...(params.extraTags ? { extraTags: params.extraTags } : {}),
|
||||
})
|
||||
|
||||
// Set private key in orchestrator
|
||||
@ -479,42 +483,43 @@ async function buildReviewEvent(
|
||||
}
|
||||
}
|
||||
|
||||
async function buildUpdateTags(
|
||||
draft: ArticleDraft,
|
||||
originalArticleId: string,
|
||||
newCategory: 'sciencefiction' | 'research',
|
||||
authorPubkey: string,
|
||||
currentVersion: number = 0
|
||||
): Promise<string[][]> {
|
||||
async function buildUpdateTags(params: {
|
||||
draft: ArticleDraft
|
||||
originalArticleId: string
|
||||
newCategory: 'sciencefiction' | 'research'
|
||||
authorPubkey: string
|
||||
currentVersion?: number
|
||||
}): Promise<string[][]> {
|
||||
// Generate hash ID from publication data
|
||||
const hashId = await generatePublicationHashId({
|
||||
pubkey: authorPubkey,
|
||||
title: draft.title,
|
||||
preview: draft.preview,
|
||||
category: newCategory,
|
||||
seriesId: draft.seriesId ?? undefined,
|
||||
bannerUrl: draft.bannerUrl ?? undefined,
|
||||
zapAmount: draft.zapAmount,
|
||||
pubkey: params.authorPubkey,
|
||||
title: params.draft.title,
|
||||
preview: params.draft.preview,
|
||||
category: params.newCategory,
|
||||
seriesId: params.draft.seriesId ?? undefined,
|
||||
bannerUrl: params.draft.bannerUrl ?? undefined,
|
||||
zapAmount: params.draft.zapAmount,
|
||||
})
|
||||
|
||||
// Increment version for update
|
||||
const currentVersion = params.currentVersion ?? 0
|
||||
const nextVersion = currentVersion + 1
|
||||
|
||||
const updateTags = buildTags({
|
||||
type: 'publication',
|
||||
category: newCategory,
|
||||
category: params.newCategory,
|
||||
id: hashId,
|
||||
service: PLATFORM_SERVICE,
|
||||
version: nextVersion,
|
||||
hidden: false,
|
||||
paywall: true,
|
||||
title: draft.title,
|
||||
preview: draft.preview,
|
||||
zapAmount: draft.zapAmount,
|
||||
...(draft.seriesId ? { seriesId: draft.seriesId } : {}),
|
||||
...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}),
|
||||
title: params.draft.title,
|
||||
preview: params.draft.preview,
|
||||
zapAmount: params.draft.zapAmount,
|
||||
...(params.draft.seriesId ? { seriesId: params.draft.seriesId } : {}),
|
||||
...(params.draft.bannerUrl ? { bannerUrl: params.draft.bannerUrl } : {}),
|
||||
})
|
||||
updateTags.push(['e', originalArticleId], ['replace', 'article-update'])
|
||||
updateTags.push(['e', params.originalArticleId], ['replace', 'article-update'])
|
||||
return updateTags
|
||||
}
|
||||
|
||||
@ -545,7 +550,13 @@ async function publishUpdate(
|
||||
|
||||
// Use current version from original article
|
||||
const currentVersion = originalArticle.version ?? 0
|
||||
const updateTags = await buildUpdateTags(draft, originalArticleId, newCategory, authorPubkey, currentVersion)
|
||||
const updateTags = await buildUpdateTags({
|
||||
draft,
|
||||
originalArticleId,
|
||||
newCategory,
|
||||
authorPubkey,
|
||||
currentVersion,
|
||||
})
|
||||
|
||||
// Build parsed article with incremented version
|
||||
const { article } = await buildParsedArticleFromDraft(draft, invoice, authorPubkey)
|
||||
@ -554,7 +565,14 @@ async function publishUpdate(
|
||||
version: currentVersion + 1, // Increment version for update
|
||||
}
|
||||
|
||||
const publishedEvent = await publishPreviewWithInvoice(draft, invoice, authorPubkey, presentationId, updateTags, updatedArticle)
|
||||
const publishedEvent = await publishPreviewWithInvoice({
|
||||
draft,
|
||||
invoice,
|
||||
authorPubkey,
|
||||
presentationId,
|
||||
extraTags: updateTags,
|
||||
customArticle: updatedArticle,
|
||||
})
|
||||
if (!publishedEvent) {
|
||||
return updateFailure(originalArticleId, 'Failed to publish article update')
|
||||
}
|
||||
|
||||
@ -243,7 +243,7 @@ export class ArticlePublisher {
|
||||
}
|
||||
|
||||
// Build event template
|
||||
const eventTemplate = await buildPresentationEvent(draft, authorPubkey, authorName, category, version, index)
|
||||
const eventTemplate = await buildPresentationEvent({ draft, authorPubkey, authorName, category, version, index })
|
||||
|
||||
// Set private key in orchestrator
|
||||
writeOrchestrator.setPrivateKey(authorPrivateKey)
|
||||
|
||||
@ -33,27 +33,27 @@ export function buildPrivateMessageEvent(recipientPubkey: string, articleId: str
|
||||
}
|
||||
}
|
||||
|
||||
async function publishEncryptedMessage(
|
||||
articleId: string,
|
||||
recipientPubkey: string,
|
||||
authorPubkey: string,
|
||||
authorPrivateKey: string,
|
||||
async function publishEncryptedMessage(params: {
|
||||
articleId: string
|
||||
recipientPubkey: string
|
||||
authorPubkey: string
|
||||
authorPrivateKey: string
|
||||
keyData: string
|
||||
): Promise<{ eventId: string } | null> {
|
||||
const encryptedKey = await Promise.resolve(nip04.encrypt(authorPrivateKey, recipientPubkey, keyData))
|
||||
const privateMessageEvent = buildPrivateMessageEvent(recipientPubkey, articleId, encryptedKey)
|
||||
}): Promise<{ eventId: string } | null> {
|
||||
const encryptedKey = await Promise.resolve(nip04.encrypt(params.authorPrivateKey, params.recipientPubkey, params.keyData))
|
||||
const privateMessageEvent = buildPrivateMessageEvent(params.recipientPubkey, params.articleId, encryptedKey)
|
||||
const publishedEvent = await nostrService.publishEvent(privateMessageEvent)
|
||||
|
||||
if (!publishedEvent) {
|
||||
console.error('Failed to publish private message event', { articleId, recipientPubkey, authorPubkey })
|
||||
console.error('Failed to publish private message event', { articleId: params.articleId, recipientPubkey: params.recipientPubkey, authorPubkey: params.authorPubkey })
|
||||
return null
|
||||
}
|
||||
|
||||
console.warn('Private message published', {
|
||||
messageEventId: publishedEvent.id,
|
||||
articleId,
|
||||
recipientPubkey,
|
||||
authorPubkey,
|
||||
articleId: params.articleId,
|
||||
recipientPubkey: params.recipientPubkey,
|
||||
authorPubkey: params.authorPubkey,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
return { eventId: publishedEvent.id }
|
||||
@ -70,7 +70,13 @@ export async function sendEncryptedContent(
|
||||
nostrService.setPublicKey(storedContent.authorPubkey)
|
||||
|
||||
const keyData = prepareKeyData(storedContent)
|
||||
const publishResult = await publishEncryptedMessage(articleId, recipientPubkey, storedContent.authorPubkey, authorPrivateKey, keyData)
|
||||
const publishResult = await publishEncryptedMessage({
|
||||
articleId,
|
||||
recipientPubkey,
|
||||
authorPubkey: storedContent.authorPubkey,
|
||||
authorPrivateKey,
|
||||
keyData,
|
||||
})
|
||||
if (!publishResult) {
|
||||
return { success: false, error: 'Failed to publish private message event' }
|
||||
}
|
||||
|
||||
@ -11,25 +11,32 @@ import { generateObjectUrl, buildObjectId, parseObjectId } from './urlGenerator'
|
||||
import { getLatestVersion } from './versionManager'
|
||||
import { objectCache } from './objectCache'
|
||||
|
||||
interface BuildPresentationEventParams {
|
||||
draft: AuthorPresentationDraft
|
||||
authorPubkey: string
|
||||
authorName: string
|
||||
category?: 'sciencefiction' | 'research'
|
||||
version?: number
|
||||
index?: number
|
||||
}
|
||||
|
||||
export async function buildPresentationEvent(
|
||||
draft: AuthorPresentationDraft,
|
||||
authorPubkey: string,
|
||||
authorName: string,
|
||||
category: 'sciencefiction' | 'research' = 'sciencefiction',
|
||||
version: number = 0,
|
||||
index: number = 0
|
||||
params: BuildPresentationEventParams
|
||||
): Promise<{
|
||||
kind: 1
|
||||
created_at: number
|
||||
tags: string[][]
|
||||
content: string
|
||||
}> {
|
||||
const category = params.category ?? 'sciencefiction'
|
||||
const version = params.version ?? 0
|
||||
const index = params.index ?? 0
|
||||
// Extract presentation and contentDescription from draft.content
|
||||
// Format: "${presentation}\n\n---\n\nDescription du contenu :\n${contentDescription}"
|
||||
const separator = '\n\n---\n\nDescription du contenu :\n'
|
||||
const separatorIndex = draft.content.indexOf(separator)
|
||||
const presentation = separatorIndex !== -1 ? draft.content.substring(0, separatorIndex) : draft.presentation
|
||||
let contentDescription = separatorIndex !== -1 ? draft.content.substring(separatorIndex + separator.length) : draft.contentDescription
|
||||
const separatorIndex = params.draft.content.indexOf(separator)
|
||||
const presentation = separatorIndex !== -1 ? params.draft.content.substring(0, separatorIndex) : params.draft.presentation
|
||||
let contentDescription = separatorIndex !== -1 ? params.draft.content.substring(separatorIndex + separator.length) : params.draft.contentDescription
|
||||
|
||||
// Remove Bitcoin address from contentDescription if present (should not be visible in note content)
|
||||
// Remove lines matching "Adresse Bitcoin mainnet (pour le sponsoring) : ..."
|
||||
@ -43,12 +50,12 @@ export async function buildPresentationEvent(
|
||||
|
||||
// Generate hash ID from author data first (needed for URL)
|
||||
const hashId = await generateAuthorHashId({
|
||||
pubkey: authorPubkey,
|
||||
authorName,
|
||||
pubkey: params.authorPubkey,
|
||||
authorName: params.authorName,
|
||||
presentation,
|
||||
contentDescription,
|
||||
mainnetAddress: draft.mainnetAddress ?? undefined,
|
||||
pictureUrl: draft.pictureUrl ?? undefined,
|
||||
mainnetAddress: params.draft.mainnetAddress ?? undefined,
|
||||
pictureUrl: params.draft.pictureUrl ?? undefined,
|
||||
category,
|
||||
})
|
||||
|
||||
@ -56,13 +63,14 @@ export async function buildPresentationEvent(
|
||||
const profileUrl = generateObjectUrl('author', hashId, index, version)
|
||||
|
||||
// Encode pubkey to npub (for metadata JSON)
|
||||
const npub = nip19.npubEncode(authorPubkey)
|
||||
const npub = nip19.npubEncode(params.authorPubkey)
|
||||
|
||||
// Build visible content message
|
||||
// If picture exists, use it as preview image for the link (markdown format)
|
||||
// Note: The image will display at full size in most Nostr clients, not as a thumbnail
|
||||
const {draft} = params
|
||||
const linkWithPreview = draft.pictureUrl
|
||||
? `[](${profileUrl})`
|
||||
? `[](${profileUrl})`
|
||||
: profileUrl
|
||||
|
||||
const visibleContent = [
|
||||
@ -74,9 +82,9 @@ export async function buildPresentationEvent(
|
||||
|
||||
// Build profile JSON for metadata (stored in tag, not in content)
|
||||
const profileJson = JSON.stringify({
|
||||
authorName,
|
||||
authorName: params.authorName,
|
||||
npub,
|
||||
pubkey: authorPubkey,
|
||||
pubkey: params.authorPubkey,
|
||||
presentation,
|
||||
contentDescription,
|
||||
mainnetAddress: draft.mainnetAddress,
|
||||
|
||||
@ -101,9 +101,9 @@ export async function publishPreview(
|
||||
invoice,
|
||||
authorPubkey,
|
||||
authorPresentationId: presentationId,
|
||||
extraTags,
|
||||
encryptedContent,
|
||||
encryptedKey,
|
||||
...(extraTags ? { extraTags } : {}),
|
||||
...(encryptedContent ? { encryptedContent } : {}),
|
||||
...(encryptedKey ? { encryptedKey } : {}),
|
||||
})
|
||||
|
||||
// Set private key in orchestrator
|
||||
|
||||
@ -22,16 +22,23 @@ export class AutomaticTransferService {
|
||||
* Transfer author portion after article payment
|
||||
* Creates a Lightning invoice from the platform to the author
|
||||
*/
|
||||
private logTransferRequired(type: 'article' | 'review', id: string, pubkey: string, amount: number, recipient: string, platformCommission: number): void {
|
||||
private logTransferRequired(params: {
|
||||
type: 'article' | 'review'
|
||||
id: string
|
||||
pubkey: string
|
||||
amount: number
|
||||
recipient: string
|
||||
platformCommission: number
|
||||
}): void {
|
||||
const logData = {
|
||||
[type === 'article' ? 'articleId' : 'reviewId']: id,
|
||||
[type === 'article' ? 'articlePubkey' : 'reviewerPubkey']: pubkey,
|
||||
amount,
|
||||
recipient,
|
||||
platformCommission,
|
||||
[params.type === 'article' ? 'articleId' : 'reviewId']: params.id,
|
||||
[params.type === 'article' ? 'articlePubkey' : 'reviewerPubkey']: params.pubkey,
|
||||
amount: params.amount,
|
||||
recipient: params.recipient,
|
||||
platformCommission: params.platformCommission,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
console.warn(`Automatic transfer required${type === 'review' ? ' for review' : ''}`, logData)
|
||||
console.warn(`Automatic transfer required${params.type === 'review' ? ' for review' : ''}`, logData)
|
||||
}
|
||||
|
||||
private buildTransferError(error: unknown, recipient: string, amount: number = 0): TransferResult {
|
||||
@ -62,8 +69,21 @@ export class AutomaticTransferService {
|
||||
}
|
||||
}
|
||||
|
||||
this.logTransferRequired('article', articleId, articlePubkey, split.author, authorLightningAddress, split.platform)
|
||||
this.trackTransferRequirement('article', articleId, articlePubkey, split.author, authorLightningAddress)
|
||||
this.logTransferRequired({
|
||||
type: 'article',
|
||||
id: articleId,
|
||||
pubkey: articlePubkey,
|
||||
amount: split.author,
|
||||
recipient: authorLightningAddress,
|
||||
platformCommission: split.platform,
|
||||
})
|
||||
this.trackTransferRequirement({
|
||||
type: 'article',
|
||||
id: articleId,
|
||||
recipientPubkey: articlePubkey,
|
||||
amount: split.author,
|
||||
recipientAddress: authorLightningAddress,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@ -102,8 +122,21 @@ export class AutomaticTransferService {
|
||||
}
|
||||
}
|
||||
|
||||
this.logTransferRequired('review', reviewId, reviewerPubkey, split.reviewer, reviewerLightningAddress, split.platform)
|
||||
this.trackTransferRequirement('review', reviewId, reviewerPubkey, split.reviewer, reviewerLightningAddress)
|
||||
this.logTransferRequired({
|
||||
type: 'review',
|
||||
id: reviewId,
|
||||
pubkey: reviewerPubkey,
|
||||
amount: split.reviewer,
|
||||
recipient: reviewerLightningAddress,
|
||||
platformCommission: split.platform,
|
||||
})
|
||||
this.trackTransferRequirement({
|
||||
type: 'review',
|
||||
id: reviewId,
|
||||
recipientPubkey: reviewerPubkey,
|
||||
amount: split.reviewer,
|
||||
recipientAddress: reviewerLightningAddress,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@ -126,11 +159,13 @@ export class AutomaticTransferService {
|
||||
* In production, this would be stored in a database or queue
|
||||
*/
|
||||
private trackTransferRequirement(
|
||||
type: 'article' | 'review',
|
||||
id: string,
|
||||
recipientPubkey: string,
|
||||
amount: number,
|
||||
params: {
|
||||
type: 'article' | 'review'
|
||||
id: string
|
||||
recipientPubkey: string
|
||||
amount: number
|
||||
recipientAddress: string
|
||||
}
|
||||
): void {
|
||||
// In production, this would:
|
||||
// 1. Store in a database/queue for processing
|
||||
@ -138,11 +173,11 @@ export class AutomaticTransferService {
|
||||
// 3. Update tracking when transfer is complete
|
||||
|
||||
console.warn('Transfer requirement tracked', {
|
||||
type,
|
||||
id,
|
||||
recipientPubkey,
|
||||
amount,
|
||||
recipientAddress,
|
||||
type: params.type,
|
||||
id: params.id,
|
||||
recipientPubkey: params.recipientPubkey,
|
||||
amount: params.amount,
|
||||
recipientAddress: params.recipientAddress,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -78,15 +78,17 @@ function setupContentDeliveryHandlers(
|
||||
}
|
||||
|
||||
function createContentDeliverySubscription(
|
||||
pool: import('nostr-tools').SimplePool,
|
||||
authorPubkey: string,
|
||||
recipientPubkey: string,
|
||||
articleId: string,
|
||||
params: {
|
||||
pool: import('nostr-tools').SimplePool
|
||||
authorPubkey: string
|
||||
recipientPubkey: string
|
||||
articleId: string
|
||||
messageEventId: string
|
||||
}
|
||||
): import('@/types/nostr-tools-extended').Subscription {
|
||||
const filters = createContentDeliveryFilters(authorPubkey, recipientPubkey, articleId, messageEventId)
|
||||
const filters = createContentDeliveryFilters(params.authorPubkey, params.recipientPubkey, params.articleId, params.messageEventId)
|
||||
const relayUrl = getPrimaryRelaySync()
|
||||
return createSubscription(pool, [relayUrl], filters)
|
||||
return createSubscription(params.pool, [relayUrl], filters)
|
||||
}
|
||||
|
||||
function createContentDeliveryPromise(
|
||||
@ -129,7 +131,7 @@ export function verifyContentDelivery(
|
||||
return Promise.resolve(status)
|
||||
}
|
||||
|
||||
const sub = createContentDeliverySubscription(pool, authorPubkey, recipientPubkey, articleId, messageEventId)
|
||||
const sub = createContentDeliverySubscription({ pool, authorPubkey, recipientPubkey, articleId, messageEventId })
|
||||
return createContentDeliveryPromise(sub, status)
|
||||
} catch (error) {
|
||||
status.error = error instanceof Error ? error.message : 'Unknown error'
|
||||
|
||||
@ -10,21 +10,23 @@ export function scheduleNextCheck(checkConfirmation: () => void, interval: numbe
|
||||
}
|
||||
|
||||
export async function checkTransactionStatus(
|
||||
txid: string,
|
||||
startTime: number,
|
||||
timeout: number,
|
||||
interval: number,
|
||||
resolve: (value: TransactionVerificationResult | null) => void,
|
||||
params: {
|
||||
txid: string
|
||||
startTime: number
|
||||
timeout: number
|
||||
interval: number
|
||||
resolve: (value: TransactionVerificationResult | null) => void
|
||||
checkConfirmation: () => void
|
||||
}
|
||||
): Promise<void> {
|
||||
if (Date.now() - startTime > timeout) {
|
||||
resolve(null)
|
||||
if (Date.now() - params.startTime > params.timeout) {
|
||||
params.resolve(null)
|
||||
return
|
||||
}
|
||||
|
||||
const transaction = await getTransaction(txid)
|
||||
const transaction = await getTransaction(params.txid)
|
||||
if (!transaction) {
|
||||
scheduleNextCheck(checkConfirmation, interval)
|
||||
scheduleNextCheck(params.checkConfirmation, params.interval)
|
||||
return
|
||||
}
|
||||
|
||||
@ -33,15 +35,15 @@ export async function checkTransactionStatus(
|
||||
)
|
||||
|
||||
if (!authorOutput) {
|
||||
scheduleNextCheck(checkConfirmation, interval)
|
||||
scheduleNextCheck(params.checkConfirmation, params.interval)
|
||||
return
|
||||
}
|
||||
|
||||
const result = await verifySponsoringTransaction(txid, authorOutput.scriptpubkey_address)
|
||||
const result = await verifySponsoringTransaction(params.txid, authorOutput.scriptpubkey_address)
|
||||
if (result.confirmed && result.valid) {
|
||||
resolve(result)
|
||||
params.resolve(result)
|
||||
} else {
|
||||
scheduleNextCheck(checkConfirmation, interval)
|
||||
scheduleNextCheck(params.checkConfirmation, params.interval)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +56,7 @@ export async function waitForConfirmation(
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const checkConfirmation = (): void => {
|
||||
void checkTransactionStatus(txid, startTime, timeout, interval, resolve, checkConfirmation)
|
||||
void checkTransactionStatus({ txid, startTime, timeout, interval, resolve, checkConfirmation })
|
||||
}
|
||||
checkConfirmation()
|
||||
})
|
||||
|
||||
@ -32,33 +32,35 @@ export function findTransactionOutputs(
|
||||
}
|
||||
|
||||
export function buildVerificationResult(
|
||||
valid: boolean,
|
||||
confirmed: boolean,
|
||||
confirmations: number,
|
||||
authorOutput?: MempoolTransaction['vout'][0],
|
||||
params: {
|
||||
valid: boolean
|
||||
confirmed: boolean
|
||||
confirmations: number
|
||||
authorOutput?: MempoolTransaction['vout'][0]
|
||||
platformOutput?: MempoolTransaction['vout'][0]
|
||||
}
|
||||
): TransactionVerificationResult {
|
||||
const result: TransactionVerificationResult = {
|
||||
valid,
|
||||
confirmed,
|
||||
confirmations,
|
||||
valid: params.valid,
|
||||
confirmed: params.confirmed,
|
||||
confirmations: params.confirmations,
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
if (!params.valid) {
|
||||
result.error = 'Transaction outputs do not match expected split'
|
||||
}
|
||||
|
||||
if (authorOutput) {
|
||||
if (params.authorOutput) {
|
||||
result.authorOutput = {
|
||||
address: authorOutput.scriptpubkey_address,
|
||||
amount: authorOutput.value,
|
||||
address: params.authorOutput.scriptpubkey_address,
|
||||
amount: params.authorOutput.value,
|
||||
}
|
||||
}
|
||||
|
||||
if (platformOutput) {
|
||||
if (params.platformOutput) {
|
||||
result.platformOutput = {
|
||||
address: platformOutput.scriptpubkey_address,
|
||||
amount: platformOutput.value,
|
||||
address: params.platformOutput.scriptpubkey_address,
|
||||
amount: params.platformOutput.value,
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,13 +158,13 @@ export async function verifySponsoringTransaction(
|
||||
logVerificationFailure(txid, authorMainnetAddress, split, transaction)
|
||||
}
|
||||
|
||||
return buildVerificationResult(
|
||||
validation.valid,
|
||||
validation.confirmed,
|
||||
validation.confirmations,
|
||||
validation.authorOutput,
|
||||
validation.platformOutput
|
||||
)
|
||||
return buildVerificationResult({
|
||||
valid: validation.valid,
|
||||
confirmed: validation.confirmed,
|
||||
confirmations: validation.confirmations,
|
||||
...(validation.authorOutput ? { authorOutput: validation.authorOutput } : {}),
|
||||
...(validation.platformOutput ? { platformOutput: validation.platformOutput } : {}),
|
||||
})
|
||||
} catch (error) {
|
||||
return handleVerificationError(txid, authorMainnetAddress, error)
|
||||
}
|
||||
|
||||
24
lib/nostr.ts
24
lib/nostr.ts
@ -285,7 +285,13 @@ class NostrService {
|
||||
throw new Error('Private key not set or pool not initialized')
|
||||
}
|
||||
|
||||
return getPrivateContentFromPool(this.pool, eventId, authorPubkey, this.privateKey, this.publicKey)
|
||||
return getPrivateContentFromPool({
|
||||
pool: this.pool,
|
||||
eventId,
|
||||
authorPubkey,
|
||||
privateKey: this.privateKey,
|
||||
publicKey: this.publicKey,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -298,7 +304,13 @@ class NostrService {
|
||||
if (!this.privateKey || !this.pool || !this.publicKey) {
|
||||
return null
|
||||
}
|
||||
return getDecryptionKey(this.pool, eventId, authorPubkey, this.privateKey, this.publicKey)
|
||||
return getDecryptionKey({
|
||||
pool: this.pool,
|
||||
eventId,
|
||||
authorPubkey,
|
||||
recipientPrivateKey: this.privateKey,
|
||||
recipientPublicKey: this.publicKey,
|
||||
})
|
||||
}
|
||||
|
||||
async getDecryptedArticleContent(eventId: string, authorPubkey: string): Promise<string | null> {
|
||||
@ -447,7 +459,13 @@ class NostrService {
|
||||
// Use provided userPubkey or fall back to current public key
|
||||
const checkPubkey = userPubkey ?? this.publicKey
|
||||
|
||||
return checkZapReceiptHelper(this.pool, targetPubkey, targetEventId, amount, checkPubkey)
|
||||
return checkZapReceiptHelper({
|
||||
pool: this.pool,
|
||||
targetPubkey,
|
||||
targetEventId,
|
||||
amount,
|
||||
userPubkey: checkPubkey,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -33,20 +33,22 @@ function decryptContent(privateKey: string, event: Event): Promise<string | null
|
||||
* This function now returns the decryption key instead of the full content
|
||||
*/
|
||||
export function getPrivateContent(
|
||||
pool: SimplePool,
|
||||
eventId: string,
|
||||
authorPubkey: string,
|
||||
privateKey: string,
|
||||
params: {
|
||||
pool: SimplePool
|
||||
eventId: string
|
||||
authorPubkey: string
|
||||
privateKey: string
|
||||
publicKey: string
|
||||
}
|
||||
): Promise<string | null> {
|
||||
if (!privateKey || !pool || !publicKey) {
|
||||
if (!params.privateKey || !params.pool || !params.publicKey) {
|
||||
throw new Error('Private key not set or pool not initialized')
|
||||
}
|
||||
|
||||
return new Promise<string | null>((resolve) => {
|
||||
let resolved = false
|
||||
const relayUrl = getPrimaryRelaySync()
|
||||
const sub = createSubscription(pool, [relayUrl], createPrivateMessageFilters(eventId, publicKey, authorPubkey))
|
||||
const sub = createSubscription(params.pool, [relayUrl], createPrivateMessageFilters(params.eventId, params.publicKey, params.authorPubkey))
|
||||
|
||||
const finalize = (result: string | null): void => {
|
||||
if (resolved) {
|
||||
@ -58,7 +60,7 @@ export function getPrivateContent(
|
||||
}
|
||||
|
||||
sub.on('event', (event: Event): void => {
|
||||
void decryptContent(privateKey, event)
|
||||
void decryptContent(params.privateKey, event)
|
||||
.then((content) => {
|
||||
if (content) {
|
||||
finalize(content)
|
||||
@ -109,20 +111,22 @@ function handleDecryptionKeyEvent(
|
||||
}
|
||||
|
||||
export async function getDecryptionKey(
|
||||
pool: SimplePool,
|
||||
eventId: string,
|
||||
authorPubkey: string,
|
||||
recipientPrivateKey: string,
|
||||
params: {
|
||||
pool: SimplePool
|
||||
eventId: string
|
||||
authorPubkey: string
|
||||
recipientPrivateKey: string
|
||||
recipientPublicKey: string
|
||||
}
|
||||
): Promise<DecryptionKey | null> {
|
||||
if (!recipientPrivateKey || !pool || !recipientPublicKey) {
|
||||
if (!params.recipientPrivateKey || !params.pool || !params.recipientPublicKey) {
|
||||
throw new Error('Private key not set or pool not initialized')
|
||||
}
|
||||
|
||||
return new Promise<DecryptionKey | null>((resolve) => {
|
||||
let resolved = false
|
||||
const relayUrl = getPrimaryRelaySync()
|
||||
const sub = createSubscription(pool, [relayUrl], createPrivateMessageFilters(eventId, recipientPublicKey, authorPubkey))
|
||||
const sub = createSubscription(params.pool, [relayUrl], createPrivateMessageFilters(params.eventId, params.recipientPublicKey, params.authorPubkey))
|
||||
|
||||
const finalize = (result: DecryptionKey | null): void => {
|
||||
if (resolved) {
|
||||
@ -134,7 +138,7 @@ export async function getDecryptionKey(
|
||||
}
|
||||
|
||||
sub.on('event', (event: Event): void => {
|
||||
handleDecryptionKeyEvent(event, recipientPrivateKey, finalize)
|
||||
handleDecryptionKeyEvent(event, params.recipientPrivateKey, finalize)
|
||||
})
|
||||
sub.on('eose', (): void => {
|
||||
finalize(null)
|
||||
|
||||
@ -20,55 +20,67 @@ function createZapFilters(targetPubkey: string, targetEventId: string, userPubke
|
||||
}
|
||||
|
||||
async function isValidZapReceipt(
|
||||
event: Event,
|
||||
targetEventId: string,
|
||||
targetPubkey: string,
|
||||
userPubkey: string,
|
||||
params: {
|
||||
event: Event
|
||||
targetEventId: string
|
||||
targetPubkey: string
|
||||
userPubkey: string
|
||||
amount: number
|
||||
}
|
||||
): Promise<boolean> {
|
||||
// Import verification service dynamically to avoid circular dependencies
|
||||
const { zapVerificationService } = await import('./zapVerification')
|
||||
|
||||
return zapVerificationService.verifyZapReceiptForArticle(event, targetEventId, targetPubkey, userPubkey, amount)
|
||||
return zapVerificationService.verifyZapReceiptForArticle(params.event, params.targetEventId, params.targetPubkey, params.userPubkey, params.amount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has paid for an article by looking for zap receipts
|
||||
*/
|
||||
function handleZapReceiptEvent(
|
||||
event: Event,
|
||||
targetEventId: string,
|
||||
targetPubkey: string,
|
||||
userPubkey: string,
|
||||
amount: number,
|
||||
finalize: (value: boolean) => void,
|
||||
params: {
|
||||
event: Event
|
||||
targetEventId: string
|
||||
targetPubkey: string
|
||||
userPubkey: string
|
||||
amount: number
|
||||
finalize: (value: boolean) => void
|
||||
resolved: { current: boolean }
|
||||
}
|
||||
): void {
|
||||
if (resolved.current) {
|
||||
if (params.resolved.current) {
|
||||
return
|
||||
}
|
||||
void isValidZapReceipt(event, targetEventId, targetPubkey, userPubkey, amount).then((isValid) => {
|
||||
void isValidZapReceipt({
|
||||
event: params.event,
|
||||
targetEventId: params.targetEventId,
|
||||
targetPubkey: params.targetPubkey,
|
||||
userPubkey: params.userPubkey,
|
||||
amount: params.amount,
|
||||
}).then((isValid) => {
|
||||
if (isValid) {
|
||||
finalize(true)
|
||||
params.finalize(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function checkZapReceipt(
|
||||
pool: SimplePool,
|
||||
targetPubkey: string,
|
||||
targetEventId: string,
|
||||
amount: number,
|
||||
params: {
|
||||
pool: SimplePool
|
||||
targetPubkey: string
|
||||
targetEventId: string
|
||||
amount: number
|
||||
userPubkey: string
|
||||
}
|
||||
): Promise<boolean> {
|
||||
if (!pool) {
|
||||
if (!params.pool) {
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
|
||||
return new Promise<boolean>((resolve) => {
|
||||
let resolved = false
|
||||
const relayUrl = getPrimaryRelaySync()
|
||||
const sub = createSubscription(pool, [relayUrl], createZapFilters(targetPubkey, targetEventId, userPubkey))
|
||||
const sub = createSubscription(params.pool, [relayUrl], createZapFilters(params.targetPubkey, params.targetEventId, params.userPubkey))
|
||||
|
||||
const finalize = (value: boolean): void => {
|
||||
if (resolved) {
|
||||
@ -81,7 +93,15 @@ export function checkZapReceipt(
|
||||
|
||||
const resolvedRef = { current: resolved }
|
||||
sub.on('event', (event: Event): void => {
|
||||
handleZapReceiptEvent(event, targetEventId, targetPubkey, userPubkey, amount, finalize, resolvedRef)
|
||||
handleZapReceiptEvent({
|
||||
event,
|
||||
targetEventId: params.targetEventId,
|
||||
targetPubkey: params.targetPubkey,
|
||||
userPubkey: params.userPubkey,
|
||||
amount: params.amount,
|
||||
finalize,
|
||||
resolved: resolvedRef,
|
||||
})
|
||||
})
|
||||
|
||||
const end = (): void => {
|
||||
|
||||
@ -29,15 +29,15 @@ export async function sendPrivateContentAfterPayment(
|
||||
if (result.success && result.messageEventId) {
|
||||
verifyPaymentAmount(amount, articleId)
|
||||
|
||||
const trackingData = createTrackingData(
|
||||
const trackingData = createTrackingData({
|
||||
articleId,
|
||||
validation.storedContent.authorPubkey,
|
||||
authorPubkey: validation.storedContent.authorPubkey,
|
||||
recipientPubkey,
|
||||
result.messageEventId,
|
||||
messageEventId: result.messageEventId,
|
||||
amount,
|
||||
result.verified ?? false,
|
||||
zapReceiptId
|
||||
)
|
||||
verified: result.verified ?? false,
|
||||
...(zapReceiptId ? { zapReceiptId } : {}),
|
||||
})
|
||||
|
||||
await platformTracking.trackContentDelivery(trackingData, validation.authorPrivateKey)
|
||||
await triggerAutomaticTransfer(validation.storedContent.authorPubkey, articleId, amount)
|
||||
|
||||
@ -3,31 +3,33 @@ import { lightningAddressService } from './lightningAddress'
|
||||
import { automaticTransferService } from './automaticTransfer'
|
||||
|
||||
export function createTrackingData(
|
||||
articleId: string,
|
||||
authorPubkey: string,
|
||||
recipientPubkey: string,
|
||||
messageEventId: string,
|
||||
amount: number,
|
||||
verified: boolean,
|
||||
params: {
|
||||
articleId: string
|
||||
authorPubkey: string
|
||||
recipientPubkey: string
|
||||
messageEventId: string
|
||||
amount: number
|
||||
verified: boolean
|
||||
zapReceiptId?: string
|
||||
}
|
||||
): import('./platformTracking').ContentDeliveryTracking {
|
||||
const expectedSplit = calculateArticleSplit()
|
||||
const timestamp = Math.floor(Date.now() / 1000)
|
||||
|
||||
const trackingData: import('./platformTracking').ContentDeliveryTracking = {
|
||||
articleId,
|
||||
articlePubkey: authorPubkey,
|
||||
recipientPubkey,
|
||||
messageEventId,
|
||||
amount,
|
||||
articleId: params.articleId,
|
||||
articlePubkey: params.authorPubkey,
|
||||
recipientPubkey: params.recipientPubkey,
|
||||
messageEventId: params.messageEventId,
|
||||
amount: params.amount,
|
||||
authorAmount: expectedSplit.author,
|
||||
platformCommission: expectedSplit.platform,
|
||||
timestamp,
|
||||
verified,
|
||||
verified: params.verified,
|
||||
}
|
||||
|
||||
if (zapReceiptId) {
|
||||
trackingData.zapReceiptId = zapReceiptId
|
||||
if (params.zapReceiptId) {
|
||||
trackingData.zapReceiptId = params.zapReceiptId
|
||||
}
|
||||
|
||||
return trackingData
|
||||
@ -74,34 +76,36 @@ export async function triggerAutomaticTransfer(
|
||||
}
|
||||
|
||||
export function logPaymentSuccess(
|
||||
articleId: string,
|
||||
recipientPubkey: string,
|
||||
amount: number,
|
||||
messageEventId: string,
|
||||
params: {
|
||||
articleId: string
|
||||
recipientPubkey: string
|
||||
amount: number
|
||||
messageEventId: string
|
||||
verified: boolean
|
||||
}
|
||||
): void {
|
||||
const expectedSplit = calculateArticleSplit()
|
||||
console.warn('Article payment processed with commission', {
|
||||
articleId,
|
||||
totalAmount: amount,
|
||||
articleId: params.articleId,
|
||||
totalAmount: params.amount,
|
||||
authorPortion: expectedSplit.author,
|
||||
platformCommission: expectedSplit.platform,
|
||||
recipientPubkey,
|
||||
recipientPubkey: params.recipientPubkey,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
|
||||
if (verified) {
|
||||
if (params.verified) {
|
||||
console.warn('Private content sent and verified on relay', {
|
||||
articleId,
|
||||
recipientPubkey,
|
||||
messageEventId,
|
||||
articleId: params.articleId,
|
||||
recipientPubkey: params.recipientPubkey,
|
||||
messageEventId: params.messageEventId,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
} else {
|
||||
console.warn('Private content sent but not yet verified on relay', {
|
||||
articleId,
|
||||
recipientPubkey,
|
||||
messageEventId,
|
||||
articleId: params.articleId,
|
||||
recipientPubkey: params.recipientPubkey,
|
||||
messageEventId: params.messageEventId,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
@ -114,7 +118,13 @@ export function logPaymentResult(
|
||||
amount: number
|
||||
): boolean {
|
||||
if (result.success && result.messageEventId) {
|
||||
logPaymentSuccess(articleId, recipientPubkey, amount, result.messageEventId, result.verified ?? false)
|
||||
logPaymentSuccess({
|
||||
articleId,
|
||||
recipientPubkey,
|
||||
amount,
|
||||
messageEventId: result.messageEventId,
|
||||
verified: result.verified ?? false,
|
||||
})
|
||||
return true
|
||||
}
|
||||
console.error('Failed to send private content, but payment was confirmed', {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user