Use bn.js to handle big numbers

This commit is contained in:
Adw8 2025-01-28 18:34:03 +05:30
parent 8b5dbb46fa
commit 53bd18f031
7 changed files with 55 additions and 48 deletions

1
package-lock.json generated
View File

@ -12,6 +12,7 @@
"@google/generative-ai": "^0.21.0", "@google/generative-ai": "^0.21.0",
"@solana/spl-token": "^0.3.8", "@solana/spl-token": "^0.3.8",
"@solana/web3.js": "^1.78.4", "@solana/web3.js": "^1.78.4",
"bn.js": "^5.2.0",
"next": "13.5.4", "next": "13.5.4",
"openai": "^4.77.0", "openai": "^4.77.0",
"react": "^18", "react": "^18",

View File

@ -13,6 +13,7 @@
"@google/generative-ai": "^0.21.0", "@google/generative-ai": "^0.21.0",
"@solana/spl-token": "^0.3.8", "@solana/spl-token": "^0.3.8",
"@solana/web3.js": "^1.78.4", "@solana/web3.js": "^1.78.4",
"bn.js": "^5.2.0",
"next": "13.5.4", "next": "13.5.4",
"openai": "^4.77.0", "openai": "^4.77.0",
"react": "^18", "react": "^18",

View File

@ -1,4 +1,6 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import BN from 'bn.js';
import { fal } from "@fal-ai/client" 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'
@ -49,11 +51,10 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
const MTMFor1USDC = await getUSDCToMTMQuote(); const MTMFor1USDC = await getUSDCToMTMQuote();
// TODO: Use bn.js for better precision const scale = new BN(100);
const scale = BigInt(100); const scaledCost = new BN(model.cost * 100);
const scaledCost = BigInt(Math.round(model.cost * Number(scale)));
const expectedAmount = (scaledCost * MTMFor1USDC) / scale; const expectedAmount = scaledCost.mul(MTMFor1USDC).div(scale);
const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount) const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount)
if (!isPaymentVerified) { if (!isPaymentVerified) {

View File

@ -1,6 +1,8 @@
'use client' 'use client'
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import BN from 'bn.js';
import WalletHeader from '../components/WalletHeader' import WalletHeader from '../components/WalletHeader'
import AIServiceCard from '../components/AIServiceCard' import AIServiceCard from '../components/AIServiceCard'
import { generateWithFlux, FluxGenerationResult, FLUX_MODELS } from '../services/fluxService' import { generateWithFlux, FluxGenerationResult, FLUX_MODELS } from '../services/fluxService'
@ -15,7 +17,7 @@ const Page: React.FC = (): React.ReactElement => {
type: null type: null
}) })
const [MTMFor1USDC, setMTMFor1USDC] = useState<bigint>(BigInt(0)); const [MTMFor1USDC, setMTMFor1USDC] = useState<BN>(new BN(0));
useEffect(() => { useEffect(() => {
const fetchPrice = async () => { const fetchPrice = async () => {
@ -58,7 +60,7 @@ const Page: React.FC = (): React.ReactElement => {
} }
}; };
const handleFluxGeneration = (modelId: string, cost: bigint) => { const handleFluxGeneration = (modelId: string, cost: BN) => {
return async (prompt: string): Promise<FluxGenerationResult> => { return async (prompt: string): Promise<FluxGenerationResult> => {
const { connected, publicKey, type } = walletState; const { connected, publicKey, type } = walletState;
@ -111,36 +113,31 @@ const Page: React.FC = (): React.ReactElement => {
walletState={walletState} walletState={walletState}
onConnect={handleConnect} onConnect={handleConnect}
onDisconnect={handleDisconnect} onDisconnect={handleDisconnect}
/> />
</div> </div>
{/* Flux Models Grid */} {/* Flux Models Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
{FLUX_MODELS.map((model) => ( {FLUX_MODELS.map((model) => {
<AIServiceCard // Convert cost from number to BN
key={model.modelId} const costBN = new BN(model.cost * 100);
title={model.name} const mtmPriceBN = costBN.mul(MTMFor1USDC);
description={model.description}
isWalletConnected={walletState.connected}
// 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;
})()}
/>
))}
return (
<AIServiceCard
key={model.modelId}
title={model.name}
description={model.description}
isWalletConnected={walletState.connected}
onGenerate={handleFluxGeneration(
model.modelId,
// Calculate scaled cost directly in bn.js
mtmPriceBN.div(new BN(100))
)}
mtmPrice={mtmPriceBN.div(new BN(100))}
/>
);
})}
{/* Coming Soon Card */} {/* Coming Soon Card */}
<div className="relative bg-gray-800/50 backdrop-blur-lg rounded-2xl shadow-xl border border-gray-700/50 overflow-hidden group"> <div className="relative bg-gray-800/50 backdrop-blur-lg rounded-2xl shadow-xl border border-gray-700/50 overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-br from-yellow-500/10 to-orange-500/10 opacity-50"></div> <div className="absolute inset-0 bg-gradient-to-br from-yellow-500/10 to-orange-500/10 opacity-50"></div>

View File

@ -1,13 +1,14 @@
'use client' 'use client'
import React, { useState } from 'react' import React, { useState } from 'react'
import BN from 'bn.js';
interface AIServiceCardProps { interface AIServiceCardProps {
title: string title: string
description: string description: string
isWalletConnected: boolean isWalletConnected: boolean
onGenerate: (prompt: string) => Promise<{ imageUrl?: string, error?: string }> onGenerate: (prompt: string) => Promise<{ imageUrl?: string, error?: string }>
mtmPrice: bigint mtmPrice: BN
} }
interface GenerationState { interface GenerationState {
@ -81,7 +82,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 / BigInt(10 ** 6)).toString() : '...'} MTM Cost: {mtmPrice ? mtmPrice.div(new BN(10).pow(new BN(6))).toString() : '...'} MTM
</div> </div>
</div> </div>
@ -105,7 +106,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 / BigInt(10 ** 6)).toString() : '...'} MTM & Generate`} {generationState.loading ? 'Processing...' : `Pay ${mtmPrice ? mtmPrice.div(new BN(10).pow(new BN(6))).toString() : '...'} MTM & Generate`}
</button> </button>
</div> </div>

View File

@ -1,4 +1,5 @@
import assert from 'assert'; import assert from 'assert';
import BN from 'bn.js';
import { Connection, PublicKey, Transaction } from '@solana/web3.js' import { Connection, PublicKey, Transaction } from '@solana/web3.js'
import { import {
@ -50,7 +51,7 @@ async function findAssociatedTokenAddress(
)[0] )[0]
} }
export async function getUSDCToMTMQuote() { export async function getUSDCToMTMQuote(): Promise<BN> {
const url = `https://api.jup.ag/price/v2?ids=${USDC_MINT}&vsToken=${MTM_TOKEN_MINT}`; const url = `https://api.jup.ag/price/v2?ids=${USDC_MINT}&vsToken=${MTM_TOKEN_MINT}`;
const response = await fetch(url); const response = await fetch(url);
@ -61,9 +62,8 @@ export async function getUSDCToMTMQuote() {
const quoteResponse = await response.json(); const quoteResponse = await response.json();
// TODO: Use bn.js for better precision const priceFromAPI = Number(quoteResponse['data'][USDC_MINT]['price']).toFixed(6);
const priceFromAPI = Number(quoteResponse['data'][USDC_MINT]['price'])* (10 ** 6); const price = new BN(priceFromAPI.toString().replace('.', ''));
const price = BigInt(Number(priceFromAPI.toFixed(0)));
return price; return price;
} }
@ -73,7 +73,7 @@ interface WalletAdapter {
export async function processMTMPayment( export async function processMTMPayment(
walletPublicKey: string, walletPublicKey: string,
tokenAmount: bigint, tokenAmount: BN,
walletType: WalletType walletType: WalletType
): Promise<PaymentResult> { ): Promise<PaymentResult> {
try { try {
@ -145,14 +145,15 @@ export async function processMTMPayment(
) )
} }
const tokens = tokenAmount / BigInt(10 ** 6); const tokens = tokenAmount.div(new BN(10).pow(new BN(6)));
console.log('TA:', tokenAmount.toString())
transaction.add( transaction.add(
createTransferInstruction( createTransferInstruction(
senderATA, senderATA,
receiverATA, receiverATA,
senderPublicKey, senderPublicKey,
tokens * BigInt(10 ** 6) tokens.mul(new BN(10).pow(new BN(6)))
) )
) )

View File

@ -1,4 +1,5 @@
import assert from 'assert'; import assert from 'assert';
import BN from 'bn.js';
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';
@ -46,7 +47,7 @@ export async function markSignatureAsUsed(transactionSignature: string): Promise
export async function verifyPayment( export async function verifyPayment(
transactionSignature: string, transactionSignature: string,
expectedAmount: bigint, expectedAmount: BN,
): Promise<boolean> { ): Promise<boolean> {
try { try {
// Check if the signature is already used // Check if the signature is already used
@ -71,17 +72,21 @@ export async function verifyPayment(
const { info } = parsed; const { info } = parsed;
const { amount } = info; const { amount } = info;
const transactionAmount = BigInt(amount); console.log('Parsed transfer instruction:', parsed);
const expectedAmountInMicroTokens = expectedAmount; console.log('Transfer info:', info);
console.log('Transfer amount:', amount);
const lowerBound = expectedAmountInMicroTokens * BigInt(9) / BigInt(10); const transactionAmount = new BN(amount);
const upperBound = expectedAmountInMicroTokens * BigInt(11) / BigInt(10); 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 // transaction within the appropriate range
if (transactionAmount >= lowerBound && transactionAmount <= upperBound) { if (transactionAmount.gte(lowerBound) && transactionAmount.lte(upperBound)) {
console.log('Transaction amount is within the expected range.');
return true; return true;
} }
console.log('Transaction amount is not within the expected range.');
// TODO: Handle difference between paid amount and token price during verification being greater than 10%
return false; return false;
} catch (error) { } catch (error) {
console.error('Verification error:', error); console.error('Verification error:', error);