import { Connection, Keypair } from '@solana/web3.js' import { DriftClient, Wallet, OrderType, PositionDirection, MarketType, convertToNumber, BASE_PRECISION, PRICE_PRECISION, BN, type PerpPosition } from '@drift-labs/sdk' export interface TradeParams { symbol: string side: 'BUY' | 'SELL' amount: number // USD amount orderType?: 'MARKET' | 'LIMIT' price?: number } export interface TradeResult { success: boolean txId?: string error?: string executedPrice?: number executedAmount?: number } export interface Position { symbol: string side: 'LONG' | 'SHORT' size: number entryPrice: number unrealizedPnl: number } export class DriftTradingService { private connection: Connection private wallet: Wallet private driftClient: DriftClient constructor() { const rpcUrl = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com' const secret = process.env.SOLANA_PRIVATE_KEY if (!secret) throw new Error('Missing SOLANA_PRIVATE_KEY in env') const keypair = Keypair.fromSecretKey(Buffer.from(JSON.parse(secret))) this.connection = new Connection(rpcUrl, 'confirmed') this.wallet = new Wallet(keypair) this.driftClient = new DriftClient({ connection: this.connection, wallet: this.wallet, env: 'mainnet-beta', opts: { commitment: 'confirmed' } }) } async executeTrade(params: TradeParams): Promise { try { await this.driftClient.subscribe() const marketIndex = await this.getMarketIndex(params.symbol) const direction = params.side === 'BUY' ? PositionDirection.LONG : PositionDirection.SHORT const orderType = params.orderType === 'LIMIT' ? OrderType.LIMIT : OrderType.MARKET const price = params.price ? new BN(Math.round(params.price * PRICE_PRECISION.toNumber())) : undefined const baseAmount = new BN(Math.round(params.amount * BASE_PRECISION.toNumber())) const txSig = await this.driftClient.placeAndTakePerpOrder({ marketIndex, direction, baseAssetAmount: baseAmount, orderType, price, marketType: MarketType.PERP }) // Fetch fill price and amount (simplified) return { success: true, txId: txSig } } catch (e: any) { return { success: false, error: e.message } } finally { await this.driftClient.unsubscribe() } } async getPositions(): Promise { await this.driftClient.subscribe() const user = this.driftClient.getUser() // Example: check first 10 market indices (should be replaced with actual market list) const positions: Position[] = [] for (let marketIndex = 0; marketIndex < 10; marketIndex++) { const p = user.getPerpPosition(marketIndex) if (!p || p.baseAssetAmount.isZero()) continue // TODO: Calculate unrealizedPnl if SDK exposes it positions.push({ symbol: this.getSymbolFromMarketIndex(marketIndex), side: p.baseAssetAmount.gt(new BN(0)) ? 'LONG' : 'SHORT', size: convertToNumber(p.baseAssetAmount, BASE_PRECISION), entryPrice: convertToNumber(p.quoteEntryAmount, PRICE_PRECISION), unrealizedPnl: 0 }) } await this.driftClient.unsubscribe() return positions } // Helper: map symbol to market index (stub, should use Drift markets config) private async getMarketIndex(symbol: string): Promise { // TODO: Replace with real mapping if (symbol === 'BTCUSD') return 0 if (symbol === 'ETHUSD') return 1 throw new Error('Unknown symbol: ' + symbol) } // Helper: map market index to symbol (stub) private getSymbolFromMarketIndex(index: number): string { if (index === 0) return 'BTCUSD' if (index === 1) return 'ETHUSD' return 'UNKNOWN' } } export const driftTradingService = new DriftTradingService()