Get fees to claims

This commit is contained in:
2025-06-13 14:22:38 +07:00
parent 5188b5faec
commit 3f34c56968
13 changed files with 580 additions and 20 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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',
},
]