From 35ad732a0c4def7d8c342495dbc5bbccaccd8cec Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:02:52 +0200 Subject: [PATCH 1/3] fix: Gains USDC collateral array index (2 not 3) --- src/app/api/fee-compare/route.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index 7572fdf1b..25ea3b578 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -41,8 +41,9 @@ const EVM_WALLET_VENUES = new Set(["hyperliquid", "gains", "gmx-v2"]); const GAINS_CARRY_PRECISION = 1e18; // 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; From 04c3bfe19e9f46e15c6962691c48210f8f61c394 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:11:23 +0200 Subject: [PATCH 2/3] feat: fallback Gains funding estimate when API returns zero --- src/app/api/fee-compare/route.ts | 14 ++++++++++++++ src/components/fee-compare-client.tsx | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index 25ea3b578..e547a4f0d 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -146,6 +146,7 @@ type GainsWalletData = { events: number; feesUsdc: number; fundingFeesUsdc: number; + fundingEstimated: boolean; borrowingFeesUsdc: number; netCostUsdc: number; positionSizeUsdc: number; @@ -1047,11 +1048,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 From 9878d2671b6881bf8be4d992aba1a12d03d2b283 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:22:26 +0200 Subject: [PATCH 3/3] fix: gains funding precision 1e21 not 1e18 (1000x correction) --- src/app/api/fee-compare/route.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index e547a4f0d..c7ffe03c3 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -37,8 +37,9 @@ 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 is array[2] in vars.collaterals (0-indexed); its collateralIndex field = 3 (1-indexed). @@ -285,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; } }