- Correction toutes erreurs TypeScript : - Variables non utilisées supprimées - Types optionnels corrigés (exactOptionalPropertyTypes) - Imports corrigés (PLATFORM_BITCOIN_ADDRESS depuis platformConfig) - Gestion correcte des propriétés optionnelles - Suppression fichiers obsolètes : - code-cleanup-summary.md (redondant) - todo-implementation*.md (todos obsolètes) - corrections-completed.md, fallbacks-found.md (corrections faites) - implementation-summary.md (redondant) - documentation-plan.md (plan, pas documentation) - Suppression scripts temporaires : - add-ssh-key.sh - add-ssh-key-plink.sh - Réorganisation documentation dans docs/ : - architecture.md (nouveau) - commissions.md (nouveau) - implementation-summary.md - remaining-tasks.md - split-and-transfer.md - commission-system.md - commission-implementation.md - content-delivery-verification.md Toutes erreurs TypeScript corrigées, documentation centralisée.
240 lines
7.3 KiB
TypeScript
240 lines
7.3 KiB
TypeScript
import { calculateSponsoringSplit, PLATFORM_COMMISSIONS } from './platformCommissions'
|
|
import { PLATFORM_BITCOIN_ADDRESS } from './platformConfig'
|
|
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<SponsoringPaymentResult> {
|
|
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<boolean> {
|
|
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<void> {
|
|
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()
|