forked from mito-systems/sol-mem-gen
Add replay protection to meme generation API #5
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,3 +6,5 @@ node_modules
|
|||||||
.env.development
|
.env.development
|
||||||
.env.production
|
.env.production
|
||||||
.env.test
|
.env.test
|
||||||
|
|
||||||
|
database.sqlite
|
||||||
|
1864
package-lock.json
generated
1864
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,9 @@
|
|||||||
"next": "13.5.4",
|
"next": "13.5.4",
|
||||||
"openai": "^4.77.0",
|
"openai": "^4.77.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"typeorm": "^0.3.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
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 { verifyPayment } from '../../../utils/verifyPayment'
|
import { verifyPayment, isSignatureUsed, markSignatureAsUsed } from '../../../utils/verifyPayment'
|
||||||
|
|
||||||
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')
|
||||||
@ -19,6 +20,8 @@ const IMAGE_HEIGHT: number = 1024
|
|||||||
|
|
||||||
export async function POST(req: NextRequest): Promise<NextResponse> {
|
export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||||
try {
|
try {
|
||||||
|
await initializeDataSource();
|
||||||
|
|
||||||
const { prompt, modelId, transactionSignature } = await req.json()
|
const { prompt, modelId, transactionSignature } = await req.json()
|
||||||
|
|
||||||
if (!prompt || !modelId) {
|
if (!prompt || !modelId) {
|
||||||
@ -47,9 +50,14 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|||||||
const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount)
|
const isPaymentVerified = await verifyPayment(transactionSignature, expectedAmount)
|
||||||
|
|
||||||
if (!isPaymentVerified) {
|
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('Generating with Flux model:', modelId)
|
||||||
console.log('Prompt:', prompt)
|
console.log('Prompt:', prompt)
|
||||||
|
|
||||||
|
25
src/data-source.ts
Normal file
25
src/data-source.ts
Normal 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
10
src/entity/Payment.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Payment {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id!: number;
|
||||||
|
|
||||||
|
@Column({ unique: true })
|
||||||
|
transactionSignature!: string;
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
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 { Payment } from '../entity/Payment';
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@ -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(
|
export async function verifyPayment(
|
||||||
transactionSignature: string,
|
transactionSignature: string,
|
||||||
expectedAmount: number,
|
expectedAmount: number,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
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) {
|
if (!transaction) {
|
||||||
throw new Error('Transaction not found');
|
throw new Error('Transaction not found');
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "next"
|
"name": "next"
|
||||||
|
Loading…
Reference in New Issue
Block a user