From e2b8e6c58414571545b56a63197ba03fd47c995d Mon Sep 17 00:00:00 2001 From: Adw8 Date: Mon, 27 Jan 2025 16:07:07 +0530 Subject: [PATCH 1/3] Verify payment on chain before generating image --- src/app/api/flux/route.ts | 21 ++++++++++++++--- src/app/page.tsx | 4 +++- src/services/fluxService.ts | 8 +++++-- src/services/paymentService.ts | 41 +++++++++++++++++++++++++++++++--- 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/app/api/flux/route.ts b/src/app/api/flux/route.ts index 24a0469..f0b1e67 100644 --- a/src/app/api/flux/route.ts +++ b/src/app/api/flux/route.ts @@ -1,6 +1,8 @@ import { NextRequest, NextResponse } from 'next/server' import { fal } from "@fal-ai/client" +import { verifyPayment } from '../../../services/paymentService' + if (!process.env.FAL_AI_KEY) { throw new Error('FAL_AI_KEY is not configured in environment variables') } @@ -16,15 +18,28 @@ const IMAGE_HEIGHT: number = 1024 export async function POST(req: NextRequest): Promise { try { - const { prompt, modelId } = await req.json() + const { prompt, modelId, transactionSignature, expectedAmount } = await req.json() if (!prompt || !modelId) { return NextResponse.json( - { error: 'Prompt and modelId are required' }, - { status: 400 } + { error: 'Prompt and modelId are required' }, + { status: 400 } ) } + if (!transactionSignature || !expectedAmount) { + return NextResponse.json( + { error: 'Transaction signature and expected amount are required' }, + { status: 400 } + ) + } + + const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount); + console.log({isPaymentVerified}); + if (!isPaymentVerified) { + throw new Error('Payment verification failed'); + } + console.log('Generating with Flux model:', modelId) console.log('Prompt:', prompt) diff --git a/src/app/page.tsx b/src/app/page.tsx index bbcfb5f..75c527f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -49,8 +49,10 @@ const Page: React.FC = (): React.ReactElement => { return { error: paymentResult.error } } + const transactionSignature = paymentResult.signature; + // Then generate image with specified model - return generateWithFlux(prompt, modelId) + return generateWithFlux(prompt, modelId, transactionSignature, cost) } } diff --git a/src/services/fluxService.ts b/src/services/fluxService.ts index c0af3f9..d20dfdf 100644 --- a/src/services/fluxService.ts +++ b/src/services/fluxService.ts @@ -34,7 +34,9 @@ export const FLUX_MODELS: FluxModelConfig[] = [ export async function generateWithFlux( prompt: string, - modelId: string + modelId: string, + transactionSignature: string, + expectedAmount: number, ): Promise { try { const response = await fetch('/api/flux', { @@ -44,7 +46,9 @@ export async function generateWithFlux( }, body: JSON.stringify({ prompt, - modelId + modelId, + transactionSignature, + expectedAmount }), }) diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts index 5ecb302..9cd0f17 100644 --- a/src/services/paymentService.ts +++ b/src/services/paymentService.ts @@ -1,4 +1,4 @@ -import { Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js' +import { Connection, PublicKey, Transaction } from '@solana/web3.js' import { TOKEN_PROGRAM_ID, createTransferInstruction, @@ -48,7 +48,7 @@ export async function processMTMPayment( walletPublicKey: string, tokenAmount: number, walletType: WalletType -): Promise { +): Promise { try { let wallet: WalletAdapter | null = null; @@ -146,7 +146,7 @@ export async function processMTMPayment( throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) } - return { success: true } + return { success: true, signature }; } catch (error) { console.error('Payment error:', error) return { @@ -155,3 +155,38 @@ export async function processMTMPayment( } } } + +export async function verifyPayment( + transactionSignature: string, + expectedAmount: number, +): Promise { + try { + console.log('Verifying payment:'); + + const transaction = await connection.getParsedTransaction(transactionSignature, 'confirmed'); + if (!transaction) { + throw new Error('Transaction not found'); + } + + const transferInstruction = transaction.transaction.message.instructions.find( + (instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID) + ); + + if (!transferInstruction || !('parsed' in transferInstruction)) { + throw new Error('Transfer instruction not found'); + } + + const { parsed } = transferInstruction; + const { info } = parsed; + const { amount } = info; + + if (BigInt(amount) === BigInt(expectedAmount * (10 ** 6))) { + return true; + } + + return false; + } catch (error) { + console.error('Verification error:', error); + return false; + } +} -- 2.45.2 From 86b168af4de48375763fa8d7d804e7d2d44d1117 Mon Sep 17 00:00:00 2001 From: Adw8 Date: Mon, 27 Jan 2025 16:53:57 +0530 Subject: [PATCH 2/3] Move util to separate file --- src/app/api/flux/route.ts | 22 +++++++++++----- src/app/page.tsx | 4 +-- src/services/fluxService.ts | 2 -- src/services/paymentService.ts | 40 +++-------------------------- src/utils/verifyPayment.ts | 47 ++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 47 deletions(-) create mode 100644 src/utils/verifyPayment.ts diff --git a/src/app/api/flux/route.ts b/src/app/api/flux/route.ts index f0b1e67..8749c4f 100644 --- a/src/app/api/flux/route.ts +++ b/src/app/api/flux/route.ts @@ -1,7 +1,8 @@ import { NextRequest, NextResponse } from 'next/server' import { fal } from "@fal-ai/client" +import { FLUX_MODELS } from '../../../services/fluxService' -import { verifyPayment } from '../../../services/paymentService' +import { verifyPayment } from '../../../utils/verifyPayment' if (!process.env.FAL_AI_KEY) { throw new Error('FAL_AI_KEY is not configured in environment variables') @@ -18,7 +19,7 @@ const IMAGE_HEIGHT: number = 1024 export async function POST(req: NextRequest): Promise { try { - const { prompt, modelId, transactionSignature, expectedAmount } = await req.json() + const { prompt, modelId, transactionSignature } = await req.json() if (!prompt || !modelId) { return NextResponse.json( @@ -27,15 +28,24 @@ export async function POST(req: NextRequest): Promise { ) } - if (!transactionSignature || !expectedAmount) { + if (!transactionSignature) { return NextResponse.json( - { error: 'Transaction signature and expected amount are required' }, + { error: 'Transaction signature is required' }, { status: 400 } ) } - const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount); - console.log({isPaymentVerified}); + const model = FLUX_MODELS.find((model) => model.modelId === modelId) + if (!model) { + return NextResponse.json( + { error: 'Invalid modelId' }, + { status: 400 } + ) + } + + const expectedAmount = model.cost; + const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount) + if (!isPaymentVerified) { throw new Error('Payment verification failed'); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 75c527f..287c7a2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -49,10 +49,10 @@ const Page: React.FC = (): React.ReactElement => { return { error: paymentResult.error } } - const transactionSignature = paymentResult.signature; + const transactionSignature = paymentResult.transactionSignature; // Then generate image with specified model - return generateWithFlux(prompt, modelId, transactionSignature, cost) + return generateWithFlux(prompt, modelId, transactionSignature) } } diff --git a/src/services/fluxService.ts b/src/services/fluxService.ts index d20dfdf..7f22c2e 100644 --- a/src/services/fluxService.ts +++ b/src/services/fluxService.ts @@ -36,7 +36,6 @@ export async function generateWithFlux( prompt: string, modelId: string, transactionSignature: string, - expectedAmount: number, ): Promise { try { const response = await fetch('/api/flux', { @@ -48,7 +47,6 @@ export async function generateWithFlux( prompt, modelId, transactionSignature, - expectedAmount }), }) diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts index 9cd0f17..fd518e0 100644 --- a/src/services/paymentService.ts +++ b/src/services/paymentService.ts @@ -23,6 +23,7 @@ const connection = new Connection( export interface PaymentResult { success: boolean + transactionSignature?: string, error?: string } @@ -48,7 +49,7 @@ export async function processMTMPayment( walletPublicKey: string, tokenAmount: number, walletType: WalletType -): Promise { +): Promise { try { let wallet: WalletAdapter | null = null; @@ -146,7 +147,7 @@ export async function processMTMPayment( throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) } - return { success: true, signature }; + return { success: true, transactionSignature: signature }; } catch (error) { console.error('Payment error:', error) return { @@ -155,38 +156,3 @@ export async function processMTMPayment( } } } - -export async function verifyPayment( - transactionSignature: string, - expectedAmount: number, -): Promise { - try { - console.log('Verifying payment:'); - - const transaction = await connection.getParsedTransaction(transactionSignature, 'confirmed'); - if (!transaction) { - throw new Error('Transaction not found'); - } - - const transferInstruction = transaction.transaction.message.instructions.find( - (instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID) - ); - - if (!transferInstruction || !('parsed' in transferInstruction)) { - throw new Error('Transfer instruction not found'); - } - - const { parsed } = transferInstruction; - const { info } = parsed; - const { amount } = info; - - if (BigInt(amount) === BigInt(expectedAmount * (10 ** 6))) { - return true; - } - - return false; - } catch (error) { - console.error('Verification error:', error); - return false; - } -} diff --git a/src/utils/verifyPayment.ts b/src/utils/verifyPayment.ts new file mode 100644 index 0000000..40cd0c9 --- /dev/null +++ b/src/utils/verifyPayment.ts @@ -0,0 +1,47 @@ +import { Connection } from '@solana/web3.js'; +import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; + +const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL || 'https://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'; +const SOLANA_WEBSOCKET_URL = process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL || 'wss://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'; + +const connection = new Connection( + SOLANA_RPC_URL, + { + commitment: 'confirmed', + wsEndpoint: SOLANA_WEBSOCKET_URL, + confirmTransactionInitialTimeout: 60000, + } +); + +export async function verifyPayment( + transactionSignature: string, + expectedAmount: number, +): Promise { + try { + const transaction = await connection.getParsedTransaction(transactionSignature, 'finalized'); + if (!transaction) { + throw new Error('Transaction not found'); + } + + const transferInstruction = transaction.transaction.message.instructions.find( + (instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID) + ); + + if (!transferInstruction || !('parsed' in transferInstruction)) { + throw new Error('Transfer instruction not found'); + } + + const { parsed } = transferInstruction; + const { info } = parsed; + const { amount } = info; + + if (BigInt(amount) === BigInt(expectedAmount * (10 ** 6))) { + return true; + } + + return false; + } catch (error) { + console.error('Verification error:', error); + return false; + } +} -- 2.45.2 From b8013b6f23b66edc45a7d54e092076aaf13e15a7 Mon Sep 17 00:00:00 2001 From: Adw8 Date: Mon, 27 Jan 2025 17:07:54 +0530 Subject: [PATCH 3/3] Remove defaults for env vars --- src/app/api/flux/route.ts | 8 ++++---- src/services/paymentService.ts | 2 +- src/utils/verifyPayment.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/api/flux/route.ts b/src/app/api/flux/route.ts index 8749c4f..b6047d3 100644 --- a/src/app/api/flux/route.ts +++ b/src/app/api/flux/route.ts @@ -23,15 +23,15 @@ export async function POST(req: NextRequest): Promise { if (!prompt || !modelId) { return NextResponse.json( - { error: 'Prompt and modelId are required' }, - { status: 400 } + { error: 'Prompt and modelId are required' }, + { status: 400 } ) } if (!transactionSignature) { return NextResponse.json( - { error: 'Transaction signature is required' }, - { status: 400 } + { error: 'Transaction signature is required' }, + { status: 400 } ) } diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts index fd518e0..a966b7a 100644 --- a/src/services/paymentService.ts +++ b/src/services/paymentService.ts @@ -23,7 +23,7 @@ const connection = new Connection( export interface PaymentResult { success: boolean - transactionSignature?: string, + transactionSignature?: string error?: string } diff --git a/src/utils/verifyPayment.ts b/src/utils/verifyPayment.ts index 40cd0c9..2f5282b 100644 --- a/src/utils/verifyPayment.ts +++ b/src/utils/verifyPayment.ts @@ -1,8 +1,8 @@ import { Connection } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL || 'https://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'; -const SOLANA_WEBSOCKET_URL = process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL || 'wss://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'; +const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL; +const SOLANA_WEBSOCKET_URL = process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL; const connection = new Connection( SOLANA_RPC_URL, -- 2.45.2