Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a35591e
feat: fee-compare tool — Hyperliquid vs Gains wallet analysis (#2054)
Flotapponnier Aug 25, 2026
570a057
fix: mobile table Gains fee column always visible, symmetric panels (…
Flotapponnier Aug 25, 2026
a2f4738
fix: revalidate hl-cohort + hl-history on aggregate purge (#2069)
Flotapponnier Aug 26, 2026
62e76e7
fix(app-store-ratings): last_over_time[31m] on reviews panel for 7d+ …
Flotapponnier Aug 26, 2026
82c5c79
fix: sitemap 90s timeout — maxDuration=60 + tighten internal deadline…
Flotapponnier Aug 26, 2026
41efe89
Merge pull request #2074 from ChainBench/fix/sitemap-timeout
Flotapponnier Aug 26, 2026
0035d7a
fix: suppress orphaned buildFullSitemap rejection + guard unhandled g…
Flotapponnier Aug 26, 2026
f350458
Merge pull request #2075 from ChainBench/fix/sitemap-timeout
Flotapponnier Aug 26, 2026
9cea463
feat(rpc): add Dogecoin bench #235 (Tatum, dRPC, BlockCypher)
Flotapponnier Aug 26, 2026
da19adb
Merge pull request #2082 from ChainBench/hotfix/dogecoin-bench
Flotapponnier Aug 26, 2026
29b1ce5
fix(dogecoin-rpc): remove em-dash from seo_intro
Flotapponnier Aug 26, 2026
dc99f5a
Merge pull request #2084 from ChainBench/fix/dogecoin-rpc-emdash
Flotapponnier Aug 26, 2026
fcc4986
fix(dogecoin-rpc): em-dash in seo_intro
Flotapponnier Aug 26, 2026
b2bcb36
Merge pull request #2086 from ChainBench/fix/dogecoin-emdash2
Flotapponnier Aug 26, 2026
27f7898
fix: sitemap aggregate timeout + CDN caching (#2090) (#2092)
Flotapponnier Aug 26, 2026
9f657ab
fix: raise maxDuration to 300s and extend sitemap JS timeout to 240s
Flotapponnier Aug 26, 2026
c9331ac
feat(zcash): add bench #236 with Tatum zcashd, Tatum Zebra, Blockchair
Flotapponnier Aug 27, 2026
59fc5b5
Merge pull request #2100 from ChainBench/feat/zcash-rpc-bench
Flotapponnier Aug 27, 2026
ed162c6
fix: raise maxDuration to 300s and extend sitemap JS timeout to 240s
Flotapponnier Aug 27, 2026
c77e350
merge: main into ship/dev-to-main-aug27, keep maxDuration=300 + 240s …
Flotapponnier Aug 27, 2026
dcd3db0
ship: dev to main (aug 27)
Flotapponnier Aug 27, 2026
6484eca
fix: include pre-window HL positions in carry projection
Flotapponnier Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/app/api/fee-compare/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,8 @@ function buildHlWalletData(
function reconstructHlPositions(fills: HlFill[], cutoffMs: number): PositionSlice[] {
const state = new Map<string, { sz: number; openTs: number; openPx: number; isLong: boolean }>();
const slices: PositionSlice[] = [];
const sorted = fills.filter((f) => f.time >= cutoffMs).sort((a, b) => a.time - b.time);
// Process ALL fills chronologically so positions opened before cutoffMs are visible
const sorted = fills.slice().sort((a, b) => a.time - b.time);

for (const f of sorted) {
const sz = parseFloat(f.sz);
Expand All @@ -724,15 +725,20 @@ function reconstructHlPositions(fills: HlFill[], cutoffMs: number): PositionSlic
const pos = state.get(key);
if (pos && pos.sz > 0) {
const closeSz = Math.min(sz, pos.sz);
slices.push({ coin: f.coin, notionalUsd: closeSz * pos.openPx, openMs: pos.openTs, closeMs: f.time, isLong });
// Cap slice start to cutoffMs so carry is only for the measurement window
const sliceOpen = Math.max(pos.openTs, cutoffMs);
const sliceClose = f.time;
if (sliceClose > cutoffMs) {
slices.push({ coin: f.coin, notionalUsd: closeSz * pos.openPx, openMs: sliceOpen, closeMs: sliceClose, isLong });
}
pos.sz -= closeSz;
if (pos.sz < 0.00001) state.delete(key);
else state.set(key, pos);
}
}
}

// Still-open positions — close at now
// Still-open positions — close at now, cap open to cutoffMs
const now = Date.now();
for (const [key, pos] of state) {
if (pos.sz > 0.00001) {
Expand All @@ -742,7 +748,7 @@ function reconstructHlPositions(fills: HlFill[], cutoffMs: number): PositionSlic
slices.push({
coin,
notionalUsd: pos.sz * parseFloat(lastFill.px),
openMs: pos.openTs,
openMs: Math.max(pos.openTs, cutoffMs),
closeMs: now,
isLong: pos.isLong,
});
Expand Down
14 changes: 9 additions & 5 deletions src/app/sitemap.xml/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ import { buildSitemap } from "@/lib/sitemap-builder";
// serialized to XML here and shipped with a real edge cache header.
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
// Hard platform ceiling so Vercel never holds the connection open longer
// than this when the SRH/Redis fan-out runs over budget. The internal
// buildSitemap() timeout is 20 s; this gives 40 s of headroom and still
// replies well within the smoke-test's 90 s limit.
export const maxDuration = 60;
// 300 s: the aggregate blob is 4.25 MB — larger than Next.js Data Cache's
// 2 MB limit, so unstable_cache can never store it and every request must
// re-fetch from the network. The /api/aggregate ISR proxy responds in
// 18-65 s on a cold cache. With maxDuration=60 the process received
// SIGKILL before the fetch resolved, discarding the buffered response and
// returning 500. 300 s gives the full build time to complete on cold
// starts; the internal JS timeout (240 s) fires first and falls back to
// the static sitemap if the full build hangs.
export const maxDuration = 300;

function escapeXml(s: string): string {
return s
Expand Down
10 changes: 6 additions & 4 deletions src/lib/sitemap-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,11 +577,13 @@ async function buildFullSitemap(): Promise<MetadataRoute.Sitemap> {

export async function buildSitemap(): Promise<MetadataRoute.Sitemap> {
try {
// 20 s: tight enough to reply well within the smoke-test's 90 s window
// even if the static fallback itself takes a few seconds. The Vercel
// maxDuration on the route is 60 s; this leaves 40 s of headroom.
// 240 s: maxDuration on the route is 300 s. The aggregate blob (4.25 MB)
// exceeds Next.js Data Cache's 2 MB cap, so every sitemap request
// re-fetches from /api/aggregate whose FETCH_TIMEOUT_MS is 65 s. A 240 s
// budget covers cold aggregate + HL-builder Prometheus sweeps with 60 s of
// headroom for buildStaticFallback() before the 300 s SIGKILL ceiling.
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("sitemap build timeout")), 20_000),
setTimeout(() => reject(new Error("sitemap build timeout")), 240_000),
);
// Attach a no-op .catch() to prevent unhandled-rejection crashes if
// buildFullSitemap() rejects AFTER the race has already settled (via
Expand Down
Loading