Get fees to claims
This commit is contained in:
@@ -75,6 +75,20 @@ namespace Managing.Api.Controllers
|
|||||||
return Ok(await _AccountService.GetAccountByUser(user, name, true, true));
|
return Ok(await _AccountService.GetAccountByUser(user, name, true, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the GMX claimable fees summary for a specific account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the account to get claimable fees for.</param>
|
||||||
|
/// <returns>The GMX claimable fees summary including funding fees, UI fees, and rebate stats.</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[Route("{name}/gmx-claimable-summary")]
|
||||||
|
public async Task<ActionResult<GmxClaimableSummary>> GetGmxClaimableSummary(string name)
|
||||||
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
var result = await _AccountService.GetGmxClaimableSummaryAsync(user, name);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a specific account by name for the authenticated user.
|
/// Deletes a specific account by name for the authenticated user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ public interface IAccountService
|
|||||||
Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
|
Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
|
||||||
Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
|
Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
|
||||||
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
|
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
|
||||||
|
Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Managing.Infrastructure.Evm.Abstractions
|
using Managing.Domain.Accounts;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions.Services
|
||||||
{
|
{
|
||||||
public interface IWeb3ProxyService
|
public interface IWeb3ProxyService
|
||||||
{
|
{
|
||||||
@@ -6,5 +8,6 @@ namespace Managing.Infrastructure.Evm.Abstractions
|
|||||||
Task<T> GetPrivyServiceAsync<T>(string endpoint, object payload = null);
|
Task<T> GetPrivyServiceAsync<T>(string endpoint, object payload = null);
|
||||||
Task<T> CallGmxServiceAsync<T>(string endpoint, object payload);
|
Task<T> CallGmxServiceAsync<T>(string endpoint, object payload);
|
||||||
Task<T> GetGmxServiceAsync<T>(string endpoint, object payload = null);
|
Task<T> GetGmxServiceAsync<T>(string endpoint, object payload = null);
|
||||||
|
Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(string account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Repositories;
|
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
@@ -14,6 +13,7 @@ public class AccountService : IAccountService
|
|||||||
private readonly IExchangeService _exchangeService;
|
private readonly IExchangeService _exchangeService;
|
||||||
private readonly IEvmManager _evmManager;
|
private readonly IEvmManager _evmManager;
|
||||||
private readonly ICacheService _cacheService;
|
private readonly ICacheService _cacheService;
|
||||||
|
private readonly IWeb3ProxyService _web3ProxyService;
|
||||||
private readonly ILogger<AccountService> _logger;
|
private readonly ILogger<AccountService> _logger;
|
||||||
|
|
||||||
public AccountService(
|
public AccountService(
|
||||||
@@ -21,13 +21,15 @@ public class AccountService : IAccountService
|
|||||||
ILogger<AccountService> logger,
|
ILogger<AccountService> logger,
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
IEvmManager evmManager,
|
IEvmManager evmManager,
|
||||||
ICacheService cacheService)
|
ICacheService cacheService,
|
||||||
|
IWeb3ProxyService web3ProxyService)
|
||||||
{
|
{
|
||||||
_accountRepository = accountRepository;
|
_accountRepository = accountRepository;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
_evmManager = evmManager;
|
_evmManager = evmManager;
|
||||||
_cacheService = cacheService;
|
_cacheService = cacheService;
|
||||||
|
_web3ProxyService = web3ProxyService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Account> CreateAccount(User user, Account request)
|
public async Task<Account> CreateAccount(User user, Account request)
|
||||||
@@ -161,6 +163,38 @@ public class AccountService : IAccountService
|
|||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName)
|
||||||
|
{
|
||||||
|
// Get the account for the user
|
||||||
|
var account = await GetAccountByUser(user, accountName, true, false);
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Account '{accountName}' not found for user '{user.Name}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the account has a valid address/key
|
||||||
|
if (string.IsNullOrEmpty(account.Key))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Account '{accountName}' does not have a valid address");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Call the Web3ProxyService to get GMX claimable summary
|
||||||
|
var infrastructureResponse = await _web3ProxyService.GetGmxClaimableSummaryAsync(account.Key);
|
||||||
|
|
||||||
|
// Map from infrastructure model to domain model
|
||||||
|
return infrastructureResponse;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is ArgumentException || ex is InvalidOperationException))
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting GMX claimable summary for account {AccountName} and user {UserName}",
|
||||||
|
accountName, user.Name);
|
||||||
|
throw new InvalidOperationException($"Failed to get GMX claimable summary: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ManageProperties(bool hideSecrets, bool getBalance, Account account)
|
private void ManageProperties(bool hideSecrets, bool getBalance, Account account)
|
||||||
{
|
{
|
||||||
if (account != null)
|
if (account != null)
|
||||||
|
|||||||
38
src/Managing.Domain/Accounts/GmxClaimableSummary.cs
Normal file
38
src/Managing.Domain/Accounts/GmxClaimableSummary.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
namespace Managing.Domain.Accounts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GMX claimable summary data containing funding fees, UI fees, and rebate stats
|
||||||
|
/// </summary>
|
||||||
|
public class GmxClaimableSummary
|
||||||
|
{
|
||||||
|
public FundingFeesData ClaimableFundingFees { get; set; }
|
||||||
|
public UiFeesData ClaimableUiFees { get; set; }
|
||||||
|
public RebateStatsData RebateStats { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Funding fees claimable data
|
||||||
|
/// </summary>
|
||||||
|
public class FundingFeesData
|
||||||
|
{
|
||||||
|
public double TotalUsdc { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UI fees claimable data
|
||||||
|
/// </summary>
|
||||||
|
public class UiFeesData
|
||||||
|
{
|
||||||
|
public double TotalUsdc { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rebate statistics data
|
||||||
|
/// </summary>
|
||||||
|
public class RebateStatsData
|
||||||
|
{
|
||||||
|
public double TotalRebateUsdc { get; set; }
|
||||||
|
public double DiscountUsdc { get; set; }
|
||||||
|
public double RebateFactor { get; set; }
|
||||||
|
public double DiscountFactor { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Managing.Infrastructure.Evm.Models.Proxy;
|
||||||
|
|
||||||
|
public class ClaimableFundingFees
|
||||||
|
{
|
||||||
|
[JsonProperty("totalUsdc")] public double TotalUsdc { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClaimableUiFees
|
||||||
|
{
|
||||||
|
[JsonProperty("totalUsdc")] public double TotalUsdc { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Data
|
||||||
|
{
|
||||||
|
[JsonProperty("claimableFundingFees")] public ClaimableFundingFees ClaimableFundingFees { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("claimableUiFees")] public ClaimableUiFees ClaimableUiFees { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("rebateStats")] public RebateStats RebateStats { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RebateStats
|
||||||
|
{
|
||||||
|
[JsonProperty("totalRebateUsdc")] public double TotalRebateUsdc { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("discountUsdc")] public double DiscountUsdc { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("rebateFactor")] public double RebateFactor { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("discountFactor")] public double DiscountFactor { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClaimingFeesResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("success")] public bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("data")] public Data Data { get; set; }
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using Managing.Infrastructure.Evm.Abstractions;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Infrastructure.Evm.Models.Proxy;
|
using Managing.Infrastructure.Evm.Models.Proxy;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Evm.Services
|
namespace Managing.Infrastructure.Evm.Services
|
||||||
{
|
{
|
||||||
@@ -146,6 +148,38 @@ namespace Managing.Infrastructure.Evm.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(string account)
|
||||||
|
{
|
||||||
|
var payload = new { account };
|
||||||
|
var response = await GetGmxServiceAsync<ClaimingFeesResponse>("/claimable-summary", payload);
|
||||||
|
|
||||||
|
|
||||||
|
if (response.Data == null)
|
||||||
|
{
|
||||||
|
throw new Web3ProxyException("GMX claimable summary data is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map from Web3Proxy response model to domain model
|
||||||
|
return new GmxClaimableSummary
|
||||||
|
{
|
||||||
|
ClaimableFundingFees = new FundingFeesData
|
||||||
|
{
|
||||||
|
TotalUsdc = response.Data.ClaimableFundingFees.TotalUsdc
|
||||||
|
},
|
||||||
|
ClaimableUiFees = new UiFeesData
|
||||||
|
{
|
||||||
|
TotalUsdc = response.Data.ClaimableUiFees.TotalUsdc
|
||||||
|
},
|
||||||
|
RebateStats = new RebateStatsData
|
||||||
|
{
|
||||||
|
TotalRebateUsdc = response.Data.RebateStats.TotalRebateUsdc,
|
||||||
|
DiscountUsdc = response.Data.RebateStats.DiscountUsdc,
|
||||||
|
RebateFactor = response.Data.RebateStats.RebateFactor,
|
||||||
|
DiscountFactor = response.Data.RebateStats.DiscountFactor
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task HandleErrorResponse(HttpResponseMessage response)
|
private async Task HandleErrorResponse(HttpResponseMessage response)
|
||||||
{
|
{
|
||||||
var statusCode = (int)response.StatusCode;
|
var statusCode = (int)response.StatusCode;
|
||||||
@@ -198,7 +232,7 @@ namespace Managing.Infrastructure.Evm.Services
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryString = new System.Text.StringBuilder("?");
|
var queryString = new StringBuilder("?");
|
||||||
bool isFirst = true;
|
bool isFirst = true;
|
||||||
|
|
||||||
foreach (var prop in properties)
|
foreach (var prop in properties)
|
||||||
|
|||||||
@@ -1122,10 +1122,10 @@ export const getClaimableFundingFeesImpl = async (
|
|||||||
sdk: GmxSdk
|
sdk: GmxSdk
|
||||||
): Promise<ClaimableFundingData> => {
|
): Promise<ClaimableFundingData> => {
|
||||||
try {
|
try {
|
||||||
const { marketsInfoData } = await getMarketsInfoWithCache(sdk);
|
const { marketsInfoData, tokensData } = await getMarketsInfoWithCache(sdk);
|
||||||
|
|
||||||
if (!marketsInfoData) {
|
if (!marketsInfoData || !tokensData) {
|
||||||
throw new Error("No markets info data available");
|
throw new Error("No markets info data or tokens data available");
|
||||||
}
|
}
|
||||||
|
|
||||||
const marketAddresses = Object.keys(marketsInfoData);
|
const marketAddresses = Object.keys(marketsInfoData);
|
||||||
@@ -1173,7 +1173,7 @@ export const getClaimableFundingFeesImpl = async (
|
|||||||
|
|
||||||
const result = await sdk.executeMulticall(multicallRequest);
|
const result = await sdk.executeMulticall(multicallRequest);
|
||||||
|
|
||||||
// Parse the response
|
// Parse the response and convert to USD
|
||||||
return Object.entries(result.data).reduce((claimableFundingData, [marketAddress, callsResult]: [string, any]) => {
|
return Object.entries(result.data).reduce((claimableFundingData, [marketAddress, callsResult]: [string, any]) => {
|
||||||
const market = marketsInfoData[marketAddress];
|
const market = marketsInfoData[marketAddress];
|
||||||
|
|
||||||
@@ -1181,12 +1181,30 @@ export const getClaimableFundingFeesImpl = async (
|
|||||||
return claimableFundingData;
|
return claimableFundingData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get market divisor for proper decimal conversion
|
// Get token data for price conversion
|
||||||
const marketDivisor = 1; // You might need to implement getMarketDivisor function
|
const longTokenData = tokensData[market.longToken.address];
|
||||||
|
const shortTokenData = tokensData[market.shortToken.address];
|
||||||
|
|
||||||
|
if (!longTokenData || !shortTokenData) {
|
||||||
|
console.warn(`Missing token data for market ${marketAddress}`);
|
||||||
|
return claimableFundingData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from wei to token units and then to USD
|
||||||
|
const longAmount = Number(callsResult.claimableFundingAmountLong.returnValues[0]);
|
||||||
|
const shortAmount = Number(callsResult.claimableFundingAmountShort.returnValues[0]);
|
||||||
|
|
||||||
|
// Convert from wei to token units using decimals
|
||||||
|
const longTokenUnits = longAmount / Math.pow(10, longTokenData.decimals);
|
||||||
|
const shortTokenUnits = shortAmount / Math.pow(10, shortTokenData.decimals);
|
||||||
|
|
||||||
|
// Convert to USD using token prices
|
||||||
|
const longUsdValue = longTokenUnits * (Number(longTokenData.prices.minPrice) / Math.pow(10, 30)); // GMX prices are in 30 decimals
|
||||||
|
const shortUsdValue = shortTokenUnits * (Number(shortTokenData.prices.minPrice) / Math.pow(10, 30));
|
||||||
|
|
||||||
claimableFundingData[marketAddress] = {
|
claimableFundingData[marketAddress] = {
|
||||||
claimableFundingAmountLong: Number(callsResult.claimableFundingAmountLong.returnValues[0]) / marketDivisor,
|
claimableFundingAmountLong: longUsdValue,
|
||||||
claimableFundingAmountShort: Number(callsResult.claimableFundingAmountShort.returnValues[0]) / marketDivisor,
|
claimableFundingAmountShort: shortUsdValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
return claimableFundingData;
|
return claimableFundingData;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import {FastifyPluginAsyncTypebox} from '@fastify/type-provider-typebox'
|
import {FastifyPluginAsyncTypebox} from '@fastify/type-provider-typebox'
|
||||||
import {Type} from '@sinclair/typebox'
|
import {Type} from '@sinclair/typebox'
|
||||||
import {TradeDirection} from '../../../generated/ManagingApiTypes'
|
import {TradeDirection} from '../../../generated/ManagingApiTypes'
|
||||||
|
import {
|
||||||
|
getClaimableFundingFeesImpl,
|
||||||
|
getClaimableUiFeesImpl,
|
||||||
|
getGmxRebateStatsImpl
|
||||||
|
} from '../../../plugins/custom/gmx.js'
|
||||||
|
|
||||||
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||||
// Define route to open a position
|
// Define route to open a position
|
||||||
@@ -149,6 +154,93 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
|||||||
account
|
account
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Define route to get all claimable fees and rebate stats
|
||||||
|
fastify.get('/claimable-summary', {
|
||||||
|
schema: {
|
||||||
|
querystring: Type.Object({
|
||||||
|
account: Type.String()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: Type.Object({
|
||||||
|
success: Type.Boolean(),
|
||||||
|
data: Type.Optional(Type.Object({
|
||||||
|
claimableFundingFees: Type.Object({
|
||||||
|
totalUsdc: Type.Number()
|
||||||
|
}),
|
||||||
|
claimableUiFees: Type.Object({
|
||||||
|
totalUsdc: Type.Number()
|
||||||
|
}),
|
||||||
|
rebateStats: Type.Object({
|
||||||
|
totalRebateUsdc: Type.Number(),
|
||||||
|
discountUsdc: Type.Number(),
|
||||||
|
rebateFactor: Type.Number(),
|
||||||
|
discountFactor: Type.Number()
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
error: Type.Optional(Type.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, async (request, reply) => {
|
||||||
|
try {
|
||||||
|
const { account } = request.query
|
||||||
|
|
||||||
|
// Get GMX client for the account
|
||||||
|
const sdk = await request.getClientForAddress(account)
|
||||||
|
|
||||||
|
// Call all three implementation functions in parallel
|
||||||
|
const [fundingFeesData, uiFeesData, rebateStatsData] = await Promise.all([
|
||||||
|
getClaimableFundingFeesImpl(sdk),
|
||||||
|
getClaimableUiFeesImpl(sdk),
|
||||||
|
getGmxRebateStatsImpl(sdk)
|
||||||
|
])
|
||||||
|
|
||||||
|
// Process funding fees data - only calculate totals
|
||||||
|
let totalFundingLong = 0
|
||||||
|
let totalFundingShort = 0
|
||||||
|
|
||||||
|
if (fundingFeesData) {
|
||||||
|
Object.values(fundingFeesData).forEach(marketData => {
|
||||||
|
totalFundingLong += marketData.claimableFundingAmountLong
|
||||||
|
totalFundingShort += marketData.claimableFundingAmountShort
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process UI fees data - only calculate totals
|
||||||
|
let totalUiFees = 0
|
||||||
|
|
||||||
|
if (uiFeesData) {
|
||||||
|
Object.values(uiFeesData).forEach(marketData => {
|
||||||
|
totalUiFees += marketData.claimableUiFeeAmount
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
claimableFundingFees: {
|
||||||
|
totalUsdc: totalFundingLong + totalFundingShort
|
||||||
|
},
|
||||||
|
claimableUiFees: {
|
||||||
|
totalUsdc: totalUiFees
|
||||||
|
},
|
||||||
|
rebateStats: {
|
||||||
|
totalRebateUsdc: rebateStatsData?.totalRebateUsd || 0,
|
||||||
|
discountUsdc: rebateStatsData?.discountUsd || 0,
|
||||||
|
rebateFactor: rebateStatsData?.rebateFactor || 0,
|
||||||
|
discountFactor: rebateStatsData?.discountFactor || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting claimable summary:', error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Failed to get claimable summary: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default plugin
|
export default plugin
|
||||||
@@ -16,9 +16,9 @@ describe('swap tokens implementation', () => {
|
|||||||
try {
|
try {
|
||||||
const result = await swapGmxTokensImpl(
|
const result = await swapGmxTokensImpl(
|
||||||
sdk,
|
sdk,
|
||||||
Ticker.GMX, // fromTicker
|
Ticker.BTC, // fromTicker
|
||||||
Ticker.USDC, // toTicker
|
Ticker.USDC, // toTicker
|
||||||
2.06 // amount
|
0.000056 // amount
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.strictEqual(typeof result, 'string')
|
assert.strictEqual(typeof result, 'string')
|
||||||
|
|||||||
@@ -209,6 +209,44 @@ export class AccountClient extends AuthorizedApiBase {
|
|||||||
}
|
}
|
||||||
return Promise.resolve<Account[]>(null as any);
|
return Promise.resolve<Account[]>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account_GetGmxClaimableSummary(name: string): Promise<GmxClaimableSummary> {
|
||||||
|
let url_ = this.baseUrl + "/Account/{name}/gmx-claimable-summary";
|
||||||
|
if (name === undefined || name === null)
|
||||||
|
throw new Error("The parameter 'name' must be defined.");
|
||||||
|
url_ = url_.replace("{name}", encodeURIComponent("" + name));
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||||
|
return this.http.fetch(url_, transformedOptions_);
|
||||||
|
}).then((_response: Response) => {
|
||||||
|
return this.processAccount_GetGmxClaimableSummary(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processAccount_GetGmxClaimableSummary(response: Response): Promise<GmxClaimableSummary> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as GmxClaimableSummary;
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<GmxClaimableSummary>(null as any);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BacktestClient extends AuthorizedApiBase {
|
export class BacktestClient extends AuthorizedApiBase {
|
||||||
@@ -2703,6 +2741,50 @@ export interface Chain {
|
|||||||
chainId?: number;
|
chainId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GmxClaimableSummary {
|
||||||
|
claimableFundingFees?: FundingFeesData | null;
|
||||||
|
claimableUiFees?: UiFeesData | null;
|
||||||
|
rebateStats?: RebateStatsData | null;
|
||||||
|
summary?: SummaryData | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FundingFeesData {
|
||||||
|
totalLongUsdc?: number;
|
||||||
|
totalShortUsdc?: number;
|
||||||
|
totalUsdc?: number;
|
||||||
|
markets?: { [key: string]: FundingFeesMarketData; } | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FundingFeesMarketData {
|
||||||
|
claimableFundingAmountLong?: number;
|
||||||
|
claimableFundingAmountShort?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UiFeesData {
|
||||||
|
totalUsdc?: number;
|
||||||
|
markets?: { [key: string]: UiFeesMarketData; } | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UiFeesMarketData {
|
||||||
|
claimableUiFeeAmount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RebateStatsData {
|
||||||
|
totalRebateUsdc?: number;
|
||||||
|
discountUsdc?: number;
|
||||||
|
volume?: number;
|
||||||
|
tier?: number;
|
||||||
|
rebateFactor?: number;
|
||||||
|
discountFactor?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SummaryData {
|
||||||
|
totalClaimableUsdc?: number;
|
||||||
|
hasFundingFees?: boolean;
|
||||||
|
hasUiFees?: boolean;
|
||||||
|
hasRebates?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Backtest {
|
export interface Backtest {
|
||||||
id: string;
|
id: string;
|
||||||
finalPnl: number;
|
finalPnl: number;
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import {useMemo, useState} from 'react'
|
||||||
|
import {AccountClient, GmxClaimableSummary} from '../../../generated/ManagingApi'
|
||||||
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
|
import {Table} from '../../../components/mollecules'
|
||||||
|
|
||||||
|
function AccountFee() {
|
||||||
|
const [accountName, setAccountName] = useState('')
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [data, setData] = useState<GmxClaimableSummary | null>(null)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const { apiUrl } = useApiUrlStore()
|
||||||
|
|
||||||
|
const handleFetch = async () => {
|
||||||
|
if (!accountName.trim()) {
|
||||||
|
setError('Please enter an account name')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const accountClient = new AccountClient({}, apiUrl)
|
||||||
|
const result = await accountClient.account_GetGmxClaimableSummary(accountName.trim())
|
||||||
|
setData(result)
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.message || 'Failed to fetch GMX claimable summary')
|
||||||
|
setData(null)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table columns for all sections
|
||||||
|
const dataColumns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: 'Type',
|
||||||
|
accessor: 'type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Amount (USDC)',
|
||||||
|
accessor: 'amount',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Funding fees table data
|
||||||
|
const fundingFeesData = useMemo(() => {
|
||||||
|
if (!data?.claimableFundingFees) return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'Total Funding Fees',
|
||||||
|
amount: `$${data.claimableFundingFees.totalUsdc?.toFixed(2) || '0.00'}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
// UI fees table data
|
||||||
|
const uiFeesData = useMemo(() => {
|
||||||
|
if (!data?.claimableUiFees) return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'Total UI Fees',
|
||||||
|
amount: `$${data.claimableUiFees.totalUsdc?.toFixed(2) || '0.00'}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
// Rebate stats table data
|
||||||
|
const rebateStatsData = useMemo(() => {
|
||||||
|
if (!data?.rebateStats) return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'Total Rebate USDC',
|
||||||
|
amount: `$${data.rebateStats.totalRebateUsdc?.toFixed(2) || '0.00'}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Discount USDC',
|
||||||
|
amount: `$${data.rebateStats.discountUsdc?.toFixed(2) || '0.00'}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Rebate Factor',
|
||||||
|
amount: (data.rebateStats.rebateFactor || 0).toFixed(4),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Discount Factor',
|
||||||
|
amount: (data.rebateStats.discountFactor || 0).toFixed(4),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
// Calculate total claimable for display
|
||||||
|
const totalClaimable = useMemo(() => {
|
||||||
|
if (!data) return 0
|
||||||
|
const fundingTotal = data.claimableFundingFees?.totalUsdc || 0
|
||||||
|
const uiTotal = data.claimableUiFees?.totalUsdc || 0
|
||||||
|
return fundingTotal + uiTotal
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6">
|
||||||
|
<h2 className="text-2xl font-bold mb-6">GMX Account Fees</h2>
|
||||||
|
|
||||||
|
{/* Input Section */}
|
||||||
|
<div className="mb-6 flex gap-4 items-end">
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Account Name</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter account name"
|
||||||
|
className="input input-bordered w-full max-w-xs"
|
||||||
|
value={accountName}
|
||||||
|
onChange={(e) => setAccountName(e.target.value)}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className={`btn btn-primary ${loading ? 'loading' : ''}`}
|
||||||
|
onClick={handleFetch}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? 'Fetching...' : 'Fetch'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error Display */}
|
||||||
|
{error && (
|
||||||
|
<div className="alert alert-error mb-6">
|
||||||
|
<div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m-2 2" />
|
||||||
|
</svg>
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Results Section */}
|
||||||
|
{data && (
|
||||||
|
<div className="space-y-8">
|
||||||
|
{/* Total Summary */}
|
||||||
|
<div className="stats shadow">
|
||||||
|
<div className="stat">
|
||||||
|
<div className="stat-title">Total Claimable</div>
|
||||||
|
<div className="stat-value text-primary">${totalClaimable.toFixed(2)}</div>
|
||||||
|
<div className="stat-desc">Combined funding fees and UI fees</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Funding Fees Section */}
|
||||||
|
{data.claimableFundingFees && (data.claimableFundingFees.totalUsdc ?? 0) > 0 && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold mb-4">Funding Fees</h3>
|
||||||
|
<Table
|
||||||
|
columns={dataColumns}
|
||||||
|
data={fundingFeesData}
|
||||||
|
showPagination={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* UI Fees Section */}
|
||||||
|
{data.claimableUiFees && (data.claimableUiFees.totalUsdc ?? 0) > 0 && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold mb-4">UI Fees</h3>
|
||||||
|
<Table
|
||||||
|
columns={dataColumns}
|
||||||
|
data={uiFeesData}
|
||||||
|
showPagination={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Rebate Stats Section */}
|
||||||
|
{data.rebateStats && ((data.rebateStats.totalRebateUsdc ?? 0) > 0 || (data.rebateStats.discountUsdc ?? 0) > 0) && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold mb-4">Rebate Statistics</h3>
|
||||||
|
<Table
|
||||||
|
columns={dataColumns}
|
||||||
|
data={rebateStatsData}
|
||||||
|
showPagination={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountFee
|
||||||
@@ -8,6 +8,7 @@ import MoneyManagementSettings from './moneymanagement/moneyManagement'
|
|||||||
import Theme from './theme'
|
import Theme from './theme'
|
||||||
import DefaultConfig from './defaultConfig/defaultConfig'
|
import DefaultConfig from './defaultConfig/defaultConfig'
|
||||||
import UserInfoSettings from './UserInfoSettings'
|
import UserInfoSettings from './UserInfoSettings'
|
||||||
|
import AccountFee from './accountFee/accountFee'
|
||||||
|
|
||||||
type TabsType = {
|
type TabsType = {
|
||||||
label: string
|
label: string
|
||||||
@@ -33,18 +34,23 @@ const tabs: TabsType = [
|
|||||||
label: 'Account Settings',
|
label: 'Account Settings',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: Theme,
|
Component: AccountFee,
|
||||||
index: 4,
|
index: 4,
|
||||||
|
label: 'Account Fee',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Component: Theme,
|
||||||
|
index: 5,
|
||||||
label: 'Theme',
|
label: 'Theme',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: DefaultConfig,
|
Component: DefaultConfig,
|
||||||
index: 5,
|
index: 6,
|
||||||
label: 'Quick Start Config',
|
label: 'Quick Start Config',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: HealthChecks,
|
Component: HealthChecks,
|
||||||
index: 6,
|
index: 7,
|
||||||
label: 'Health Checks',
|
label: 'Health Checks',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user