story-research-zapwall/lib/sponsoringPayment.ts

96 lines
3.5 KiB
TypeScript

import { calculateSponsoringSplit } from './platformCommissions'
import { PLATFORM_BITCOIN_ADDRESS } from './platformConfig'
import type { SponsoringPaymentRequest, SponsoringPaymentResult } from './sponsoringPaymentTypes'
import { validateSponsoringAmount, buildErrorResult, isValidBitcoinAddress } from './sponsoringPaymentValidation'
import { verifySponsoringPayment } from './sponsoringPaymentVerification'
import { trackSponsoringPayment } from './sponsoringPaymentTracking'
/**
* 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 type { SponsoringPaymentRequest, SponsoringPaymentResult } from './sponsoringPaymentTypes'
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<SponsoringPaymentResult> {
try {
const amountValidation = validateSponsoringAmount(request.amount)
if (!amountValidation.valid) {
return buildErrorResult(amountValidation.error ?? 'Invalid amount', request)
}
const split = calculateSponsoringSplit(request.amount)
if (!isValidBitcoinAddress(request.authorMainnetAddress)) {
return buildErrorResult('Invalid author Bitcoin address', request)
}
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 buildErrorResult(errorMessage, request)
}
}
/**
* 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<boolean> {
return await verifySponsoringPayment(transactionId, authorPubkey, authorMainnetAddress)
}
/**
* Track sponsoring payment
*/
async trackSponsoringPayment(
transactionId: string,
authorPubkey: string,
authorMainnetAddress: string,
authorPrivateKey: string
): Promise<void> {
return await trackSponsoringPayment(transactionId, authorPubkey, authorMainnetAddress, authorPrivateKey)
}
}
export const sponsoringPaymentService = new SponsoringPaymentService()