Fix get Gas fees + position direction list

This commit is contained in:
2025-09-26 12:02:07 +07:00
parent bcfeb693ce
commit 1e19e29cec
7 changed files with 213 additions and 50 deletions

View File

@@ -1,4 +1,5 @@
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Evm;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services namespace Managing.Application.Abstractions.Services
@@ -19,5 +20,7 @@ namespace Managing.Application.Abstractions.Services
Task<List<Balance>> GetWalletBalanceAsync(string address, Ticker[] assets, string[] chains); Task<List<Balance>> GetWalletBalanceAsync(string address, Ticker[] assets, string[] chains);
Task<decimal> GetEstimatedGasFeeUsdAsync(); Task<decimal> GetEstimatedGasFeeUsdAsync();
Task<GasFeeData> GetGasFeeDataAsync();
} }
} }

View File

@@ -614,6 +614,7 @@ public class TradingBotBase : ITradingBot
{ {
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled);
} }
if (positionForSignal.TakeProfit2 != null) if (positionForSignal.TakeProfit2 != null)
{ {
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Cancelled); positionForSignal.TakeProfit2.SetStatus(TradeStatus.Cancelled);
@@ -679,6 +680,7 @@ public class TradingBotBase : ITradingBot
{ {
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled);
} }
if (positionForSignal.TakeProfit2 != null) if (positionForSignal.TakeProfit2 != null)
{ {
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Cancelled); positionForSignal.TakeProfit2.SetStatus(TradeStatus.Cancelled);
@@ -1138,7 +1140,8 @@ public class TradingBotBase : ITradingBot
} }
else else
{ {
var command = new ClosePositionCommand(position, position.AccountId, lastPrice, isForBacktest: Config.IsForBacktest); var command = new ClosePositionCommand(position, position.AccountId, lastPrice,
isForBacktest: Config.IsForBacktest);
try try
{ {
Position closedPosition = null; Position closedPosition = null;
@@ -1265,7 +1268,7 @@ public class TradingBotBase : ITradingBot
{ {
// Use actual execution price based on direction // Use actual execution price based on direction
closingPrice = position.OriginDirection == TradeDirection.Long closingPrice = position.OriginDirection == TradeDirection.Long
? minPriceRecent // For LONG, SL hits at the low ? minPriceRecent // For LONG, SL hits at the low
: maxPriceRecent; // For SHORT, SL hits at the high : maxPriceRecent; // For SHORT, SL hits at the high
position.StopLoss.SetPrice(closingPrice, 2); position.StopLoss.SetPrice(closingPrice, 2);
@@ -1277,6 +1280,7 @@ public class TradingBotBase : ITradingBot
{ {
position.TakeProfit1.SetStatus(TradeStatus.Cancelled); position.TakeProfit1.SetStatus(TradeStatus.Cancelled);
} }
if (position.TakeProfit2 != null) if (position.TakeProfit2 != null)
{ {
position.TakeProfit2.SetStatus(TradeStatus.Cancelled); position.TakeProfit2.SetStatus(TradeStatus.Cancelled);
@@ -1292,7 +1296,7 @@ public class TradingBotBase : ITradingBot
{ {
// Use actual execution price based on direction // Use actual execution price based on direction
closingPrice = position.OriginDirection == TradeDirection.Long closingPrice = position.OriginDirection == TradeDirection.Long
? maxPriceRecent // For LONG, TP hits at the high ? maxPriceRecent // For LONG, TP hits at the high
: minPriceRecent; // For SHORT, TP hits at the low : minPriceRecent; // For SHORT, TP hits at the low
position.TakeProfit1.SetPrice(closingPrice, 2); position.TakeProfit1.SetPrice(closingPrice, 2);
@@ -1349,6 +1353,7 @@ public class TradingBotBase : ITradingBot
{ {
position.TakeProfit1.SetStatus(TradeStatus.Cancelled); position.TakeProfit1.SetStatus(TradeStatus.Cancelled);
} }
if (position.TakeProfit2 != null) if (position.TakeProfit2 != null)
{ {
position.TakeProfit2.SetStatus(TradeStatus.Cancelled); position.TakeProfit2.SetStatus(TradeStatus.Cancelled);
@@ -2163,7 +2168,8 @@ public class TradingBotBase : ITradingBot
PositionIdentifier = position.Identifier, PositionIdentifier = position.Identifier,
Ticker = position.Ticker, Ticker = position.Ticker,
Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage, Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage,
Fee = position.Open.Fee Fee = position.GasFees + position.UiFees,
Direction = position.OriginDirection
}; };
await platformGrain.OnPositionOpenAsync(positionOpenEvent); await platformGrain.OnPositionOpenAsync(positionOpenEvent);
break; break;

View File

@@ -0,0 +1,70 @@
using System.Text.Json.Serialization;
namespace Managing.Domain.Evm
{
/// <summary>
/// Gas fee data model
/// </summary>
public class GasFeeData
{
/// <summary>
/// Estimated gas fee in Wei
/// </summary>
[JsonPropertyName("estimatedGasFeeWei")]
public string? EstimatedGasFeeWei { get; set; }
/// <summary>
/// Estimated gas fee in ETH
/// </summary>
[JsonPropertyName("estimatedGasFeeEth")]
public string? EstimatedGasFeeEth { get; set; }
/// <summary>
/// Estimated gas fee in USD
/// </summary>
[JsonPropertyName("estimatedGasFeeUsd")]
public double? EstimatedGasFeeUsd { get; set; }
/// <summary>
/// ETH balance of the account
/// </summary>
[JsonPropertyName("ethBalance")]
public string? EthBalance { get; set; }
/// <summary>
/// Current ETH price in USD
/// </summary>
[JsonPropertyName("ethPrice")]
public double? EthPrice { get; set; }
/// <summary>
/// Whether the account has sufficient balance for gas fees
/// </summary>
[JsonPropertyName("hasSufficientBalance")]
public bool? HasSufficientBalance { get; set; }
/// <summary>
/// Error message if insufficient balance
/// </summary>
[JsonPropertyName("errorMessage")]
public string? ErrorMessage { get; set; }
/// <summary>
/// Maximum allowed gas fee in USD
/// </summary>
[JsonPropertyName("maxAllowedUsd")]
public double? MaxAllowedUsd { get; set; }
/// <summary>
/// Gas price in Wei
/// </summary>
[JsonPropertyName("gasPrice")]
public string? GasPrice { get; set; }
/// <summary>
/// Gas limit
/// </summary>
[JsonPropertyName("gasLimit")]
public string? GasLimit { get; set; }
}
}

View File

@@ -1,5 +1,6 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Evm;
namespace Managing.Infrastructure.Evm.Models.Proxy namespace Managing.Infrastructure.Evm.Models.Proxy
{ {
@@ -158,21 +159,10 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
public class GasFeeResponse : Web3ProxyResponse public class GasFeeResponse : Web3ProxyResponse
{ {
/// <summary> /// <summary>
/// Estimated gas fee in USD /// Gas fee data object
/// </summary> /// </summary>
[JsonPropertyName("estimatedGasFeeUsd")] [JsonPropertyName("data")]
public double? EstimatedGasFeeUsd { get; set; } public GasFeeData? Data { 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; }
} }
} }

View File

@@ -7,6 +7,7 @@ using System.Web;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Core.Exceptions; using Managing.Core.Exceptions;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Evm;
using Managing.Infrastructure.Evm.Models.Proxy; using Managing.Infrastructure.Evm.Models.Proxy;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -408,7 +409,34 @@ namespace Managing.Infrastructure.Evm.Services
throw new Web3ProxyException($"Gas fee request failed: {response.Error}"); throw new Web3ProxyException($"Gas fee request failed: {response.Error}");
} }
return (decimal)(response.EstimatedGasFeeUsd ?? 0); if (response.Data is null)
{
throw new Web3ProxyException("Gas fee data is null");
}
return (decimal)(response.Data.EstimatedGasFeeUsd ?? 0);
}
public async Task<GasFeeData> GetGasFeeDataAsync()
{
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}");
}
if (response.Data is null)
{
throw new Web3ProxyException("Gas fee data is null");
}
return response.Data;
} }
private async Task HandleErrorResponse(HttpResponseMessage response) private async Task HandleErrorResponse(HttpResponseMessage response)

View File

@@ -82,7 +82,7 @@ function checkMemoryUsage() {
* @param estimatedGasFee The estimated gas fee in wei * @param estimatedGasFee The estimated gas fee in wei
* @returns Object with balance check result and details * @returns Object with balance check result and details
*/ */
async function checkGasFeeBalance( export async function checkGasFeeBalance(
sdk: GmxSdk, sdk: GmxSdk,
estimatedGasFee: bigint estimatedGasFee: bigint
): Promise<{ ): Promise<{
@@ -151,9 +151,8 @@ async function checkGasFeeBalance(
* @param params The position increase parameters * @param params The position increase parameters
* @returns Estimated gas fee in wei * @returns Estimated gas fee in wei
*/ */
async function estimatePositionGasFee( export async function estimatePositionGasFee(
sdk: GmxSdk, sdk: GmxSdk,
params: PositionIncreaseParams
): Promise<bigint> { ): Promise<bigint> {
try { try {
// Estimate gas for the position opening transaction // Estimate gas for the position opening transaction
@@ -440,6 +439,7 @@ declare module 'fastify' {
getGmxTrade: typeof getGmxTrade; getGmxTrade: typeof getGmxTrade;
getGmxPositions: typeof getGmxPositions; getGmxPositions: typeof getGmxPositions;
swapGmxTokens: typeof swapGmxTokens; swapGmxTokens: typeof swapGmxTokens;
estimatePositionGasFee: typeof estimatePositionGasFee;
} }
} }
@@ -633,7 +633,7 @@ export const openGmxPositionImpl = async (
// Check gas fees before opening position // Check gas fees before opening position
console.log('⛽ Checking gas fees before opening position...'); console.log('⛽ Checking gas fees before opening position...');
const estimatedGasFee = await estimatePositionGasFee(sdk, params); const estimatedGasFee = await estimatePositionGasFee(sdk);
const gasFeeCheck = await checkGasFeeBalance(sdk, estimatedGasFee); const gasFeeCheck = await checkGasFeeBalance(sdk, estimatedGasFee);
if (!gasFeeCheck.hasSufficientBalance) { if (!gasFeeCheck.hasSufficientBalance) {
@@ -1417,6 +1417,7 @@ export default fp(async (fastify) => {
fastify.decorateRequest('claimGmxUiFees', claimGmxUiFees) fastify.decorateRequest('claimGmxUiFees', claimGmxUiFees)
fastify.decorateRequest('swapGmxTokens', swapGmxTokens) fastify.decorateRequest('swapGmxTokens', swapGmxTokens)
fastify.decorateRequest('checkGmxTokenAllowances', checkGmxTokenAllowances) fastify.decorateRequest('checkGmxTokenAllowances', checkGmxTokenAllowances)
fastify.decorateRequest('estimatePositionGasFee', estimatePositionGasFee)
// Set up cache refresh without blocking plugin registration // Set up cache refresh without blocking plugin registration
setupCacheRefresh(); setupCacheRefresh();

View File

@@ -2,11 +2,15 @@ import {FastifyPluginAsyncTypebox} from '@fastify/type-provider-typebox'
import {Type} from '@sinclair/typebox' import {Type} from '@sinclair/typebox'
import {TradeDirection} from '../../../generated/ManagingApiTypes.js' import {TradeDirection} from '../../../generated/ManagingApiTypes.js'
import { import {
checkGasFeeBalance,
estimatePositionGasFee,
getClaimableFundingFeesImpl, getClaimableFundingFeesImpl,
getClaimableUiFeesImpl, getClaimableUiFeesImpl,
getGmxRebateStatsImpl getGmxRebateStatsImpl
} from '../../../plugins/custom/gmx.js' } from '../../../plugins/custom/gmx.js'
const MAX_GAS_FEE_USD = 1.5; // Maximum gas fee in USD
const plugin: FastifyPluginAsyncTypebox = async (fastify) => { const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
// Define route to open a position // Define route to open a position
fastify.post('/open-position', { fastify.post('/open-position', {
@@ -194,6 +198,67 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
) )
}) })
// Define route to get gas fee estimation for opening a position
fastify.get('/gas-fee', {
schema: {
response: {
200: Type.Object({
success: Type.Boolean(),
data: Type.Optional(Type.Object({
estimatedGasFeeWei: Type.String(),
estimatedGasFeeEth: Type.String(),
estimatedGasFeeUsd: Type.Number(),
ethBalance: Type.String(),
ethPrice: Type.Number(),
hasSufficientBalance: Type.Boolean(),
errorMessage: Type.Optional(Type.String()),
maxAllowedUsd: Type.Number(),
gasPrice: Type.String(),
gasLimit: Type.String()
})),
error: Type.Optional(Type.String())
})
}
}
}, async (request, reply) => {
try {
// Use a default address for gas fee estimation
const defaultAccount = "0x0000000000000000000000000000000000000000"
// Get GMX client for the default account
const sdk = await request.getClientForAddress(defaultAccount)
// Call the two methods as mentioned
const estimatedGasFee = await estimatePositionGasFee(sdk);
const gasFeeCheck = await checkGasFeeBalance(sdk, estimatedGasFee);
// Format the response data
const data = {
estimatedGasFeeWei: estimatedGasFee.toString(),
estimatedGasFeeEth: (Number(estimatedGasFee) / 1e18).toFixed(6),
estimatedGasFeeUsd: gasFeeCheck.estimatedGasFeeUsd,
ethBalance: gasFeeCheck.ethBalance,
ethPrice: gasFeeCheck.ethPrice,
hasSufficientBalance: gasFeeCheck.hasSufficientBalance,
errorMessage: gasFeeCheck.errorMessage,
maxAllowedUsd: MAX_GAS_FEE_USD,
gasPrice: (Number(estimatedGasFee) / 500000).toString(), // Approximate gas price
gasLimit: "500000" // Base gas limit used in estimation
}
return {
success: true,
data
}
} catch (error) {
console.error('Error estimating gas fee:', error)
return {
success: false,
error: `Failed to estimate gas fee: ${error instanceof Error ? error.message : 'Unknown error'}`
}
}
})
// Define route to get all claimable fees and rebate stats // Define route to get all claimable fees and rebate stats
fastify.get('/claimable-summary', { fastify.get('/claimable-summary', {
schema: { schema: {