From a943619cbba09fc03e51e7311b74fac706e9e7e3 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:23:38 +0200 Subject: [PATCH 1/2] fix: dYdX drop inaccurate funding, takers-only fills, timeout 50s, Gains 6 pages - Remove perpetualPositions funding fetch (not date-scopable, cumulative all-time) - dYdX fills count = takers only (consistent with fee/notional calc) - maxDuration 30 -> 50 to cover paginated fetches in parallel - Gains MAX_PAGES 10 -> 6 (600 trades, safer budget) - Footnote: note dYdX funding excluded --- src/app/api/fee-compare/route.ts | 29 ++++++--------------------- src/components/fee-compare-client.tsx | 3 +-- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index b1c1c7e1..f13b0730 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -3,7 +3,7 @@ import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit"; import { keccak256 } from "js-sha3"; export const runtime = "nodejs"; -export const maxDuration = 30; +export const maxDuration = 50; const HL_API = "https://api.hyperliquid.xyz/info"; const GAINS_VARS_URL = "https://backend-arbitrum.gains.trade/trading-variables"; @@ -499,23 +499,7 @@ async function fetchDydxFills(dydxAddress: string, cutoffMs: number): Promise; - }; - for (const p of pfBody.positions ?? []) { - fundingUsd += parseFloat(p.netFunding ?? "0"); - } - } - } catch { /* funding is optional */ } - + // Only count taker fills — maker fees are rebates and distort the comparison const takers = allFills.filter((f) => (f.liquidity ?? "").toUpperCase() !== "MAKER"); let feesUsdc = 0; let notionalUsd = 0; @@ -523,13 +507,12 @@ async function fetchDydxFills(dydxAddress: string, cutoffMs: number): Promise 0 ? (feesUsdc / notionalUsd) * 10000 : 0, }; @@ -540,7 +523,7 @@ async function fetchGainsTrades(wallet: string, cutoffMs: number): Promise - dYdX v4: public indexer with full pagination and date filter. Funding from{" "} - perpetualPositions.netFunding. Address must be{" "} + dYdX v4: public indexer with full pagination and date filter. Taker fills only. Funding excluded (not date-scopable from public API). Address must be{" "} dydx1... Cosmos format.{" "} )} From 3c4f832a402d97c62a86f782838828d653a4dbaf Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 26 Aug 2026 23:31:04 +0200 Subject: [PATCH 2/2] fix: dydx fills cursor pagination (page=N was oldest-first, broke cutoff) --- src/app/api/fee-compare/route.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index f13b0730..42b8c490 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -474,18 +474,26 @@ async function fetchGmxTrades(wallet: string, cutoffMs: number): Promise { const allFills: Array<{ fee: string; price: string; size: string; liquidity?: string; createdAt: string }> = []; - let page = 1; + // Use createdBeforeOrAt cursor pagination — newest first, no page= param + // page=N returns oldest-first and breaks the cutoff early-exit logic + let cursor: string | null = null; const limit = 100; - outer: while (page <= 20) { + outer: for (let i = 0; i < 20; i++) { + const params = new URLSearchParams({ + address: dydxAddress, + subaccountNumber: "0", + limit: String(limit), + }); + if (cursor) params.set("createdBeforeOrAt", cursor); + const res = await fetch( - `${DYDX_INDEXER}/v4/fills?address=${encodeURIComponent(dydxAddress)}&subaccountNumber=0&limit=${limit}&page=${page}`, + `${DYDX_INDEXER}/v4/fills?${params}`, { signal: AbortSignal.timeout(10000) } ); if (!res.ok) break; const body = (await res.json()) as { fills?: Array<{ fee: string; price: string; size: string; liquidity?: string; createdAt: string }>; - totalResults?: number; }; const pageFills = body.fills ?? []; if (pageFills.length === 0) break; @@ -496,7 +504,8 @@ async function fetchDydxFills(dydxAddress: string, cutoffMs: number): Promise