Add option to accept payments in Gorbagana chain native tokens (#5)
Part of https://www.notion.so/Laconic-Mainnet-Plan-1eca6b22d47280569cd0d1e6d711d949 Co-authored-by: Shreerang Kale <shreerangkale@gmail.com> Reviewed-on: #5 Co-authored-by: shreerang <shreerang@noreply.git.vdb.to> Co-committed-by: shreerang <shreerang@noreply.git.vdb.to>
This commit was merged in pull request #5.
This commit is contained in:
+70
-43
@@ -1,32 +1,76 @@
|
||||
import BN from 'bn.js';
|
||||
|
||||
import { Connection, PublicKey } from '@solana/web3.js';
|
||||
import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
||||
import { Connection, ParsedInstruction, PartiallyDecodedInstruction } from '@solana/web3.js';
|
||||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
||||
|
||||
// Simplified transaction info extraction following reference implementation
|
||||
const extractTxInfo = async (connection: Connection, transactionSignature: string): Promise<{ authority: string; amount: string }> => {
|
||||
import { getRecipientAddress } from '@/services/solana';
|
||||
import { PaymentMethod } from '../types';
|
||||
|
||||
// Extract transaction info for native GOR transfers
|
||||
const extractTxInfo = async (
|
||||
connection: Connection,
|
||||
transactionSignature: string,
|
||||
paymentMethod: PaymentMethod
|
||||
): Promise<{ authority: string; amount: string; destination: string }> => {
|
||||
const result = await connection.getParsedTransaction(transactionSignature, 'confirmed');
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Transaction not found');
|
||||
}
|
||||
|
||||
const transferInstruction = result.transaction.message.instructions.find(
|
||||
(instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID)
|
||||
);
|
||||
let transferInstruction: ParsedInstruction | PartiallyDecodedInstruction | undefined;
|
||||
|
||||
if (!transferInstruction || !('parsed' in transferInstruction)) {
|
||||
throw new Error('Transfer instruction not found');
|
||||
switch (paymentMethod) {
|
||||
case PaymentMethod.NAT_GOR:
|
||||
// Look for system program transfer instruction
|
||||
transferInstruction = result.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 = result.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');
|
||||
}
|
||||
|
||||
const { info: { amount, authority } } = transferInstruction.parsed;
|
||||
return { authority, amount };
|
||||
};
|
||||
|
||||
export const verifyUnusedSolanaPayment = async (
|
||||
connection: Connection,
|
||||
transactionSignature: string,
|
||||
tokenAmount: BN
|
||||
expectedAmount: BN,
|
||||
paymentMethod: PaymentMethod,
|
||||
): Promise<{
|
||||
valid: boolean,
|
||||
reason?: string,
|
||||
@@ -74,45 +118,28 @@ export const verifyUnusedSolanaPayment = async (
|
||||
};
|
||||
}
|
||||
|
||||
// Extract transaction info using simplified approach
|
||||
const { amount, authority } = await extractTxInfo(connection, transactionSignature);
|
||||
// Extract transaction info based on payment method
|
||||
const transferInfo = await extractTxInfo(connection, transactionSignature, paymentMethod);
|
||||
const amount = transferInfo.amount;
|
||||
const authority = transferInfo.authority;
|
||||
const destination = transferInfo.destination;
|
||||
|
||||
// Verify amount using BN comparison like in reference
|
||||
// Verify amount using BN comparison
|
||||
const transactionAmount = new BN(amount);
|
||||
if (transactionAmount.lt(tokenAmount)) {
|
||||
if (transactionAmount.lt(expectedAmount)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Payment amount (${amount}) is less than required (${tokenAmount.toString()})`
|
||||
reason: `Payment amount (${amount}) is less than required (${expectedAmount.toString()})`
|
||||
};
|
||||
}
|
||||
|
||||
const expectedTokenAccount = getAssociatedTokenAddressSync(
|
||||
new PublicKey(process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS!),
|
||||
new PublicKey(process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS!),
|
||||
// Allow recipient account address to be off the curve (i.e now having a private key)
|
||||
true
|
||||
);
|
||||
// Verify recipient address
|
||||
const expectedRecipientAddress = getRecipientAddress(paymentMethod);
|
||||
|
||||
// Verify recipient address by checking the transaction instructions
|
||||
let foundValidTransfer = false;
|
||||
|
||||
for (const instruction of transactionResult.transaction.message.instructions) {
|
||||
if ('parsed' in instruction && instruction.programId.equals(TOKEN_PROGRAM_ID)) {
|
||||
const parsed = instruction.parsed;
|
||||
if (parsed.type === 'transferChecked' || parsed.type === 'transfer') {
|
||||
// Verify amount and recipient's associated token address
|
||||
if (parsed.info.amount === amount && parsed.info.destination === expectedTokenAccount.toBase58() ) {
|
||||
foundValidTransfer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundValidTransfer) {
|
||||
if (destination !== expectedRecipientAddress) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Valid Solana token transfer not found in transaction'
|
||||
reason: `Invalid recipient address. Expected: ${expectedRecipientAddress}, Got: ${destination}`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,4 +155,4 @@ export const verifyUnusedSolanaPayment = async (
|
||||
reason: `Failed to verify transaction: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user