Use bigint for handling token price

This commit is contained in:
Adw8 2025-01-28 17:00:30 +05:30
parent 36a140734a
commit 8b5dbb46fa
6 changed files with 84 additions and 46 deletions

View File

@ -3,7 +3,8 @@ import { fal } from "@fal-ai/client"
import { FLUX_MODELS } from '../../../services/fluxService'
import { initializeDataSource } from '../../../data-source'
import { verifyPayment, isSignatureUsed, markSignatureAsUsed } from '../../../utils/verifyPayment'
import { verifyPayment, markSignatureAsUsed } from '../../../utils/verifyPayment'
import { getUSDCToMTMQuote } from '../../../services/paymentService'
if (!process.env.FAL_AI_KEY) {
throw new Error('FAL_AI_KEY is not configured in environment variables')
@ -46,7 +47,13 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
)
}
const expectedAmount = model.cost;
const MTMFor1USDC = await getUSDCToMTMQuote();
// TODO: Use bn.js for better precision
const scale = BigInt(100);
const scaledCost = BigInt(Math.round(model.cost * Number(scale)));
const expectedAmount = (scaledCost * MTMFor1USDC) / scale;
const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount)
if (!isPaymentVerified) {

View File

@ -15,14 +15,13 @@ const Page: React.FC = (): React.ReactElement => {
type: null
})
const [usdcPrice, setUsdcPrice] = useState<number>(0);
const [MTMFor1USDC, setMTMFor1USDC] = useState<bigint>(BigInt(0));
useEffect(() => {
const fetchPrice = async () => {
try {
const price = await getUSDCToMTMQuote();
setUsdcPrice(price);
setMTMFor1USDC(price);
} catch (error) {
console.error('Failed to fetch price:', error);
}
@ -55,38 +54,44 @@ const Page: React.FC = (): React.ReactElement => {
connected: false,
publicKey: null,
type: null
})
});
}
}
};
const handleFluxGeneration = (modelId: string, cost: number) => {
const handleFluxGeneration = (modelId: string, cost: bigint) => {
return async (prompt: string): Promise<FluxGenerationResult> => {
const type = walletState.type;
if (!walletState.connected || !walletState.publicKey || !walletState.type ||
const { connected, publicKey, type } = walletState;
if (!connected || !publicKey || !type ||
(type === 'phantom' && !window.phantom) ||
(type === 'solflare' && !window.solflare)) {
return { error: 'Wallet not connected' }
return { error: 'Wallet not connected' }
}
// Process payment first
const paymentResult = await processMTMPayment(
walletState.publicKey,
cost,
walletState.type
)
try {
// Convert cost in USDC to MTM tokens using the price ratio
const paymentResult = await processMTMPayment(
publicKey,
cost,
type
)
if (!paymentResult.success) {
return { error: paymentResult.error }
if (!paymentResult.success) {
return { error: paymentResult.error }
}
const transactionSignature = paymentResult.transactionSignature;
if (!transactionSignature) {
return { error: 'Transaction signature not found' }
}
// Generate image with specified model and transaction reference
return generateWithFlux(prompt, modelId, transactionSignature)
} catch (error) {
console.error('Error in handleFluxGeneration:', error)
return { error: error instanceof Error ? error.message : 'Unknown error' }
}
const transactionSignature = paymentResult.transactionSignature;
if (!transactionSignature) {
return { error: 'Transaction signature not found' }
}
// Then generate image with specified model
return generateWithFlux(prompt, modelId, transactionSignature)
}
}
@ -116,11 +121,24 @@ const Page: React.FC = (): React.ReactElement => {
key={model.modelId}
title={model.name}
description={model.description}
tokenCost={model.cost}
isWalletConnected={walletState.connected}
onGenerate={handleFluxGeneration(model.modelId, model.cost)}
mtmPrice={model.cost * usdcPrice}
/>
// TODO: Use bn.js for better precision
onGenerate={handleFluxGeneration(
model.modelId,
(() => {
const scale = BigInt(100); // Scaling factor
const scaledCost = BigInt(Math.round(model.cost * Number(scale)));
const scaledResult = (scaledCost * MTMFor1USDC) / scale;
return scaledResult;
})()
)}
// TODO: Use bn.js for better precision
mtmPrice={(() => {
const scale = BigInt(100);
const scaledCost = BigInt(Math.round(model.cost * Number(scale)));
return (scaledCost * MTMFor1USDC) / scale;
})()}
/>
))}
{/* Coming Soon Card */}

View File

@ -5,10 +5,9 @@ import React, { useState } from 'react'
interface AIServiceCardProps {
title: string
description: string
tokenCost: number
isWalletConnected: boolean
onGenerate: (prompt: string) => Promise<{ imageUrl?: string, error?: string }>
mtmPrice: number | null
mtmPrice: bigint
}
interface GenerationState {
@ -21,7 +20,6 @@ interface GenerationState {
const AIServiceCard: React.FC<AIServiceCardProps> = ({
title,
description,
tokenCost,
isWalletConnected,
onGenerate,
mtmPrice
@ -83,7 +81,7 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
</h2>
<p className="text-gray-400 mt-2">{description}</p>
<div className="mt-2 inline-block px-3 py-1 bg-green-500/20 rounded-full text-green-300 text-sm">
Cost: {mtmPrice ? mtmPrice.toFixed(2) : '...'} MTM
Cost: {mtmPrice ? (mtmPrice / BigInt(10 ** 6)).toString() : '...'} MTM
</div>
</div>
@ -107,7 +105,7 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
transition-all duration-200 shadow-lg hover:shadow-green-500/25
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none"
>
{generationState.loading ? 'Processing...' : `Pay ${mtmPrice ? mtmPrice.toFixed(2) : '...'} MTM & Generate`}
{generationState.loading ? 'Processing...' : `Pay ${mtmPrice ? (mtmPrice / BigInt(10 ** 6)).toString() : '...'} MTM & Generate`}
</button>
</div>

View File

@ -16,19 +16,19 @@ export const FLUX_MODELS: FluxModelConfig[] = [
modelId: "fal-ai/flux/schnell",
name: "Schnell",
description: "Fast meme generator",
cost: 0.1
cost: 0.05
},
{
modelId: "fal-ai/recraft-v3",
name: "Recraft",
description: "Advanced meme generator",
cost: 0.15
cost: 0.10
},
{
modelId: "fal-ai/stable-diffusion-v35-large",
name: "Marquee",
description: "Best meme generator",
cost: 0.2
cost: 0.15
}
]

View File

@ -60,8 +60,10 @@ export async function getUSDCToMTMQuote() {
}
const quoteResponse = await response.json();
const price = quoteResponse['data'][USDC_MINT]['price']
// TODO: Use bn.js for better precision
const priceFromAPI = Number(quoteResponse['data'][USDC_MINT]['price'])* (10 ** 6);
const price = BigInt(Number(priceFromAPI.toFixed(0)));
return price;
}
@ -71,7 +73,7 @@ interface WalletAdapter {
export async function processMTMPayment(
walletPublicKey: string,
tokenAmount: number,
tokenAmount: bigint,
walletType: WalletType
): Promise<PaymentResult> {
try {
@ -143,12 +145,14 @@ export async function processMTMPayment(
)
}
const tokens = tokenAmount / BigInt(10 ** 6);
transaction.add(
createTransferInstruction(
senderATA,
receiverATA,
senderPublicKey,
BigInt(tokenAmount * (10 ** 6))
tokens * BigInt(10 ** 6)
)
)
@ -157,7 +161,7 @@ export async function processMTMPayment(
transaction.feePayer = senderPublicKey
console.log('Sending transaction...')
const { signature } = await wallet.signAndSendTransaction(transaction)
const { signature } = await wallet.signAndSendTransaction(transaction);
console.log('Transaction sent:', signature)
const confirmation = await connection.confirmTransaction({

View File

@ -1,9 +1,13 @@
import assert from 'assert';
import { Connection } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { AppDataSource } from '../data-source';
import { Payment } from '../entity/Payment';
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
const SOLANA_WEBSOCKET_URL = process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL;
@ -42,7 +46,7 @@ export async function markSignatureAsUsed(transactionSignature: string): Promise
export async function verifyPayment(
transactionSignature: string,
expectedAmount: number,
expectedAmount: bigint,
): Promise<boolean> {
try {
// Check if the signature is already used
@ -67,7 +71,14 @@ export async function verifyPayment(
const { info } = parsed;
const { amount } = info;
if (BigInt(amount) === BigInt(expectedAmount * (10 ** 6))) {
const transactionAmount = BigInt(amount);
const expectedAmountInMicroTokens = expectedAmount;
const lowerBound = expectedAmountInMicroTokens * BigInt(9) / BigInt(10);
const upperBound = expectedAmountInMicroTokens * BigInt(11) / BigInt(10);
// transaction within the appropriate range
if (transactionAmount >= lowerBound && transactionAmount <= upperBound) {
return true;
}