feat: Add automated profit withdrawal system
- UI page: /withdrawals with stats dashboard and config form - Settings API: GET/POST for .env configuration - Stats API: Real-time profit and withdrawal calculations - Execute API: Safe withdrawal with Drift SDK integration - Drift service: withdrawFromDrift() with USDC spot market (index 0) - Safety checks: Min withdrawal amount, min account balance, profit-only - Telegram notifications: Withdrawal alerts with Solscan links - Dashboard navigation: Added Withdrawals card (3-card grid) User goal: 10% of profits automatically withdrawn on schedule Current: Manual trigger ready, scheduled automation pending Files: 5 new (withdrawals page, 3 APIs, Drift service), 2 modified
This commit is contained in:
83
app/api/withdrawals/stats/route.ts
Normal file
83
app/api/withdrawals/stats/route.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Withdrawal Stats API
|
||||
*
|
||||
* Returns current account statistics for withdrawal calculations
|
||||
*/
|
||||
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getPrismaClient } from '@/lib/database/trades'
|
||||
import { initializeDriftService } from '@/lib/drift/client'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const prisma = getPrismaClient()
|
||||
|
||||
// Get total invested (from roadmap: $546)
|
||||
const totalInvested = 546
|
||||
|
||||
// Get current Drift balance
|
||||
const driftService = await initializeDriftService()
|
||||
const health = await driftService.getAccountHealth()
|
||||
const currentBalance = parseFloat(health.freeCollateral)
|
||||
|
||||
// Calculate total P&L from database
|
||||
const trades = await prisma.trade.findMany({
|
||||
where: {
|
||||
exitReason: { not: null },
|
||||
},
|
||||
select: {
|
||||
realizedPnL: true,
|
||||
},
|
||||
})
|
||||
|
||||
const totalPnL = trades.reduce((sum, trade) => sum + (trade.realizedPnL || 0), 0)
|
||||
|
||||
// Get total withdrawn from .env
|
||||
const totalWithdrawn = parseFloat(process.env.TOTAL_WITHDRAWN || '0')
|
||||
|
||||
// Calculate available profit
|
||||
const availableProfit = Math.max(0, currentBalance - totalInvested)
|
||||
|
||||
// Calculate next withdrawal amount
|
||||
const withdrawalPercent = parseFloat(process.env.WITHDRAWAL_PROFIT_PERCENT || '10')
|
||||
const nextWithdrawalAmount = availableProfit * (withdrawalPercent / 100)
|
||||
|
||||
// Calculate next withdrawal time
|
||||
let nextWithdrawalTime: string | null = null
|
||||
if (process.env.ENABLE_AUTO_WITHDRAWALS === 'true' && process.env.LAST_WITHDRAWAL_TIME) {
|
||||
const lastWithdrawal = new Date(process.env.LAST_WITHDRAWAL_TIME)
|
||||
const intervalHours = parseFloat(process.env.WITHDRAWAL_INTERVAL_HOURS || '168')
|
||||
const nextTime = new Date(lastWithdrawal.getTime() + intervalHours * 60 * 60 * 1000)
|
||||
nextWithdrawalTime = nextTime.toISOString()
|
||||
} else if (process.env.ENABLE_AUTO_WITHDRAWALS === 'true') {
|
||||
// First withdrawal - schedule from now
|
||||
const intervalHours = parseFloat(process.env.WITHDRAWAL_INTERVAL_HOURS || '168')
|
||||
const nextTime = new Date(Date.now() + intervalHours * 60 * 60 * 1000)
|
||||
nextWithdrawalTime = nextTime.toISOString()
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
currentBalance: parseFloat(currentBalance.toFixed(2)),
|
||||
totalInvested,
|
||||
totalPnL: parseFloat(totalPnL.toFixed(2)),
|
||||
totalWithdrawn,
|
||||
availableProfit: parseFloat(availableProfit.toFixed(2)),
|
||||
nextWithdrawalAmount: parseFloat(nextWithdrawalAmount.toFixed(2)),
|
||||
nextWithdrawalTime,
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('Failed to load withdrawal stats:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
currentBalance: 0,
|
||||
totalInvested: 546,
|
||||
totalPnL: 0,
|
||||
totalWithdrawn: 0,
|
||||
availableProfit: 0,
|
||||
nextWithdrawalAmount: 0,
|
||||
nextWithdrawalTime: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user