- Fixed LAST_WITHDRAWAL_TIME type (null | string) - Removed parseFloat on health.freeCollateral (already number) - Fixed getDriftClient() → getClient() method name - Build now compiles successfully Deployed: Withdrawal system now live on dashboard
161 lines
5.2 KiB
TypeScript
161 lines
5.2 KiB
TypeScript
/**
|
|
* Withdrawal Execution Service
|
|
*
|
|
* Handles actual withdrawal from Drift Protocol to wallet
|
|
*/
|
|
|
|
import { BN } from '@project-serum/anchor'
|
|
import { PublicKey } from '@solana/web3.js'
|
|
import { initializeDriftService } from './client'
|
|
|
|
export interface WithdrawalResult {
|
|
success: boolean
|
|
amount?: number
|
|
signature?: string
|
|
error?: string
|
|
}
|
|
|
|
export async function withdrawFromDrift(
|
|
amountUSD: number,
|
|
destinationWallet?: string
|
|
): Promise<WithdrawalResult> {
|
|
try {
|
|
console.log(`💸 Initiating withdrawal: $${amountUSD.toFixed(2)}`)
|
|
|
|
const driftService = await initializeDriftService()
|
|
|
|
// Get USDC spot market index (typically 0 for USDC)
|
|
const usdcMarketIndex = 0
|
|
|
|
// Convert USD amount to token amount (USDC has 6 decimals)
|
|
// $50.25 → 50,250,000 (raw token amount with 6 decimals)
|
|
const tokenAmount = new BN(Math.floor(amountUSD * 1_000_000))
|
|
|
|
console.log(`📊 Withdrawal details:`)
|
|
console.log(` Amount: $${amountUSD.toFixed(2)} USDC`)
|
|
console.log(` Token amount: ${tokenAmount.toString()} (raw with 6 decimals)`)
|
|
console.log(` Market index: ${usdcMarketIndex}`)
|
|
|
|
// Get destination address (default to wallet public key if not specified)
|
|
const destination = destinationWallet
|
|
? new PublicKey(destinationWallet)
|
|
: new PublicKey(process.env.WALLET_PUBLIC_KEY!)
|
|
|
|
console.log(` Destination: ${destination.toString()}`)
|
|
|
|
// Execute withdrawal via Drift SDK
|
|
// withdraw(amount, marketIndex, associatedTokenAddress, reduceOnly, subAccountId, txParams, updateFuel)
|
|
const signature = await driftService.getClient().withdraw(
|
|
tokenAmount,
|
|
usdcMarketIndex,
|
|
destination,
|
|
false, // reduceOnly
|
|
0, // subAccountId
|
|
undefined, // txParams
|
|
false // updateFuel
|
|
)
|
|
|
|
console.log(`✅ Withdrawal successful!`)
|
|
console.log(` Transaction: ${signature}`)
|
|
console.log(` Explorer: https://solscan.io/tx/${signature}`)
|
|
|
|
// Confirm transaction
|
|
console.log('⏳ Confirming transaction on-chain...')
|
|
const connection = driftService.getConnection()
|
|
const confirmation = await connection.confirmTransaction(signature, 'confirmed')
|
|
|
|
if (confirmation.value.err) {
|
|
throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`)
|
|
}
|
|
|
|
console.log('✅ Transaction confirmed on-chain')
|
|
|
|
return {
|
|
success: true,
|
|
amount: amountUSD,
|
|
signature,
|
|
}
|
|
} catch (error: any) {
|
|
console.error('❌ Withdrawal failed:', error)
|
|
return {
|
|
success: false,
|
|
error: error.message || 'Unknown error during withdrawal',
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function calculateWithdrawalAmount(): Promise<{
|
|
availableProfit: number
|
|
withdrawalAmount: number
|
|
safeToWithdraw: boolean
|
|
reason?: string
|
|
}> {
|
|
try {
|
|
const driftService = await initializeDriftService()
|
|
const health = await driftService.getAccountHealth()
|
|
const currentBalance = health.freeCollateral // Already a number
|
|
|
|
// Configuration
|
|
const totalInvested = 546 // From roadmap
|
|
const withdrawalPercent = parseFloat(process.env.WITHDRAWAL_PROFIT_PERCENT || '10')
|
|
const minWithdrawalAmount = parseFloat(process.env.MIN_WITHDRAWAL_AMOUNT || '50')
|
|
const minAccountBalance = parseFloat(process.env.MIN_ACCOUNT_BALANCE || '500')
|
|
|
|
// Calculate available profit
|
|
const availableProfit = Math.max(0, currentBalance - totalInvested)
|
|
const withdrawalAmount = availableProfit * (withdrawalPercent / 100)
|
|
|
|
console.log(`💰 Withdrawal calculation:`)
|
|
console.log(` Current balance: $${currentBalance.toFixed(2)}`)
|
|
console.log(` Total invested: $${totalInvested.toFixed(2)}`)
|
|
console.log(` Available profit: $${availableProfit.toFixed(2)}`)
|
|
console.log(` Withdrawal %: ${withdrawalPercent}%`)
|
|
console.log(` Withdrawal amount: $${withdrawalAmount.toFixed(2)}`)
|
|
console.log(` Min withdrawal: $${minWithdrawalAmount.toFixed(2)}`)
|
|
console.log(` Min account balance: $${minAccountBalance.toFixed(2)}`)
|
|
|
|
// Safety checks
|
|
if (availableProfit <= 0) {
|
|
return {
|
|
availableProfit,
|
|
withdrawalAmount: 0,
|
|
safeToWithdraw: false,
|
|
reason: 'No profits available (current balance ≤ total invested)',
|
|
}
|
|
}
|
|
|
|
if (withdrawalAmount < minWithdrawalAmount) {
|
|
return {
|
|
availableProfit,
|
|
withdrawalAmount,
|
|
safeToWithdraw: false,
|
|
reason: `Withdrawal amount ($${withdrawalAmount.toFixed(2)}) below minimum ($${minWithdrawalAmount.toFixed(2)})`,
|
|
}
|
|
}
|
|
|
|
const balanceAfterWithdrawal = currentBalance - withdrawalAmount
|
|
if (balanceAfterWithdrawal < minAccountBalance) {
|
|
return {
|
|
availableProfit,
|
|
withdrawalAmount,
|
|
safeToWithdraw: false,
|
|
reason: `Withdrawal would drop balance to $${balanceAfterWithdrawal.toFixed(2)} (below minimum $${minAccountBalance.toFixed(2)})`,
|
|
}
|
|
}
|
|
|
|
return {
|
|
availableProfit,
|
|
withdrawalAmount,
|
|
safeToWithdraw: true,
|
|
}
|
|
} catch (error: any) {
|
|
console.error('❌ Withdrawal calculation failed:', error)
|
|
return {
|
|
availableProfit: 0,
|
|
withdrawalAmount: 0,
|
|
safeToWithdraw: false,
|
|
reason: error.message,
|
|
}
|
|
}
|
|
}
|