Add ETH and USDC balance check before start/restart bot and autoswap
This commit is contained in:
@@ -891,11 +891,15 @@ public class EvmManager : IEvmManager
|
||||
|
||||
public async Task<decimal> GetFee(string chainName)
|
||||
{
|
||||
var chain = ChainService.GetChain(chainName);
|
||||
var web3 = new Web3(chain.RpcUrl);
|
||||
var etherPrice = (await GetPrices(new List<string> { "ethereum" }))["ethereum"]["usd"];
|
||||
var fee = await GmxService.GetFee(web3, etherPrice);
|
||||
return fee;
|
||||
try
|
||||
{
|
||||
return await _web3ProxyService.GetEstimatedGasFeeUsdAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error getting estimated gas fee: {ex.Message}");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Trade>> GetOrders(Account account, Ticker ticker)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
|
||||
/// </summary>
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Error message if not successful
|
||||
/// </summary>
|
||||
@@ -44,25 +44,25 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Error message
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Error stack trace
|
||||
/// </summary>
|
||||
[JsonPropertyName("stack")]
|
||||
public string Stack { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// HTTP status code (added by service)
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public int StatusCode { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a formatted error message with type and message
|
||||
/// </summary>
|
||||
@@ -90,7 +90,7 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
|
||||
/// The error details from the API
|
||||
/// </summary>
|
||||
public Web3ProxyError Error { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Simple error message from API
|
||||
/// </summary>
|
||||
@@ -100,12 +100,12 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
|
||||
/// Creates a new Web3ProxyException from a structured error
|
||||
/// </summary>
|
||||
/// <param name="error">The error details</param>
|
||||
public Web3ProxyException(Web3ProxyError error)
|
||||
public Web3ProxyException(Web3ProxyError error)
|
||||
: base(error?.Message ?? "An error occurred in the Web3Proxy API")
|
||||
{
|
||||
Error = error;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Web3ProxyException from a simple error message
|
||||
/// </summary>
|
||||
@@ -121,7 +121,7 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
|
||||
/// </summary>
|
||||
/// <param name="message">Custom error message</param>
|
||||
/// <param name="error">The error details</param>
|
||||
public Web3ProxyException(string message, Web3ProxyError error)
|
||||
public Web3ProxyException(string message, Web3ProxyError error)
|
||||
: base(message)
|
||||
{
|
||||
Error = error;
|
||||
@@ -151,4 +151,28 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
|
||||
[JsonPropertyName("balances")]
|
||||
public List<Balance> Balances { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response model for gas fee information
|
||||
/// </summary>
|
||||
public class GasFeeResponse : Web3ProxyResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Estimated gas fee in USD
|
||||
/// </summary>
|
||||
[JsonPropertyName("estimatedGasFeeUsd")]
|
||||
public double? EstimatedGasFeeUsd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current ETH price in USD
|
||||
/// </summary>
|
||||
[JsonPropertyName("ethPrice")]
|
||||
public double? EthPrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gas price in Gwei
|
||||
/// </summary>
|
||||
[JsonPropertyName("gasPriceGwei")]
|
||||
public double? GasPriceGwei { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Core.Exceptions;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Infrastructure.Evm.Models.Proxy;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -77,6 +78,64 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
statusCode == HttpStatusCode.GatewayTimeout;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an error message indicates insufficient funds or allowance that should not be retried
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The error message to check</param>
|
||||
/// <returns>True if this is a non-retryable insufficient funds error</returns>
|
||||
private static bool IsInsufficientFundsError(string errorMessage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(errorMessage))
|
||||
return false;
|
||||
|
||||
var lowerError = errorMessage.ToLowerInvariant();
|
||||
|
||||
// Check for common insufficient funds/allowance error patterns
|
||||
return lowerError.Contains("erc20: transfer amount exceeds allowance") ||
|
||||
lowerError.Contains("insufficient funds") ||
|
||||
lowerError.Contains("insufficient balance") ||
|
||||
lowerError.Contains("execution reverted") &&
|
||||
(lowerError.Contains("allowance") || lowerError.Contains("balance")) ||
|
||||
lowerError.Contains("gas required exceeds allowance") ||
|
||||
lowerError.Contains("out of gas") ||
|
||||
lowerError.Contains("insufficient eth") ||
|
||||
lowerError.Contains("insufficient token");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the type of insufficient funds error based on the error message
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The error message to analyze</param>
|
||||
/// <returns>The type of insufficient funds error</returns>
|
||||
private static InsufficientFundsType GetInsufficientFundsType(string errorMessage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(errorMessage))
|
||||
return InsufficientFundsType.General;
|
||||
|
||||
var lowerError = errorMessage.ToLowerInvariant();
|
||||
|
||||
if (lowerError.Contains("erc20: transfer amount exceeds allowance") ||
|
||||
lowerError.Contains("allowance"))
|
||||
{
|
||||
return InsufficientFundsType.InsufficientAllowance;
|
||||
}
|
||||
|
||||
if (lowerError.Contains("gas required exceeds allowance") ||
|
||||
lowerError.Contains("out of gas") ||
|
||||
lowerError.Contains("insufficient eth"))
|
||||
{
|
||||
return InsufficientFundsType.InsufficientEth;
|
||||
}
|
||||
|
||||
if (lowerError.Contains("insufficient balance") ||
|
||||
lowerError.Contains("insufficient token"))
|
||||
{
|
||||
return InsufficientFundsType.InsufficientBalance;
|
||||
}
|
||||
|
||||
return InsufficientFundsType.General;
|
||||
}
|
||||
|
||||
private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<HttpResponseMessage>> httpCall, string operationName)
|
||||
{
|
||||
try
|
||||
@@ -91,6 +150,11 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
var result = await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
|
||||
return result ?? throw new Web3ProxyException($"Failed to deserialize response for {operationName}");
|
||||
}
|
||||
catch (InsufficientFundsException)
|
||||
{
|
||||
// Re-throw insufficient funds exceptions immediately without retrying
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Web3ProxyException))
|
||||
{
|
||||
_logger.LogError(ex, "Operation {OperationName} failed after all retry attempts", operationName);
|
||||
@@ -113,6 +177,11 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
var result = await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
|
||||
return result ?? throw new Web3ProxyException($"Failed to deserialize response for {operationName}");
|
||||
}
|
||||
catch (InsufficientFundsException)
|
||||
{
|
||||
// Re-throw insufficient funds exceptions immediately without retrying
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Web3ProxyException))
|
||||
{
|
||||
_logger.LogError(ex, "Operation {OperationName} failed after all retry attempts (IdempotencyKey: {IdempotencyKey})", operationName, idempotencyKey);
|
||||
@@ -325,6 +394,23 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
return response.Balances ?? new List<Balance>();
|
||||
}
|
||||
|
||||
public async Task<decimal> GetEstimatedGasFeeUsdAsync()
|
||||
{
|
||||
var response = await GetGmxServiceAsync<GasFeeResponse>("/gas-fee", null);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
throw new Web3ProxyException("Gas fee response is null");
|
||||
}
|
||||
|
||||
if (!response.Success)
|
||||
{
|
||||
throw new Web3ProxyException($"Gas fee request failed: {response.Error}");
|
||||
}
|
||||
|
||||
return (decimal)(response.EstimatedGasFeeUsd ?? 0);
|
||||
}
|
||||
|
||||
private async Task HandleErrorResponse(HttpResponseMessage response)
|
||||
{
|
||||
var statusCode = (int)response.StatusCode;
|
||||
@@ -337,6 +423,13 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
|
||||
if (errorResponse != null && !errorResponse.Success && !string.IsNullOrEmpty(errorResponse.Error))
|
||||
{
|
||||
// Check if this is an insufficient funds error that should not be retried
|
||||
if (IsInsufficientFundsError(errorResponse.Error))
|
||||
{
|
||||
var errorType = GetInsufficientFundsType(errorResponse.Error);
|
||||
throw new InsufficientFundsException(errorResponse.Error, errorType);
|
||||
}
|
||||
|
||||
// Handle the standard Web3Proxy error format
|
||||
throw new Web3ProxyException(errorResponse.Error);
|
||||
}
|
||||
@@ -351,20 +444,53 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
if (structuredErrorResponse?.ErrorDetails != null)
|
||||
{
|
||||
structuredErrorResponse.ErrorDetails.StatusCode = statusCode;
|
||||
|
||||
// Check if this is an insufficient funds error that should not be retried
|
||||
if (IsInsufficientFundsError(structuredErrorResponse.ErrorDetails.Message))
|
||||
{
|
||||
var errorType = GetInsufficientFundsType(structuredErrorResponse.ErrorDetails.Message);
|
||||
throw new InsufficientFundsException(structuredErrorResponse.ErrorDetails.Message, errorType);
|
||||
}
|
||||
|
||||
throw new Web3ProxyException(structuredErrorResponse.ErrorDetails);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is InsufficientFundsException)
|
||||
{
|
||||
// Re-throw insufficient funds exceptions as-is
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Check if the raw content contains insufficient funds errors
|
||||
if (IsInsufficientFundsError(content))
|
||||
{
|
||||
var errorType = GetInsufficientFundsType(content);
|
||||
throw new InsufficientFundsException(content, errorType);
|
||||
}
|
||||
|
||||
// If we couldn't parse as structured error, use the simple error or fallback
|
||||
throw new Web3ProxyException($"HTTP error {statusCode}: {content}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is InsufficientFundsException)
|
||||
{
|
||||
// Re-throw insufficient funds exceptions as-is
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Web3ProxyException))
|
||||
{
|
||||
SentrySdk.CaptureException(ex);
|
||||
// If we couldn't parse the error as JSON or another issue occurred
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Check if the raw content contains insufficient funds errors
|
||||
if (IsInsufficientFundsError(content))
|
||||
{
|
||||
var errorType = GetInsufficientFundsType(content);
|
||||
throw new InsufficientFundsException(content, errorType);
|
||||
}
|
||||
|
||||
throw new Web3ProxyException($"HTTP error {statusCode}: {content}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user