story-research-zapwall/lib/mempoolSpace.ts
Nicolas Cantu f7bd7faa73 fix: Correction erreurs TypeScript, nettoyage et réorganisation documentation
- 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.
2025-12-27 21:25:19 +01:00

234 lines
6.4 KiB
TypeScript

import { calculateSponsoringSplit } from './platformCommissions'
import { PLATFORM_BITCOIN_ADDRESS } from './platformConfig'
const MEMPOOL_API_BASE = 'https://mempool.space/api'
export interface MempoolTransaction {
txid: string
vout: Array<{
value: number // in sats
scriptpubkey_address: string
}>
status: {
confirmed: boolean
block_height?: number
block_hash?: string
}
}
export interface TransactionVerificationResult {
valid: boolean
confirmed: boolean
confirmations: number
authorOutput?: {
address: string
amount: number
}
platformOutput?: {
address: string
amount: number
}
error?: string | undefined
}
/**
* Mempool.space API service
* Used to verify Bitcoin mainnet transactions for sponsoring payments
*/
export class MempoolSpaceService {
/**
* Fetch transaction from mempool.space
*/
async getTransaction(txid: string): Promise<MempoolTransaction | null> {
try {
const response = await fetch(`${MEMPOOL_API_BASE}/tx/${txid}`)
if (!response.ok) {
if (response.status === 404) {
console.warn('Transaction not found on mempool.space', { txid })
return null
}
throw new Error(`Failed to fetch transaction: ${response.status} ${response.statusText}`)
}
const transaction = await response.json() as MempoolTransaction
return transaction
} catch (error) {
console.error('Error fetching transaction from mempool.space', {
txid,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
})
return null
}
}
/**
* Verify sponsoring payment transaction
* Checks that transaction has correct outputs for both author and platform
*/
async verifySponsoringTransaction(
txid: string,
authorMainnetAddress: string
): Promise<TransactionVerificationResult> {
try {
const transaction = await this.getTransaction(txid)
if (!transaction) {
return {
valid: false,
confirmed: false,
confirmations: 0,
error: 'Transaction not found',
}
}
const split = calculateSponsoringSplit()
const expectedAuthorAmount = split.authorSats
const expectedPlatformAmount = split.platformSats
// Find outputs matching expected addresses and amounts
const authorOutput = transaction.vout.find(
(output) =>
output.scriptpubkey_address === authorMainnetAddress &&
output.value === expectedAuthorAmount
)
const platformOutput = transaction.vout.find(
(output) =>
output.scriptpubkey_address === PLATFORM_BITCOIN_ADDRESS &&
output.value === expectedPlatformAmount
)
const valid = Boolean(authorOutput && platformOutput)
const confirmed = transaction.status.confirmed
const confirmations = confirmed && transaction.status.block_height
? await this.getConfirmations(transaction.status.block_height)
: 0
if (!valid) {
console.error('Transaction verification failed', {
txid,
authorAddress: authorMainnetAddress,
platformAddress: PLATFORM_BITCOIN_ADDRESS,
expectedAuthorAmount,
expectedPlatformAmount,
actualOutputs: transaction.vout.map((o) => ({
address: o.scriptpubkey_address,
amount: o.value,
})),
timestamp: new Date().toISOString(),
})
}
const result: TransactionVerificationResult = {
valid,
confirmed,
confirmations,
}
if (!valid) {
result.error = 'Transaction outputs do not match expected split'
}
if (authorOutput) {
result.authorOutput = {
address: authorOutput.scriptpubkey_address,
amount: authorOutput.value,
}
}
if (platformOutput) {
result.platformOutput = {
address: platformOutput.scriptpubkey_address,
amount: platformOutput.value,
}
}
return result
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
console.error('Error verifying sponsoring transaction', {
txid,
authorAddress: authorMainnetAddress,
error: errorMessage,
timestamp: new Date().toISOString(),
})
return {
valid: false,
confirmed: false,
confirmations: 0,
error: errorMessage,
}
}
}
/**
* Get current block height and calculate confirmations
*/
private async getConfirmations(blockHeight: number): Promise<number> {
try {
const response = await fetch(`${MEMPOOL_API_BASE}/blocks/tip/height`)
if (!response.ok) {
return 0
}
const currentHeight = await response.json() as number
return Math.max(0, currentHeight - blockHeight + 1)
} catch (error) {
console.error('Error getting current block height', {
error: error instanceof Error ? error.message : 'Unknown error',
})
return 0
}
}
/**
* Wait for transaction confirmation
* Polls mempool.space until transaction is confirmed or timeout
*/
async waitForConfirmation(
txid: string,
timeout: number = 600000, // 10 minutes
interval: number = 10000 // 10 seconds
): Promise<TransactionVerificationResult | null> {
const startTime = Date.now()
return new Promise((resolve) => {
const checkConfirmation = async () => {
if (Date.now() - startTime > timeout) {
resolve(null)
return
}
// Get author address from transaction (first output that's not platform)
const transaction = await this.getTransaction(txid)
if (!transaction) {
setTimeout(checkConfirmation, interval)
return
}
const authorOutput = transaction.vout.find(
(output) => output.scriptpubkey_address !== PLATFORM_BITCOIN_ADDRESS
)
if (!authorOutput) {
setTimeout(checkConfirmation, interval)
return
}
const result = await this.verifySponsoringTransaction(txid, authorOutput.scriptpubkey_address)
if (result.confirmed && result.valid) {
resolve(result)
} else {
setTimeout(checkConfirmation, interval)
}
}
checkConfirmation()
})
}
}
export const mempoolSpaceService = new MempoolSpaceService()