Add replay protection to meme generation API #5

Merged
nabarun merged 6 commits from ag-replay-protection into main 2025-01-27 14:02:13 +00:00
8 changed files with 1871 additions and 82 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ node_modules
.env.development
.env.production
.env.test
database.sqlite

1864
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,9 @@
"next": "13.5.4",
"openai": "^4.77.0",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"sqlite3": "^5.1.7",
"typeorm": "^0.3.12"
},
"devDependencies": {
"@types/node": "^20",

View File

@ -1,8 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'
import { fal } from "@fal-ai/client"
import { FLUX_MODELS } from '../../../services/fluxService'
import { initializeDataSource } from '../../../data-source'
import { verifyPayment } from '../../../utils/verifyPayment'
import { verifyPayment, isSignatureUsed, markSignatureAsUsed } from '../../../utils/verifyPayment'
if (!process.env.FAL_AI_KEY) {
throw new Error('FAL_AI_KEY is not configured in environment variables')
@ -19,6 +20,8 @@ const IMAGE_HEIGHT: number = 1024
export async function POST(req: NextRequest): Promise<NextResponse> {
try {
await initializeDataSource();
const { prompt, modelId, transactionSignature } = await req.json()
if (!prompt || !modelId) {
@ -47,9 +50,14 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount)
if (!isPaymentVerified) {
throw new Error('Payment verification failed');
return NextResponse.json(
{ error: 'Payment verification failed or transaction signature has already been used' },
{ status: 400 }
)
}
await markSignatureAsUsed(transactionSignature);
console.log('Generating with Flux model:', modelId)
console.log('Prompt:', prompt)

25
src/data-source.ts Normal file
View File

@ -0,0 +1,25 @@
import { DataSource } from 'typeorm';
import { Payment } from './entity/Payment';
export const AppDataSource = new DataSource({
type: 'sqlite',
database: './database.sqlite',
synchronize: true,
logging: false,
entities: [Payment],
migrations: [],
subscribers: [],
});
export async function initializeDataSource() {
try {
if (!AppDataSource.isInitialized){
await AppDataSource.initialize();
console.log('Data Source has been initialized!');
};
} catch (err) {
console.error('Error during Data Source initialization:', err);
throw err;
}
}

10
src/entity/Payment.ts Normal file
View File

@ -0,0 +1,10 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Payment {
@PrimaryGeneratedColumn()
id!: number;
@Column({ unique: true })
transactionSignature!: string;
}

View File

@ -1,6 +1,9 @@
import { Connection } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { AppDataSource } from '../data-source';
import { Payment } from '../entity/Payment';
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
const SOLANA_WEBSOCKET_URL = process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL;
@ -13,12 +16,41 @@ const connection = new Connection(
}
);
export async function isSignatureUsed(transactionSignature: string): Promise<boolean> {
const paymentRepository = AppDataSource.getRepository(Payment);
const payment = await paymentRepository.findOneBy({ transactionSignature });
if (payment) {
return true;
}
return false;
}
export async function markSignatureAsUsed(transactionSignature: string): Promise<void> {
await AppDataSource.transaction(async (transactionalEntityManager) => {
const paymentRepository = transactionalEntityManager.getRepository(Payment);
// Check if the payment with the given signature already exists
const exists = await paymentRepository.exists({ where: { transactionSignature } });
// If not, create a new payment entry
if (!exists) {
const newPayment = paymentRepository.create({ transactionSignature });
await paymentRepository.save(newPayment);
}
});
}
export async function verifyPayment(
transactionSignature: string,
expectedAmount: number,
): Promise<boolean> {
try {
const transaction = await connection.getParsedTransaction(transactionSignature, 'finalized');
// Check if the signature is already used
if (await isSignatureUsed(transactionSignature)) {
return false;
}
const transaction = await connection.getParsedTransaction(transactionSignature, 'confirmed');
if (!transaction) {
throw new Error('Transaction not found');
}

View File

@ -16,6 +16,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"plugins": [
{
"name": "next"