170 lines
5.0 KiB
TypeScript
170 lines
5.0 KiB
TypeScript
import { calculateSponsoringSplit } from './platformCommissions'
|
|
import { PLATFORM_BITCOIN_ADDRESS } from './platformConfig'
|
|
import type { MempoolTransaction, TransactionVerificationResult } from './mempoolSpaceTypes'
|
|
import { getTransaction, getConfirmations } from './mempoolSpaceApi'
|
|
|
|
export function findTransactionOutputs(
|
|
transaction: MempoolTransaction,
|
|
authorMainnetAddress: string,
|
|
expectedAuthorAmount: number,
|
|
expectedPlatformAmount: number
|
|
): { authorOutput?: MempoolTransaction['vout'][0]; platformOutput?: MempoolTransaction['vout'][0] } {
|
|
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 result: { authorOutput?: MempoolTransaction['vout'][0]; platformOutput?: MempoolTransaction['vout'][0] } = {}
|
|
if (authorOutput) {
|
|
result.authorOutput = authorOutput
|
|
}
|
|
if (platformOutput) {
|
|
result.platformOutput = platformOutput
|
|
}
|
|
return result
|
|
}
|
|
|
|
export function buildVerificationResult(
|
|
valid: boolean,
|
|
confirmed: boolean,
|
|
confirmations: number,
|
|
authorOutput?: MempoolTransaction['vout'][0],
|
|
platformOutput?: MempoolTransaction['vout'][0]
|
|
): TransactionVerificationResult {
|
|
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
|
|
}
|
|
|
|
export async function validateTransactionOutputs(
|
|
transaction: MempoolTransaction,
|
|
authorMainnetAddress: string,
|
|
split: { authorSats: number; platformSats: number }
|
|
): Promise<{ valid: boolean; confirmed: boolean; confirmations: number; authorOutput?: MempoolTransaction['vout'][0]; platformOutput?: MempoolTransaction['vout'][0] }> {
|
|
const { authorOutput, platformOutput } = findTransactionOutputs(
|
|
transaction,
|
|
authorMainnetAddress,
|
|
split.authorSats,
|
|
split.platformSats
|
|
)
|
|
|
|
const valid = Boolean(authorOutput && platformOutput)
|
|
const confirmed = transaction.status.confirmed
|
|
const confirmations = confirmed && transaction.status.block_height
|
|
? await getConfirmations(transaction.status.block_height)
|
|
: 0
|
|
|
|
const result: { valid: boolean; confirmed: boolean; confirmations: number; authorOutput?: MempoolTransaction['vout'][0]; platformOutput?: MempoolTransaction['vout'][0] } = {
|
|
valid,
|
|
confirmed,
|
|
confirmations,
|
|
}
|
|
if (authorOutput) {
|
|
result.authorOutput = authorOutput
|
|
}
|
|
if (platformOutput) {
|
|
result.platformOutput = platformOutput
|
|
}
|
|
return result
|
|
}
|
|
|
|
export function logVerificationFailure(
|
|
txid: string,
|
|
authorMainnetAddress: string,
|
|
split: { authorSats: number; platformSats: number },
|
|
transaction: MempoolTransaction
|
|
): void {
|
|
console.error('Transaction verification failed', {
|
|
txid,
|
|
authorAddress: authorMainnetAddress,
|
|
platformAddress: PLATFORM_BITCOIN_ADDRESS,
|
|
expectedAuthorAmount: split.authorSats,
|
|
expectedPlatformAmount: split.platformSats,
|
|
actualOutputs: transaction.vout.map((o) => ({
|
|
address: o.scriptpubkey_address,
|
|
amount: o.value,
|
|
})),
|
|
timestamp: new Date().toISOString(),
|
|
})
|
|
}
|
|
|
|
export function handleVerificationError(txid: string, authorMainnetAddress: string, error: unknown): TransactionVerificationResult {
|
|
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,
|
|
}
|
|
}
|
|
|
|
export async function verifySponsoringTransaction(
|
|
txid: string,
|
|
authorMainnetAddress: string
|
|
): Promise<TransactionVerificationResult> {
|
|
try {
|
|
const transaction = await getTransaction(txid)
|
|
|
|
if (!transaction) {
|
|
return {
|
|
valid: false,
|
|
confirmed: false,
|
|
confirmations: 0,
|
|
error: 'Transaction not found',
|
|
}
|
|
}
|
|
|
|
const split = calculateSponsoringSplit()
|
|
const validation = await validateTransactionOutputs(transaction, authorMainnetAddress, split)
|
|
|
|
if (!validation.valid) {
|
|
logVerificationFailure(txid, authorMainnetAddress, split, transaction)
|
|
}
|
|
|
|
return buildVerificationResult(
|
|
validation.valid,
|
|
validation.confirmed,
|
|
validation.confirmations,
|
|
validation.authorOutput,
|
|
validation.platformOutput
|
|
)
|
|
} catch (error) {
|
|
return handleVerificationError(txid, authorMainnetAddress, error)
|
|
}
|
|
}
|