- Update trade validation to use real wallet balances from /api/wallet/balance - Enhance wallet API to auto-discover all major SPL tokens (USDC, USDT, etc.) - Improve AIAnalysisPanel to better extract and pass AI values to TradeModal - Configure Docker Compose for hot reloading with proper volume mounts - Remove hardcoded balance fallbacks in favor of live wallet data Result: Trading validation now uses accurate real-time wallet balances
155 lines
5.8 KiB
JavaScript
155 lines
5.8 KiB
JavaScript
import { NextResponse } from 'next/server'
|
||
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
|
||
import { getAssociatedTokenAddress, getAccount, TOKEN_PROGRAM_ID } from '@solana/spl-token'
|
||
|
||
export async function GET() {
|
||
try {
|
||
console.log('💰 Fetching real Solana wallet balance...')
|
||
|
||
// Check if wallet is configured
|
||
if (!process.env.SOLANA_PRIVATE_KEY) {
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: 'Wallet not configured',
|
||
message: 'SOLANA_PRIVATE_KEY not found in environment'
|
||
}, { status: 503 })
|
||
}
|
||
|
||
// Initialize connection and keypair
|
||
const rpcUrl = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
|
||
const connection = new Connection(rpcUrl, 'confirmed')
|
||
|
||
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY)
|
||
const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray))
|
||
|
||
// Get SOL balance
|
||
const balance = await connection.getBalance(keypair.publicKey)
|
||
const solBalance = balance / 1000000000 // Convert lamports to SOL
|
||
|
||
// Get current SOL price with fallback
|
||
let solPrice = 168.11 // Fallback price from our current market data
|
||
let change24h = 0
|
||
|
||
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 usdValue = solBalance * solPrice
|
||
|
||
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('<27> 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: totalValue,
|
||
availableBalance: totalValue,
|
||
positions: positions
|
||
},
|
||
wallet: {
|
||
publicKey: keypair.publicKey.toString(),
|
||
solBalance: solBalance,
|
||
usdValue: totalValue
|
||
},
|
||
timestamp: Date.now()
|
||
})
|
||
|
||
} catch (error) {
|
||
console.error('❌ Wallet balance API error:', error)
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: 'Failed to fetch wallet balance',
|
||
message: error.message
|
||
}, { status: 500 })
|
||
}
|
||
}
|