Skip to content

Commit 7609ee6

Browse files
committed
style: fix ESLint and format issues in funding pagination
- 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
1 parent 92411f2 commit 7609ee6

File tree

9 files changed

+298
-18
lines changed

9 files changed

+298
-18
lines changed

app/components/UI/Perps/Perps.testIds.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ export const PerpsTransactionsViewSelectorsIDs = {
759759
TAB_ORDERS: 'perps-transactions-tab-orders',
760760
TAB_FUNDING: 'perps-transactions-tab-funding',
761761
TAB_DEPOSITS: 'perps-transactions-tab-deposits',
762+
FUNDING_LOAD_MORE_SPINNER: 'perps-transactions-funding-load-more-spinner',
762763
} as const;
763764

764765
// ========================================

app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.styles.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,9 @@ export const styleSheet = (params: { theme: Theme }) => {
8989
paddingVertical: 12,
9090
paddingHorizontal: 16,
9191
},
92+
loadMoreContainer: {
93+
paddingVertical: 16,
94+
alignItems: 'center' as const,
95+
},
9296
};
9397
};

app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ describe('PerpsTransactionsView', () => {
175175
isLoading: false,
176176
error: null,
177177
refetch: mockRefetchTransactions,
178+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
179+
hasFundingMore: true,
180+
isFetchingMoreFunding: false,
178181
});
179182

180183
mockUsePerpsEventTracking.mockReturnValue({
@@ -299,6 +302,9 @@ describe('PerpsTransactionsView', () => {
299302
isLoading: false,
300303
error: null,
301304
refetch: mockRefetch,
305+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
306+
hasFundingMore: true,
307+
isFetchingMoreFunding: false,
302308
});
303309

304310
renderWithProvider(<PerpsTransactionsView />, {
@@ -318,6 +324,9 @@ describe('PerpsTransactionsView', () => {
318324
isLoading: false,
319325
error: null,
320326
refetch: jest.fn(),
327+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
328+
hasFundingMore: true,
329+
isFetchingMoreFunding: false,
321330
});
322331

323332
const component = renderWithProvider(<PerpsTransactionsView />, {
@@ -350,6 +359,9 @@ describe('PerpsTransactionsView', () => {
350359
isLoading: false,
351360
error: null,
352361
refetch: mockRefetchTransactions,
362+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
363+
hasFundingMore: true,
364+
isFetchingMoreFunding: false,
353365
});
354366

355367
const component = renderWithProvider(<PerpsTransactionsView />, {
@@ -368,6 +380,9 @@ describe('PerpsTransactionsView', () => {
368380
isLoading: false,
369381
error: 'API Error',
370382
refetch: jest.fn(),
383+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
384+
hasFundingMore: true,
385+
isFetchingMoreFunding: false,
371386
});
372387

373388
const component = renderWithProvider(<PerpsTransactionsView />, {
@@ -426,6 +441,9 @@ describe('PerpsTransactionsView', () => {
426441
isLoading: false,
427442
error: null,
428443
refetch: jest.fn(),
444+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
445+
hasFundingMore: true,
446+
isFetchingMoreFunding: false,
429447
});
430448

431449
renderWithProvider(<PerpsTransactionsView />, {
@@ -459,6 +477,9 @@ describe('PerpsTransactionsView', () => {
459477
isLoading: false,
460478
error: null,
461479
refetch: jest.fn(),
480+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
481+
hasFundingMore: true,
482+
isFetchingMoreFunding: false,
462483
});
463484

464485
renderWithProvider(<PerpsTransactionsView />, {
@@ -477,6 +498,9 @@ describe('PerpsTransactionsView', () => {
477498
isLoading: false,
478499
error: 'Network error',
479500
refetch: jest.fn(),
501+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
502+
hasFundingMore: true,
503+
isFetchingMoreFunding: false,
480504
});
481505

482506
const component = renderWithProvider(<PerpsTransactionsView />, {
@@ -499,6 +523,9 @@ describe('PerpsTransactionsView', () => {
499523
isLoading: false,
500524
error: null,
501525
refetch: jest.fn(),
526+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
527+
hasFundingMore: true,
528+
isFetchingMoreFunding: false,
502529
});
503530

504531
const component = renderWithProvider(<PerpsTransactionsView />, {
@@ -614,6 +641,9 @@ describe('PerpsTransactionsView', () => {
614641
isLoading: false,
615642
error: null,
616643
refetch: jest.fn(),
644+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
645+
hasFundingMore: true,
646+
isFetchingMoreFunding: false,
617647
});
618648

619649
const component = renderWithProvider(<PerpsTransactionsView />, {

app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { useFocusEffect, useNavigation } from '@react-navigation/native';
22
import { FlashList } from '@shopify/flash-list';
33
import React, { useCallback, useMemo, useRef, useState } from 'react';
4-
import { RefreshControl, ScrollView, View } from 'react-native';
4+
import {
5+
ActivityIndicator,
6+
RefreshControl,
7+
ScrollView,
8+
View,
9+
} from 'react-native';
510
import { useSelector } from 'react-redux';
611
import { strings } from '../../../../../../locales/i18n';
712
import {
@@ -77,6 +82,9 @@ const PerpsTransactionsView: React.FC = () => {
7782
transactions: allTransactions,
7883
isLoading: transactionsLoading,
7984
refetch: refreshTransactions,
85+
loadMoreFunding,
86+
hasFundingMore,
87+
isFetchingMoreFunding,
8088
} = usePerpsTransactionHistory({
8189
skipInitialFetch: !isConnected,
8290
accountId,
@@ -490,9 +498,26 @@ const PerpsTransactionsView: React.FC = () => {
490498
item.type === 'header' ? 'header' : 'transaction'
491499
}
492500
ListEmptyComponent={shouldShowEmptyState ? renderEmptyState : null}
501+
ListFooterComponent={
502+
activeFilter === 'Funding' && isFetchingMoreFunding ? (
503+
<View style={styles.loadMoreContainer}>
504+
<ActivityIndicator
505+
testID={
506+
PerpsTransactionsViewSelectorsIDs.FUNDING_LOAD_MORE_SPINNER
507+
}
508+
/>
509+
</View>
510+
) : null
511+
}
493512
refreshControl={
494513
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
495514
}
515+
onEndReached={
516+
activeFilter === 'Funding' && hasFundingMore
517+
? loadMoreFunding
518+
: undefined
519+
}
520+
onEndReachedThreshold={0.5}
496521
showsVerticalScrollIndicator={false}
497522
drawDistance={
498523
PERPS_TRANSACTIONS_HISTORY_CONSTANTS.FLASH_LIST_DRAW_DISTANCE

app/components/UI/Perps/hooks/usePerpsHomeData.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ describe('usePerpsHomeData', () => {
302302
isLoading: false,
303303
error: null,
304304
refetch: jest.fn().mockResolvedValue(undefined),
305+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
306+
hasFundingMore: true,
307+
isFetchingMoreFunding: false,
305308
});
306309

307310
// Mock sortMarkets to return markets as-is by default
@@ -876,6 +879,9 @@ describe('usePerpsHomeData', () => {
876879
isLoading: false,
877880
error: null,
878881
refetch: jest.fn().mockResolvedValue(undefined),
882+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
883+
hasFundingMore: true,
884+
isFetchingMoreFunding: false,
879885
});
880886

881887
const { result } = renderHook(() => usePerpsHomeData());

app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,23 @@ import { useSelector } from 'react-redux';
33
import usePrevious from '../../../hooks/usePrevious';
44
import { BigNumber } from 'bignumber.js';
55
import { TransactionType } from '@metamask/transaction-controller';
6-
import type { OrderFill } from '@metamask/perps-controller';
6+
import {
7+
PERPS_TRANSACTIONS_HISTORY_CONSTANTS,
8+
type OrderFill,
9+
} from '@metamask/perps-controller';
10+
11+
const PAGE_WINDOW_MS =
12+
PERPS_TRANSACTIONS_HISTORY_CONSTANTS.FUNDING_HISTORY_PAGE_WINDOW_DAYS *
13+
24 *
14+
60 *
15+
60 *
16+
1000;
17+
const MAX_LOOKBACK_MS =
18+
PERPS_TRANSACTIONS_HISTORY_CONSTANTS.DEFAULT_FUNDING_HISTORY_DAYS *
19+
24 *
20+
60 *
21+
60 *
22+
1000;
723
import Engine from '../../../../core/Engine';
824
import DevLogger from '../../../../core/SDKConnect/utils/DevLogger';
925
import type { CaipAccountId } from '@metamask/utils';
@@ -23,8 +39,6 @@ import {
2339
} from '../utils/transactionTransforms';
2440

2541
interface UsePerpsTransactionHistoryParams {
26-
startTime?: number;
27-
endTime?: number;
2842
accountId?: CaipAccountId;
2943
skipInitialFetch?: boolean;
3044
}
@@ -34,6 +48,9 @@ interface UsePerpsTransactionHistoryResult {
3448
isLoading: boolean;
3549
error: string | null;
3650
refetch: () => Promise<void>;
51+
loadMoreFunding: () => Promise<void>;
52+
hasFundingMore: boolean;
53+
isFetchingMoreFunding: boolean;
3754
}
3855

3956
/**
@@ -42,8 +59,6 @@ interface UsePerpsTransactionHistoryResult {
4259
* Uses HyperLiquid user history as the single source of truth for withdrawals
4360
*/
4461
export const usePerpsTransactionHistory = ({
45-
startTime,
46-
endTime,
4762
accountId,
4863
skipInitialFetch = false,
4964
}: UsePerpsTransactionHistoryParams = {}): UsePerpsTransactionHistoryResult => {
@@ -52,13 +67,19 @@ export const usePerpsTransactionHistory = ({
5267
const [isLoading, setIsLoading] = useState(false);
5368
const [error, setError] = useState<string | null>(null);
5469

70+
// Cursor tracks the startTime of the oldest funding window already fetched.
71+
// null = initial fetch not done yet.
72+
const fundingCursorRef = useRef<number | null>(null);
73+
const [hasFundingMore, setHasFundingMore] = useState(true);
74+
const [isFetchingMoreFunding, setIsFetchingMoreFunding] = useState(false);
75+
5576
// Get user history (includes deposits/withdrawals) - single source of truth
5677
const {
5778
userHistory,
5879
isLoading: userHistoryLoading,
5980
error: userHistoryError,
6081
refetch: refetchUserHistory,
61-
} = useUserHistory({ startTime, endTime, accountId });
82+
} = useUserHistory({ accountId });
6283

6384
// Subscribe to live WebSocket fills for instant trade updates
6485
// This ensures new trades appear immediately without waiting for REST refetch
@@ -108,18 +129,16 @@ export const usePerpsTransactionHistory = ({
108129

109130
DevLogger.log('Fetching comprehensive transaction history...');
110131

132+
const fetchEndTime = Date.now();
133+
111134
// Fetch all transaction data in parallel
112135
const [fills, orders, funding] = await Promise.all([
113136
provider.getOrderFills({
114137
accountId,
115138
aggregateByTime: false,
116139
}),
117140
provider.getOrders({ accountId }),
118-
provider.getFunding({
119-
accountId,
120-
startTime,
121-
endTime,
122-
}),
141+
provider.getFunding({ accountId }),
123142
]);
124143

125144
DevLogger.log('Transaction data fetched:', { fills, orders, funding });
@@ -180,6 +199,10 @@ export const usePerpsTransactionHistory = ({
180199

181200
DevLogger.log('Combined transactions:', uniqueTransactions);
182201
setTransactions(uniqueTransactions);
202+
203+
// Reset funding pagination cursor to the start of the first (most recent) window
204+
fundingCursorRef.current = fetchEndTime - PAGE_WINDOW_MS;
205+
setHasFundingMore(true);
183206
} catch (err) {
184207
const errorMessage =
185208
err instanceof Error
@@ -192,7 +215,7 @@ export const usePerpsTransactionHistory = ({
192215
} finally {
193216
setIsLoading(false);
194217
}
195-
}, [startTime, endTime, accountId]);
218+
}, [accountId]);
196219

197220
const refetch = useCallback(async () => {
198221
// Fetch user history first, then fetch all transactions
@@ -201,6 +224,67 @@ export const usePerpsTransactionHistory = ({
201224
await fetchAllTransactions();
202225
}, [fetchAllTransactions, refetchUserHistory]);
203226

227+
const loadMoreFunding = useCallback(async () => {
228+
if (!hasFundingMore || isFetchingMoreFunding) return;
229+
230+
const controller = Engine.context.PerpsController;
231+
if (!controller) return;
232+
233+
const provider = controller.getActiveProviderOrNull();
234+
if (!provider) return;
235+
236+
const cursorEndTime = fundingCursorRef.current;
237+
if (cursorEndTime === null) return;
238+
239+
const cursorStartTime = cursorEndTime - PAGE_WINDOW_MS;
240+
const maxStartTime = Date.now() - MAX_LOOKBACK_MS;
241+
242+
if (cursorStartTime <= maxStartTime) {
243+
setHasFundingMore(false);
244+
return;
245+
}
246+
247+
DevLogger.log('[PERPS-FUNDING] loadMoreFunding: fetching older window', {
248+
cursorStartTime,
249+
cursorEndTime,
250+
windowDays: Math.round(PAGE_WINDOW_MS / (24 * 60 * 60 * 1000)),
251+
});
252+
253+
setIsFetchingMoreFunding(true);
254+
try {
255+
const olderFunding = await provider.getFunding({
256+
accountId,
257+
startTime: Math.max(cursorStartTime, maxStartTime),
258+
endTime: cursorEndTime,
259+
});
260+
261+
DevLogger.log('[PERPS-FUNDING] loadMoreFunding: older records loaded', {
262+
count: olderFunding.length,
263+
newCursor: cursorStartTime,
264+
hasMore: olderFunding.length > 0,
265+
});
266+
267+
if (olderFunding.length === 0) {
268+
setHasFundingMore(false);
269+
return;
270+
}
271+
272+
fundingCursorRef.current = cursorStartTime;
273+
274+
const olderFundingTxs = transformFundingToTransactions(olderFunding);
275+
setTransactions((prev) => {
276+
const combined = [...prev, ...olderFundingTxs];
277+
return combined.sort((a, b) => b.timestamp - a.timestamp);
278+
});
279+
280+
if (Math.max(cursorStartTime, maxStartTime) <= maxStartTime) {
281+
setHasFundingMore(false);
282+
}
283+
} finally {
284+
setIsFetchingMoreFunding(false);
285+
}
286+
}, [accountId, hasFundingMore, isFetchingMoreFunding]);
287+
204288
useEffect(() => {
205289
// Detect transition from skipping (not connected) to not skipping (connected)
206290
// This fixes the case where the component mounts before connection is established
@@ -279,5 +363,8 @@ export const usePerpsTransactionHistory = ({
279363
isLoading: combinedIsLoading,
280364
error: combinedError,
281365
refetch,
366+
loadMoreFunding,
367+
hasFundingMore,
368+
isFetchingMoreFunding,
282369
};
283370
};

app/controllers/perps/constants/transactionsHistoryConfig.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ export const PERPS_TRANSACTIONS_HISTORY_CONSTANTS = {
1414
DEFAULT_FUNDING_HISTORY_DAYS: 365,
1515
/**
1616
* Number of days per pagination window when fetching funding history.
17-
* Each window is fetched in parallel. A 30-day window stays well under the
18-
* API limit for any realistic number of open positions.
17+
* Each window is fetched via fetchWindowWithAutoSplit, which recursively
18+
* halves any window that hits FUNDING_HISTORY_API_LIMIT, guaranteeing
19+
* complete results regardless of position count or trading activity.
1920
*/
2021
FUNDING_HISTORY_PAGE_WINDOW_DAYS: 30,
2122
/**

0 commit comments

Comments
 (0)