import { NextResponse } from 'next/server' import { executeWithFailover, getRpcStatus } from '../../../../lib/rpc-failover.js' export async function GET() { try { console.log('📊 Getting Drift positions...') // Log RPC status const rpcStatus = getRpcStatus() console.log('🌐 RPC Status:', rpcStatus) // Check if environment is configured if (!process.env.SOLANA_PRIVATE_KEY) { return NextResponse.json({ success: false, error: 'Drift not configured - missing SOLANA_PRIVATE_KEY' }, { status: 400 }) } // Execute positions check with RPC failover const result = await executeWithFailover(async (connection) => { // Import Drift SDK components const { DriftClient, initialize, calculatePositionPNL, MarketType } = await import('@drift-labs/sdk') const { Keypair } = await import('@solana/web3.js') const { AnchorProvider } = await import('@coral-xyz/anchor') const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY) const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray)) // Use the correct Wallet class from @coral-xyz/anchor/dist/cjs/nodewallet const { default: NodeWallet } = await import('@coral-xyz/anchor/dist/cjs/nodewallet.js') const wallet = new NodeWallet(keypair) // Initialize Drift SDK const env = 'mainnet-beta' const sdkConfig = initialize({ env }) const driftClient = new DriftClient({ connection, wallet, programID: sdkConfig.DRIFT_PROGRAM_ID, opts: { commitment: 'confirmed', }, }) try { await driftClient.subscribe() console.log('✅ Connected to Drift for positions') // Check if user has account let userAccount try { userAccount = await driftClient.getUserAccount() } catch (accountError) { await driftClient.unsubscribe() throw new Error('No Drift user account found. Please initialize your account first.') } // Get perpetual positions const perpPositions = userAccount.perpPositions || [] // Filter active positions const activePositions = perpPositions.filter(pos => pos.baseAssetAmount && !pos.baseAssetAmount.isZero() ) console.log(`📋 Found ${activePositions.length} active positions`) const positions = [] // Market symbols mapping (simplified) const marketSymbols = { 0: 'SOL-PERP', 1: 'BTC-PERP', 2: 'ETH-PERP', 3: 'APT-PERP', 4: 'BNB-PERP' } for (const position of activePositions) { try { const marketIndex = position.marketIndex const symbol = marketSymbols[marketIndex] || `MARKET-${marketIndex}` // Convert base asset amount from lamports const baseAssetAmount = Number(position.baseAssetAmount) const size = Math.abs(baseAssetAmount) / 1e9 // Convert from lamports to token amount // Determine side const side = baseAssetAmount > 0 ? 'long' : 'short' // Get quote asset amount (PnL) const quoteAssetAmount = Number(position.quoteAssetAmount) / 1e6 // Convert from micro-USDC // Get market data for current price (simplified - in production you'd get from oracle) let markPrice = 0 let entryPrice = 0 try { // Try to get market data from Drift const perpMarketAccount = driftClient.getPerpMarketAccount(marketIndex) if (perpMarketAccount) { markPrice = Number(perpMarketAccount.amm.lastMarkPriceTwap) / 1e6 } } catch (marketError) { console.warn(`⚠️ Could not get market data for ${symbol}:`, marketError.message) // Fallback prices markPrice = symbol.includes('SOL') ? 166.75 : symbol.includes('BTC') ? 121819 : symbol.includes('ETH') ? 3041.66 : 100 } // Calculate entry price (simplified) if (size > 0) { entryPrice = Math.abs(quoteAssetAmount / size) || markPrice } else { entryPrice = markPrice } // Calculate unrealized PnL const unrealizedPnl = side === 'long' ? (markPrice - entryPrice) * size : (entryPrice - markPrice) * size // Calculate notional value const notionalValue = size * markPrice const positionData = { symbol: symbol, side: side, size: size, entryPrice: entryPrice, markPrice: markPrice, unrealizedPnl: unrealizedPnl, notionalValue: notionalValue, marketIndex: marketIndex, marketType: 'perp', quoteAssetAmount: quoteAssetAmount, lastUpdateSlot: Number(position.lastCumulativeFundingRate || 0) } positions.push(positionData) console.log(`📊 Position: ${symbol} ${side.toUpperCase()} ${size.toFixed(4)} @ $${markPrice.toFixed(2)}`) } catch (positionError) { console.error(`❌ Error processing position ${position.marketIndex}:`, positionError) } } await driftClient.unsubscribe() return { success: true, positions: positions, totalPositions: positions.length, timestamp: Date.now(), rpcEndpoint: getRpcStatus().currentEndpoint, wallet: keypair.publicKey.toString() } } catch (driftError) { console.error('❌ Drift positions error:', driftError) try { await driftClient.unsubscribe() } catch (cleanupError) { console.warn('⚠️ Cleanup error:', cleanupError.message) } throw driftError } }, 3) // Max 3 retries across different RPCs return NextResponse.json(result) } catch (error) { console.error('❌ Positions API error:', error) return NextResponse.json({ success: false, error: 'Failed to get Drift positions', details: error.message, rpcStatus: getRpcStatus(), positions: [] }, { status: 500 }) } } export async function POST() { return NextResponse.json({ message: 'Use GET method to retrieve Drift positions' }, { status: 405 }) }