Add ETH and USDC balance check before start/restart bot and autoswap
This commit is contained in:
@@ -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