gor-deploy/src/utils/solana-verify.ts
2025-08-01 10:08:05 +05:30

156 lines
4.7 KiB
TypeScript

import BN from 'bn.js';
import { Connection, ParsedInstruction, ParsedTransactionWithMeta, PartiallyDecodedInstruction } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { getRecipientAddress } from '@/services/solana';
import { PaymentMethod } from '../types';
// Extract transaction info for native GOR transfers
const extractTxInfo = async (
connection: Connection,
parsedTx: ParsedTransactionWithMeta,
paymentMethod: PaymentMethod
): Promise<{ authority: string; amount: string; destination: string }> => {
if (!parsedTx) {
throw new Error('Transaction not found');
}
let transferInstruction: ParsedInstruction | PartiallyDecodedInstruction | undefined;
switch (paymentMethod) {
case PaymentMethod.NAT_GOR:
// Look for system program transfer instruction
transferInstruction = parsedTx.transaction.message.instructions.find(
(instr) => 'parsed' in instr && instr.parsed.type === 'transfer'
);
if (!transferInstruction || !('parsed' in transferInstruction)) {
throw new Error('Native GOR transfer instruction not found');
}
const { info: { lamports, source, destination } } = transferInstruction.parsed;
return { authority: source, amount: lamports.toString(), destination };
case PaymentMethod.SPL_TOKEN:
// Look for token transfer instruction using TOKEN_PROGRAM_ID
transferInstruction = parsedTx.transaction.message.instructions.find(
(instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID)
);
if (!transferInstruction || !('parsed' in transferInstruction)) {
throw new Error('SPL token transfer instruction not found');
}
const parsed = transferInstruction.parsed;
// Handle both transferChecked and transfer types
if (parsed.type === 'transferChecked') {
const { info: { tokenAmount, authority, destination } } = parsed;
return {
authority,
amount: tokenAmount.amount,
destination
};
} else if (parsed.type === 'transfer') {
const { info: { amount, authority, destination } } = parsed;
return { authority, amount, destination };
}
throw new Error('Unsupported token transfer type');
default:
throw new Error('Invalid payment method');
}
};
export const verifyUnusedSolanaPayment = async (
connection: Connection,
parsedTx: ParsedTransactionWithMeta,
expectedAmount: BN,
paymentMethod: PaymentMethod,
): Promise<{
valid: boolean,
reason?: string,
amount?: string,
sender?: string
}> => {
try {
// TODO: Check if provided signature is already used
// Fetch transaction details
if (!parsedTx) {
return {
valid: false,
reason: 'Transaction not found on Solana blockchain'
};
}
// Check if transaction was successful
if (parsedTx.meta?.err) {
return {
valid: false,
reason: `Transaction failed: ${JSON.stringify(parsedTx.meta.err)}`
};
}
// Check transaction timestamp (5-minute window)
const txTimestamp = parsedTx.blockTime ? new Date(parsedTx.blockTime * 1000) : null;
if (!txTimestamp) {
return {
valid: false,
reason: 'Transaction timestamp not available'
};
}
const now = new Date();
const timeDiffMs = now.getTime() - txTimestamp.getTime();
const timeWindowMs = 5 * 60 * 1000; // 5 minutes
if (timeDiffMs > timeWindowMs) {
return {
valid: false,
reason: `Transaction is older than 5 minutes (${Math.round(timeDiffMs / 60000)} minutes old)`
};
}
// Extract transaction info based on payment method
const transferInfo = await extractTxInfo(connection, parsedTx, paymentMethod);
const amount = transferInfo.amount;
const authority = transferInfo.authority;
const destination = transferInfo.destination;
// Verify amount using BN comparison
const transactionAmount = new BN(amount);
if (transactionAmount.lt(expectedAmount)) {
return {
valid: false,
reason: `Payment amount (${amount}) is less than required (${expectedAmount.toString()})`
};
}
// Verify recipient address
const expectedRecipientAddress = getRecipientAddress(paymentMethod);
if (destination !== expectedRecipientAddress) {
return {
valid: false,
reason: `Invalid recipient address. Expected: ${expectedRecipientAddress}, Got: ${destination}`
};
}
return {
valid: true,
amount,
sender: authority
};
} catch (error) {
console.error('Error verifying Solana payment:', error);
return {
valid: false,
reason: `Failed to verify transaction: ${error instanceof Error ? error.message : 'Unknown error'}`
};
}
};