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 { checkRateLimit } from '@/lib/rate-limit' const DISCORD_WEBHOOK_URL = ''; const TRADE_ROLE_ID = '1465848690785652900'; async function sendDiscordPriceAlert(coin: any, trade: any, priceChangePercent: number, oldPrice: number, newPrice: number) { try { const embed = { title: `${coin.name} Price Alert :bangbang:`, description: `**[$${coin.ticker}](https://pummmp.fun/coin/${coin._id})** price ${priceChangePercent > 0 ? 'increased' : 'decreased'} by **${Math.abs(priceChangePercent).toFixed(2)}%** ${priceChangePercent > 0 ? '↗' : '↘'}`, color: priceChangePercent > 0 ? 0x00ff00 : 0xff0000, fields: [ { name: 'New Price <:price:1465842044986724425>', value: `\`$${newPrice.toFixed(9)}\``, inline: true }, { name: 'Market Cap ', value: `\`${(coin.marketCap * (newPrice / coin.price)).toLocaleString()} SOL\``, inline: true }, { name: 'Trader <:trade:1465843050604396738>', value: trade.userVerified ? `${trade.username} <:verified:1465841044275859722>` : trade.username, inline: false }, { name: `Action ${trade.type === 'buy' ? '<:greencandle:1465842982556139590>' : '<:redcandle:1465843012222324767>'}`, value: `${trade.type === 'buy' ? 'Bought' : 'Sold'} **${trade.amount.toLocaleString()}** $${coin.ticker}`, inline: true }, { name: 'Value', value: `> **${trade.total.toFixed(4)} SOL**`, inline: false }, { name: 'Change', value: `> \`$${oldPrice.toFixed(9)}\` → \`$${newPrice.toFixed(9)}\``, inline: true } ], timestamp: new Date().toISOString(), footer: { text: 'pummmp.fun', icon_url: 'https://pummmp.fun/logo.png' } } await fetch(DISCORD_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ content: `<@&${TRADE_ROLE_ID}>`, embeds: [embed] }) }) } catch (error) { console.error('[/api/trade] Failed to send Discord webhook:', error) } } export async function POST(request: NextRequest) { try { const session = await getServerSession(authOptions) if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } if (checkRateLimit(session.user.id, 'trade', 20, 60000)) { return NextResponse.json({ error: 'Rate limit exceeded. Please slow down.' }, { status: 429 }) } const { db } = await connectToDatabase() const body = await request.json() const { coinId, type, amount } = body if (!coinId || !type || !amount || amount <= 0) { return NextResponse.json({ error: 'Invalid trade parameters' }, { status: 400 }) } const coin = await db.collection('coins').findOne({ _id: new ObjectId(coinId) }) if (!coin) { return NextResponse.json({ error: 'Coin not found' }, { status: 404 }) } const user = await db.collection('users').findOne({ _id: new ObjectId(session.user.id) }) if (!user) { return NextResponse.json({ error: 'User not found' }, { status: 404 }) } // Check trade ban if (user.tradeBannedUntil) { const banExpiry = new Date(user.tradeBannedUntil); const nowCheck = new Date(); if (banExpiry > nowCheck) { const isPermanent = banExpiry.getFullYear && banExpiry.getFullYear() === 9999; return NextResponse.json({ error: `You are banned from trading ${isPermanent ? 'permanently' : 'for a while'}` }, { status: 403 }) } else { try { await db.collection('users').updateOne({ _id: user._id }, { $unset: { tradeBannedUntil: 1 } }) } catch (e) {} } } const vSol = coin.virtualSolReserves || 30; const vTokens = coin.virtualTokenReserves || 1073000000; const k = vSol * vTokens; let solAmount = 0; let newPrice = coin.price; let newVSol = vSol; let newVTokens = vTokens; if (type === 'buy') { if (amount >= vTokens - 1) { return NextResponse.json({ error: 'Cannot buy entire supply' }, { status: 400 }) } newVTokens = vTokens - amount; newVSol = k / newVTokens; solAmount = newVSol - vSol; if (user.balance < solAmount) { return NextResponse.json({ error: 'Insufficient balance' }, { status: 400 }) } newPrice = newVSol / newVTokens; const existingPosition = user.portfolio?.find((p: any) => p.coinId === coinId) if (existingPosition) { const newAmount = existingPosition.amount + amount const newAvgPrice = (existingPosition.amount * existingPosition.avgBuyPrice + solAmount) / newAmount await db.collection('users').updateOne( { _id: new ObjectId(session.user.id), 'portfolio.coinId': coinId }, { $inc: { balance: -solAmount }, $set: { 'portfolio.$.amount': newAmount, 'portfolio.$.avgBuyPrice': newAvgPrice, }, } ) } else { await db.collection('users').updateOne( { _id: new ObjectId(session.user.id) }, { $inc: { balance: -solAmount }, $push: { portfolio: { coinId, amount, avgBuyPrice: solAmount / amount, }, } as any, } ) } // check for graduation (if virtualSolReserves > 85) // real pump.fun is ~85 SOL. const GRADUATION_THRESHOLD = 85; const shouldGraduate = !coin.graduated && newVSol >= GRADUATION_THRESHOLD; // if graduating, we "lock" a portion of liquidity or just mark it // we also notify via socket (handled by client listening to trade) const updateData: any = { $set: { price: newPrice, marketCap: newPrice * coin.supply, virtualSolReserves: newVSol, virtualTokenReserves: newVTokens, }, $inc: { volume24h: solAmount, // volume in SOL holders: existingPosition ? 0 : 1, liquidity: solAmount, // increase real liquidity }, $push: { priceHistory: { timestamp: new Date(), price: newPrice, volume: solAmount, }, } as any, } if (shouldGraduate) { updateData.$set.graduated = true; updateData.$set.graduatedAt = new Date(); } await db.collection('coins').updateOne( { _id: new ObjectId(coinId) }, updateData ) } else if (type === 'sell') { const position = user.portfolio?.find((p: any) => p.coinId === coinId) if (!position || position.amount < amount) { return NextResponse.json({ error: 'Insufficient tokens' }, { status: 400 }) } newVTokens = vTokens + amount; newVSol = k / newVTokens; solAmount = vSol - newVSol; // ? this is what user receives newPrice = newVSol / newVTokens; const newAmount = position.amount - amount if (newAmount === 0) { await db.collection('users').updateOne( { _id: new ObjectId(session.user.id) }, { $inc: { balance: solAmount }, $pull: { portfolio: { coinId } } as any, } ) await db.collection('coins').updateOne( { _id: new ObjectId(coinId) }, { $inc: { holders: -1 } } ) } else { await db.collection('users').updateOne( { _id: new ObjectId(session.user.id), 'portfolio.coinId': coinId }, { $inc: { balance: solAmount }, $set: { 'portfolio.$.amount': newAmount }, } ) } await db.collection('coins').updateOne( { _id: new ObjectId(coinId) }, { $set: { price: newPrice, marketCap: newPrice * coin.supply, virtualSolReserves: newVSol, virtualTokenReserves: newVTokens, }, $inc: { volume24h: solAmount, liquidity: -solAmount, // decrease real liquidity }, $push: { priceHistory: { timestamp: new Date(), price: newPrice, volume: solAmount, }, } as any, } ) } const updatedCoin = await db.collection('coins').findOne({ _id: new ObjectId(coinId) }); if (updatedCoin && !updatedCoin.graduated) { const GRADUATION_THRESHOLD = 45; const currentVirtualSol = updatedCoin.virtualSolReserves || 0; const shouldGraduate = currentVirtualSol >= GRADUATION_THRESHOLD; if (shouldGraduate) { await db.collection('coins').updateOne( { _id: new ObjectId(coinId) }, { $set: { graduated: true, graduatedAt: new Date(), } } ); console.log(`Coin ${coinId} graduated with ${currentVirtualSol} virtual SOL reserves`); } } // record trade const trade = { userId: session.user.id, username: user.name || 'Anonymous', userImage: user.image || '', userVerified: !!user.verified, userIsAdmin: !!user.isAdmin, userIsBetaTester: !!user.isBetaTester, userIsBugHunter: !!user.isBugHunter, coinId, type, amount, // ? token amount price: newPrice, total: solAmount, // ? SOL value timestamp: new Date(), } const result = await db.collection('trades').insertOne(trade) const savedTrade = { ...trade, _id: result.insertedId } const oldPrice = coin.price const priceChangePercent = ((newPrice - oldPrice) / oldPrice) * 100 // Send alert for price changes > 25% if (Math.abs(priceChangePercent) >= 25) { await sendDiscordPriceAlert(coin, savedTrade, priceChangePercent, oldPrice, newPrice) } return NextResponse.json({ success: true, trade: savedTrade }) } catch (error) { console.error('Error executing trade:', error) return NextResponse.json({ error: 'Failed to execute trade' }, { status: 500 }) } }