Files
trading_bot_v3/app/api/drift/orders/route.js
mindesbunister d38511f580 feat: fix orphaned order detection and enhance automation UI
- Fixed Drift orders API to handle new object-based status format
- Updated cleanup API to properly detect orphaned TAKE PROFIT orders
- Changed status filtering from order.status === 0 to order.status.hasOwnProperty('open')

- Restored automation-v2 page with emergency stop functionality
- Added position monitor integration with real-time cleanup status
- Enhanced UI with emoji indicators and better status display
- Added emergency stop API endpoint for immediate trading halt

- Enhanced orphaned order detection for lingering SL/TP orders
- Added comprehensive debug logging for order processing
- Improved error handling and status reporting
- Real-time cleanup reporting in position monitor

 Verified working:
- Orders API correctly detects active orders with new Drift format
- Cleanup system successfully processes orphaned orders
- Position monitor shows accurate cleanup status
- Emergency stop functionality integrated
2025-07-26 20:07:40 +02:00

236 lines
8.6 KiB
JavaScript

import { NextResponse } from 'next/server'
import { executeWithFailover, getRpcStatus } from '../../../../lib/rpc-failover.js'
export async function GET() {
try {
console.log('📋 Getting Drift open orders...')
// Log RPC status
const rpcStatus = getRpcStatus()
console.log('🌐 RPC Status:', rpcStatus)
// Check if environment is configured
if (!process.env.SOLANA_PRIVATE_KEY) {
return NextResponse.json({
success: false,
error: 'Drift not configured - missing SOLANA_PRIVATE_KEY'
}, { status: 400 })
}
// Execute orders check with RPC failover
const result = await executeWithFailover(async (connection) => {
// Import Drift SDK components
const { DriftClient, initialize } = await import('@drift-labs/sdk')
const { Keypair } = await import('@solana/web3.js')
const { AnchorProvider, BN } = await import('@coral-xyz/anchor')
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY)
const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray))
// Use the correct Wallet class from @coral-xyz/anchor/dist/cjs/nodewallet
const { default: NodeWallet } = await import('@coral-xyz/anchor/dist/cjs/nodewallet.js')
const wallet = new NodeWallet(keypair)
// Initialize Drift SDK
const env = 'mainnet-beta'
const sdkConfig = initialize({ env })
const driftClient = new DriftClient({
connection,
wallet,
programID: sdkConfig.DRIFT_PROGRAM_ID,
opts: {
commitment: 'confirmed',
},
})
try {
await driftClient.subscribe()
console.log('✅ Connected to Drift for orders check')
// Check if user has account
let userAccount
try {
userAccount = await driftClient.getUserAccount()
} catch (accountError) {
await driftClient.unsubscribe()
throw new Error('No Drift user account found. Please initialize your account first.')
}
// Get open orders
const orders = userAccount.orders || []
// Debug: log ALL orders to see what we have
console.log(`📋 Raw orders length: ${orders.length}`)
orders.forEach((order, index) => {
if (!order.baseAssetAmount.isZero()) {
console.log(`📋 Order ${index}:`, {
orderId: order.orderId,
status: order.status,
orderType: order.orderType,
baseAssetAmount: order.baseAssetAmount.toString(),
price: order.price ? order.price.toString() : null,
triggerPrice: order.triggerPrice ? order.triggerPrice.toString() : null,
reduceOnly: order.reduceOnly,
marketIndex: order.marketIndex
})
}
})
// Filter for active orders - handle both numeric and object status formats
const activeOrders = orders.filter(order => {
if (order.baseAssetAmount.isZero()) return false
// Handle object-based status (new format)
if (typeof order.status === 'object') {
return order.status.hasOwnProperty('open')
}
// Handle numeric status (old format)
return order.status === 0
})
// Show ALL orders with non-zero amounts for debugging
const allOrders = orders.filter(order =>
!order.baseAssetAmount.isZero() // Only filter out empty orders
)
console.log(`📋 Found ${activeOrders.length} active orders, ${allOrders.length} total orders (${orders.length} order slots)`)
// Debug: log all order statuses
const statusCounts = orders.reduce((acc, order) => {
if (!order.baseAssetAmount.isZero()) {
acc[order.status] = (acc[order.status] || 0) + 1
}
return acc
}, {})
console.log('📊 Order status breakdown:', statusCounts)
const formattedOrders = allOrders.map(order => {
const marketIndex = order.marketIndex
const symbol = ['SOL', 'BTC', 'ETH', 'APT', 'AVAX', 'BNB', 'MATIC', 'ARB', 'DOGE', 'OP'][marketIndex] || `UNKNOWN_${marketIndex}`
let orderType = 'UNKNOWN'
// Handle object-based orderType
if (typeof order.orderType === 'object') {
if (order.orderType.market !== undefined) orderType = 'MARKET'
else if (order.orderType.limit !== undefined) orderType = 'LIMIT'
else if (order.orderType.triggerMarket !== undefined) orderType = 'TRIGGER_MARKET'
else if (order.orderType.triggerLimit !== undefined) orderType = 'TRIGGER_LIMIT'
else if (order.orderType.oracle !== undefined) orderType = 'ORACLE'
} else {
// Handle numeric orderType
switch (order.orderType) {
case 0: orderType = 'MARKET'; break
case 1: orderType = 'LIMIT'; break
case 2: orderType = 'TRIGGER_MARKET'; break
case 3: orderType = 'TRIGGER_LIMIT'; break
case 4: orderType = 'ORACLE'; break
}
}
let direction = 'UNKNOWN'
// Handle object-based direction
if (typeof order.direction === 'object') {
if (order.direction.long !== undefined) direction = 'LONG'
else if (order.direction.short !== undefined) direction = 'SHORT'
} else {
// Handle numeric direction
switch (order.direction) {
case 0: direction = 'LONG'; break
case 1: direction = 'SHORT'; break
}
}
// Get status as string
let statusString = 'UNKNOWN'
if (typeof order.status === 'object') {
if (order.status.open !== undefined) statusString = 'OPEN'
else if (order.status.filled !== undefined) statusString = 'FILLED'
else if (order.status.canceled !== undefined) statusString = 'CANCELED'
} else {
switch (order.status) {
case 0: statusString = 'OPEN'; break
case 1: statusString = 'FILLED'; break
case 2: statusString = 'CANCELED'; break
}
}
return {
orderId: order.orderId,
symbol: `${symbol}-PERP`,
orderType,
direction,
size: (Number(order.baseAssetAmount) / 1e9).toFixed(6),
price: order.price ? (Number(order.price) / 1e6).toFixed(4) : null,
triggerPrice: order.triggerPrice ? (Number(order.triggerPrice) / 1e6).toFixed(4) : null,
reduceOnly: order.reduceOnly,
status: statusString,
marketIndex,
isActive: typeof order.status === 'object' ? order.status.hasOwnProperty('open') : order.status === 0,
raw: {
status: order.status,
orderType: order.orderType,
direction: order.direction,
baseAssetAmount: order.baseAssetAmount.toString()
}
}
})
const ordersResult = {
success: true,
orders: formattedOrders,
totalOrders: allOrders.length, // Return ALL orders, not just active
activeOrders: activeOrders.length, // But also show active count
timestamp: Date.now(),
rpcEndpoint: getRpcStatus().currentEndpoint,
details: {
wallet: keypair.publicKey.toString(),
totalOrderSlots: orders.length,
activeOrderSlots: activeOrders.length,
allOrderSlots: allOrders.length
}
}
await driftClient.unsubscribe()
console.log('📋 Orders retrieved:', {
totalActiveOrders: activeOrders.length,
rpcEndpoint: getRpcStatus().currentEndpoint
})
return ordersResult
} catch (driftError) {
console.error('❌ Drift orders error:', driftError)
try {
await driftClient.unsubscribe()
} catch (cleanupError) {
console.warn('⚠️ Cleanup error:', cleanupError.message)
}
throw driftError
}
}, 3) // Max 3 retries across different RPCs
return NextResponse.json(result)
} catch (error) {
console.error('❌ Orders API error:', error)
return NextResponse.json({
success: false,
error: 'Failed to get Drift orders',
details: error.message,
rpcStatus: getRpcStatus()
}, { status: 500 })
}
}
export async function POST() {
return NextResponse.json({
message: 'Use GET method to retrieve Drift orders'
}, { status: 405 })
}