Add ETH and USDC balance check before start/restart bot and autoswap
This commit is contained in:
@@ -5,6 +5,7 @@ using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Bots.Models;
|
||||
using Managing.Application.Orleans;
|
||||
using Managing.Common;
|
||||
using Managing.Core.Exceptions;
|
||||
using Managing.Domain.Statistics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -237,184 +238,193 @@ public class AgentGrain : Grain, IAgentGrain
|
||||
|
||||
public async Task<BalanceCheckResult> CheckAndEnsureEthBalanceAsync(Guid requestingBotId, string accountName)
|
||||
{
|
||||
try
|
||||
// Check if a swap is already in progress
|
||||
if (_state.State.IsSwapInProgress)
|
||||
{
|
||||
// Check if a swap is already in progress
|
||||
if (_state.State.IsSwapInProgress)
|
||||
{
|
||||
_logger.LogInformation("Swap already in progress for agent {UserId}, bot {RequestingBotId} will wait",
|
||||
this.GetPrimaryKeyLong(), requestingBotId);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.SwapInProgress,
|
||||
Message = "Swap operation already in progress",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
// Check cooldown period (5 minutes between swaps)
|
||||
if (_state.State.LastSwapTime.HasValue &&
|
||||
DateTime.UtcNow - _state.State.LastSwapTime.Value < TimeSpan.FromMinutes(5))
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Swap cooldown period active for agent {UserId}, bot {RequestingBotId} will wait",
|
||||
this.GetPrimaryKeyLong(), requestingBotId);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.SwapCooldownActive,
|
||||
Message = "Swap cooldown period active",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
// Get or refresh cached balance data
|
||||
var balanceData = await GetOrRefreshBalanceDataAsync(accountName);
|
||||
if (balanceData == null)
|
||||
{
|
||||
_logger.LogError("Failed to get balance data for account {AccountName}, user {UserId}",
|
||||
accountName, this.GetPrimaryKeyLong());
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.BalanceFetchError,
|
||||
Message = "Failed to fetch balance data",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Agent {UserId} balance check - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (cached: {IsCached})",
|
||||
this.GetPrimaryKeyLong(), balanceData.EthValueInUsd, balanceData.UsdcValue,
|
||||
_state.State.CachedBalanceData?.IsValid == true);
|
||||
|
||||
// Check USDC minimum balance first (this will stop the bot if insufficient)
|
||||
if (balanceData.UsdcValue < Constants.GMX.Config.MinimumPositionAmount)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"USDC balance is below minimum required amount - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (minimum: {Minimum})",
|
||||
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.MinimumPositionAmount);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.InsufficientUsdcBelowMinimum,
|
||||
Message =
|
||||
$"USDC balance below minimum required amount ({Constants.GMX.Config.MinimumPositionAmount} USD)",
|
||||
ShouldStopBot = true
|
||||
};
|
||||
}
|
||||
|
||||
// If ETH balance is sufficient, return success
|
||||
if (balanceData.EthValueInUsd >= Constants.GMX.Config.MinimumEthBalance)
|
||||
{
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = true,
|
||||
FailureReason = BalanceCheckFailureReason.None,
|
||||
Message = "Balance check successful",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
// Check if we have enough USDC for swap (need at least 5 USD for swap)
|
||||
if (balanceData.UsdcValue <
|
||||
(Constants.GMX.Config.MinimumPositionAmount + (decimal)Constants.GMX.Config.AutoSwapAmount))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Insufficient USDC balance for swap - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (need {AutoSwapAmount} USD for swap)",
|
||||
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.AutoSwapAmount);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.InsufficientUsdcForSwap,
|
||||
Message = $"Insufficient USDC balance for swap (need {Constants.GMX.Config.AutoSwapAmount} USD)",
|
||||
ShouldStopBot = true
|
||||
};
|
||||
}
|
||||
|
||||
// Mark swap as in progress
|
||||
_state.State.IsSwapInProgress = true;
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Initiating USDC to ETH swap for agent {UserId} - swapping 5 USDC",
|
||||
this.GetPrimaryKeyLong());
|
||||
|
||||
// Get user for the swap
|
||||
var userId = (int)this.GetPrimaryKeyLong();
|
||||
var user = await _userService.GetUserByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogError("User {UserId} not found for swap operation", userId);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.BalanceFetchError,
|
||||
Message = "User not found for swap operation",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
// Perform the swap
|
||||
var swapInfo = await _accountService.SwapGmxTokensAsync(user, accountName,
|
||||
Ticker.USDC, Ticker.ETH, Constants.GMX.Config.AutoSwapAmount);
|
||||
|
||||
if (swapInfo.Success)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Successfully swapped 5 USDC to ETH for agent {UserId}, transaction hash: {Hash}",
|
||||
userId, swapInfo.Hash);
|
||||
|
||||
// Update last swap time and invalidate cache
|
||||
_state.State.LastSwapTime = DateTime.UtcNow;
|
||||
_state.State.CachedBalanceData = null; // Invalidate cache after successful swap
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = true,
|
||||
FailureReason = BalanceCheckFailureReason.None,
|
||||
Message = "Swap completed successfully",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Failed to swap USDC to ETH for agent {UserId}: {Error}",
|
||||
userId, swapInfo.Error ?? swapInfo.Message);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.SwapExecutionError,
|
||||
Message = swapInfo.Error ?? swapInfo.Message ?? "Swap execution failed",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Always clear the swap in progress flag
|
||||
_state.State.IsSwapInProgress = false;
|
||||
await _state.WriteStateAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error checking/ensuring ETH balance for agent {UserId}, bot {RequestingBotId}",
|
||||
_logger.LogInformation("Swap already in progress for agent {UserId}, bot {RequestingBotId} will wait",
|
||||
this.GetPrimaryKeyLong(), requestingBotId);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.SwapInProgress,
|
||||
Message = "Swap operation already in progress",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
// Clear swap in progress flag on error
|
||||
_state.State.IsSwapInProgress = false;
|
||||
await _state.WriteStateAsync();
|
||||
// Check cooldown period (5 minutes between swaps)
|
||||
if (_state.State.LastSwapTime.HasValue &&
|
||||
DateTime.UtcNow - _state.State.LastSwapTime.Value < TimeSpan.FromMinutes(5))
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Swap cooldown period active for agent {UserId}, bot {RequestingBotId} will wait",
|
||||
this.GetPrimaryKeyLong(), requestingBotId);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.SwapCooldownActive,
|
||||
Message = "Swap cooldown period active",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
// Get or refresh cached balance data
|
||||
var balanceData = await GetOrRefreshBalanceDataAsync(accountName);
|
||||
if (balanceData == null)
|
||||
{
|
||||
_logger.LogError("Failed to get balance data for account {AccountName}, user {UserId}",
|
||||
accountName, this.GetPrimaryKeyLong());
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.BalanceFetchError,
|
||||
Message = ex.Message,
|
||||
Message = "Failed to fetch balance data",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Agent {UserId} balance check - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (cached: {IsCached})",
|
||||
this.GetPrimaryKeyLong(), balanceData.EthValueInUsd, balanceData.UsdcValue,
|
||||
_state.State.CachedBalanceData?.IsValid == true);
|
||||
|
||||
// Check USDC minimum balance first (this will stop the bot if insufficient)
|
||||
if (balanceData.UsdcValue < Constants.GMX.Config.MinimumPositionAmount)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"USDC balance is below minimum required amount - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (minimum: {Minimum})",
|
||||
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.MinimumPositionAmount);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.InsufficientUsdcBelowMinimum,
|
||||
Message =
|
||||
$"USDC balance below minimum required amount ({Constants.GMX.Config.MinimumPositionAmount} USD)",
|
||||
ShouldStopBot = true
|
||||
};
|
||||
}
|
||||
|
||||
// If ETH balance is sufficient, return success
|
||||
if (balanceData.EthValueInUsd >= Constants.GMX.Config.MinimumTradeEthBalanceUsd)
|
||||
{
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = true,
|
||||
FailureReason = BalanceCheckFailureReason.None,
|
||||
Message = "Balance check successful - Enough ETH balance for trading",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
|
||||
if (balanceData.EthValueInUsd < Constants.GMX.Config.MinimumSwapEthBalanceUsd)
|
||||
{
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.InsufficientEthBelowMinimum,
|
||||
Message = "ETH balance below minimum required amount",
|
||||
ShouldStopBot = true
|
||||
};
|
||||
}
|
||||
|
||||
// Check if we have enough USDC for swap (need at least 5 USD for swap)
|
||||
if (balanceData.UsdcValue <
|
||||
(Constants.GMX.Config.MinimumPositionAmount + (decimal)Constants.GMX.Config.AutoSwapAmount))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Insufficient USDC balance for swap - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (need {AutoSwapAmount} USD for swap)",
|
||||
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.AutoSwapAmount);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.InsufficientUsdcForSwap,
|
||||
Message = $"Insufficient USDC balance for swap (need {Constants.GMX.Config.AutoSwapAmount} USD)",
|
||||
ShouldStopBot = true
|
||||
};
|
||||
}
|
||||
|
||||
// Mark swap as in progress
|
||||
_state.State.IsSwapInProgress = true;
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Initiating USDC to ETH swap for agent {UserId} - swapping 5 USDC",
|
||||
this.GetPrimaryKeyLong());
|
||||
|
||||
// Get user for the swap
|
||||
var userId = (int)this.GetPrimaryKeyLong();
|
||||
var user = await _userService.GetUserByIdAsync(userId);
|
||||
|
||||
// Perform the swap
|
||||
var swapInfo = await _accountService.SwapGmxTokensAsync(user, accountName,
|
||||
Ticker.USDC, Ticker.ETH, Constants.GMX.Config.AutoSwapAmount);
|
||||
|
||||
if (swapInfo.Success)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Successfully swapped 5 USDC to ETH for agent {UserId}, transaction hash: {Hash}",
|
||||
userId, swapInfo.Hash);
|
||||
|
||||
// Update last swap time and invalidate cache
|
||||
_state.State.LastSwapTime = DateTime.UtcNow;
|
||||
_state.State.CachedBalanceData = null; // Invalidate cache after successful swap
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = true,
|
||||
FailureReason = BalanceCheckFailureReason.None,
|
||||
Message = "Swap completed successfully",
|
||||
ShouldStopBot = false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Failed to swap USDC to ETH for agent {UserId}: {Error}",
|
||||
userId, swapInfo.Error ?? swapInfo.Message);
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.SwapExecutionError,
|
||||
Message = swapInfo.Error ?? swapInfo.Message ?? "Swap execution failed",
|
||||
ShouldStopBot = true
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.InnerException is InsufficientFundsException insufficientFundsEx)
|
||||
{
|
||||
// Handle allowance exception (insufficient ETH for gas fees)
|
||||
_logger.LogError(insufficientFundsEx,
|
||||
"Insufficient funds during autoswap for agent {UserId}: {ErrorMessage}",
|
||||
this.GetPrimaryKeyLong(), insufficientFundsEx.Message);
|
||||
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.SwapExecutionError,
|
||||
Message = insufficientFundsEx.UserMessage ??
|
||||
"Insufficient ETH for gas fees during autoswap. Bot cannot continue trading.",
|
||||
ShouldStopBot = true
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during autoswap for agent {UserId}, bot {RequestingBotId}",
|
||||
this.GetPrimaryKeyLong(), requestingBotId);
|
||||
|
||||
return new BalanceCheckResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
FailureReason = BalanceCheckFailureReason.SwapExecutionError,
|
||||
Message = ex.Message,
|
||||
ShouldStopBot = true
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Always clear the swap in progress flag
|
||||
_state.State.IsSwapInProgress = false;
|
||||
await _state.WriteStateAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user