1+ import {
2+ chainId ,
3+ evmAddress ,
4+ OrderDirection ,
5+ PageSize ,
6+ useUserTransactionHistory ,
7+ } from '@aave/react' ;
8+ import { Cursor } from '@aave/types' ;
19import { OrderBookApi } from '@cowprotocol/cow-sdk' ;
210import { useInfiniteQuery } from '@tanstack/react-query' ;
3- import { useEffect , useState } from 'react' ;
11+ import { useEffect , useMemo , useRef , useState } from 'react' ;
412import {
513 ADAPTER_APP_CODE ,
614 HEADER_WIDGET_APP_CODE ,
715} from 'src/components/transactions/Switch/cowprotocol/cowprotocol.helpers' ;
816import { isChainIdSupportedByCoWProtocol } from 'src/components/transactions/Switch/switch.constants' ;
17+ import { getTransactionAction , getTransactionId } from 'src/modules/history/helpers' ;
918import {
1019 actionFilterMap ,
1120 hasCollateralReserve ,
@@ -14,12 +23,8 @@ import {
1423 hasSrcOrDestToken ,
1524 HistoryFilters ,
1625 TransactionHistoryItemUnion ,
26+ UserTransactionItem ,
1727} from 'src/modules/history/types' ;
18- import {
19- USER_TRANSACTIONS_V2 ,
20- USER_TRANSACTIONS_V2_WITH_POOL ,
21- } from 'src/modules/history/v2-user-history-query' ;
22- import { USER_TRANSACTIONS_V3 } from 'src/modules/history/v3-user-history-query' ;
2328import { ERC20Service } from 'src/services/Erc20Service' ;
2429import { useRootStore } from 'src/store/root' ;
2530import { queryKeysFactory } from 'src/ui-config/queries' ;
@@ -29,6 +34,13 @@ import { useShallow } from 'zustand/shallow';
2934
3035import { useAppDataContext } from './app-data-provider/useAppDataProvider' ;
3136
37+ const sortTransactionsByTimestampDesc = (
38+ a : TransactionHistoryItemUnion ,
39+ b : TransactionHistoryItemUnion
40+ ) => {
41+ return new Date ( b . timestamp ) . getTime ( ) - new Date ( a . timestamp ) . getTime ( ) ;
42+ } ;
43+
3244export const applyTxHistoryFilters = ( {
3345 searchQuery,
3446 filterQuery,
@@ -50,21 +62,23 @@ export const applyTxHistoryFilters = ({
5062 let srcToken = '' ;
5163 let destToken = '' ;
5264
65+ //SDK structure
5366 if ( hasCollateralReserve ( txn ) ) {
54- collateralSymbol = txn . collateralReserve . symbol . toLowerCase ( ) ;
55- collateralName = txn . collateralReserve . name . toLowerCase ( ) ;
67+ collateralSymbol = txn . collateral . reserve . underlyingToken . symbol . toLowerCase ( ) ;
68+ collateralName = txn . collateral . reserve . underlyingToken . name . toLowerCase ( ) ;
5669 }
5770
5871 if ( hasPrincipalReserve ( txn ) ) {
59- principalSymbol = txn . principalReserve . symbol . toLowerCase ( ) ;
60- principalName = txn . principalReserve . name . toLowerCase ( ) ;
72+ principalSymbol = txn . debtRepaid . reserve . underlyingToken . symbol . toLowerCase ( ) ;
73+ principalName = txn . debtRepaid . reserve . underlyingToken . name . toLowerCase ( ) ;
6174 }
6275
6376 if ( hasReserve ( txn ) ) {
64- symbol = txn . reserve . symbol . toLowerCase ( ) ;
65- name = txn . reserve . name . toLowerCase ( ) ;
77+ symbol = txn . reserve . underlyingToken . symbol . toLowerCase ( ) ;
78+ name = txn . reserve . underlyingToken . name . toLowerCase ( ) ;
6679 }
6780
81+ // CowSwap structure
6882 if ( hasSrcOrDestToken ( txn ) ) {
6983 srcToken = txn . underlyingSrcToken . symbol . toLowerCase ( ) ;
7084 destToken = txn . underlyingDestToken . symbol . toLowerCase ( ) ;
@@ -92,7 +106,8 @@ export const applyTxHistoryFilters = ({
92106 // apply txn type filter
93107 if ( filterQuery . length > 0 ) {
94108 filteredTxns = filteredTxns . filter ( ( txn : TransactionHistoryItemUnion ) => {
95- if ( filterQuery . includes ( actionFilterMap ( txn . action ) ) ) {
109+ const action = getTransactionAction ( txn ) ;
110+ if ( filterQuery . includes ( actionFilterMap ( action ) ) ) {
96111 return true ;
97112 } else {
98113 return false ;
@@ -108,89 +123,107 @@ export const useTransactionHistory = ({ isFilterActive }: { isFilterActive: bool
108123 ) ;
109124
110125 const { reserves, loading : reservesLoading } = useAppDataContext ( ) ;
111-
126+ const [ sdkCursor , setSdkCursor ] = useState < Cursor | null > ( null ) ;
127+ const [ sdkTransactions , setSdkTransactions ] = useState < UserTransactionItem [ ] > ( [ ] ) ;
128+ const sdkTransactionIds = useRef < Set < string > > ( new Set ( ) ) ;
129+ const [ isFetchingAllSdkPages , setIsFetchingAllSdkPages ] = useState ( true ) ;
130+ const [ hasLoadedInitialSdkPage , setHasLoadedInitialSdkPage ] = useState ( false ) ;
112131 const [ shouldKeepFetching , setShouldKeepFetching ] = useState ( false ) ;
113132
114- // Handle subgraphs with multiple markets (currently only ETH V2 and ETH V2 AMM)
115- let selectedPool : string | undefined = undefined ;
116- if ( ! currentMarketData . v3 && currentMarketData . marketTitle === 'Ethereum' ) {
117- selectedPool = currentMarketData . addresses . LENDING_POOL_ADDRESS_PROVIDER . toLowerCase ( ) ;
118- }
133+ const isAccountValid = account && account . length > 0 ;
119134
120- interface TransactionHistoryParams {
121- account : string ;
122- subgraphUrl : string ;
123- first : number ;
124- skip : number ;
125- v3 : boolean ;
126- pool ?: string ;
127- }
128- const fetchTransactionHistory = async ( {
129- account,
130- subgraphUrl,
131- first,
132- skip,
133- v3,
134- pool,
135- } : TransactionHistoryParams ) => {
136- let query = '' ;
137- if ( v3 ) {
138- query = USER_TRANSACTIONS_V3 ;
139- } else if ( pool ) {
140- query = USER_TRANSACTIONS_V2_WITH_POOL ;
141- } else {
142- query = USER_TRANSACTIONS_V2 ;
135+ const {
136+ data : sdkData ,
137+ loading : sdkLoading ,
138+ error : sdkError ,
139+ } = useUserTransactionHistory ( {
140+ market : evmAddress ( currentMarketData . addresses . LENDING_POOL ) ,
141+ user : isAccountValid
142+ ? evmAddress ( account as string )
143+ : evmAddress ( '0x0000000000000000000000000000000000000000' ) ,
144+ chainId : chainId ( currentMarketData . chainId ) ,
145+ orderBy : { date : OrderDirection . Desc } ,
146+ pageSize : PageSize . Fifty ,
147+ cursor : sdkCursor ,
148+ } ) ;
149+
150+ useEffect ( ( ) => {
151+ setSdkCursor ( null ) ;
152+ setSdkTransactions ( [ ] ) ;
153+ sdkTransactionIds . current . clear ( ) ;
154+ setIsFetchingAllSdkPages ( true ) ;
155+ setHasLoadedInitialSdkPage ( false ) ;
156+ } , [ account , currentMarketData . addresses . LENDING_POOL , currentMarketData . chainId ] ) ;
157+
158+ useEffect ( ( ) => {
159+ if ( ! sdkData ?. items ?. length ) {
160+ if ( ! sdkLoading && ! sdkData ?. pageInfo ?. next ) {
161+ setIsFetchingAllSdkPages ( false ) ;
162+ if ( ! hasLoadedInitialSdkPage ) {
163+ setHasLoadedInitialSdkPage ( true ) ;
164+ }
165+ }
166+ return ;
143167 }
144168
145- const requestBody = {
146- query,
147- variables : { userAddress : account , first, skip, pool } ,
148- } ;
149- try {
150- const response = await fetch ( subgraphUrl , {
151- method : 'POST' ,
152- headers : {
153- 'Content-Type' : 'application/json' ,
154- } ,
155- body : JSON . stringify ( requestBody ) ,
156- } ) ;
157-
158- if ( ! response . ok ) {
159- throw new Error ( `Network error: ${ response . status } - ${ response . statusText } ` ) ;
169+ const newTransactions = sdkData . items . filter ( ( transaction ) => {
170+ const transactionId = getTransactionId ( transaction ) ;
171+ if ( sdkTransactionIds . current . has ( transactionId ) ) {
172+ return false ;
160173 }
174+ sdkTransactionIds . current . add ( transactionId ) ;
175+ return true ;
176+ } ) ;
161177
162- const data = await response . json ( ) ;
163- return data . data ?. userTransactions || [ ] ;
164- } catch ( error ) {
165- console . error ( 'Error fetching transaction history:' , error ) ;
166- return [ ] ;
178+ if ( newTransactions . length > 0 ) {
179+ setSdkTransactions ( ( prev ) => [ ...prev , ...newTransactions ] ) ;
180+ if ( ! hasLoadedInitialSdkPage ) {
181+ setHasLoadedInitialSdkPage ( true ) ;
182+ }
183+ }
184+ } , [ sdkData , sdkLoading , hasLoadedInitialSdkPage ] ) ;
185+
186+ useEffect ( ( ) => {
187+ if ( sdkLoading ) {
188+ setIsFetchingAllSdkPages ( true ) ;
189+ return ;
167190 }
191+
192+ const nextCursor = sdkData ?. pageInfo ?. next ?? null ;
193+ if ( nextCursor && nextCursor !== sdkCursor ) {
194+ setIsFetchingAllSdkPages ( true ) ;
195+ setSdkCursor ( nextCursor ) ;
196+ return ;
197+ }
198+
199+ if ( ! nextCursor ) {
200+ setIsFetchingAllSdkPages ( false ) ;
201+ }
202+ } , [ sdkData ?. pageInfo ?. next , sdkLoading , sdkCursor ] ) ;
203+
204+ useEffect ( ( ) => {
205+ if ( sdkError && ! hasLoadedInitialSdkPage ) {
206+ setHasLoadedInitialSdkPage ( true ) ;
207+ setIsFetchingAllSdkPages ( false ) ;
208+ }
209+ } , [ sdkError , hasLoadedInitialSdkPage ] ) ;
210+
211+ const getSDKTransactions = ( ) : UserTransactionItem [ ] => {
212+ return sdkTransactions ;
168213 } ;
169214
170215 const fetchForDownload = async ( {
171216 searchQuery,
172217 filterQuery,
173218 } : HistoryFilters ) : Promise < TransactionHistoryItemUnion [ ] > => {
174- const allTransactions = [ ] ;
175- const batchSize = 100 ;
176- let skip = 0 ;
177- let currentBatchSize = batchSize ;
178-
179- // Pagination over multiple sources is not perfect but since this is not a user facing feature, it's not noticeable
180- while ( currentBatchSize === batchSize ) {
181- const currentBatch = await fetchTransactionHistory ( {
182- first : batchSize ,
183- skip : skip ,
184- account,
185- subgraphUrl : currentMarketData . subgraphUrl ?? '' ,
186- v3 : ! ! currentMarketData . v3 ,
187- pool : selectedPool ,
188- } ) ;
189- const cowSwapOrders = await fetchCowSwapsHistory ( batchSize , skip * batchSize ) ;
190- allTransactions . push ( ...currentBatch , ...cowSwapOrders ) ;
191- currentBatchSize = currentBatch . length ;
192- skip += batchSize ;
193- }
219+ const sdkTransactions = getSDKTransactions ( ) ;
220+ const skip = 0 ;
221+ const allCowSwapOrders = await fetchCowSwapsHistory ( PAGE_SIZE , skip ) ;
222+
223+ const allTransactions : TransactionHistoryItemUnion [ ] = [
224+ ...sdkTransactions ,
225+ ...allCowSwapOrders ,
226+ ] ;
194227
195228 const filteredTxns = applyTxHistoryFilters ( { searchQuery, filterQuery, txns : allTransactions } ) ;
196229 return filteredTxns ;
@@ -308,7 +341,7 @@ export const useTransactionHistory = ({ isFilterActive }: { isFilterActive: bool
308341 return {
309342 action : srcToken . isAToken ? 'CowCollateralSwap' : 'CowSwap' ,
310343 id : order . uid ,
311- timestamp : Math . floor ( new Date ( order . creationDate ) . getTime ( ) / 1000 ) ,
344+ timestamp : new Date ( order . creationDate ) . toISOString ( ) ,
312345 underlyingSrcToken : {
313346 underlyingAsset : srcToken . address ,
314347 name : srcToken . name ,
@@ -339,8 +372,8 @@ export const useTransactionHistory = ({ isFilterActive }: { isFilterActive: bool
339372 ) . then ( ( txns ) => txns . filter ( ( txn ) => txn !== null ) ) ;
340373 } ;
341374
342- const PAGE_SIZE = 100 ;
343- // Pagination over multiple sources is not perfect but since we are using an infinite query, won't be noticeable
375+ const PAGE_SIZE = 50 ; //Limit SDK and CowSwap to same page size
376+
344377 const {
345378 data,
346379 fetchNextPage,
@@ -352,18 +385,10 @@ export const useTransactionHistory = ({ isFilterActive }: { isFilterActive: bool
352385 } = useInfiniteQuery ( {
353386 queryKey : queryKeysFactory . transactionHistory ( account , currentMarketData ) ,
354387 queryFn : async ( { pageParam = 0 } ) => {
355- const response = await fetchTransactionHistory ( {
356- account,
357- subgraphUrl : currentMarketData . subgraphUrl ?? '' ,
358- first : PAGE_SIZE ,
359- skip : pageParam ,
360- v3 : ! ! currentMarketData . v3 ,
361- pool : selectedPool ,
362- } ) ;
363- const cowSwapOrders = await fetchCowSwapsHistory ( PAGE_SIZE , pageParam * PAGE_SIZE ) ;
364- return [ ...response , ...cowSwapOrders ] . sort ( ( a , b ) => b . timestamp - a . timestamp ) ;
388+ const cowSwapOrders = await fetchCowSwapsHistory ( PAGE_SIZE , pageParam ) ;
389+ return cowSwapOrders . sort ( sortTransactionsByTimestampDesc ) ;
365390 } ,
366- enabled : ! ! account && ! ! currentMarketData . subgraphUrl && ! reservesLoading && ! ! reserves ,
391+ enabled : ! ! account && ! reservesLoading && ! ! reserves && ! sdkLoading ,
367392 getNextPageParam : (
368393 lastPage : TransactionHistoryItemUnion [ ] ,
369394 allPages : TransactionHistoryItemUnion [ ] [ ]
@@ -377,6 +402,32 @@ export const useTransactionHistory = ({ isFilterActive }: { isFilterActive: bool
377402 initialPageParam : 0 ,
378403 } ) ;
379404
405+ const mergedData = useMemo ( ( ) => {
406+ if ( ! data ) {
407+ if ( sdkTransactions . length === 0 ) {
408+ return data ;
409+ }
410+
411+ return {
412+ pageParams : [ 0 ] ,
413+ pages : [ sdkTransactions . slice ( ) . sort ( sortTransactionsByTimestampDesc ) ] ,
414+ } ;
415+ }
416+
417+ const pagesWithSdk = data . pages . map ( ( page , index ) => {
418+ if ( index === 0 ) {
419+ const combined = [ ...sdkTransactions , ...page ] ;
420+ return combined . sort ( sortTransactionsByTimestampDesc ) ;
421+ }
422+ return page ;
423+ } ) ;
424+
425+ return {
426+ ...data ,
427+ pages : pagesWithSdk ,
428+ } ;
429+ } , [ data , sdkTransactions ] ) ;
430+
380431 // If filter is active, keep fetching until all data is returned so that it's guaranteed all filter results will be returned
381432 useEffect ( ( ) => {
382433 if ( isFilterActive && hasNextPage && ! isFetchingNextPage ) {
@@ -398,15 +449,16 @@ export const useTransactionHistory = ({ isFilterActive }: { isFilterActive: bool
398449 }
399450 } , [ shouldKeepFetching , fetchNextPage , reservesLoading ] ) ;
400451
452+ const isInitialSdkLoading = ! hasLoadedInitialSdkPage && ( sdkLoading || isFetchingAllSdkPages ) ;
453+
401454 return {
402- data,
455+ data : mergedData ,
403456 fetchNextPage,
404457 isFetchingNextPage,
405458 hasNextPage,
406- isLoading : reservesLoading || isLoadingHistory ,
407- isError,
408- error,
459+ isLoading : reservesLoading || isLoadingHistory || isInitialSdkLoading ,
460+ isError : isError || ! ! sdkError ,
461+ error : error || sdkError ,
409462 fetchForDownload,
410- subgraphUrl : currentMarketData . subgraphUrl ,
411463 } ;
412464} ;
0 commit comments