Skip to content

Commit eac26df

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 dd34530 commit eac26df

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
@@ -897,6 +900,9 @@ describe('usePerpsHomeData', () => {
897900
isLoading: false,
898901
error: null,
899902
refetch: jest.fn().mockResolvedValue(undefined),
903+
loadMoreFunding: jest.fn().mockResolvedValue(undefined),
904+
hasFundingMore: true,
905+
isFetchingMoreFunding: false,
900906
});
901907

902908
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
@@ -6,7 +6,23 @@ import {
66
TransactionMeta,
77
TransactionType,
88
} from '@metamask/transaction-controller';
9-
import type { OrderFill } from '@metamask/perps-controller';
9+
import {
10+
PERPS_TRANSACTIONS_HISTORY_CONSTANTS,
11+
type OrderFill,
12+
} from '@metamask/perps-controller';
13+
14+
const PAGE_WINDOW_MS =
15+
PERPS_TRANSACTIONS_HISTORY_CONSTANTS.FUNDING_HISTORY_PAGE_WINDOW_DAYS *
16+
24 *
17+
60 *
18+
60 *
19+
1000;
20+
const MAX_LOOKBACK_MS =
21+
PERPS_TRANSACTIONS_HISTORY_CONSTANTS.DEFAULT_FUNDING_HISTORY_DAYS *
22+
24 *
23+
60 *
24+
60 *
25+
1000;
1026
import Engine from '../../../../core/Engine';
1127
import DevLogger from '../../../../core/SDKConnect/utils/DevLogger';
1228
import type { CaipAccountId } from '@metamask/utils';
@@ -43,8 +59,6 @@ function deduplicateByTxHash(
4359
}
4460

4561
interface UsePerpsTransactionHistoryParams {
46-
startTime?: number;
47-
endTime?: number;
4862
accountId?: CaipAccountId;
4963
skipInitialFetch?: boolean;
5064
}
@@ -54,6 +68,9 @@ interface UsePerpsTransactionHistoryResult {
5468
isLoading: boolean;
5569
error: string | null;
5670
refetch: () => Promise<void>;
71+
loadMoreFunding: () => Promise<void>;
72+
hasFundingMore: boolean;
73+
isFetchingMoreFunding: boolean;
5774
}
5875

5976
/**
@@ -62,8 +79,6 @@ interface UsePerpsTransactionHistoryResult {
6279
* Uses HyperLiquid user history as the single source of truth for withdrawals
6380
*/
6481
export const usePerpsTransactionHistory = ({
65-
startTime,
66-
endTime,
6782
accountId,
6883
skipInitialFetch = false,
6984
}: UsePerpsTransactionHistoryParams = {}): UsePerpsTransactionHistoryResult => {
@@ -72,13 +87,19 @@ export const usePerpsTransactionHistory = ({
7287
const [isLoading, setIsLoading] = useState(false);
7388
const [error, setError] = useState<string | null>(null);
7489

90+
// Cursor tracks the startTime of the oldest funding window already fetched.
91+
// null = initial fetch not done yet.
92+
const fundingCursorRef = useRef<number | null>(null);
93+
const [hasFundingMore, setHasFundingMore] = useState(true);
94+
const [isFetchingMoreFunding, setIsFetchingMoreFunding] = useState(false);
95+
7596
// Get user history (includes deposits/withdrawals) - single source of truth
7697
const {
7798
userHistory,
7899
isLoading: userHistoryLoading,
79100
error: userHistoryError,
80101
refetch: refetchUserHistory,
81-
} = useUserHistory({ startTime, endTime, accountId });
102+
} = useUserHistory({ accountId });
82103

83104
// Subscribe to live WebSocket fills for instant trade updates
84105
// This ensures new trades appear immediately without waiting for REST refetch
@@ -154,18 +175,16 @@ export const usePerpsTransactionHistory = ({
154175

155176
DevLogger.log('Fetching comprehensive transaction history...');
156177

178+
const fetchEndTime = Date.now();
179+
157180
// Fetch all transaction data in parallel
158181
const [fills, orders, funding] = await Promise.all([
159182
provider.getOrderFills({
160183
accountId,
161184
aggregateByTime: false,
162185
}),
163186
provider.getOrders({ accountId }),
164-
provider.getFunding({
165-
accountId,
166-
startTime,
167-
endTime,
168-
}),
187+
provider.getFunding({ accountId }),
169188
]);
170189

171190
DevLogger.log('Transaction data fetched:', { fills, orders, funding });
@@ -226,6 +245,10 @@ export const usePerpsTransactionHistory = ({
226245

227246
DevLogger.log('Combined transactions:', uniqueTransactions);
228247
setTransactions(uniqueTransactions);
248+
249+
// Reset funding pagination cursor to the start of the first (most recent) window
250+
fundingCursorRef.current = fetchEndTime - PAGE_WINDOW_MS;
251+
setHasFundingMore(true);
229252
} catch (err) {
230253
const errorMessage =
231254
err instanceof Error
@@ -238,7 +261,7 @@ export const usePerpsTransactionHistory = ({
238261
} finally {
239262
setIsLoading(false);
240263
}
241-
}, [startTime, endTime, accountId]);
264+
}, [accountId]);
242265

243266
const refetch = useCallback(async () => {
244267
// Fetch user history first, then fetch all transactions
@@ -247,6 +270,67 @@ export const usePerpsTransactionHistory = ({
247270
await fetchAllTransactions();
248271
}, [fetchAllTransactions, refetchUserHistory]);
249272

273+
const loadMoreFunding = useCallback(async () => {
274+
if (!hasFundingMore || isFetchingMoreFunding) return;
275+
276+
const controller = Engine.context.PerpsController;
277+
if (!controller) return;
278+
279+
const provider = controller.getActiveProviderOrNull();
280+
if (!provider) return;
281+
282+
const cursorEndTime = fundingCursorRef.current;
283+
if (cursorEndTime === null) return;
284+
285+
const cursorStartTime = cursorEndTime - PAGE_WINDOW_MS;
286+
const maxStartTime = Date.now() - MAX_LOOKBACK_MS;
287+
288+
if (cursorStartTime <= maxStartTime) {
289+
setHasFundingMore(false);
290+
return;
291+
}
292+
293+
DevLogger.log('[PERPS-FUNDING] loadMoreFunding: fetching older window', {
294+
cursorStartTime,
295+
cursorEndTime,
296+
windowDays: Math.round(PAGE_WINDOW_MS / (24 * 60 * 60 * 1000)),
297+
});
298+
299+
setIsFetchingMoreFunding(true);
300+
try {
301+
const olderFunding = await provider.getFunding({
302+
accountId,
303+
startTime: Math.max(cursorStartTime, maxStartTime),
304+
endTime: cursorEndTime,
305+
});
306+
307+
DevLogger.log('[PERPS-FUNDING] loadMoreFunding: older records loaded', {
308+
count: olderFunding.length,
309+
newCursor: cursorStartTime,
310+
hasMore: olderFunding.length > 0,
311+
});
312+
313+
if (olderFunding.length === 0) {
314+
setHasFundingMore(false);
315+
return;
316+
}
317+
318+
fundingCursorRef.current = cursorStartTime;
319+
320+
const olderFundingTxs = transformFundingToTransactions(olderFunding);
321+
setTransactions((prev) => {
322+
const combined = [...prev, ...olderFundingTxs];
323+
return combined.sort((a, b) => b.timestamp - a.timestamp);
324+
});
325+
326+
if (Math.max(cursorStartTime, maxStartTime) <= maxStartTime) {
327+
setHasFundingMore(false);
328+
}
329+
} finally {
330+
setIsFetchingMoreFunding(false);
331+
}
332+
}, [accountId, hasFundingMore, isFetchingMoreFunding]);
333+
250334
useEffect(() => {
251335
// Detect transition from skipping (not connected) to not skipping (connected)
252336
// This fixes the case where the component mounts before connection is established
@@ -338,5 +422,8 @@ export const usePerpsTransactionHistory = ({
338422
isLoading: combinedIsLoading,
339423
error: combinedError,
340424
refetch,
425+
loadMoreFunding,
426+
hasFundingMore,
427+
isFetchingMoreFunding,
341428
};
342429
};

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)