import { Connection, PublicKey } from '@solana/web3.js'; // Types export interface TradingBalance { totalValue: number; availableBalance: number; marginUsed: number; unrealizedPnl: number; positions: { symbol: string; size: number; notionalValue: number; unrealizedPnl: number; side: 'long' | 'short'; }[]; } export interface Position { symbol: string; side: 'long' | 'short'; size: number; entryPrice: number; markPrice: number; unrealizedPnl: number; notionalValue: number; } export interface OrderRequest { symbol: string; side: 'buy' | 'sell'; type: 'market' | 'limit'; amount: number; price?: number; } export interface OrderResponse { success: boolean; orderId?: string; error?: string; } export interface TradeParams { symbol: string; side: 'BUY' | 'SELL'; amount: number; orderType: 'MARKET' | 'LIMIT'; price?: number; stopLoss?: number; takeProfit?: number; stopLossType?: string; takeProfitType?: string; } export interface TradeResult { success: boolean; txId?: string; executedPrice?: number; executedAmount?: number; conditionalOrders?: any[]; error?: string; } class DriftTradingService { private connection: Connection; private readonly accountPDA = '7LonnWut5i3h36xyMA5jbwnGFbnzXUPY2dsPfNaSsrTk'; constructor() { this.connection = new Connection( process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com', 'confirmed' ); } async initialize(): Promise { console.log('🚀 Initializing Direct Drift Trading Service...'); console.log('✅ Direct service ready - no SDK subscriptions needed'); } async login(): Promise<{ success: boolean; message?: string }> { console.log('🔐 Login to direct drift service...'); // In direct mode, we don't need to login since we're just reading account data return { success: true, message: 'Direct service - no login required' }; } private async getAccountData(): Promise { try { const accountInfo = await this.connection.getAccountInfo( new PublicKey(this.accountPDA) ); if (!accountInfo) { throw new Error('Account not found'); } return accountInfo.data; } catch (error) { console.error('❌ Failed to fetch account data:', error); throw error; } } private parseAccountData(data: Buffer) { try { // Extract USDC balance at offset 106 const usdcBalance = data.readBigInt64LE(106); const usdcValue = Number(usdcBalance) / 1_000_000; // USDC has 6 decimals // Extract SOL position at offset 432 (most current/accurate location) const solPosition = data.readBigInt64LE(432); const solAmount = Number(solPosition) / 1_000_000_000; // SOL has 9 decimals // Estimate SOL price (you could fetch this from an oracle) const solPrice = 17.11; // Current approximate price const solValue = solAmount * solPrice; // Calculate basic total from current positions let totalValue = usdcValue + solValue; // If we have a very low total but no active positions, this might indicate // settlement lag where profits from closed positions aren't yet reflected if (totalValue < 10 && solAmount === 0) { console.log(`⚠️ Low balance detected with no positions - possible settlement lag`); console.log(` This often happens after position closure before profit settlement`); // In this case, we might want to use the last known total or a fallback // For now, we'll note this condition but keep the calculated value } console.log(`💰 Parsed account data:`); console.log(` USDC Balance: $${usdcValue.toFixed(2)}`); console.log(` SOL Position: ${solAmount.toFixed(6)} SOL`); console.log(` SOL Value: $${solValue.toFixed(2)} (@ $${solPrice})`); console.log(` Total Value: $${totalValue.toFixed(2)}`); return { usdcBalance: usdcValue, solPosition: solAmount, solPrice, solValue, totalValue, settlementPending: totalValue < 10 && solAmount === 0 }; } catch (error) { console.error('❌ Failed to parse account data:', error); throw error; } } async getTradingBalance(): Promise { try { const data = await this.getAccountData(); const parsed = this.parseAccountData(data); // Calculate unrealized PnL (SOL value minus some baseline) const unrealizedPnl = parsed.solValue - (parsed.solPosition * 15); // Assuming $15 entry price return { totalValue: parsed.totalValue, availableBalance: parsed.usdcBalance, marginUsed: 0, // Not available without full account parsing unrealizedPnl, positions: parsed.solPosition > 0 ? [{ symbol: 'SOL', size: parsed.solPosition, notionalValue: parsed.solValue, unrealizedPnl, side: 'long' as const }] : [], // Add settlement status info ...(parsed.settlementPending && { _meta: { settlementPending: true, note: 'Low balance with no positions may indicate settlement lag after position closure' } }) }; } catch (error) { console.error('❌ Failed to get trading balance:', error); throw error; } } async getPositions(): Promise { try { const data = await this.getAccountData(); const parsed = this.parseAccountData(data); if (parsed.solPosition <= 0) { return []; } const unrealizedPnl = parsed.solValue - (parsed.solPosition * 15); // Assuming $15 entry return [{ symbol: 'SOL', side: 'long', size: parsed.solPosition, entryPrice: 15, // Estimated entry price markPrice: parsed.solPrice, unrealizedPnl, notionalValue: parsed.solValue }]; } catch (error) { console.error('❌ Failed to get positions:', error); throw error; } } async placeOrder(orderRequest: OrderRequest): Promise { console.log('📝 Order placement not implemented in direct mode:', orderRequest); return { success: false, error: 'Order placement requires SDK integration' }; } async executeTrade(tradeParams: TradeParams): Promise { console.log('🎯 Execute Trade Request:', tradeParams); try { // Simulated trade execution only console.log(`📝 Simulating ${tradeParams.side} ${tradeParams.amount} ${tradeParams.symbol}`); console.log(`💰 Order Type: ${tradeParams.orderType}`); if (tradeParams.price) { console.log(`💲 Price: $${tradeParams.price}`); } if (tradeParams.stopLoss) { console.log(`🛑 Stop Loss: $${tradeParams.stopLoss}`); } if (tradeParams.takeProfit) { console.log(`🎯 Take Profit: $${tradeParams.takeProfit}`); } // Return simulated success response return { success: true, txId: 'simulated_' + Date.now(), executedPrice: tradeParams.price || (tradeParams.symbol === 'SOL' ? 17.11 : 100), executedAmount: tradeParams.amount, conditionalOrders: [], error: undefined }; } catch (error: any) { console.error('❌ Trade execution failed:', error); return { success: false, error: error.message || 'Trade execution failed' }; } } async disconnect(): Promise { console.log('✅ Direct service disconnected (no cleanup needed)'); } async getServiceStatus(): Promise<{ drift: { connected: boolean; accountPDA: string }; }> { return { drift: { connected: true, accountPDA: this.accountPDA } }; } } // Export singleton instance export const driftTradingService = new DriftTradingService();