Skip to content

fix(perps): missing latest funding payments in Activity#28671

Merged
abretonc7s merged 21 commits intomainfrom
fix/tat-2057-dont-see-latest-funding-paymen
Apr 15, 2026
Merged

fix(perps): missing latest funding payments in Activity#28671
abretonc7s merged 21 commits intomainfrom
fix/tat-2057-dont-see-latest-funding-paymen

Conversation

@abretonc7s
Copy link
Copy Markdown
Contributor

@abretonc7s abretonc7s commented Apr 10, 2026

Description

Root cause

The Activity page was not showing the most recent funding payments for accounts with many open positions. HyperLiquidProvider.getFunding() made a single API call for the full 365-day history window; HyperLiquid's API silently caps responses at 500 records (oldest-first), so the most recent payments were dropped.

Fix

  • Initial load — 1 API call: getFunding() now defaults to fetching only the most recent 30-day window instead of 365 days. For windows exceeding the 500-record cap, fetchWindowWithAutoSplit recursively halves the window until every sub-window is under the cap.
  • Boundary dedup by time+coin: Adjacent chunk windows share boundary timestamps; dedup uses time+coin composite key (not hash — HyperLiquid funding records all share a zero hash).
  • On-demand older history: A cursor-based loadMoreFunding in usePerpsTransactionHistory loads the next 30-day page when the user scrolls to the bottom of the Funding tab. Max lookback remains 365 days.
  • Auto-advance on empty: When the Funding tab is empty after initial load (no funding in the most recent 30 days), PerpsTransactionsView auto-triggers loadMoreFunding to fetch the next older window.
  • Error resilience: Transient API failures during pull-to-refresh preserve existing loaded data instead of wiping to empty state.
  • Stable sort: Secondary sort by asset name ensures deterministic ordering when multiple coins share the same funding timestamp.
  • Performance: Shared deduplicateById helper (O(n) Set) replaces O(n²) reduce+findIndex. Redundant intermediate sorts removed — single sort pass in mergedTransactions memo.
  • Rate limit improvement: 1 API call per navigation/refresh vs 13 previously — ~50 loads/min safe vs 3.

Changes

File Change
HyperLiquidProvider.ts getFunding() defaults to 30-day window; fetchWindowWithAutoSplit for cap-safe fetching; boundary dedup by time+coin (not hash)
usePerpsTransactionHistory.ts Cursor pagination (loadMoreFunding, hasFundingMore, isFetchingMoreFunding); ref-based lock to prevent double-fire; generation counter to discard stale results; shared deduplicateById helper; preserve data on error; stable sort by asset; removed redundant intermediate sorts
PerpsTransactionsView.tsx Wired onEndReached → loadMoreFunding on Funding tab; auto-advance effect when Funding tab is empty; ActivityIndicator spinner in ListFooterComponent
transactionTransforms.ts Removed sort from transformFundingToTransactions — sorting centralized in consumer (mergedTransactions memo)
transactionsHistoryConfig.ts Added FUNDING_HISTORY_PAGE_WINDOW_DAYS, FUNDING_HISTORY_API_LIMIT, MIN_SPLIT_WINDOW_MS constants
PerpsTransactionsView.styles.ts Added loadMoreContainer style
Perps.testIds.ts Added FUNDING_LOAD_MORE_SPINNER testID
usePerpsTransactionHistory.test.ts 5 new loadMoreFunding tests (happy path, empty result, error handling, exhausted cursor, dedup); 93% new code coverage
HyperLiquidProvider.test.ts Tests for parallel pagination, auto-split, null page handling, explicit time range forwarding
transactionTransforms.test.ts Updated sort test to reflect consumer-side sorting
PerpsTransactionsView.test.tsx, usePerpsHomeData.test.ts Updated mocks for new hook API surface

Changelog

CHANGELOG entry: Fixed missing recent perpetuals funding payments — getFunding now fetches the most recent 30-day window by default (1 API call) and loads older history on demand as the user scrolls, replacing the previous 365-day call that silently dropped recent records past the 500-record cap.

Related issues

Fixes: TAT-2057

Manual testing steps

```gherkin
Feature: On-demand funding history pagination

Scenario: AC1 — Activity shows most recent funding payments (1 API call)
Given the user has an account with perpetuals positions
When the user navigates to Activity > Perps > Funding tab
Then the most recent funding payments appear (457 records in 30-day window)
And only 1 getFunding API call is made (visible in Metro logs)

Scenario: AC2 — Consecutive fetches return consistent results
Given the user is on the Funding tab
When getFunding is called twice with the same time window
Then both calls return identical record counts and latest timestamps

Scenario: AC3 — Scrolling to bottom loads older history
Given the user is on the Funding tab with recent data loaded
When the user scrolls to the bottom of the Funding list
Then a loading spinner appears at the bottom
And a second API call fetches the next 30-day window
And older funding entries appear in the list

Scenario: AC4 — Pull-to-refresh failure preserves existing data
Given the user has funding data loaded
When the API returns an error during pull-to-refresh
Then existing funding entries remain visible
And no empty state is shown

Scenario: AC5 — Refresh resets to fresh recent data
Given the user pulls to refresh on the Funding tab
Then the cursor resets and the most recent 30-day window is re-fetched (1 call)
```

Screenshots/Recordings

Latest funding entries

Before After
Latest funding entries before Latest funding entries after

Consecutive fetch consistency

Before After
Funding consistency before Funding consistency after

Video

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test changes)
  • I confirm that this PR addresses all items listed in the description

Note

Medium Risk
Changes funding-history fetching and pagination logic in both the HyperLiquid provider and UI hook; regressions could hide funding rows or cause extra API calls/spinners if cursor/generation handling is wrong.

Overview
Fixes missing recent perps funding payments by changing HyperLiquidProvider.getFunding() to fetch funding in 30‑day pages (defaulting to the most recent window) and auto-splitting any window that hits the API record cap, with boundary deduping.

Adds cursor-based on-demand funding pagination to usePerpsTransactionHistory (loadMoreFunding, hasFundingMore, isFetchingMoreFunding) with deduping, stable sorting, and refresh/error behavior that preserves already-loaded data. PerpsTransactionsView wires infinite scroll + an empty-list auto-advance for the Funding tab and shows a footer spinner, with updated constants/test IDs and expanded tests to cover the new behavior.

Reviewed by Cursor Bugbot for commit c7506b5. Bugbot is set up for automated code reviews on this repo. Configure here.

@abretonc7s abretonc7s added DO-NOT-MERGE Pull requests that should not be merged agentic labels Apr 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-perps Perps team label Apr 10, 2026
@abretonc7s
Copy link
Copy Markdown
Contributor Author

Automated fix-bug run — TAT-2057

Metric Value
Run 54ed1019
Duration ?
Model claude/sonnet
Nudges 0
Difficulty medium (-/10)
Grade rationale No structured response
Cost estimate $24.71
Worker report

Fix Report — TAT-2057: Don't see latest funding payments in Activity

Summary

HyperLiquidProvider.getFunding() made a single API call covering the full 365-day history window. The HyperLiquid userFunding API returns records in ascending chronological order capped at 500 per call, so active accounts silently receive only their oldest 500 records. The fix replaces the single call with parallel 30-day window fetches that guarantee the most recent records are always included.

Root Cause

File: app/controllers/perps/providers/HyperLiquidProvider.ts:5416

The original getFunding() called:

const rawFunding = await infoClient.userFunding({
  user: userAddress,
  startTime: Date.now() - 365 * 24 * 60 * 60 * 1000,
  endTime: undefined,
});

HyperLiquid's userFunding API returns records ascending (oldest first), capped at 500 per request. For accounts with >500 funding records in the past year (e.g., 0x316BDE with 1174 records from 2025-05 to 2026-04), only the oldest 500 were returned. The most recent 674 payments were silently dropped.

The oscillation between two cutoff dates on consecutive refreshes was caused by Date.now() being evaluated fresh on each call, producing slightly different window boundaries. Even small shifts changed which 500 records fell within the cap, making the cutoff jump between dates.

Reproduction Commit

SHA: a39d03de17 — "debug(pr-28671): add reproduction marker"

Added DevLogger.log('[PR-28671] BUG_MARKER: getFunding hit 500-record API cap') that fires when rawFunding.length >= 500. For 0x316BDE (1174 records), this marker would fire on every call.

Changes

File Description
app/controllers/perps/constants/transactionsHistoryConfig.ts Added FUNDING_HISTORY_PAGE_WINDOW_DAYS: 30 and FUNDING_HISTORY_API_LIMIT: 500 constants
app/controllers/perps/providers/HyperLiquidProvider.ts Replaced single API call with parallel 30-day window pagination via Promise.all
app/controllers/perps/providers/HyperLiquidProvider.test.ts Added 3 regression tests: parallel windows, recent records in long history, null page handling

Fix Detail

Divided the 365-day range into 30-day windows (~13 chunks), fetched all in parallel via Promise.all, then merged and sorted results. Each 30-day window stays well under the 500-record cap for any realistic user. The most recent window always contains today's records.

const chunks: { start: number; end: number }[] = [];
let chunkEnd = finalEndTime;
while (chunkEnd > finalStartTime) {
  const chunkStart = Math.max(finalStartTime, chunkEnd - pageWindowMs);
  chunks.push({ start: chunkStart, end: chunkEnd });
  chunkEnd = chunkStart;
}
const pages = await Promise.all(
  chunks.map((chunk) => infoClient.userFunding({ user: userAddress, startTime: chunk.start, endTime: chunk.end }))
);
const allRaw = pages.flatMap((page) => page ?? []);
allRaw.sort((a, b) => a.time - b.time);

Test Plan

Unit tests: yarn jest app/controllers/perps/providers/HyperLiquidProvider.test.ts — 4/4 pass (3 new regression tests)

Recipe validation (account 0x316BDE, 1174 records):

  • ac1-fetch-latest: count=1174 > 500 cap, isRecent=true, exceedsCap=true ✅
  • ac2-consistency-check: count1=99, count2=99, latestTs1=latestTs2, consistent=true ✅

CI gates: lint (0 errors), lint:tsc (0 errors), format:check (pass) ✅

Evidence

Artifact Description
before.mp4 Recipe on buggy code (single-call, 500-record cap)
after.mp4 Recipe on fixed code — count=1174, consistent results
before-ac1-funding-latest.png Funding tab before fix
after-ac1-funding-latest.png Funding tab after fix — "Today" entries visible
after-ac2-funding-consistent.png Stable list — no oscillation
recipe-coverage.md Per-AC coverage matrix

Ticket

TAT-2057 — https://consensyssoftware.atlassian.net/browse/TAT-2057

@abretonc7s abretonc7s marked this pull request as ready for review April 10, 2026 15:19
@abretonc7s abretonc7s requested a review from a team as a code owner April 10, 2026 15:19
Comment thread app/controllers/perps/providers/HyperLiquidProvider.ts
Comment thread app/controllers/perps/constants/transactionsHistoryConfig.ts
Comment thread app/controllers/perps/providers/HyperLiquidProvider.ts
@github-actions github-actions Bot added the risk-medium Moderate testing recommended · Possible bug introduction risk label Apr 11, 2026
Comment thread app/controllers/perps/providers/HyperLiquidProvider.test.ts
…issing latest payments

HyperLiquid userFunding API returns records ascending (oldest first) and
caps each call at 500 records. A single 365-day window silently drops the
most recent payments for accounts with >500 funding events.

Replace the single call with parallel 30-day window fetches via Promise.all.
Each window stays well under the cap, guaranteeing the most recent records
are always present regardless of history length.

Fixes TAT-2057
Comment thread app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts
Comment thread app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts Outdated
- Merge duplicate perps-controller imports (no-duplicate-imports)
- Move PAGE_WINDOW_MS/MAX_LOOKBACK_MS to module level (exhaustive-deps)
- Remove dead startTime/endTime from useCallback dep array
- Apply prettier formatting to all changed files
@abretonc7s abretonc7s force-pushed the fix/tat-2057-dont-see-latest-funding-paymen branch from 7609ee6 to eac26df Compare April 11, 2026 06:13
@github-actions github-actions Bot added size-L risk-medium Moderate testing recommended · Possible bug introduction risk and removed size-M risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 11, 2026
…nding

- Fix early return guard: check cursorEndTime instead of cursorStartTime
  so the last partial funding window is fetched rather than silently dropped
  (365d / 30d = 12.17 pages; ~5 days were previously skipped)
- Deduplicate transactions by id when merging in loadMoreFunding to prevent
  boundary-timestamp duplicates from HyperLiquid's inclusive API ranges
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 11, 2026
Comment thread app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts Outdated
Comment thread app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts Outdated
- Add try/catch so API rejections don't propagate as unhandled promise
  rejections through FlashList's fire-and-forget onEndReached callback
- Add fetchGenerationRef counter bumped on every fetchAllTransactions;
  loadMoreFunding captures it before the await and discards results if a
  concurrent refresh has reset the cursor, preventing stale cursor writes
  and stale data being appended after pull-to-refresh
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 11, 2026
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 13, 2026
abretonc7s and others added 2 commits April 13, 2026 14:38
Replace O(n²) reduce+findIndex and inline Set+filter with a single
shared deduplicateById function for consistent, efficient dedup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sort only once in the mergedTransactions memo (the final output).
Remove duplicate sorts from fetchAllTransactions, loadMoreFunding,
and transformFundingToTransactions — they were all overwritten by
the downstream sort anyway.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 13, 2026
…_MS constant

Replace localeCompare with plain string comparison for ASCII coin
symbols. Move minSplitWindowMs to PERPS_TRANSACTIONS_HISTORY_CONSTANTS
for discoverability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 13, 2026
@abretonc7s abretonc7s changed the title fix(perps): Don't see latest funding payments in Activity fix(perps): missing latest funding payments in Activity Apr 13, 2026
@abretonc7s abretonc7s enabled auto-merge April 13, 2026 07:07
@abretonc7s abretonc7s removed the DO-NOT-MERGE Pull requests that should not be merged label Apr 13, 2026
Copy link
Copy Markdown
Contributor

@geositta geositta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the Bugbot finding in usePerpsTransactionHistory.ts:325-327, I agree with keeping this PR scoped to TAT-2057 and Im not asking it to solve every historical gap pagination case.

My concern is that the empty-window stop is itself a new optimization/tradeoff to reduce extra API calls, which is somewhat separate from the core recent funding fix.

Because of that behavior, the current implementation does not fully match the PR description/comments: phrases like "on demand older history" and "max lookback remains 365 days" read as though users can continue paging back through that full range, but in practice pagination stops at the first empty 30 day window and older funding beyond a gap becomes unreachable.

Im fine with keeping that trade-off in this PR if that is the intended product decision. In that case, I think the PR description and relevant code comments should be narrowed so they reflect the actual behavior rather than implying full 365 day reachability across gaps.

Comment thread app/controllers/perps/constants/transactionsHistoryConfig.ts Outdated
Comment thread app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts
Update DEFAULT_FUNDING_HISTORY_DAYS comment to reflect actual behavior:
365 days is a max ceiling, not a guaranteed range. Initial load fetches
only the most recent 30-day window, and pagination stops at the first
empty window — users with activity gaps may not reach the full lookback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 15, 2026
Empty 30-day windows (no open positions during that period) now advance
the cursor instead of terminating pagination, so users with activity
gaps can still reach older funding history within the 365-day lookback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 15, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePerps, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: @PerformancePreps
  • Risk Level: medium
  • AI Confidence: 85%
click to see 🤖 AI reasoning details

E2E Test Selection:
All 12 changed files are scoped to the Perps feature area. The key changes are:

  1. HyperLiquidProvider.ts - Major refactor of getFunding(): Changed from a single 365-day API call to a paginated 30-day window approach with recursive auto-split logic when hitting the 500-record API cap. This is a significant behavioral change that could affect funding history display correctness.

  2. transactionsHistoryConfig.ts - New pagination constants (FUNDING_HISTORY_PAGE_WINDOW_DAYS=30, FUNDING_HISTORY_API_LIMIT=500, MIN_SPLIT_WINDOW_MS) supporting the new approach.

  3. usePerpsTransactionHistory.ts - New comprehensive hook with cursor-based pagination (loadMoreFunding), deduplication logic, WebSocket/REST fill merging, and wallet deposit/withdrawal integration.

  4. PerpsTransactionsView.tsx - New UI component with tabbed transaction history (Trades, Orders, Funding, Deposits), FlashList rendering, auto-advance funding cursor for empty windows.

  5. transactionTransforms.ts - Minor: removed sorting from transformFundingToTransactions (sorting now handled at merge level).

  6. Perps.testIds.ts - Added FUNDING_LOAD_MORE_SPINNER test ID.

Tag selection rationale:

  • SmokePerps: Primary tag - all changes are in the Perps feature (transaction history, funding pagination, provider changes). The Add Funds flow and balance verification are core to SmokePerps.
  • SmokeWalletPlatform: Required per SmokePerps description - Perps is a section inside the Trending tab, and changes to Perps views affect Trending.
  • SmokeConfirmations: Required per SmokePerps description - Add Funds deposits are on-chain transactions requiring confirmations.

No other tags are needed as changes are isolated to Perps functionality with no impact on accounts, identity, network, swaps, or other features.

Performance Test Selection:
The PerpsTransactionsView introduces FlashList with explicit performance constants (FLASH_LIST_DRAW_DISTANCE=200, FLASH_LIST_SCROLL_EVENT_THROTTLE=16) and the new pagination approach changes how data is loaded. The @PerformancePreps tag covers perps market loading, position management, and add funds flow - all of which are affected by the new transaction history loading mechanism. The cursor-based pagination and deduplication logic could impact rendering performance for users with large transaction histories.

View GitHub Actions results

@abretonc7s
Copy link
Copy Markdown
Contributor Author

abretonc7s commented Apr 15, 2026

Regarding the Bugbot finding in usePerpsTransactionHistory.ts:325-327, I agree with keeping this PR scoped to TAT-2057 and Im not asking it to solve every historical gap pagination case.

My concern is that the empty-window stop is itself a new optimization/tradeoff to reduce extra API calls, which is somewhat separate from the core recent funding fix.

Because of that behavior, the current implementation does not fully match the PR description/comments: phrases like "on demand older history" and "max lookback remains 365 days" read as though users can continue paging back through that full range, but in practice pagination stops at the first empty 30 day window and older funding beyond a gap becomes unreachable.

Im fine with keeping that trade-off in this PR if that is the intended product decision. In that case, I think the PR description and relevant code comments should be narrowed so they reflect the actual behavior rather than implying full 365 day reachability across gaps.

after.mp4

Updated proof of work after changes

@github-actions
Copy link
Copy Markdown
Contributor

E2E Fixture Validation — Schema is up to date
11 value mismatches detected (expected — fixture represents an existing user).
View details

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@geositta geositta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good now. The followup change resolves the earlier pagination concern by skipping empty 30 day funding windows instead of stopping permanently, and the updated comments/tests match that behavior.

I am comfortable approving from here, thanks!

@abretonc7s abretonc7s added this pull request to the merge queue Apr 15, 2026
Merged via the queue into main with commit f285cd7 Apr 15, 2026
103 checks passed
@abretonc7s abretonc7s deleted the fix/tat-2057-dont-see-latest-funding-paymen branch April 15, 2026 04:38
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 15, 2026
@metamaskbot metamaskbot added the release-7.74.0 Issue or pull request that will be included in release 7.74.0 label Apr 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

agentic release-7.74.0 Issue or pull request that will be included in release 7.74.0 risk-medium Moderate testing recommended · Possible bug introduction risk size-L team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants