@@ -5429,27 +5429,60 @@ export class HyperLiquidProvider implements PerpsProvider {
54295429 params ?. accountId ,
54305430 ) ;
54315431
5432- // Divide the full history range into 30-day windows and fetch all in
5433- // parallel. The HyperLiquid API returns records in ascending order and
5434- // caps each call at FUNDING_HISTORY_API_LIMIT (500) records. A single
5435- // large window therefore returns only the oldest 500 items, silently
5436- // dropping the most recent payments. Parallel small windows guarantee
5437- // every window is under the cap so the latest records are always present.
5432+ // On-demand loading: the default window is one 30-day page so the
5433+ // initial fetch costs exactly 1 API call (~24 weight vs 312 previously).
5434+ // When loadMoreFunding in usePerpsTransactionHistory passes explicit
5435+ // startTime/endTime for an older 30-day page the while-loop below still
5436+ // produces exactly 1 chunk. The 365-day max lookback is enforced by the
5437+ // caller.
5438+ //
5439+ // Each chunk is fetched via fetchWindowWithAutoSplit: if a call returns
5440+ // FUNDING_HISTORY_API_LIMIT records the window has hit the API cap and
5441+ // the oldest records would be silently dropped. The function splits the
5442+ // window in half and recurses until every sub-window is under the cap,
5443+ // guaranteeing complete results regardless of position count or activity.
54385444 const finalEndTime = params ?. endTime ?? Date . now ( ) ;
5439- const finalStartTime =
5440- params ?. startTime ??
5441- finalEndTime -
5442- PERPS_TRANSACTIONS_HISTORY_CONSTANTS . DEFAULT_FUNDING_HISTORY_DAYS *
5443- 24 *
5444- 60 *
5445- 60 *
5446- 1000 ;
54475445 const pageWindowMs =
54485446 PERPS_TRANSACTIONS_HISTORY_CONSTANTS . FUNDING_HISTORY_PAGE_WINDOW_DAYS *
54495447 24 *
54505448 60 *
54515449 60 *
54525450 1000 ;
5451+ const finalStartTime = params ?. startTime ?? finalEndTime - pageWindowMs ; // Default: most recent 30-day window only
5452+
5453+ // Minimum window size to bound recursion depth. The HyperLiquid funding
5454+ // interval is 8 h, so a 1-hour window holds at most a fraction of one
5455+ // event per position — well under the 500-record cap in any scenario.
5456+ const minSplitWindowMs = 60 * 60 * 1000 ;
5457+ const apiLimit =
5458+ PERPS_TRANSACTIONS_HISTORY_CONSTANTS . FUNDING_HISTORY_API_LIMIT ;
5459+
5460+ // Fetches a single window. If the result hits the API cap the window is
5461+ // split in half and both halves are fetched in parallel, recursively,
5462+ // until every sub-window is under the cap.
5463+ const fetchWindowWithAutoSplit = async (
5464+ windowStart : number ,
5465+ windowEnd : number ,
5466+ ) : Promise < Awaited < ReturnType < typeof infoClient . userFunding > > > => {
5467+ const result = await infoClient . userFunding ( {
5468+ user : userAddress ,
5469+ startTime : windowStart ,
5470+ endTime : windowEnd ,
5471+ } ) ;
5472+ const records = result ?? [ ] ;
5473+ if (
5474+ records . length >= apiLimit &&
5475+ windowEnd - windowStart > minSplitWindowMs
5476+ ) {
5477+ const mid = windowStart + Math . floor ( ( windowEnd - windowStart ) / 2 ) ;
5478+ const [ left , right ] = await Promise . all ( [
5479+ fetchWindowWithAutoSplit ( windowStart , mid ) ,
5480+ fetchWindowWithAutoSplit ( mid , windowEnd ) ,
5481+ ] ) ;
5482+ return [ ...( left ?? [ ] ) , ...( right ?? [ ] ) ] ;
5483+ }
5484+ return records ;
5485+ } ;
54535486
54545487 const chunks : { start : number ; end : number } [ ] = [ ] ;
54555488 let chunkEnd = finalEndTime ;
@@ -5460,21 +5493,24 @@ export class HyperLiquidProvider implements PerpsProvider {
54605493 }
54615494
54625495 const pages = await Promise . all (
5463- chunks . map ( ( chunk ) =>
5464- infoClient . userFunding ( {
5465- user : userAddress ,
5466- startTime : chunk . start ,
5467- endTime : chunk . end ,
5468- } ) ,
5469- ) ,
5496+ chunks . map ( ( chunk ) => fetchWindowWithAutoSplit ( chunk . start , chunk . end ) ) ,
54705497 ) ;
54715498
5472- const allRaw = pages . flatMap ( ( page ) => page ?? [ ] ) ;
5499+ // Deduplicate by hash before sorting — adjacent chunk windows share their
5500+ // boundary timestamp (chunkEnd of chunk N === chunkStart of chunk N+1),
5501+ // and the HyperLiquid API is inclusive on both sides, so a record whose
5502+ // timestamp falls exactly on a boundary can appear in both adjacent calls.
5503+ const seen = new Set < string > ( ) ;
5504+ const allRaw = pages . flat ( ) . filter ( ( record ) => {
5505+ if ( seen . has ( record . hash ) ) { return false ; }
5506+ seen . add ( record . hash ) ;
5507+ return true ;
5508+ } ) ;
54735509 allRaw . sort ( ( a , b ) => a . time - b . time ) ;
54745510
54755511 this . #deps. debugLogger . log ( 'User funding received:' , {
54765512 count : allRaw . length ,
5477- pages : chunks . length ,
5513+ chunks : chunks . length ,
54785514 } ) ;
54795515
54805516 // Transform HyperLiquid funding to abstract Funding type
0 commit comments