diff --git a/lib/articleInvoice.ts b/lib/articleInvoice.ts index ca9ece7..363eeb5 100644 --- a/lib/articleInvoice.ts +++ b/lib/articleInvoice.ts @@ -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 { diff --git a/lib/articleMutations.ts b/lib/articleMutations.ts index 81ca92a..38ad529 100644 --- a/lib/articleMutations.ts +++ b/lib/articleMutations.ts @@ -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 { // 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 { +async function buildUpdateTags(params: { + draft: ArticleDraft + originalArticleId: string + newCategory: 'sciencefiction' | 'research' + authorPubkey: string + currentVersion?: number +}): Promise { // 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') } diff --git a/lib/articlePublisher.ts b/lib/articlePublisher.ts index 5b2aaba..8e64083 100644 --- a/lib/articlePublisher.ts +++ b/lib/articlePublisher.ts @@ -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) diff --git a/lib/articlePublisherHelpersEncryption.ts b/lib/articlePublisherHelpersEncryption.ts index d95c168..9d7005f 100644 --- a/lib/articlePublisherHelpersEncryption.ts +++ b/lib/articlePublisherHelpersEncryption.ts @@ -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' } } diff --git a/lib/articlePublisherHelpersPresentation.ts b/lib/articlePublisherHelpersPresentation.ts index a741249..92f60da 100644 --- a/lib/articlePublisherHelpersPresentation.ts +++ b/lib/articlePublisherHelpersPresentation.ts @@ -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 - ? `[![${authorName}](${draft.pictureUrl})](${profileUrl})` + ? `[![${params.authorName}](${draft.pictureUrl})](${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, diff --git a/lib/articlePublisherPublish.ts b/lib/articlePublisherPublish.ts index fea59db..ece7bca 100644 --- a/lib/articlePublisherPublish.ts +++ b/lib/articlePublisherPublish.ts @@ -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 diff --git a/lib/automaticTransfer.ts b/lib/automaticTransfer.ts index 6a6198c..688b137 100644 --- a/lib/automaticTransfer.ts +++ b/lib/automaticTransfer.ts @@ -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, - recipientAddress: string + 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(), }) } diff --git a/lib/contentDeliveryVerification.ts b/lib/contentDeliveryVerification.ts index 978724d..54e0c38 100644 --- a/lib/contentDeliveryVerification.ts +++ b/lib/contentDeliveryVerification.ts @@ -78,15 +78,17 @@ function setupContentDeliveryHandlers( } function createContentDeliverySubscription( - pool: import('nostr-tools').SimplePool, - authorPubkey: string, - recipientPubkey: string, - articleId: string, - messageEventId: 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' diff --git a/lib/mempoolSpaceConfirmation.ts b/lib/mempoolSpaceConfirmation.ts index 36270b8..c28249f 100644 --- a/lib/mempoolSpaceConfirmation.ts +++ b/lib/mempoolSpaceConfirmation.ts @@ -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, - checkConfirmation: () => void + params: { + txid: string + startTime: number + timeout: number + interval: number + resolve: (value: TransactionVerificationResult | null) => void + checkConfirmation: () => void + } ): Promise { - 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() }) diff --git a/lib/mempoolSpaceVerification.ts b/lib/mempoolSpaceVerification.ts index d3fcb6d..e3045f1 100644 --- a/lib/mempoolSpaceVerification.ts +++ b/lib/mempoolSpaceVerification.ts @@ -32,33 +32,35 @@ export function findTransactionOutputs( } export function buildVerificationResult( - valid: boolean, - confirmed: boolean, - confirmations: number, - authorOutput?: MempoolTransaction['vout'][0], - platformOutput?: 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) } diff --git a/lib/nostr.ts b/lib/nostr.ts index 5bc500f..9db5443 100644 --- a/lib/nostr.ts +++ b/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 { @@ -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, + }) } /** diff --git a/lib/nostrPrivateMessages.ts b/lib/nostrPrivateMessages.ts index 9a3e8c4..24d4d50 100644 --- a/lib/nostrPrivateMessages.ts +++ b/lib/nostrPrivateMessages.ts @@ -33,20 +33,22 @@ function decryptContent(privateKey: string, event: Event): Promise { - 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((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, - recipientPublicKey: string + params: { + pool: SimplePool + eventId: string + authorPubkey: string + recipientPrivateKey: string + recipientPublicKey: string + } ): Promise { - 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((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) diff --git a/lib/nostrZapVerification.ts b/lib/nostrZapVerification.ts index 947c5dd..719619c 100644 --- a/lib/nostrZapVerification.ts +++ b/lib/nostrZapVerification.ts @@ -20,55 +20,67 @@ function createZapFilters(targetPubkey: string, targetEventId: string, userPubke } async function isValidZapReceipt( - event: Event, - targetEventId: string, - targetPubkey: string, - userPubkey: string, - amount: number + params: { + event: Event + targetEventId: string + targetPubkey: string + userPubkey: string + amount: number + } ): Promise { // 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, - resolved: { current: boolean } + 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, - userPubkey: string + params: { + pool: SimplePool + targetPubkey: string + targetEventId: string + amount: number + userPubkey: string + } ): Promise { - if (!pool) { + if (!params.pool) { return Promise.resolve(false) } return new Promise((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 => { diff --git a/lib/paymentPollingMain.ts b/lib/paymentPollingMain.ts index c9a1301..3b8e85a 100644 --- a/lib/paymentPollingMain.ts +++ b/lib/paymentPollingMain.ts @@ -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) diff --git a/lib/paymentPollingTracking.ts b/lib/paymentPollingTracking.ts index 1edb234..27dd81d 100644 --- a/lib/paymentPollingTracking.ts +++ b/lib/paymentPollingTracking.ts @@ -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, - zapReceiptId?: string + 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, - verified: boolean + 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', {