pummmp.fun/app/api/coins/route.ts
2026-07-04 12:49:09 -07:00

186 lines
6 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { connectToDatabase } from '@/lib/mongodb'
import { ObjectId } from 'mongodb'
import { coinSchema } from '@/lib/validations'
import { checkRateLimit } from '@/lib/rate-limit'
export async function GET() {
try {
const { db } = await connectToDatabase()
const coins = await db
.collection('coins')
.find({})
.sort({ createdAt: -1 })
.toArray()
return NextResponse.json(coins)
} catch (error) {
console.error('Error fetching coins:', error)
return NextResponse.json({ error: 'Failed to fetch coins' }, { status: 500 })
}
}
export async function POST(request: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { db } = await connectToDatabase()
const body = await request.json()
const validationResult = coinSchema.safeParse(body)
if (!validationResult.success) {
const errorMessage = validationResult.error.errors.map(e => e.message).join(', ')
return NextResponse.json({ error: errorMessage }, { status: 400 })
}
const { name, ticker, description, website, twitter, telegram, initialBuy, useUserImage, verified, boosted } = validationResult.data
const cleanName = name
const cleanTicker = ticker
const cleanDescription = description
if (!cleanName || !cleanTicker) {
return NextResponse.json({ error: 'Name and ticker are required' }, { status: 400 })
}
const user = await db.collection('users').findOne({ _id: new ObjectId(session.user.id) })
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
//! verify via DB for security cuz client can be manipulated
let isVerified = false
if (verified && user.verified) {
isVerified = true
}
//? costs
const creationCost = 0.02;
const boostCost = boosted ? 1.0 : 0;
const initialBuyAmount = typeof initialBuy === 'number' && initialBuy > 0 ? initialBuy : 0;
const totalCost = creationCost + boostCost + initialBuyAmount;
if ((user.balance || 0) < totalCost) {
return NextResponse.json({ error: `Insufficient balance (${totalCost.toFixed(3)} SOL required)` }, { status: 400 })
}
const existingCoin = await db.collection('coins').findOne({ ticker: cleanTicker })
if (existingCoin) {
return NextResponse.json({ error: 'Ticker already exists' }, { status: 400 })
}
if (checkRateLimit(session.user.id, 'coin_creation', 1, 60000 * 5)) {
return NextResponse.json({ error: 'Rate limit exceeded. You can only create 1 coin every 5 minutes.' }, { status: 429 })
}
let virtualSolReserves = 30;
const INITIAL_SUPPLY = 1000000000;
let virtualTokenReserves = 1073000000;
let initialLiquidity = 0;
let initialPrice = virtualSolReserves / virtualTokenReserves;
let initialVolume = 0;
let boughtTokens = 0;
// ! process IB (Snipe)
if (initialBuyAmount > 0) {
// chat wahat does calc stand for
// i hate my life
const k = virtualSolReserves * virtualTokenReserves;
const newVirtualSolReserves = virtualSolReserves + initialBuyAmount;
const newVirtualTokenReserves = k / newVirtualSolReserves;
boughtTokens = virtualTokenReserves - newVirtualTokenReserves;
// ? update states
virtualSolReserves = newVirtualSolReserves;
virtualTokenReserves = newVirtualTokenReserves;
initialPrice = virtualSolReserves / virtualTokenReserves;
initialLiquidity = initialBuyAmount; // ! real liquidity
initialVolume = initialBuyAmount;
}
const now = new Date()
const priceHistory = [{
timestamp: now,
price: initialPrice,
volume: initialVolume,
}]
const coinImage = (useUserImage && session.user.image)
? session.user.image
: `https://api.dicebear.com/7.x/identicon/svg?seed=${cleanTicker}`;
const coin = {
name: cleanName,
ticker: cleanTicker,
description: cleanDescription,
image: coinImage,
website: website || '',
twitter: twitter || '',
telegram: telegram || '',
creatorId: session.user.id,
creatorName: user.name || 'Anonymous', // Use DB name, not session name
createdAt: now,
verified: isVerified,
boosted: boosted ? new Date(Date.now() + 24 * 60 * 60 * 1000) : null,
marketCap: initialPrice * INITIAL_SUPPLY,
price: initialPrice,
priceHistory,
volume24h: initialVolume,
holders: boughtTokens > 0 ? 1 : 0,
supply: INITIAL_SUPPLY,
liquidity: initialLiquidity,
virtualSolReserves,
virtualTokenReserves,
}
const result = await db.collection('coins').insertOne(coin)
// deduct total cost
await db.collection('users').updateOne(
{ _id: new ObjectId(session.user.id) },
{ $inc: { balance: -totalCost } }
)
// give tokens if bought
if (boughtTokens > 0) {
await db.collection('users').updateOne(
{ _id: new ObjectId(session.user.id) },
{
$push: {
portfolio: {
coinId: result.insertedId.toString(),
amount: boughtTokens,
avgBuyPrice: initialBuyAmount / boughtTokens,
},
} as any,
}
)
//? record trade
await db.collection('trades').insertOne({
userId: session.user.id,
coinId: result.insertedId.toString(),
type: 'buy',
amount: boughtTokens,
price: initialPrice,
total: initialBuyAmount,
timestamp: now,
})
}
return NextResponse.json({ ...coin, _id: result.insertedId })
} catch (error) {
console.error('Error creating coin:', error)
return NextResponse.json({ error: 'Failed to create coin' }, { status: 500 })
}
}