- 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
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
/**
|
|
* Withdrawal Execution API
|
|
*
|
|
* POST: Execute withdrawal of trading profits
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
import { calculateWithdrawalAmount, withdrawFromDrift } from '@/lib/drift/withdraw'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { sendTelegramNotification } from '@/lib/notifications/telegram'
|
|
|
|
const ENV_PATH = path.join(process.cwd(), '.env')
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
console.log('🎯 Manual withdrawal triggered')
|
|
|
|
// Calculate withdrawal amount with safety checks
|
|
const calculation = await calculateWithdrawalAmount()
|
|
|
|
if (!calculation.safeToWithdraw) {
|
|
console.log(`⚠️ Withdrawal blocked: ${calculation.reason}`)
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: calculation.reason,
|
|
availableProfit: calculation.availableProfit,
|
|
withdrawalAmount: calculation.withdrawalAmount,
|
|
})
|
|
}
|
|
|
|
console.log(`✅ Withdrawal approved: $${calculation.withdrawalAmount.toFixed(2)}`)
|
|
|
|
// Execute withdrawal
|
|
const result = await withdrawFromDrift(calculation.withdrawalAmount)
|
|
|
|
if (!result.success) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: result.error,
|
|
}, { status: 500 })
|
|
}
|
|
|
|
// Update LAST_WITHDRAWAL_TIME and TOTAL_WITHDRAWN in .env
|
|
const currentTotal = parseFloat(process.env.TOTAL_WITHDRAWN || '0')
|
|
const newTotal = currentTotal + calculation.withdrawalAmount
|
|
const now = new Date().toISOString()
|
|
|
|
let envContent = fs.readFileSync(ENV_PATH, 'utf-8')
|
|
|
|
// Update LAST_WITHDRAWAL_TIME
|
|
const timeRegex = /^LAST_WITHDRAWAL_TIME=.*$/m
|
|
if (timeRegex.test(envContent)) {
|
|
envContent = envContent.replace(timeRegex, `LAST_WITHDRAWAL_TIME=${now}`)
|
|
} else {
|
|
envContent += `\nLAST_WITHDRAWAL_TIME=${now}`
|
|
}
|
|
|
|
// Update TOTAL_WITHDRAWN
|
|
const totalRegex = /^TOTAL_WITHDRAWN=.*$/m
|
|
if (totalRegex.test(envContent)) {
|
|
envContent = envContent.replace(totalRegex, `TOTAL_WITHDRAWN=${newTotal}`)
|
|
} else {
|
|
envContent += `\nTOTAL_WITHDRAWN=${newTotal}`
|
|
}
|
|
|
|
fs.writeFileSync(ENV_PATH, envContent)
|
|
|
|
// Send Telegram notification
|
|
try {
|
|
await sendTelegramNotification({
|
|
type: 'withdrawal',
|
|
amount: calculation.withdrawalAmount,
|
|
signature: result.signature!,
|
|
availableProfit: calculation.availableProfit,
|
|
totalWithdrawn: newTotal,
|
|
})
|
|
} catch (telegramError) {
|
|
console.error('Failed to send Telegram notification:', telegramError)
|
|
// Don't fail the withdrawal if notification fails
|
|
}
|
|
|
|
console.log(`✅ Withdrawal complete! $${calculation.withdrawalAmount.toFixed(2)} → wallet`)
|
|
console.log(`📊 Total withdrawn: $${newTotal.toFixed(2)}`)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
amount: calculation.withdrawalAmount,
|
|
signature: result.signature,
|
|
totalWithdrawn: newTotal,
|
|
timestamp: now,
|
|
})
|
|
} catch (error: any) {
|
|
console.error('❌ Withdrawal execution failed:', error)
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: error.message || 'Withdrawal execution failed',
|
|
}, { status: 500 })
|
|
}
|
|
}
|