feat: implement and type-safe DriftTradingService for Solana/Drift trading (Node 20+)
This commit is contained in:
121
lib/drift-trading.ts
Normal file
121
lib/drift-trading.ts
Normal 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()
|
||||
3381
package-lock.json
generated
3381
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,9 +8,13 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@drift-labs/sdk": "^2.126.0-beta.14",
|
||||
"@prisma/client": "^6.11.1",
|
||||
"@solana/web3.js": "^1.98.2",
|
||||
"next": "15.3.5",
|
||||
"prisma": "^6.11.1"
|
||||
"openai": "^5.8.3",
|
||||
"prisma": "^6.11.1",
|
||||
"puppeteer": "^24.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
@@ -20,6 +24,6 @@
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.5",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user