Use backend cache for getting quotes during payment verification

This commit is contained in:
Adw8 2025-01-30 11:47:28 +05:30
parent f712adfec2
commit ddda589da9
7 changed files with 24 additions and 39 deletions

View File

@ -34,9 +34,8 @@ class QuotesService {
}
}
getLatestQuote(): BN | undefined {
console.log({ cachedQuotes: this.cachedQuotes });
return this.cachedQuotes[this.cachedQuotes.length - 1];
getQuotes(): BN[] {
return this.cachedQuotes;
}
}

View File

@ -4,9 +4,7 @@ import BN from 'bn.js';
import { fal } from "@fal-ai/client"
import { FLUX_MODELS } from '../../../services/fluxService'
import { initializeDataSource } from '../../../data-source'
import { verifyPayment, markSignatureAsUsed } from '../../../utils/verifyPayment'
import { getUSDCToMTMQuote } from '../../../services/paymentService'
import { verifyPayment, markSignatureAsUsed } from '../../../utils/verifyPayment';
if (!process.env.FAL_AI_KEY) {
throw new Error('FAL_AI_KEY is not configured in environment variables')
@ -49,17 +47,18 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
)
}
const mtmFor1USDC = await getUSDCToMTMQuote();
const quotes: BN[] = (global as any).quotesService.getQuotes();
const mtmFor1USDC = quotes.reduce((min, quote) => quote.lt(min) ? quote : min, quotes[0]); // Take the least cost in the array
const scale = new BN(100);
const scaledCost = new BN(model.cost * 100);
const expectedAmount = scaledCost.mul(mtmFor1USDC).div(scale);
const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount)
const lowestQuote = scaledCost.mul(new BN(mtmFor1USDC)).div(scale);
const isPaymentVerified = await verifyPayment(transactionSignature, lowestQuote);
if (!isPaymentVerified) {
return NextResponse.json(
{ error: 'Payment verification failed or transaction signature has already been used' },
{ error: 'Payment verification failed or transaction signature has already been used', reload: true },
{ status: 400 }
)
}

View File

@ -1,6 +1,12 @@
import BN from 'bn.js';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
const quote = (global as any).quoteService.getLatestQuote();
return NextResponse.json(quote);
try {
const quotes: BN[] = (global as any).quotesService.getQuotes();
const quotesAsString = quotes.map(quote => quote.toString());
return NextResponse.json(quotesAsString);
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch quotes' }, { status: 500 });
}
}

View File

@ -24,7 +24,10 @@ const Page: React.FC = (): React.ReactElement => {
try {
const response = await fetch('/api/quotes');
const data = await response.json();
setMtmFor1USDC(new BN(data.latestQuote)); // Convert the string back to BN
// Convert the string back to BN
const price = new BN(data[data.length - 1]);
setMtmFor1USDC(price);
} catch (error) {
console.error('Failed to fetch price:', error);
}

View File

@ -65,6 +65,8 @@ export async function generateWithFlux(
}
} catch (error) {
console.error('Flux generation error:', error)
// Reload the page to get latest prices
window.location.reload();
return {
error: error instanceof Error ? error.message : 'Generation failed'
}

View File

@ -14,13 +14,11 @@ import { WalletType } from './types'
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
assert(process.env.NEXT_PUBLIC_MTM_TOKEN_MINT, 'MTM_TOKEN_MINT is required');
assert(process.env.NEXT_PUBLIC_PAYMENT_RECEIVER_ADDRESS, 'PAYMENT_RECEIVER_ADDRESS is required');
assert(process.env.NEXT_PUBLIC_USDC_MINT, 'USDC_MINT is required');
const MTM_TOKEN_MINT = process.env.NEXT_PUBLIC_MTM_TOKEN_MINT;
const PAYMENT_RECEIVER_ADDRESS = process.env.NEXT_PUBLIC_PAYMENT_RECEIVER_ADDRESS;
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
const SOLANA_WEBSOCKET_URL = process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL;
const USDC_MINT = process.env.NEXT_PUBLIC_USDC_MINT;
const connection = new Connection(
SOLANA_RPC_URL,
@ -51,22 +49,6 @@ async function findAssociatedTokenAddress(
)[0]
}
export async function getUSDCToMTMQuote(): Promise<BN> {
const url = `https://api.jup.ag/price/v2?ids=${USDC_MINT}&vsToken=${MTM_TOKEN_MINT}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch quote: ${response.statusText}`);
}
const quoteResponse = await response.json();
const priceFromAPI = Number(quoteResponse['data'][USDC_MINT]['price']).toFixed(6);
const price = new BN(priceFromAPI.toString().replace('.', ''));
return price;
}
interface WalletAdapter {
signAndSendTransaction(transaction: Transaction): Promise<{ signature: string }>
}

View File

@ -47,7 +47,7 @@ export async function markSignatureAsUsed(transactionSignature: string): Promise
export async function verifyPayment(
transactionSignature: string,
expectedAmount: BN,
lowestQuote: BN,
): Promise<boolean> {
try {
// Check if the signature is already used
@ -74,17 +74,11 @@ export async function verifyPayment(
const transactionAmount = new BN(amount);
// Transaction amount should be within 10% of the expected amount
const lowerBound = expectedAmount.mul(new BN(9)).div(new BN(10));
const upperBound = expectedAmount.mul(new BN(11)).div(new BN(10));
// transaction within the appropriate range
if (transactionAmount.gte(lowerBound) && transactionAmount.lte(upperBound)) {
if (transactionAmount.gte(lowestQuote)) {
console.log('Transaction amount is within the expected range.');
return true;
}
console.log('Transaction amount is not within the expected range.');
// TODO: Handle slippage being greater than 10%
return false;
} catch (error) {
console.error('Verification error:', error);