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));
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Deletes a specific account by name for the authenticated user.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,4 +13,5 @@ public interface IAccountService
|
||||
Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
|
||||
Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
|
||||
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
|
||||
{
|
||||
@@ -6,5 +8,6 @@ namespace Managing.Infrastructure.Evm.Abstractions
|
||||
Task<T> GetPrivyServiceAsync<T>(string endpoint, object payload = null);
|
||||
Task<T> CallGmxServiceAsync<T>(string endpoint, object payload);
|
||||
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.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
@@ -14,6 +13,7 @@ public class AccountService : IAccountService
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly IEvmManager _evmManager;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly IWeb3ProxyService _web3ProxyService;
|
||||
private readonly ILogger<AccountService> _logger;
|
||||
|
||||
public AccountService(
|
||||
@@ -21,13 +21,15 @@ public class AccountService : IAccountService
|
||||
ILogger<AccountService> logger,
|
||||
IExchangeService exchangeService,
|
||||
IEvmManager evmManager,
|
||||
ICacheService cacheService)
|
||||
ICacheService cacheService,
|
||||
IWeb3ProxyService web3ProxyService)
|
||||
{
|
||||
_accountRepository = accountRepository;
|
||||
_logger = logger;
|
||||
_exchangeService = exchangeService;
|
||||
_evmManager = evmManager;
|
||||
_cacheService = cacheService;
|
||||
_web3ProxyService = web3ProxyService;
|
||||
}
|
||||
|
||||
public async Task<Account> CreateAccount(User user, Account request)
|
||||
@@ -161,6 +163,38 @@ public class AccountService : IAccountService
|
||||
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)
|
||||
{
|
||||
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 Managing.Infrastructure.Evm.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Infrastructure.Evm.Models.Proxy;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
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)
|
||||
{
|
||||
var statusCode = (int)response.StatusCode;
|
||||
@@ -198,7 +232,7 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var queryString = new System.Text.StringBuilder("?");
|
||||
var queryString = new StringBuilder("?");
|
||||
bool isFirst = true;
|
||||
|
||||
foreach (var prop in properties)
|
||||
|
||||
@@ -1122,10 +1122,10 @@ export const getClaimableFundingFeesImpl = async (
|
||||
sdk: GmxSdk
|
||||
): Promise<ClaimableFundingData> => {
|
||||
try {
|
||||
const { marketsInfoData } = await getMarketsInfoWithCache(sdk);
|
||||
const { marketsInfoData, tokensData } = await getMarketsInfoWithCache(sdk);
|
||||
|
||||
if (!marketsInfoData) {
|
||||
throw new Error("No markets info data available");
|
||||
if (!marketsInfoData || !tokensData) {
|
||||
throw new Error("No markets info data or tokens data available");
|
||||
}
|
||||
|
||||
const marketAddresses = Object.keys(marketsInfoData);
|
||||
@@ -1173,7 +1173,7 @@ export const getClaimableFundingFeesImpl = async (
|
||||
|
||||
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]) => {
|
||||
const market = marketsInfoData[marketAddress];
|
||||
|
||||
@@ -1181,12 +1181,30 @@ export const getClaimableFundingFeesImpl = async (
|
||||
return claimableFundingData;
|
||||
}
|
||||
|
||||
// Get market divisor for proper decimal conversion
|
||||
const marketDivisor = 1; // You might need to implement getMarketDivisor function
|
||||
// Get token data for price conversion
|
||||
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] = {
|
||||
claimableFundingAmountLong: Number(callsResult.claimableFundingAmountLong.returnValues[0]) / marketDivisor,
|
||||
claimableFundingAmountShort: Number(callsResult.claimableFundingAmountShort.returnValues[0]) / marketDivisor,
|
||||
claimableFundingAmountLong: longUsdValue,
|
||||
claimableFundingAmountShort: shortUsdValue,
|
||||
};
|
||||
|
||||
return claimableFundingData;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import {FastifyPluginAsyncTypebox} from '@fastify/type-provider-typebox'
|
||||
import {Type} from '@sinclair/typebox'
|
||||
import {TradeDirection} from '../../../generated/ManagingApiTypes'
|
||||
import {
|
||||
getClaimableFundingFeesImpl,
|
||||
getClaimableUiFeesImpl,
|
||||
getGmxRebateStatsImpl
|
||||
} from '../../../plugins/custom/gmx.js'
|
||||
|
||||
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
// Define route to open a position
|
||||
@@ -149,6 +154,93 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
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
|
||||
@@ -16,9 +16,9 @@ describe('swap tokens implementation', () => {
|
||||
try {
|
||||
const result = await swapGmxTokensImpl(
|
||||
sdk,
|
||||
Ticker.GMX, // fromTicker
|
||||
Ticker.BTC, // fromTicker
|
||||
Ticker.USDC, // toTicker
|
||||
2.06 // amount
|
||||
0.000056 // amount
|
||||
)
|
||||
|
||||
assert.strictEqual(typeof result, 'string')
|
||||
|
||||
@@ -209,6 +209,44 @@ export class AccountClient extends AuthorizedApiBase {
|
||||
}
|
||||
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 {
|
||||
@@ -2703,6 +2741,50 @@ export interface Chain {
|
||||
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 {
|
||||
id: string;
|
||||
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 DefaultConfig from './defaultConfig/defaultConfig'
|
||||
import UserInfoSettings from './UserInfoSettings'
|
||||
import AccountFee from './accountFee/accountFee'
|
||||
|
||||
type TabsType = {
|
||||
label: string
|
||||
@@ -33,18 +34,23 @@ const tabs: TabsType = [
|
||||
label: 'Account Settings',
|
||||
},
|
||||
{
|
||||
Component: Theme,
|
||||
Component: AccountFee,
|
||||
index: 4,
|
||||
label: 'Account Fee',
|
||||
},
|
||||
{
|
||||
Component: Theme,
|
||||
index: 5,
|
||||
label: 'Theme',
|
||||
},
|
||||
{
|
||||
Component: DefaultConfig,
|
||||
index: 5,
|
||||
index: 6,
|
||||
label: 'Quick Start Config',
|
||||
},
|
||||
{
|
||||
Component: HealthChecks,
|
||||
index: 6,
|
||||
index: 7,
|
||||
label: 'Health Checks',
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user