diff --git a/.env.example b/.env.example index 5218f16..6fb7422 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,9 @@ # Client-side environment variables must be prefixed with NEXT_PUBLIC_ # Solana Payment Configuration -# TODO: Use different RPC URL -NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158 +# Run the solana proxy to setup RPC and WS URLs: https://git.vdb.to/LaconicNetwork/solana-proxy +NEXT_PUBLIC_SOLANA_RPC_URL= +NEXT_PUBLIC_SOLANA_WS_URL= NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR diff --git a/src/app/api/registry/route.ts b/src/app/api/registry/route.ts index 4d0c429..fe3abe7 100644 --- a/src/app/api/registry/route.ts +++ b/src/app/api/registry/route.ts @@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from 'next/server'; import axios from 'axios'; import assert from 'assert'; -import { Connection } from '@solana/web3.js'; +import { Connection, ParsedTransactionWithMeta } from '@solana/web3.js'; import { verifyUnusedSolanaPayment } from '@/utils/solana-verify'; import { transferLNTTokens } from '@/services/laconic-transfer'; @@ -167,6 +167,7 @@ export async function POST(request: NextRequest) { // First check if the request body is valid JSON let url, txHash, senderPublicKey, paymentMethod; let connection: Connection; + let parsedTx: ParsedTransactionWithMeta | null; try { const body = await request.json(); @@ -186,6 +187,8 @@ export async function POST(request: NextRequest) { }, { status: 400 }); } + parsedTx = tx; + const signerKeys = tx.transaction.message.accountKeys .filter(k => k.signer) .map(k => k.pubkey.toBase58()); @@ -243,9 +246,13 @@ export async function POST(request: NextRequest) { const requiredAmountInBaseUnits = requiredTokenInfo.requiredAmountInBaseUnits; const expectedTokenAmount = Math.round(requiredAmountInBaseUnits - ALLOWED_SLIPPAGE_FACTOR * requiredAmountInBaseUnits); + if (!parsedTx) { + throw new Error(`Unable to find the tx with hash: ${txHash}`) + } + const solanaPaymentResult = await verifyUnusedSolanaPayment( connection, - txHash, + parsedTx, new BN(expectedTokenAmount), paymentMethod, ); @@ -516,4 +523,4 @@ export async function POST(request: NextRequest) { message: error instanceof Error ? error.message : 'Unknown error', }, { status: 500 }); } -} \ No newline at end of file +} diff --git a/src/components/WalletProviders.tsx b/src/components/WalletProviders.tsx index 11788eb..69b79d0 100644 --- a/src/components/WalletProviders.tsx +++ b/src/components/WalletProviders.tsx @@ -16,7 +16,10 @@ import { PaymentMethod } from '@/types'; import '@solana/wallet-adapter-react-ui/styles.css'; assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required'); +assert(process.env.NEXT_PUBLIC_SOLANA_WS_URL, 'SOLANA_WS_URL is required'); + const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL; +const SOLANA_WS_URL = process.env.NEXT_PUBLIC_SOLANA_WS_URL; interface WalletProvidersProps { children: React.ReactNode; @@ -59,7 +62,7 @@ export default function WalletProviders({ children }: WalletProvidersProps) { }, [allWallets, selectedPaymentMethod]); return ( - + {children} diff --git a/src/utils/solana-verify.ts b/src/utils/solana-verify.ts index 0e627dd..898960c 100644 --- a/src/utils/solana-verify.ts +++ b/src/utils/solana-verify.ts @@ -1,6 +1,6 @@ import BN from 'bn.js'; -import { Connection, ParsedInstruction, PartiallyDecodedInstruction } from '@solana/web3.js'; +import { Connection, ParsedInstruction, ParsedTransactionWithMeta, PartiallyDecodedInstruction } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { getRecipientAddress } from '@/services/solana'; @@ -9,12 +9,11 @@ import { PaymentMethod } from '../types'; // Extract transaction info for native GOR transfers const extractTxInfo = async ( connection: Connection, - transactionSignature: string, + parsedTx: ParsedTransactionWithMeta, paymentMethod: PaymentMethod ): Promise<{ authority: string; amount: string; destination: string }> => { - const result = await connection.getParsedTransaction(transactionSignature, 'confirmed'); - if (!result) { + if (!parsedTx) { throw new Error('Transaction not found'); } @@ -23,7 +22,7 @@ const extractTxInfo = async ( switch (paymentMethod) { case PaymentMethod.NAT_GOR: // Look for system program transfer instruction - transferInstruction = result.transaction.message.instructions.find( + transferInstruction = parsedTx.transaction.message.instructions.find( (instr) => 'parsed' in instr && instr.parsed.type === 'transfer' ); @@ -36,7 +35,7 @@ const extractTxInfo = async ( case PaymentMethod.SPL_TOKEN: // Look for token transfer instruction using TOKEN_PROGRAM_ID - transferInstruction = result.transaction.message.instructions.find( + transferInstruction = parsedTx.transaction.message.instructions.find( (instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID) ); @@ -68,7 +67,7 @@ const extractTxInfo = async ( export const verifyUnusedSolanaPayment = async ( connection: Connection, - transactionSignature: string, + parsedTx: ParsedTransactionWithMeta, expectedAmount: BN, paymentMethod: PaymentMethod, ): Promise<{ @@ -81,9 +80,7 @@ export const verifyUnusedSolanaPayment = async ( // TODO: Check if provided signature is already used // Fetch transaction details - const transactionResult = await connection.getParsedTransaction(transactionSignature, 'confirmed'); - - if (!transactionResult) { + if (!parsedTx) { return { valid: false, reason: 'Transaction not found on Solana blockchain' @@ -91,15 +88,15 @@ export const verifyUnusedSolanaPayment = async ( } // Check if transaction was successful - if (transactionResult.meta?.err) { + if (parsedTx.meta?.err) { return { valid: false, - reason: `Transaction failed: ${JSON.stringify(transactionResult.meta.err)}` + reason: `Transaction failed: ${JSON.stringify(parsedTx.meta.err)}` }; } // Check transaction timestamp (5-minute window) - const txTimestamp = transactionResult.blockTime ? new Date(transactionResult.blockTime * 1000) : null; + const txTimestamp = parsedTx.blockTime ? new Date(parsedTx.blockTime * 1000) : null; if (!txTimestamp) { return { valid: false, @@ -119,7 +116,7 @@ export const verifyUnusedSolanaPayment = async ( } // Extract transaction info based on payment method - const transferInfo = await extractTxInfo(connection, transactionSignature, paymentMethod); + const transferInfo = await extractTxInfo(connection, parsedTx, paymentMethod); const amount = transferInfo.amount; const authority = transferInfo.authority; const destination = transferInfo.destination;