diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index 7572fdf1b..c7ffe03c3 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -37,12 +37,14 @@ const EVM_WALLET_VENUES = new Set(["hyperliquid", "gains", "gmx-v2"]); // Rate caches // ────────────────────────────────────────────────────────────────────── -// Gains v6 borrowingRatePerSecondP and lastFundingRatePerSecondP use 1e18 precision -const GAINS_CARRY_PRECISION = 1e18; +// Gains v6 borrowingRatePerSecondP uses 1e18; lastFundingRatePerSecondP uses 1e21 +const GAINS_BORROW_PRECISION = 1e18; +const GAINS_FUNDING_PRECISION = 1e21; // Fallback when API doesn't expose per-pair data (~0.04%/day expressed per-second) const GAINS_BORROW_DEFAULT_PER_SEC = 0.0004 / 86400; -// USDC collateral index on Arbitrum (same as collateralIndex filter in fetchGainsTrades) -const GAINS_USDC_COLLATERAL_IDX = 3; +// USDC is array[2] in vars.collaterals (0-indexed); its collateralIndex field = 3 (1-indexed). +// The collateralIndex===3 filter in fetchGainsTrades refers to the field value, not the array position. +const GAINS_USDC_COLLATERAL_IDX = 2; let gainsFeeCache: { coinRoundTrip: Record; @@ -145,6 +147,7 @@ type GainsWalletData = { events: number; feesUsdc: number; fundingFeesUsdc: number; + fundingEstimated: boolean; borrowingFeesUsdc: number; netCostUsdc: number; positionSizeUsdc: number; @@ -283,13 +286,13 @@ async function fetchGainsFeeRates(): Promise<{ // Borrow rate per second (v2 takes precedence over legacy) const v2Borrow = v2BorrowParams[i]?.borrowingRatePerSecondP; if (v2Borrow) { - borrowPerSecPerCoin[p.from] = parseFloat(v2Borrow) / GAINS_CARRY_PRECISION; + borrowPerSecPerCoin[p.from] = parseFloat(v2Borrow) / GAINS_BORROW_PRECISION; } // Funding rate per second (absolute value — longs and shorts may face same magnitude) const fundingRate = fundingPairData[i]?.lastFundingRatePerSecondP; if (fundingRate) { - fundingPerSecPerCoin[p.from] = Math.abs(parseFloat(fundingRate)) / GAINS_CARRY_PRECISION; + fundingPerSecPerCoin[p.from] = Math.abs(parseFloat(fundingRate)) / GAINS_FUNDING_PRECISION; } } @@ -1046,11 +1049,24 @@ export async function GET(req: Request) { } } + // When the API doesn't return per-trade funding (meta absent or zero), fall back to + // the same per-second rate estimation used in the crossSim projection. + let fundingEstimated = false; + if (fundingFeesUsdc < 0.01 && Object.keys(gainsData.fundingPerSecPerCoin).length > 0) { + const gainsPositions = reconstructGainsPositions(usdcTrades, cutoffMs); + const est = estimateGainsFundingFees(gainsPositions, gainsData.fundingPerSecPerCoin); + if (est > 0.01) { + fundingFeesUsdc = est; + fundingEstimated = true; + } + } + const netCostUsdc = feesUsdc + fundingFeesUsdc + borrowingFeesUsdc; walletData = { events: usdcTrades.length, feesUsdc, fundingFeesUsdc, + fundingEstimated, borrowingFeesUsdc, netCostUsdc, positionSizeUsdc: notionalUsd, diff --git a/src/components/fee-compare-client.tsx b/src/components/fee-compare-client.tsx index c2434193b..a98e7b838 100644 --- a/src/components/fee-compare-client.tsx +++ b/src/components/fee-compare-client.tsx @@ -76,6 +76,7 @@ type GainsWalletData = { events: number; feesUsdc: number; fundingFeesUsdc: number; + fundingEstimated: boolean; borrowingFeesUsdc: number; netCostUsdc: number; positionSizeUsdc: number; @@ -748,6 +749,9 @@ function WalletSide({

Funding {gW.fundingFeesUsdc > 0 ? "paid" : "received"} + {gW.fundingEstimated && ( + est. + )}

0 ? "text-red-400" : "text-emerald-500"}`}> {gW.fundingFeesUsdc > 0