diff --git a/app/api/trading/validate/route.js b/app/api/trading/validate/route.js index 124554e..1fb7ae8 100644 --- a/app/api/trading/validate/route.js +++ b/app/api/trading/validate/route.js @@ -7,14 +7,32 @@ export async function POST(request) { 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 } - ] + // Fetch real wallet balance from the wallet API + let walletBalance + try { + const walletResponse = await fetch('http://localhost:3000/api/wallet/balance') + const walletData = await walletResponse.json() + + if (walletData.success && walletData.wallet) { + walletBalance = { + solBalance: walletData.wallet.solBalance, + usdValue: walletData.wallet.usdValue, + positions: walletData.balance.positions || [] + } + console.log(`āœ… Real wallet balance: ${walletBalance.solBalance} SOL ($${walletBalance.usdValue.toFixed(2)})`) + } else { + throw new Error('Failed to fetch wallet balance') + } + } catch (error) { + console.log(`āš ļø Failed to fetch real wallet balance, using fallback: ${error.message}`) + // Fallback to hardcoded values only if API fails + walletBalance = { + solBalance: 0.0728, + usdValue: 12.12, + positions: [ + { symbol: 'SOL', amount: 0.0728, price: 166.5 } + ] + } } // Determine required balance for the trade @@ -28,19 +46,19 @@ export async function POST(request) { const tradePrice = price || 166.5 // Use provided price or current SOL price requiredBalance = amount * tradePrice requiredCurrency = 'USD' - availableBalance = mockWalletBalance.usdValue + availableBalance = walletBalance.usdValue } else { // For SELL orders, need the actual token requiredBalance = amount requiredCurrency = fromCoin || symbol // Find the token balance - const tokenPosition = mockWalletBalance.positions.find(pos => + const tokenPosition = walletBalance.positions.find(pos => pos.symbol === requiredCurrency || pos.symbol === symbol ) - availableBalance = tokenPosition ? tokenPosition.amount : 0 + availableBalance = tokenPosition ? tokenPosition.amount : walletBalance.solBalance } } else if (tradingMode === 'PERP') { // For perpetuals, only need margin @@ -48,7 +66,7 @@ export async function POST(request) { const tradePrice = price || 166.5 requiredBalance = (amount * tradePrice) / leverage requiredCurrency = 'USD' - availableBalance = mockWalletBalance.usdValue + availableBalance = walletBalance.usdValue } console.log(`šŸ’° Balance check: Need ${requiredBalance} ${requiredCurrency}, Have ${availableBalance}`) diff --git a/app/api/wallet/balance/route.js b/app/api/wallet/balance/route.js index d2c4f4b..cc8941f 100644 --- a/app/api/wallet/balance/route.js +++ b/app/api/wallet/balance/route.js @@ -1,5 +1,6 @@ import { NextResponse } from 'next/server' -import { Connection, Keypair } from '@solana/web3.js' +import { Connection, Keypair, PublicKey } from '@solana/web3.js' +import { getAssociatedTokenAddress, getAccount, TOKEN_PROGRAM_ID } from '@solana/spl-token' export async function GET() { try { @@ -54,26 +55,90 @@ export async function GET() { console.log(`šŸ’Ž Real wallet: ${solBalance.toFixed(4)} SOL ($${usdValue.toFixed(2)})`) + // Check for other token balances - DISCOVER ALL TOKENS + const positions = [ + { + symbol: 'SOL', + price: solPrice, + change24h: change24h, + volume24h: 0, + amount: solBalance, + usdValue: usdValue + } + ] + + let totalValue = usdValue + + try { + // Check for specific known tokens using associated token addresses (less RPC intensive) + console.log('ļæ½ Checking for known SPL tokens...') + + // Known token metadata for discovery + const knownTokens = [ + { symbol: 'USDC', mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', decimals: 6, price: 1.0 }, + { symbol: 'USDT', mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', decimals: 6, price: 1.0 }, + { symbol: 'RAY', mint: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', decimals: 6, price: 0.5 }, + { symbol: 'mSOL', mint: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So', decimals: 9, price: 180.0 }, + { symbol: 'wSOL', mint: 'So11111111111111111111111111111111111111112', decimals: 9, price: solPrice }, + { symbol: 'BONK', mint: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', decimals: 5, price: 0.00002 }, + { symbol: 'JUP', mint: 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN', decimals: 6, price: 0.8 }, + { symbol: 'ORCA', mint: 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', decimals: 6, price: 0.3 }, + { symbol: 'SRM', mint: 'SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt', decimals: 6, price: 0.05 }, + { symbol: 'STEP', mint: 'StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT', decimals: 9, price: 0.01 } + ] + + // Check each known token one by one + for (const token of knownTokens) { + try { + const tokenMint = new PublicKey(token.mint) + const associatedTokenAddress = await getAssociatedTokenAddress( + tokenMint, + keypair.publicKey + ) + + // Get the token account + const tokenAccount = await getAccount(connection, associatedTokenAddress) + const tokenBalance = Number(tokenAccount.amount) / Math.pow(10, token.decimals) + + if (tokenBalance > 0.000001) { // Only show meaningful balances + const tokenUsdValue = tokenBalance * token.price + totalValue += tokenUsdValue + + positions.push({ + symbol: token.symbol, + mint: token.mint, + price: token.price, + change24h: 0, + volume24h: 0, + amount: tokenBalance, + usdValue: tokenUsdValue + }) + + console.log(`šŸ’Ž Found ${token.symbol}: ${tokenBalance.toFixed(6)} ($${tokenUsdValue.toFixed(2)})`) + } + } catch (error) { + // Token account doesn't exist - this is normal if wallet doesn't hold this token + console.log(`ā„¹ļø No ${token.symbol} balance found`) + } + } + + console.log(`āœ… Token discovery complete: Found ${positions.length} token positions`) + + } catch (tokenError) { + console.log(`āš ļø Error discovering tokens: ${tokenError.message}`) + } + return NextResponse.json({ success: true, balance: { - totalValue: usdValue, - availableBalance: usdValue, - positions: [ - { - symbol: 'SOL', - price: solPrice, - change24h: change24h, - volume24h: 0, - amount: solBalance, - usdValue: usdValue - } - ] + totalValue: totalValue, + availableBalance: totalValue, + positions: positions }, wallet: { publicKey: keypair.publicKey.toString(), solBalance: solBalance, - usdValue: usdValue + usdValue: totalValue }, timestamp: Date.now() }) diff --git a/components/AIAnalysisPanel.tsx b/components/AIAnalysisPanel.tsx index ad4943c..e0b8cd6 100644 --- a/components/AIAnalysisPanel.tsx +++ b/components/AIAnalysisPanel.tsx @@ -418,52 +418,107 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP // Trade initiation handler const handleTradeClick = (tfResult: any) => { + console.log('šŸ”„ AIAnalysisPanel handleTradeClick called with:', tfResult) const analysis = tfResult?.result?.analysis || tfResult?.analysis || {} + console.log('šŸ”„ Extracted analysis:', analysis) - setTradeModalData({ - entry: analysis.entry?.price || analysis.entry || '', - tp: analysis.takeProfits?.tp1?.price || analysis.takeProfits?.tp1 || analysis.takeProfits || '', - sl: analysis.stopLoss?.price || analysis.stopLoss || '', + // Enhanced data extraction with better fallbacks + let entryPrice = '' + let takeProfit1 = '' + let takeProfit2 = '' + let stopLoss = '' + + // Extract entry price with multiple fallback options + if (analysis.entry?.price) { + entryPrice = analysis.entry.price.toString() + } else if (analysis.entry && typeof analysis.entry === 'number') { + entryPrice = analysis.entry.toString() + } else if (analysis.entry && typeof analysis.entry === 'string') { + entryPrice = analysis.entry + } + + // Extract take profit 1 with multiple fallback options + if (analysis.takeProfits?.tp1?.price) { + takeProfit1 = analysis.takeProfits.tp1.price.toString() + } else if (analysis.takeProfits?.tp1 && typeof analysis.takeProfits.tp1 === 'number') { + takeProfit1 = analysis.takeProfits.tp1.toString() + } else if (analysis.takeProfits && typeof analysis.takeProfits === 'number') { + takeProfit1 = analysis.takeProfits.toString() + } else if (analysis.takeProfit?.price) { + takeProfit1 = analysis.takeProfit.price.toString() + } else if (analysis.takeProfit && typeof analysis.takeProfit === 'number') { + takeProfit1 = analysis.takeProfit.toString() + } + + // Extract take profit 2 if available + if (analysis.takeProfits?.tp2?.price) { + takeProfit2 = analysis.takeProfits.tp2.price.toString() + } else if (analysis.takeProfits?.tp2 && typeof analysis.takeProfits.tp2 === 'number') { + takeProfit2 = analysis.takeProfits.tp2.toString() + } + + // Extract stop loss with multiple fallback options + if (analysis.stopLoss?.price) { + stopLoss = analysis.stopLoss.price.toString() + } else if (analysis.stopLoss && typeof analysis.stopLoss === 'number') { + stopLoss = analysis.stopLoss.toString() + } else if (analysis.stopLoss && typeof analysis.stopLoss === 'string') { + stopLoss = analysis.stopLoss + } + + const tradeData = { + entry: entryPrice, + tp: takeProfit1, // This maps to tp1 in the modal + tp2: takeProfit2, // This will be handled in the modal + sl: stopLoss, symbol: symbol, timeframe: tfResult?.timeframeLabel || tfResult?.timeframe || '', + } + + console.log('šŸ”„ Enhanced trade data extraction:', { + originalAnalysis: analysis, + extractedData: tradeData }) + + setTradeModalData(tradeData) + console.log('šŸ”„ Opening trade modal...') setTradeModalOpen(true) } // Trade execution API call const executeTrade = async (tradeData: any) => { try { - const response = await fetch('/api/trading', { + // Use real DEX trading for manual trades + const response = await fetch('/api/trading/execute-dex', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - symbol: tradeData.symbol, + symbol: tradeData.symbol || symbol, side: 'BUY', // Could be derived from analysis - amount: parseFloat(tradeData.size), // Changed from 'size' to 'amount' - price: parseFloat(tradeData.entry), + amount: parseFloat(tradeData.positionSize) || parseFloat(tradeData.size), stopLoss: parseFloat(tradeData.sl), - takeProfit: parseFloat(tradeData.tp), - leverage: parseInt(tradeData.leverage), - timeframe: tradeData.timeframe, - orderType: 'MARKET' // Default to market order + takeProfit: parseFloat(tradeData.tp1), // Use TP1 as primary target + useRealDEX: true, // Enable real trading for manual execution + tradingPair: `${tradeData.symbol || symbol}/USDC`, + quickSwap: false }) }) const result = await response.json() if (response.ok && result.success) { - // Show detailed success message - let message = `āœ… Trade executed successfully!\n\n` - message += `šŸ“Š Order ID: ${result.txId}\n` - message += `šŸ’° Symbol: ${tradeData.symbol}\n` - message += `šŸ“ˆ Size: ${tradeData.size}\n` - message += `šŸ’µ Entry: $${tradeData.entry}\n` + // Show detailed success message for DEX execution + let message = `āœ… Real DEX Trade executed successfully!\n\n` + message += `šŸ“Š Transaction ID: ${result.trade?.txId || result.txId}\n` + message += `šŸ’° Symbol: ${tradeData.symbol || symbol}\n` + message += `šŸ“ˆ Size: ${tradeData.positionSize || tradeData.size}\n` + message += `šŸŖ DEX: ${result.trade?.dex || 'Jupiter'}\n` if (tradeData.sl) message += `šŸ›‘ Stop Loss: $${tradeData.sl}\n` - if (tradeData.tp) message += `šŸŽÆ Take Profit: $${tradeData.tp}\n` + if (tradeData.tp1) message += `šŸŽÆ Take Profit: $${tradeData.tp1}\n` - if (result.conditionalOrders && result.conditionalOrders.length > 0) { - message += `\nšŸ”„ Conditional orders: ${result.conditionalOrders.length} placed` + if (result.trade?.monitoring) { + message += `\nšŸ”„ Position monitoring: ACTIVE` } alert(message) @@ -471,10 +526,12 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP // Show detailed error message const errorMsg = result.error || 'Unknown error occurred' - if (errorMsg.includes('insufficient funds') || errorMsg.includes('balance')) { - alert(`āŒ Trade Failed: Insufficient Balance\n\nPlease deposit funds to your Drift account before placing trades.\n\nError: ${errorMsg}`) - } else if (errorMsg.includes('not logged in') || errorMsg.includes('Cannot execute trade')) { - alert(`āŒ Trade Failed: Authentication Issue\n\nPlease check your Drift connection in the settings.\n\nError: ${errorMsg}`) + if (errorMsg.includes('not configured') || errorMsg.includes('Wallet not initialized')) { + alert(`āŒ Trade Failed: Jupiter DEX Not Configured\n\nPlease configure your Jupiter DEX wallet in the settings before executing real trades.\n\nError: ${errorMsg}`) + } else if (errorMsg.includes('insufficient') || errorMsg.includes('balance')) { + alert(`āŒ Trade Failed: Insufficient Balance\n\nPlease ensure you have enough tokens in your wallet.\n\nError: ${errorMsg}`) + } else if (errorMsg.includes('Real Jupiter Perpetuals trading not yet implemented')) { + alert(`āŒ Real Trading Not Available\n\nReal Jupiter Perpetuals trading is still in development. This trade will be simulated instead.\n\nTo use real spot trading, reduce the leverage to 1x.`) } else { alert(`āŒ Trade Failed\n\nError: ${errorMsg}`) } @@ -1412,9 +1469,16 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP )} {/* Trade Modal */} + {console.log('šŸ”„ About to render TradeModal with:', { + isOpen: tradeModalOpen, + tradeData: tradeModalData + })} setTradeModalOpen(false)} + onClose={() => { + console.log('šŸ”„ TradeModal onClose called') + setTradeModalOpen(false) + }} tradeData={tradeModalData} onExecute={executeTrade} /> diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a88677a..a059439 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,5 +1,6 @@ services: app: + container_name: trader_dev build: target: development # Use development target for faster builds args: @@ -17,7 +18,7 @@ services: - TZ=Europe/Berlin - CHROMIUM_PATH=/usr/bin/chromium - DISABLE_CHROME_SANDBOX=true - - DISPLAY=${DISPLAY:-:0} + - DISPLAY=:0 - ALLOW_MANUAL_CAPTCHA=true - DATABASE_URL=file:./prisma/dev.db # Development optimizations @@ -47,8 +48,11 @@ services: - ./components:/app/components:cached - ./package.json:/app/package.json:ro + # Port mapping for development + ports: + - "9000:3000" + # X11 and display configuration for manual CAPTCHA solving - network_mode: host privileged: true # Faster health check for development