122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
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()
|