CRITICAL FIX (Dec 9, 2025): Emergency place-exit-orders endpoint now updates database with on-chain order transaction signatures. Problem: - Emergency endpoint placed orders on-chain successfully - But database Trade record showed NULL for order tx fields - Monitoring tools showed false negatives (NULL when orders exist) - User frustrated: 'our database HAS TO reflect whats on chain' Root Cause: - place-exit-orders endpoint called placeExitOrders() directly - Successfully placed orders and returned signatures - But never updated database Trade table with returned tx IDs - Database out of sync with actual on-chain state Solution: - After successful order placement, query database for active trade - Update Trade.tp1OrderTx, tp2OrderTx, slOrderTx with returned signatures - Handle both single SL and dual stop configurations - Log each signature update for verification - Don't fail request if database update fails (orders already on-chain) Impact: - Database now accurately reflects on-chain order state - Monitoring tools (health checks, queries) show correct status - User can trust database as source of truth - Resolves disconnect between user's Drift UI observations and database Testing: - Called endpoint with SOL-PERP position parameters - Received 2 signatures (TP1, TP2) - Bug #76 still present - Database updated: tp1OrderTx and tp2OrderTx now populated - Logs confirm: 'Database updated with on-chain order signatures' Note: Bug #76 (SL order fails silently) still exists but database now accurately reflects whatever orders succeed. Files changed: - app/api/trading/place-exit-orders/route.ts (added database update logic)
146 lines
5.0 KiB
TypeScript
146 lines
5.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { placeExitOrders } from '@/lib/drift/orders'
|
|
|
|
/**
|
|
* Emergency endpoint to manually place exit orders
|
|
* Use when orders vanish but position still exists
|
|
*/
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
// Auth check
|
|
const authHeader = req.headers.get('authorization')
|
|
const apiSecret = process.env.API_SECRET_KEY
|
|
|
|
if (!authHeader || !apiSecret || !authHeader.includes(apiSecret)) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const body = await req.json()
|
|
const {
|
|
symbol,
|
|
direction,
|
|
entryPrice,
|
|
tp1Price,
|
|
tp2Price,
|
|
slPrice,
|
|
positionSizeUSD,
|
|
tp1SizePercent = 75,
|
|
tp2SizePercent = 0,
|
|
} = body
|
|
|
|
// Validate required fields
|
|
if (!symbol || !direction || !entryPrice || !tp1Price || !tp2Price || !slPrice || !positionSizeUSD) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: 'Missing required fields'
|
|
}, { status: 400 })
|
|
}
|
|
|
|
console.log('🛡️ Manually placing exit orders for existing position...')
|
|
console.log(` ${symbol} ${direction} @ $${entryPrice}`)
|
|
console.log(` TP1: $${tp1Price} (${tp1SizePercent}%)`)
|
|
console.log(` TP2: $${tp2Price} (${tp2SizePercent}%)`)
|
|
console.log(` SL: $${slPrice}`)
|
|
|
|
// Initialize Drift service BEFORE calling placeExitOrders
|
|
const { initializeDriftService } = await import('@/lib/drift/client')
|
|
await initializeDriftService()
|
|
console.log('✅ Drift service ready for order placement')
|
|
|
|
const result = await placeExitOrders({
|
|
symbol,
|
|
direction,
|
|
entryPrice,
|
|
tp1Price,
|
|
tp2Price,
|
|
stopLossPrice: slPrice,
|
|
positionSizeUSD,
|
|
tp1SizePercent,
|
|
tp2SizePercent,
|
|
})
|
|
|
|
if (result.success) {
|
|
console.log('✅ Exit orders placed successfully!')
|
|
console.log(` Signatures: ${result.signatures?.length || 0} orders placed`)
|
|
|
|
// CRITICAL FIX (Dec 9, 2025): Update database with on-chain order signatures
|
|
// Without this, database shows NULL even when orders exist on Drift
|
|
try {
|
|
const { getPrismaClient } = await import('@/lib/database/trades')
|
|
const prisma = getPrismaClient()
|
|
|
|
// Find the active trade for this symbol
|
|
const trade = await prisma.trade.findFirst({
|
|
where: {
|
|
symbol,
|
|
exitReason: null, // Still open
|
|
},
|
|
orderBy: {
|
|
createdAt: 'desc',
|
|
},
|
|
})
|
|
|
|
if (trade) {
|
|
console.log(`💾 Updating database Trade ${trade.id} with order signatures...`)
|
|
|
|
// Update with all returned signatures
|
|
// placeExitOrders returns: [tp1Sig, tp2Sig, slSig] or [tp1Sig, tp2Sig, softSlSig, hardSlSig]
|
|
const updateData: any = {}
|
|
|
|
if (result.signatures && result.signatures.length >= 2) {
|
|
updateData.tp1OrderTx = result.signatures[0]
|
|
updateData.tp2OrderTx = result.signatures[1]
|
|
console.log(` TP1 tx: ${result.signatures[0].substring(0, 8)}...`)
|
|
console.log(` TP2 tx: ${result.signatures[1].substring(0, 8)}...`)
|
|
}
|
|
|
|
if (result.signatures && result.signatures.length >= 3) {
|
|
updateData.slOrderTx = result.signatures[2]
|
|
console.log(` SL tx: ${result.signatures[2].substring(0, 8)}...`)
|
|
}
|
|
|
|
if (result.signatures && result.signatures.length >= 4) {
|
|
// Dual stops: soft (index 2) and hard (index 3)
|
|
updateData.softStopOrderTx = result.signatures[2]
|
|
updateData.hardStopOrderTx = result.signatures[3]
|
|
updateData.slOrderTx = null // Clear single SL when dual stops used
|
|
console.log(` Soft SL tx: ${result.signatures[2].substring(0, 8)}...`)
|
|
console.log(` Hard SL tx: ${result.signatures[3].substring(0, 8)}...`)
|
|
}
|
|
|
|
await prisma.trade.update({
|
|
where: { id: trade.id },
|
|
data: updateData,
|
|
})
|
|
|
|
console.log('✅ Database updated with on-chain order signatures')
|
|
} else {
|
|
console.warn('⚠️ No active trade found for symbol, skipping database update')
|
|
}
|
|
} catch (dbError) {
|
|
console.error('❌ Failed to update database with order signatures:', dbError)
|
|
// Don't fail the request - orders are already placed on-chain
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
signatures: result.signatures,
|
|
message: 'Exit orders placed on-chain and database updated'
|
|
})
|
|
} else {
|
|
console.error('❌ Failed to place exit orders:', result.error)
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: result.error
|
|
}, { status: 500 })
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error in place-exit-orders:', error)
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
}, { status: 500 })
|
|
}
|
|
}
|