import { calculateSponsoringSplit, PLATFORM_COMMISSIONS, PLATFORM_BITCOIN_ADDRESS } from './platformCommissions' import { mempoolSpaceService } from './mempoolSpace' import { sponsoringTrackingService } from './sponsoringTracking' /** * Sponsoring payment service * Handles Bitcoin mainnet payments for sponsoring with automatic commission split * * Sponsoring: 0.046 BTC total * - Author: 0.042 BTC (4,200,000 sats) * - Platform: 0.004 BTC (400,000 sats) * * Since Bitcoin mainnet doesn't support automatic split like Lightning, * we use a two-output transaction approach: * 1. User creates transaction with two outputs (author + platform) * 2. Platform verifies both outputs are present * 3. Transaction is broadcast */ export interface SponsoringPaymentRequest { authorPubkey: string authorMainnetAddress: string amount: number // Should be 0.046 BTC } export interface SponsoringPaymentResult { success: boolean transactionId?: string error?: string split: { author: number platform: number total: number authorSats: number platformSats: number totalSats: number } platformAddress: string authorAddress: string } export class SponsoringPaymentService { /** * Create sponsoring payment request with split information * Returns addresses and amounts for the user to create a Bitcoin transaction */ async createSponsoringPayment(request: SponsoringPaymentRequest): Promise { try { // Verify amount matches expected commission structure if (request.amount !== PLATFORM_COMMISSIONS.sponsoring.total) { return { success: false, error: `Invalid sponsoring amount: ${request.amount} BTC. Expected ${PLATFORM_COMMISSIONS.sponsoring.total} BTC`, split: calculateSponsoringSplit(), platformAddress: PLATFORM_BITCOIN_ADDRESS, authorAddress: request.authorMainnetAddress, } } const split = calculateSponsoringSplit(request.amount) // Verify addresses are valid Bitcoin addresses if (!this.isValidBitcoinAddress(request.authorMainnetAddress)) { return { success: false, error: 'Invalid author Bitcoin address', split, platformAddress: PLATFORM_BITCOIN_ADDRESS, authorAddress: request.authorMainnetAddress, } } console.log('Sponsoring payment request created', { authorPubkey: request.authorPubkey, authorAddress: request.authorMainnetAddress, platformAddress: PLATFORM_BITCOIN_ADDRESS, authorAmount: split.authorSats, platformAmount: split.platformSats, totalAmount: split.totalSats, timestamp: new Date().toISOString(), }) return { success: true, split, platformAddress: PLATFORM_BITCOIN_ADDRESS, authorAddress: request.authorMainnetAddress, } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' console.error('Error creating sponsoring payment', { authorPubkey: request.authorPubkey, error: errorMessage, timestamp: new Date().toISOString(), }) return { success: false, error: errorMessage, split: calculateSponsoringSplit(), platformAddress: PLATFORM_BITCOIN_ADDRESS, authorAddress: request.authorMainnetAddress, } } } /** * Verify sponsoring payment transaction * Checks that transaction has correct outputs for both author and platform * Uses mempool.space API to verify the transaction */ async verifySponsoringPayment( transactionId: string, authorPubkey: string, authorMainnetAddress: string ): Promise { try { const verification = await mempoolSpaceService.verifySponsoringTransaction( transactionId, authorMainnetAddress ) if (!verification.valid) { console.error('Sponsoring payment verification failed', { transactionId, authorPubkey, authorAddress: authorMainnetAddress, error: verification.error, timestamp: new Date().toISOString(), }) return false } if (!verification.confirmed) { console.warn('Sponsoring payment not yet confirmed', { transactionId, authorPubkey, confirmations: verification.confirmations, timestamp: new Date().toISOString(), }) // Return true even if not confirmed - can be checked later } console.log('Sponsoring payment verified', { transactionId, authorPubkey, authorAddress: authorMainnetAddress, authorAmount: verification.authorOutput?.amount, platformAmount: verification.platformOutput?.amount, confirmed: verification.confirmed, confirmations: verification.confirmations, timestamp: new Date().toISOString(), }) return true } catch (error) { console.error('Error verifying sponsoring payment', { transactionId, authorPubkey, error: error instanceof Error ? error.message : 'Unknown error', timestamp: new Date().toISOString(), }) return false } } /** * Track sponsoring payment */ async trackSponsoringPayment( transactionId: string, authorPubkey: string, authorMainnetAddress: string, authorPrivateKey: string ): Promise { try { const split = calculateSponsoringSplit() // Verify transaction first const verification = await mempoolSpaceService.verifySponsoringTransaction( transactionId, authorMainnetAddress ) if (!verification.valid) { console.error('Cannot track invalid sponsoring payment', { transactionId, authorPubkey, error: verification.error, timestamp: new Date().toISOString(), }) return } // Track the sponsoring payment on Nostr await sponsoringTrackingService.trackSponsoringPayment( { transactionId, authorPubkey, authorMainnetAddress, amount: split.total, authorAmount: split.authorSats, platformCommission: split.platformSats, timestamp: Math.floor(Date.now() / 1000), confirmed: verification.confirmed, confirmations: verification.confirmations, }, authorPrivateKey ) console.log('Sponsoring payment tracked', { transactionId, authorPubkey, authorAmount: split.authorSats, platformCommission: split.platformSats, confirmed: verification.confirmed, confirmations: verification.confirmations, timestamp: new Date().toISOString(), }) } catch (error) { console.error('Error tracking sponsoring payment', { transactionId, authorPubkey, error: error instanceof Error ? error.message : 'Unknown error', timestamp: new Date().toISOString(), }) } } /** * Validate Bitcoin address format */ private isValidBitcoinAddress(address: string): boolean { // Basic validation: starts with 1, 3, or bc1 const bitcoinAddressRegex = /^(1|3|bc1)[a-zA-Z0-9]{25,62}$/ return bitcoinAddressRegex.test(address) } } export const sponsoringPaymentService = new SponsoringPaymentService()