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 { FLUX_MODELS } from '../../../services/fluxService'
import { initializeDataSource } from '../../../data-source' 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) { if (!process.env.FAL_AI_KEY) {
throw new Error('FAL_AI_KEY is not configured in environment variables') 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) const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount)
if (!isPaymentVerified) { if (!isPaymentVerified) {

View File

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

View File

@ -5,10 +5,9 @@ import React, { useState } from 'react'
interface AIServiceCardProps { interface AIServiceCardProps {
title: string title: string
description: string description: string
tokenCost: number
isWalletConnected: boolean isWalletConnected: boolean
onGenerate: (prompt: string) => Promise<{ imageUrl?: string, error?: string }> onGenerate: (prompt: string) => Promise<{ imageUrl?: string, error?: string }>
mtmPrice: number | null mtmPrice: bigint
} }
interface GenerationState { interface GenerationState {
@ -21,7 +20,6 @@ interface GenerationState {
const AIServiceCard: React.FC<AIServiceCardProps> = ({ const AIServiceCard: React.FC<AIServiceCardProps> = ({
title, title,
description, description,
tokenCost,
isWalletConnected, isWalletConnected,
onGenerate, onGenerate,
mtmPrice mtmPrice
@ -83,7 +81,7 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
</h2> </h2>
<p className="text-gray-400 mt-2">{description}</p> <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"> <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>
</div> </div>
@ -107,7 +105,7 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
transition-all duration-200 shadow-lg hover:shadow-green-500/25 transition-all duration-200 shadow-lg hover:shadow-green-500/25
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none" 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> </button>
</div> </div>

View File

@ -16,19 +16,19 @@ export const FLUX_MODELS: FluxModelConfig[] = [
modelId: "fal-ai/flux/schnell", modelId: "fal-ai/flux/schnell",
name: "Schnell", name: "Schnell",
description: "Fast meme generator", description: "Fast meme generator",
cost: 0.1 cost: 0.05
}, },
{ {
modelId: "fal-ai/recraft-v3", modelId: "fal-ai/recraft-v3",
name: "Recraft", name: "Recraft",
description: "Advanced meme generator", description: "Advanced meme generator",
cost: 0.15 cost: 0.10
}, },
{ {
modelId: "fal-ai/stable-diffusion-v35-large", modelId: "fal-ai/stable-diffusion-v35-large",
name: "Marquee", name: "Marquee",
description: "Best meme generator", 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 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; return price;
} }
@ -71,7 +73,7 @@ interface WalletAdapter {
export async function processMTMPayment( export async function processMTMPayment(
walletPublicKey: string, walletPublicKey: string,
tokenAmount: number, tokenAmount: bigint,
walletType: WalletType walletType: WalletType
): Promise<PaymentResult> { ): Promise<PaymentResult> {
try { try {
@ -143,12 +145,14 @@ export async function processMTMPayment(
) )
} }
const tokens = tokenAmount / BigInt(10 ** 6);
transaction.add( transaction.add(
createTransferInstruction( createTransferInstruction(
senderATA, senderATA,
receiverATA, receiverATA,
senderPublicKey, senderPublicKey,
BigInt(tokenAmount * (10 ** 6)) tokens * BigInt(10 ** 6)
) )
) )
@ -157,7 +161,7 @@ export async function processMTMPayment(
transaction.feePayer = senderPublicKey transaction.feePayer = senderPublicKey
console.log('Sending transaction...') console.log('Sending transaction...')
const { signature } = await wallet.signAndSendTransaction(transaction) const { signature } = await wallet.signAndSendTransaction(transaction);
console.log('Transaction sent:', signature) console.log('Transaction sent:', signature)
const confirmation = await connection.confirmTransaction({ const confirmation = await connection.confirmTransaction({

View File

@ -1,9 +1,13 @@
import assert from 'assert';
import { Connection } from '@solana/web3.js'; import { Connection } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { AppDataSource } from '../data-source'; import { AppDataSource } from '../data-source';
import { Payment } from '../entity/Payment'; 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_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
const SOLANA_WEBSOCKET_URL = process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_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( export async function verifyPayment(
transactionSignature: string, transactionSignature: string,
expectedAmount: number, expectedAmount: bigint,
): Promise<boolean> { ): Promise<boolean> {
try { try {
// Check if the signature is already used // Check if the signature is already used
@ -67,7 +71,14 @@ export async function verifyPayment(
const { info } = parsed; const { info } = parsed;
const { amount } = info; 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; return true;
} }