feat: implement and type-safe DriftTradingService for Solana/Drift trading (Node 20+)

This commit is contained in:
root
2025-07-09 10:47:47 +02:00
parent cfa6660abb
commit 6a1a4576a9
3 changed files with 3449 additions and 61 deletions

121
lib/drift-trading.ts Normal file
View File

@@ -0,0 +1,121 @@
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<TradeResult> {
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<Position[]> {
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<number> {
// 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()