diff --git a/app/api/trading/execute-dex/route.js b/app/api/trading/execute-dex/route.js
index 4123b91..d1bc998 100644
--- a/app/api/trading/execute-dex/route.js
+++ b/app/api/trading/execute-dex/route.js
@@ -11,7 +11,10 @@ export async function POST(request) {
takeProfit,
useRealDEX = false,
tradingPair,
- quickSwap = false
+ quickSwap = false,
+ closePosition = false,
+ fromCoin,
+ toCoin
} = body
console.log('🔄 Execute DEX trade request:', {
@@ -56,6 +59,47 @@ export async function POST(request) {
)
}
+ // Validate balance before proceeding (skip for position closing)
+ if (!closePosition) {
+ console.log('🔍 Validating wallet balance before DEX trade...')
+
+ try {
+ const validationResponse = await fetch('http://localhost:3000/api/trading/validate', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ symbol,
+ side,
+ amount,
+ tradingMode: 'SPOT',
+ fromCoin,
+ toCoin
+ })
+ })
+
+ const validationResult = await validationResponse.json()
+
+ if (!validationResult.success) {
+ console.log('❌ DEX balance validation failed:', validationResult.message)
+ return NextResponse.json({
+ success: false,
+ error: validationResult.error,
+ message: validationResult.message,
+ validation: validationResult
+ }, { status: validationResponse.status })
+ }
+
+ console.log('✅ DEX balance validation passed')
+ } catch (validationError) {
+ console.error('❌ DEX balance validation error:', validationError)
+ return NextResponse.json({
+ success: false,
+ error: 'VALIDATION_FAILED',
+ message: 'Could not validate wallet balance for DEX trade. Please try again.'
+ }, { status: 500 })
+ }
+ }
+
// For now, simulate the trade until Jupiter integration is fully tested
if (!useRealDEX) {
console.log('🎮 Executing SIMULATED trade (real DEX integration available)')
@@ -147,29 +191,54 @@ export async function POST(request) {
message: `${side.toUpperCase()} order executed on Jupiter DEX${stopLoss || takeProfit ? ' with TP/SL monitoring' : ''}`
}
- // Create position for successful trade
+ // Add trade to history
try {
- const positionResponse = await fetch('http://localhost:3000/api/trading/positions', {
+ await fetch('http://localhost:3000/api/trading/history', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'add',
- symbol: tradingPair || `${symbol}/USDC`,
+ symbol: tradingPair || `${fromCoin || symbol}/${toCoin || 'USDC'}`,
side: side.toUpperCase(),
amount: amount,
- entryPrice: 168.1, // You'll need to get this from the actual trade execution
- stopLoss: stopLoss,
- takeProfit: takeProfit,
+ price: 168.1, // Get from actual execution
+ type: 'market',
+ status: 'executed',
txId: tradeResult.txId,
- leverage: 1
+ dex: 'JUPITER',
+ notes: closePosition ? 'Position closing trade' : null
})
})
-
- if (positionResponse.ok) {
- console.log('✅ Position created for DEX trade')
- }
+ console.log('✅ Trade added to history')
} catch (error) {
- console.error('❌ Failed to create position:', error)
+ console.error('❌ Failed to add trade to history:', error)
+ }
+
+ // Create position only if not closing an existing position
+ if (!closePosition) {
+ try {
+ const positionResponse = await fetch('http://localhost:3000/api/trading/positions', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ action: 'add',
+ symbol: tradingPair || `${fromCoin || symbol}/${toCoin || 'USDC'}`,
+ side: side.toUpperCase(),
+ amount: amount,
+ entryPrice: 168.1, // Get from actual execution
+ stopLoss: stopLoss,
+ takeProfit: takeProfit,
+ txId: tradeResult.txId,
+ leverage: 1
+ })
+ })
+
+ if (positionResponse.ok) {
+ console.log('✅ Position created for DEX trade')
+ }
+ } catch (error) {
+ console.error('❌ Failed to create position:', error)
+ }
}
return NextResponse.json(tradeResponse)
diff --git a/app/api/trading/history/route.js b/app/api/trading/history/route.js
new file mode 100644
index 0000000..12b1ac7
--- /dev/null
+++ b/app/api/trading/history/route.js
@@ -0,0 +1,123 @@
+import { NextResponse } from 'next/server'
+import fs from 'fs'
+import path from 'path'
+
+// Persistent storage for trades using JSON file
+const TRADES_FILE = path.join(process.cwd(), 'data', 'trades.json')
+
+// Ensure data directory exists
+const dataDir = path.join(process.cwd(), 'data')
+if (!fs.existsSync(dataDir)) {
+ fs.mkdirSync(dataDir, { recursive: true })
+}
+
+// Helper functions for persistent storage
+function loadTrades() {
+ try {
+ if (fs.existsSync(TRADES_FILE)) {
+ const data = fs.readFileSync(TRADES_FILE, 'utf8')
+ return JSON.parse(data)
+ }
+ } catch (error) {
+ console.error('Error loading trades:', error)
+ }
+ return []
+}
+
+function saveTrades(trades) {
+ try {
+ fs.writeFileSync(TRADES_FILE, JSON.stringify(trades, null, 2))
+ } catch (error) {
+ console.error('Error saving trades:', error)
+ }
+}
+
+export async function GET() {
+ try {
+ // Load trades from persistent storage
+ const tradesHistory = loadTrades()
+
+ // Sort trades by timestamp (newest first)
+ const sortedTrades = tradesHistory.sort((a, b) => b.timestamp - a.timestamp)
+
+ return NextResponse.json({
+ success: true,
+ trades: sortedTrades,
+ totalTrades: sortedTrades.length
+ })
+
+ } catch (error) {
+ console.error('Error fetching trades history:', error)
+ return NextResponse.json({
+ success: false,
+ error: 'Failed to fetch trades history',
+ trades: []
+ }, { status: 500 })
+ }
+}
+
+export async function POST(request) {
+ try {
+ const body = await request.json()
+ const { action, ...tradeData } = body
+
+ if (action === 'add') {
+ // Load existing trades
+ const tradesHistory = loadTrades()
+
+ // Add new trade to history
+ const newTrade = {
+ id: `trade_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`,
+ symbol: tradeData.symbol,
+ side: tradeData.side,
+ amount: parseFloat(tradeData.amount),
+ price: parseFloat(tradeData.price),
+ type: tradeData.type || 'market',
+ status: tradeData.status || 'executed',
+ timestamp: Date.now(),
+ txId: tradeData.txId || null,
+ fee: tradeData.fee || 0,
+ pnl: tradeData.pnl || null, // For closing trades
+ dex: tradeData.dex || 'JUPITER',
+ notes: tradeData.notes || null
+ }
+
+ tradesHistory.push(newTrade)
+
+ // Keep only last 100 trades to prevent memory issues
+ if (tradesHistory.length > 100) {
+ tradesHistory.splice(0, tradesHistory.length - 100)
+ }
+
+ // Save to persistent storage
+ saveTrades(tradesHistory)
+
+ return NextResponse.json({
+ success: true,
+ trade: newTrade,
+ message: `Trade added to history: ${newTrade.side} ${newTrade.amount} ${newTrade.symbol}`
+ })
+
+ } else if (action === 'clear') {
+ // Clear trade history
+ saveTrades([])
+
+ return NextResponse.json({
+ success: true,
+ message: 'Trade history cleared'
+ })
+ }
+
+ return NextResponse.json({
+ success: false,
+ error: 'Invalid action'
+ }, { status: 400 })
+
+ } catch (error) {
+ console.error('Error managing trades history:', error)
+ return NextResponse.json({
+ success: false,
+ error: 'Failed to manage trades history'
+ }, { status: 500 })
+ }
+}
diff --git a/app/api/trading/orders/route.js b/app/api/trading/orders/route.js
new file mode 100644
index 0000000..9510e41
--- /dev/null
+++ b/app/api/trading/orders/route.js
@@ -0,0 +1,255 @@
+import { NextResponse } from 'next/server'
+import fs from 'fs'
+import path from 'path'
+
+// Persistent storage for pending orders using JSON file
+const PENDING_ORDERS_FILE = path.join(process.cwd(), 'data', 'pending-orders.json')
+
+// Ensure data directory exists
+const dataDir = path.join(process.cwd(), 'data')
+if (!fs.existsSync(dataDir)) {
+ fs.mkdirSync(dataDir, { recursive: true })
+}
+
+// Helper functions for persistent storage
+function loadPendingOrders() {
+ try {
+ if (fs.existsSync(PENDING_ORDERS_FILE)) {
+ const data = fs.readFileSync(PENDING_ORDERS_FILE, 'utf8')
+ return JSON.parse(data)
+ }
+ } catch (error) {
+ console.error('Error loading pending orders:', error)
+ }
+ return []
+}
+
+function savePendingOrders(orders) {
+ try {
+ fs.writeFileSync(PENDING_ORDERS_FILE, JSON.stringify(orders, null, 2))
+ } catch (error) {
+ console.error('Error saving pending orders:', error)
+ }
+}
+
+// Helper function to map symbols to CoinGecko IDs
+function getCoinGeckoId(symbol) {
+ const mapping = {
+ 'SOL': 'solana',
+ 'SOLUSD': 'solana',
+ 'BTC': 'bitcoin',
+ 'ETH': 'ethereum',
+ 'USDC': 'usd-coin',
+ 'USDT': 'tether',
+ 'RAY': 'raydium',
+ 'ORCA': 'orca'
+ }
+ return mapping[symbol.replace('USD', '')] || 'solana'
+}
+
+export async function GET() {
+ try {
+ // Load pending orders from persistent storage
+ const pendingOrders = loadPendingOrders()
+
+ // Check current prices and update order status
+ const updatedOrders = await Promise.all(
+ pendingOrders.filter(order => order.status === 'PENDING').map(async (order) => {
+ try {
+ // Get current price from CoinGecko
+ const priceResponse = await fetch(
+ `https://api.coingecko.com/api/v3/simple/price?ids=${getCoinGeckoId(order.symbol)}&vs_currencies=usd`
+ )
+ const priceData = await priceResponse.json()
+ const currentPrice = priceData[getCoinGeckoId(order.symbol)]?.usd
+
+ if (currentPrice) {
+ order.currentPrice = currentPrice
+
+ // Check if limit order should be filled
+ const shouldFill = (
+ (order.side === 'BUY' && currentPrice <= order.limitPrice) ||
+ (order.side === 'SELL' && currentPrice >= order.limitPrice)
+ )
+
+ if (shouldFill) {
+ console.log(`🎯 Limit order ready to fill: ${order.side} ${order.amount} ${order.symbol} at $${order.limitPrice}`)
+ }
+ }
+
+ return order
+ } catch (error) {
+ console.error(`Error updating order ${order.id}:`, error)
+ return order
+ }
+ })
+ )
+
+ return NextResponse.json({
+ success: true,
+ orders: updatedOrders,
+ totalOrders: updatedOrders.length
+ })
+
+ } catch (error) {
+ console.error('Error fetching pending orders:', error)
+ return NextResponse.json({
+ success: false,
+ error: 'Failed to fetch pending orders',
+ orders: []
+ }, { status: 500 })
+ }
+}
+
+export async function POST(request) {
+ try {
+ const body = await request.json()
+ const { action, orderId, ...orderData } = body
+
+ if (action === 'add') {
+ // Load existing orders
+ const pendingOrders = loadPendingOrders()
+
+ // Add new pending order
+ const newOrder = {
+ id: `order_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`,
+ symbol: orderData.symbol,
+ side: orderData.side,
+ amount: parseFloat(orderData.amount),
+ limitPrice: parseFloat(orderData.limitPrice),
+ orderType: 'LIMIT',
+ status: 'PENDING',
+ timestamp: Date.now(),
+ stopLoss: orderData.stopLoss ? parseFloat(orderData.stopLoss) : null,
+ takeProfit: orderData.takeProfit ? parseFloat(orderData.takeProfit) : null,
+ tradingMode: orderData.tradingMode || 'SPOT',
+ fromCoin: orderData.fromCoin,
+ toCoin: orderData.toCoin,
+ expiresAt: orderData.expiresAt || null
+ }
+
+ pendingOrders.push(newOrder)
+ savePendingOrders(pendingOrders)
+
+ console.log(`📋 Limit order created: ${newOrder.side} ${newOrder.amount} ${newOrder.symbol} at $${newOrder.limitPrice}`)
+
+ return NextResponse.json({
+ success: true,
+ order: newOrder,
+ message: `Limit order created: ${newOrder.side} ${newOrder.amount} ${newOrder.symbol} at $${newOrder.limitPrice}`
+ })
+
+ } else if (action === 'cancel') {
+ // Load existing orders
+ const pendingOrders = loadPendingOrders()
+
+ // Find and cancel order
+ const orderIndex = pendingOrders.findIndex(order => order.id === orderId)
+
+ if (orderIndex === -1) {
+ return NextResponse.json({
+ success: false,
+ error: 'Order not found'
+ }, { status: 404 })
+ }
+
+ const order = pendingOrders[orderIndex]
+ order.status = 'CANCELLED'
+ order.cancelledAt = Date.now()
+
+ // Remove from pending orders or mark as cancelled
+ pendingOrders.splice(orderIndex, 1)
+ savePendingOrders(pendingOrders)
+
+ console.log(`❌ Limit order cancelled: ${order.id}`)
+
+ return NextResponse.json({
+ success: true,
+ order: order,
+ message: `Order cancelled: ${order.side} ${order.amount} ${order.symbol}`
+ })
+
+ } else if (action === 'fill') {
+ // Fill a limit order (called when price target is reached)
+ const pendingOrders = loadPendingOrders()
+
+ const orderIndex = pendingOrders.findIndex(order => order.id === orderId)
+
+ if (orderIndex === -1) {
+ return NextResponse.json({
+ success: false,
+ error: 'Order not found'
+ }, { status: 404 })
+ }
+
+ const order = pendingOrders[orderIndex]
+ const fillPrice = orderData.fillPrice || order.limitPrice
+
+ try {
+ // Execute the trade by calling the trading API
+ const tradeResponse = await fetch('http://localhost:3000/api/trading', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ symbol: order.symbol,
+ side: order.side.toLowerCase(),
+ amount: order.amount,
+ type: 'limit',
+ price: fillPrice,
+ stopLoss: order.stopLoss,
+ takeProfit: order.takeProfit,
+ tradingMode: order.tradingMode,
+ fromCoin: order.fromCoin,
+ toCoin: order.toCoin,
+ limitOrderId: order.id
+ })
+ })
+
+ if (tradeResponse.ok) {
+ const tradeData = await tradeResponse.json()
+
+ // Update order status
+ order.status = 'FILLED'
+ order.filledAt = Date.now()
+ order.fillPrice = fillPrice
+ order.tradeId = tradeData.trade?.id
+
+ // Remove from pending orders
+ pendingOrders.splice(orderIndex, 1)
+ savePendingOrders(pendingOrders)
+
+ console.log(`✅ Limit order filled: ${order.side} ${order.amount} ${order.symbol} at $${fillPrice}`)
+
+ return NextResponse.json({
+ success: true,
+ order: order,
+ trade: tradeData.trade,
+ message: `Order filled: ${order.side} ${order.amount} ${order.symbol} at $${fillPrice}`
+ })
+ } else {
+ throw new Error('Failed to execute trade for filled order')
+ }
+
+ } catch (error) {
+ console.error('Error filling limit order:', error)
+ return NextResponse.json({
+ success: false,
+ error: 'Failed to fill order',
+ message: error.message
+ }, { status: 500 })
+ }
+ }
+
+ return NextResponse.json({
+ success: false,
+ error: 'Invalid action'
+ }, { status: 400 })
+
+ } catch (error) {
+ console.error('Error managing pending order:', error)
+ return NextResponse.json({
+ success: false,
+ error: 'Failed to manage pending order'
+ }, { status: 500 })
+ }
+}
diff --git a/app/api/trading/positions/route.js b/app/api/trading/positions/route.js
index 77bc678..45e2774 100644
--- a/app/api/trading/positions/route.js
+++ b/app/api/trading/positions/route.js
@@ -1,10 +1,63 @@
import { NextResponse } from 'next/server'
+import fs from 'fs'
+import path from 'path'
-// In-memory positions storage (in production, this would be a database)
-let activePositions = []
+// Persistent storage for positions using JSON file
+const POSITIONS_FILE = path.join(process.cwd(), 'data', 'positions.json')
+const TRADES_FILE = path.join(process.cwd(), 'data', 'trades.json')
+
+// Ensure data directory exists
+const dataDir = path.join(process.cwd(), 'data')
+if (!fs.existsSync(dataDir)) {
+ fs.mkdirSync(dataDir, { recursive: true })
+}
+
+// Helper functions for persistent storage
+function loadPositions() {
+ try {
+ if (fs.existsSync(POSITIONS_FILE)) {
+ const data = fs.readFileSync(POSITIONS_FILE, 'utf8')
+ return JSON.parse(data)
+ }
+ } catch (error) {
+ console.error('Error loading positions:', error)
+ }
+ return []
+}
+
+function savePositions(positions) {
+ try {
+ fs.writeFileSync(POSITIONS_FILE, JSON.stringify(positions, null, 2))
+ } catch (error) {
+ console.error('Error saving positions:', error)
+ }
+}
+
+function loadTrades() {
+ try {
+ if (fs.existsSync(TRADES_FILE)) {
+ const data = fs.readFileSync(TRADES_FILE, 'utf8')
+ return JSON.parse(data)
+ }
+ } catch (error) {
+ console.error('Error loading trades:', error)
+ }
+ return []
+}
+
+function saveTrades(trades) {
+ try {
+ fs.writeFileSync(TRADES_FILE, JSON.stringify(trades, null, 2))
+ } catch (error) {
+ console.error('Error saving trades:', error)
+ }
+}
export async function GET() {
try {
+ // Load positions from persistent storage
+ const activePositions = loadPositions()
+
// Calculate current P&L for each position using real-time prices
const updatedPositions = await Promise.all(
activePositions.map(async (position) => {
@@ -63,6 +116,9 @@ export async function POST(request) {
const { action, positionId, ...positionData } = body
if (action === 'add') {
+ // Load existing positions
+ const activePositions = loadPositions()
+
// Add new position
const newPosition = {
id: `pos_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`,
@@ -79,6 +135,9 @@ export async function POST(request) {
}
activePositions.push(newPosition)
+ savePositions(activePositions)
+
+ console.log(`✅ Position created: ${newPosition.side} ${newPosition.amount} ${newPosition.symbol}`)
return NextResponse.json({
success: true,
@@ -87,7 +146,10 @@ export async function POST(request) {
})
} else if (action === 'close') {
- // Close position
+ // Load existing positions
+ const activePositions = loadPositions()
+
+ // Close position by executing reverse trade
const positionIndex = activePositions.findIndex(pos => pos.id === positionId)
if (positionIndex === -1) {
@@ -97,21 +159,93 @@ export async function POST(request) {
}, { status: 404 })
}
- const closedPosition = activePositions[positionIndex]
- closedPosition.status = 'CLOSED'
- closedPosition.closedAt = Date.now()
- closedPosition.exitPrice = positionData.exitPrice
+ const position = activePositions[positionIndex]
+ const exitPrice = positionData.exitPrice || position.currentPrice || position.entryPrice
- // Remove from active positions
- activePositions.splice(positionIndex, 1)
+ // Calculate P&L
+ const priceDiff = exitPrice - position.entryPrice
+ const realizedPnl = position.side === 'BUY'
+ ? priceDiff * position.amount
+ : -priceDiff * position.amount
- return NextResponse.json({
- success: true,
- position: closedPosition,
- message: `Position closed: ${closedPosition.symbol}`
- })
+ console.log(`🔄 Closing position ${positionId}: ${position.side} ${position.amount} ${position.symbol}`)
+ console.log(`💰 Entry: $${position.entryPrice}, Exit: $${exitPrice}, P&L: $${realizedPnl.toFixed(4)}`)
+
+ try {
+ // Add closing trade to history
+ const existingTrades = loadTrades()
+ const closingTrade = {
+ id: `trade_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`,
+ symbol: position.symbol,
+ side: position.side === 'BUY' ? 'SELL' : 'BUY', // Reverse side for closing
+ amount: position.amount,
+ price: exitPrice,
+ type: 'market',
+ status: 'executed',
+ timestamp: Date.now(),
+ txId: `close_${position.id}`,
+ fee: 0,
+ pnl: realizedPnl,
+ dex: 'SIMULATION',
+ notes: `Closed position ${position.id} with P&L $${realizedPnl.toFixed(4)}`
+ }
+
+ existingTrades.push(closingTrade)
+
+ // Keep only last 100 trades
+ if (existingTrades.length > 100) {
+ existingTrades.splice(0, existingTrades.length - 100)
+ }
+
+ saveTrades(existingTrades)
+ console.log('✅ Closing trade added to history')
+
+ console.log(`💰 Position closed with P&L: $${realizedPnl.toFixed(4)}`)
+
+ // Update position status and remove from active
+ position.status = 'CLOSED'
+ position.closedAt = Date.now()
+ position.exitPrice = exitPrice
+ position.realizedPnl = realizedPnl
+ position.closeTxId = closingTrade.txId
+
+ // Remove from active positions
+ activePositions.splice(positionIndex, 1)
+ savePositions(activePositions)
+
+ return NextResponse.json({
+ success: true,
+ position: position,
+ closingTrade: closingTrade,
+ realizedPnl: realizedPnl,
+ message: `Position closed: ${position.symbol} with P&L ${realizedPnl >= 0 ? '+' : ''}$${realizedPnl.toFixed(4)}`
+ })
+
+ } catch (error) {
+ console.error('Error closing position:', error)
+
+ // Fallback: just remove position
+ position.status = 'CLOSED'
+ position.closedAt = Date.now()
+ position.exitPrice = exitPrice
+ position.realizedPnl = realizedPnl
+
+ activePositions.splice(positionIndex, 1)
+ savePositions(activePositions)
+
+ return NextResponse.json({
+ success: true,
+ position: position,
+ realizedPnl: realizedPnl,
+ message: `Position closed (fallback): ${position.symbol}`,
+ warning: 'Failed to add to trade history'
+ })
+ }
} else if (action === 'update') {
+ // Load existing positions
+ const activePositions = loadPositions()
+
// Update position (for SL/TP changes)
const positionIndex = activePositions.findIndex(pos => pos.id === positionId)
@@ -126,6 +260,8 @@ export async function POST(request) {
if (positionData.stopLoss !== undefined) position.stopLoss = positionData.stopLoss
if (positionData.takeProfit !== undefined) position.takeProfit = positionData.takeProfit
+ savePositions(activePositions)
+
return NextResponse.json({
success: true,
position,
diff --git a/app/api/trading/route.ts b/app/api/trading/route.ts
index 1458195..a0ebfc9 100644
--- a/app/api/trading/route.ts
+++ b/app/api/trading/route.ts
@@ -3,7 +3,19 @@ import { NextResponse } from 'next/server'
export async function POST(request: Request) {
try {
const body = await request.json()
- const { symbol, side, amount, type = 'market' } = body
+ const {
+ symbol,
+ side,
+ amount,
+ type = 'market',
+ price,
+ stopLoss,
+ takeProfit,
+ tradingMode = 'SPOT',
+ fromCoin,
+ toCoin,
+ limitOrderId
+ } = body
// Validate input
if (!symbol || !side || !amount) {
@@ -12,20 +24,109 @@ export async function POST(request: Request) {
}, { status: 400 })
}
- // Mock trading execution
+ // Validate balance before proceeding (skip for limit order fills)
+ if (!limitOrderId) {
+ console.log('🔍 Validating wallet balance before trade execution...')
+
+ try {
+ const validationResponse = await fetch('http://localhost:3000/api/trading/validate', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ symbol,
+ side,
+ amount,
+ price,
+ tradingMode,
+ fromCoin,
+ toCoin
+ })
+ })
+
+ const validationResult = await validationResponse.json()
+
+ if (!validationResult.success) {
+ console.log('❌ Balance validation failed:', validationResult.message)
+ return NextResponse.json({
+ success: false,
+ error: validationResult.error,
+ message: validationResult.message,
+ validation: validationResult
+ }, { status: validationResponse.status })
+ }
+
+ console.log('✅ Balance validation passed')
+ } catch (validationError) {
+ console.error('❌ Balance validation error:', validationError)
+ return NextResponse.json({
+ success: false,
+ error: 'VALIDATION_FAILED',
+ message: 'Could not validate wallet balance. Please try again.'
+ }, { status: 500 })
+ }
+ }
+
+ // Handle limit orders
+ if (type === 'limit' && price && !limitOrderId) {
+ console.log(`📋 Creating limit order: ${side.toUpperCase()} ${amount} ${symbol} at $${price}`)
+
+ // Create pending order instead of executing immediately
+ try {
+ const orderResponse = await fetch('http://localhost:3000/api/trading/orders', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ action: 'add',
+ symbol: symbol,
+ side: side.toUpperCase(),
+ amount: amount,
+ limitPrice: price,
+ stopLoss: stopLoss,
+ takeProfit: takeProfit,
+ tradingMode: tradingMode,
+ fromCoin: fromCoin,
+ toCoin: toCoin
+ })
+ })
+
+ if (orderResponse.ok) {
+ const orderData = await orderResponse.json()
+ return NextResponse.json({
+ success: true,
+ order: orderData.order,
+ type: 'limit_order_created',
+ message: `Limit order created: ${side.toUpperCase()} ${amount} ${symbol} at $${price}`
+ })
+ } else {
+ throw new Error('Failed to create limit order')
+ }
+ } catch (error) {
+ console.error('Failed to create limit order:', error)
+ return NextResponse.json({
+ error: 'Failed to create limit order',
+ message: error instanceof Error ? error.message : 'Unknown error'
+ }, { status: 500 })
+ }
+ }
+
+ // Get current market price for market orders or limit order fills
+ const currentPrice = type === 'market' ? (side === 'buy' ? 168.11 : 168.09) : price
+
+ // Mock trading execution (market order or limit order fill)
const mockTrade = {
- id: `trade_${Date.now()}`,
+ id: limitOrderId ? `fill_${limitOrderId}` : `trade_${Date.now()}`,
symbol,
side, // 'buy' or 'sell'
amount: parseFloat(amount),
type,
- price: side === 'buy' ? 144.11 : 144.09, // Mock prices
+ price: currentPrice,
status: 'executed',
timestamp: new Date().toISOString(),
- fee: parseFloat(amount) * 0.001 // 0.1% fee
+ fee: parseFloat(amount) * 0.001, // 0.1% fee
+ limitOrderId: limitOrderId || null
}
- console.log('Simulated trade executed:', mockTrade)
+ console.log('Trade executed:', mockTrade)
// Automatically create position for this trade
try {
@@ -34,11 +135,14 @@ export async function POST(request: Request) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'add',
- symbol: mockTrade.symbol,
+ symbol: fromCoin && toCoin ? `${fromCoin}/${toCoin}` : mockTrade.symbol,
side: mockTrade.side.toUpperCase(),
amount: mockTrade.amount,
entryPrice: mockTrade.price,
- txId: mockTrade.id
+ stopLoss: stopLoss,
+ takeProfit: takeProfit,
+ txId: mockTrade.id,
+ leverage: tradingMode === 'PERP' ? 10 : 1
})
})
diff --git a/app/api/trading/validate/route.js b/app/api/trading/validate/route.js
new file mode 100644
index 0000000..124554e
--- /dev/null
+++ b/app/api/trading/validate/route.js
@@ -0,0 +1,142 @@
+import { NextResponse } from 'next/server'
+
+export async function POST(request) {
+ try {
+ const body = await request.json()
+ const { symbol, side, amount, price, tradingMode = 'SPOT', fromCoin, toCoin } = body
+
+ console.log(`🔍 Validating trade: ${side} ${amount} ${symbol}`)
+
+ // For now, use hardcoded wallet balance values for validation
+ // In production, this would fetch from the actual wallet API
+ const mockWalletBalance = {
+ solBalance: 0.0728, // Current actual balance
+ usdValue: 12.12, // Current USD value
+ positions: [
+ { symbol: 'SOL', amount: 0.0728, price: 166.5 }
+ ]
+ }
+
+ // Determine required balance for the trade
+ let requiredBalance = 0
+ let requiredCurrency = ''
+ let availableBalance = 0
+
+ if (tradingMode === 'SPOT') {
+ if (side.toUpperCase() === 'BUY') {
+ // For BUY orders, need USDC or USD equivalent
+ const tradePrice = price || 166.5 // Use provided price or current SOL price
+ requiredBalance = amount * tradePrice
+ requiredCurrency = 'USD'
+ availableBalance = mockWalletBalance.usdValue
+ } else {
+ // For SELL orders, need the actual token
+ requiredBalance = amount
+ requiredCurrency = fromCoin || symbol
+
+ // Find the token balance
+ const tokenPosition = mockWalletBalance.positions.find(pos =>
+ pos.symbol === requiredCurrency ||
+ pos.symbol === symbol
+ )
+
+ availableBalance = tokenPosition ? tokenPosition.amount : 0
+ }
+ } else if (tradingMode === 'PERP') {
+ // For perpetuals, only need margin
+ const leverage = 10 // Default leverage
+ const tradePrice = price || 166.5
+ requiredBalance = (amount * tradePrice) / leverage
+ requiredCurrency = 'USD'
+ availableBalance = mockWalletBalance.usdValue
+ }
+
+ console.log(`💰 Balance check: Need ${requiredBalance} ${requiredCurrency}, Have ${availableBalance}`)
+
+ // Validate sufficient balance
+ if (requiredBalance > availableBalance) {
+ const shortfall = requiredBalance - availableBalance
+
+ return NextResponse.json({
+ success: false,
+ error: 'INSUFFICIENT_BALANCE',
+ message: `Insufficient balance. Need ${requiredBalance.toFixed(6)} ${requiredCurrency}, have ${availableBalance.toFixed(6)}. Shortfall: ${shortfall.toFixed(6)}`,
+ required: requiredBalance,
+ available: availableBalance,
+ shortfall: shortfall,
+ currency: requiredCurrency
+ }, { status: 400 })
+ }
+
+ // Validate minimum trade size
+ const minTradeUsd = 1.0 // Minimum $1 trade
+ const tradeValueUsd = side.toUpperCase() === 'BUY'
+ ? requiredBalance
+ : amount * (price || 166.5)
+
+ if (tradeValueUsd < minTradeUsd) {
+ return NextResponse.json({
+ success: false,
+ error: 'TRADE_TOO_SMALL',
+ message: `Trade value too small. Minimum trade: $${minTradeUsd}, your trade: $${tradeValueUsd.toFixed(2)}`,
+ minTradeUsd: minTradeUsd,
+ tradeValueUsd: tradeValueUsd
+ }, { status: 400 })
+ }
+
+ // Validate maximum trade size (safety check)
+ const maxTradePercent = 0.95 // Max 95% of balance per trade
+ const maxAllowedTrade = availableBalance * maxTradePercent
+
+ if (requiredBalance > maxAllowedTrade) {
+ return NextResponse.json({
+ success: false,
+ error: 'TRADE_TOO_LARGE',
+ message: `Trade too large. Maximum allowed: ${maxAllowedTrade.toFixed(6)} ${requiredCurrency} (95% of balance)`,
+ maxAllowed: maxAllowedTrade,
+ requested: requiredBalance,
+ currency: requiredCurrency
+ }, { status: 400 })
+ }
+
+ // If we get here, the trade is valid
+ return NextResponse.json({
+ success: true,
+ validation: {
+ requiredBalance: requiredBalance,
+ availableBalance: availableBalance,
+ currency: requiredCurrency,
+ tradeValueUsd: tradeValueUsd,
+ valid: true
+ },
+ message: `Trade validation passed: ${side} ${amount} ${symbol}`
+ })
+
+ } catch (error) {
+ console.error('❌ Balance validation error:', error)
+ return NextResponse.json({
+ success: false,
+ error: 'VALIDATION_ERROR',
+ message: 'Failed to validate trade balance: ' + error.message
+ }, { status: 500 })
+ }
+}
+
+export async function GET() {
+ return NextResponse.json({
+ message: 'Trade Balance Validation API',
+ description: 'Validates if wallet has sufficient balance for proposed trades',
+ endpoints: {
+ 'POST /api/trading/validate': 'Validate trade against wallet balance'
+ },
+ parameters: {
+ symbol: 'Trading symbol (SOL, BTC, etc.)',
+ side: 'BUY or SELL',
+ amount: 'Trade amount',
+ price: 'Trade price (optional, uses current market price)',
+ tradingMode: 'SPOT or PERP',
+ fromCoin: 'Source currency',
+ toCoin: 'Target currency'
+ }
+ })
+}
diff --git a/app/api/wallet/balance/route.js b/app/api/wallet/balance/route.js
index 868cef8..d2c4f4b 100644
--- a/app/api/wallet/balance/route.js
+++ b/app/api/wallet/balance/route.js
@@ -25,19 +25,31 @@ export async function GET() {
const balance = await connection.getBalance(keypair.publicKey)
const solBalance = balance / 1000000000 // Convert lamports to SOL
- // Get current SOL price
- const priceResponse = await fetch(
- 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd&include_24hr_change=true'
- )
+ // Get current SOL price with fallback
+ let solPrice = 168.11 // Fallback price from our current market data
+ let change24h = 0
- if (!priceResponse.ok) {
- throw new Error('Failed to fetch SOL price')
+ try {
+ const priceResponse = await fetch(
+ 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd&include_24hr_change=true'
+ )
+
+ if (priceResponse.ok) {
+ const priceData = await priceResponse.json()
+ if (priceData.solana?.usd) {
+ solPrice = priceData.solana.usd
+ change24h = priceData.solana.usd_24h_change || 0
+ console.log(`💰 Using live SOL price: $${solPrice}`)
+ } else {
+ console.log(`⚠️ Using fallback SOL price: $${solPrice} (CoinGecko data invalid)`)
+ }
+ } else {
+ console.log(`⚠️ Using fallback SOL price: $${solPrice} (CoinGecko rate limited)`)
+ }
+ } catch (priceError) {
+ console.log(`⚠️ Using fallback SOL price: $${solPrice} (CoinGecko error: ${priceError.message})`)
}
- const priceData = await priceResponse.json()
- const solPrice = priceData.solana?.usd || 0
- const change24h = priceData.solana?.usd_24h_change || 0
-
const usdValue = solBalance * solPrice
console.log(`💎 Real wallet: ${solBalance.toFixed(4)} SOL ($${usdValue.toFixed(2)})`)
@@ -46,15 +58,17 @@ export async function GET() {
success: true,
balance: {
totalValue: usdValue,
- availableBalance: usdValue, // All SOL is available for trading
- positions: [{
- symbol: 'SOL',
- price: solPrice,
- change24h: change24h,
- volume24h: 0, // Not applicable for wallet balance
- amount: solBalance,
- usdValue: usdValue
- }]
+ availableBalance: usdValue,
+ positions: [
+ {
+ symbol: 'SOL',
+ price: solPrice,
+ change24h: change24h,
+ volume24h: 0,
+ amount: solBalance,
+ usdValue: usdValue
+ }
+ ]
},
wallet: {
publicKey: keypair.publicKey.toString(),
diff --git a/app/trading/page.js b/app/trading/page.js
index 922d008..d0f9155 100644
--- a/app/trading/page.js
+++ b/app/trading/page.js
@@ -2,6 +2,8 @@
import React, { useState, useEffect } from 'react'
import TradeExecutionPanel from '../../components/TradeExecutionPanel.js'
import PositionsPanel from '../../components/PositionsPanel.js'
+import PendingOrdersPanel from '../../components/PendingOrdersPanel.js'
+import TradesHistoryPanel from '../../components/TradesHistoryPanel.js'
export default function TradingPage() {
const [selectedSymbol, setSelectedSymbol] = useState('SOL')
@@ -85,6 +87,9 @@ export default function TradingPage() {
{/* Open Positions */}