From dbb6910ce37fa6a3a7547016ce06ad357f924d2b Mon Sep 17 00:00:00 2001 From: Bartek Date: Fri, 13 Dec 2024 18:36:57 +0100 Subject: [PATCH 01/15] init --- .../TransactionHistoryTable.tsx | 33 +- .../src/hooks/useTransactionHistory.ts | 671 +++++++++++------- 2 files changed, 444 insertions(+), 260 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx index 2c05ec0da8..5b4859b965 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx @@ -28,6 +28,7 @@ import { TransactionsTableRow } from './TransactionsTableRow' import { EmptyTransactionHistory } from './EmptyTransactionHistory' import { MergedTransaction } from '../../state/app/state' import { useNativeCurrency } from '../../hooks/useNativeCurrency' +import { Loader } from '../common/atoms/Loader' export const BatchTransferNativeTokenTooltip = ({ children, @@ -139,9 +140,8 @@ export const TransactionHistoryTable = ( ) => { const { transactions, - loading, - completed, - error, + senderData, + receiverData, failedChainPairs, resume, selectedTabIndex, @@ -153,7 +153,7 @@ export const TransactionHistoryTable = ( const isTxHistoryEmpty = transactions.length === 0 const isPendingTab = selectedTabIndex === 0 - const paused = !loading && !completed + const paused = !senderData.loadingForSender && !senderData.completedForSender const contentWrapperRef = useRef(null) const tableRef = useRef(null) @@ -194,8 +194,11 @@ export const TransactionHistoryTable = ( if (isTxHistoryEmpty) { return ( - {loading ? ( + {senderData.loadingForSender ? (
@@ -222,6 +225,18 @@ export const TransactionHistoryTable = ( ) : (
+ {receiverData.loadingForReceiver && ( + + Inbound transactions received from another address are + still being fetched... + + } + > + + + )} Showing {transactions.length}{' '} @@ -230,7 +245,9 @@ export const TransactionHistoryTable = (
- {!completed && } + {!senderData.completedForSender && ( + + )}
)}
{pendingTokenDepositsCount > 0 && }
diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 428722771e..9c4db9946d 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -67,9 +67,18 @@ import { captureSentryErrorWithExtraData } from '../util/SentryUtils' export type UseTransactionHistoryResult = { transactions: MergedTransaction[] - loading: boolean - completed: boolean - error: unknown + senderData: { + senderTransactions: MergedTransaction[] + loadingForSender: boolean + completedForSender: boolean + errorForSender: unknown + } + receiverData: { + receiverTransactions: MergedTransaction[] + loadingForReceiver: boolean + completedForReceiver: boolean + errorForReceiver: unknown + } failedChainPairs: ChainPair[] pause: () => void resume: () => void @@ -244,102 +253,117 @@ function dedupeTransactions(txs: Transfer[]) { /** * Fetches transaction history only for deposits and withdrawals, without their statuses. */ -const useTransactionHistoryWithoutStatuses = (address: Address | undefined) => { +const useRawTransactionHistory = ({ + address, + fetchFor, + fetchStarted +}: { + address: Address | undefined + fetchFor: 'sender' | 'receiver' + fetchStarted: boolean +}) => { const { chain } = useNetwork() const [isTestnetMode] = useIsTestnetMode() const { isSmartContractWallet, isLoading: isLoadingAccountType } = useAccountType() - // Check what type of CCTP (deposit, withdrawal or all) to fetch - // We need this because of Smart Contract Wallets - const cctpTypeToFetch = useCallback( - (chainPair: ChainPair): 'deposits' | 'withdrawals' | 'all' | undefined => { - if (isLoadingAccountType || !chain) { - return undefined - } - if (isSmartContractWallet) { - // fetch based on the connected network - if (chain.id === chainPair.parentChainId) { - return 'deposits' - } - if (chain.id === chainPair.childChainId) { - return 'withdrawals' - } - return undefined - } - // EOA - return isNetwork(chainPair.parentChainId).isTestnet === isTestnetMode - ? 'all' - : undefined - }, - [isSmartContractWallet, isLoadingAccountType, chain, isTestnetMode] - ) - - const cctpTransfersMainnet = useCctpFetching({ - walletAddress: address, - l1ChainId: ChainId.Ethereum, - l2ChainId: ChainId.ArbitrumOne, - pageNumber: 0, - pageSize: cctpTypeToFetch({ - parentChainId: ChainId.Ethereum, - childChainId: ChainId.ArbitrumOne - }) - ? 1000 - : 0, - type: - cctpTypeToFetch({ - parentChainId: ChainId.Ethereum, - childChainId: ChainId.ArbitrumOne - }) ?? 'all' - }) - - const cctpTransfersTestnet = useCctpFetching({ - walletAddress: address, - l1ChainId: ChainId.Sepolia, - l2ChainId: ChainId.ArbitrumSepolia, - pageNumber: 0, - pageSize: cctpTypeToFetch({ - parentChainId: ChainId.Sepolia, - childChainId: ChainId.ArbitrumSepolia - }) - ? 1000 - : 0, - type: - cctpTypeToFetch({ - parentChainId: ChainId.Sepolia, - childChainId: ChainId.ArbitrumSepolia - }) ?? 'all' - }) - - // TODO: Clean up this logic when introducing testnet/mainnet split - const combinedCctpTransfers = [ - ...(cctpTransfersMainnet.deposits?.completed || []), - ...(cctpTransfersMainnet.withdrawals?.completed || []), - ...(cctpTransfersTestnet.deposits?.completed || []), - ...(cctpTransfersTestnet.withdrawals?.completed || []), - ...(cctpTransfersMainnet.deposits?.pending || []), - ...(cctpTransfersMainnet.withdrawals?.pending || []), - ...(cctpTransfersTestnet.deposits?.pending || []), - ...(cctpTransfersTestnet.withdrawals?.pending || []) - ] - - const cctpLoading = - cctpTransfersMainnet.isLoadingDeposits || - cctpTransfersMainnet.isLoadingWithdrawals || - cctpTransfersTestnet.isLoadingDeposits || - cctpTransfersTestnet.isLoadingWithdrawals - - const { data: failedChainPairs, mutate: addFailedChainPair } = - useSWRImmutable( - address ? ['failed_chain_pairs', address] : null - ) + // // Check what type of CCTP (deposit, withdrawal or all) to fetch + // // We need this because of Smart Contract Wallets + // const cctpTypeToFetch = useCallback( + // (chainPair: ChainPair): 'deposits' | 'withdrawals' | 'all' | undefined => { + // if (isLoadingAccountType || !chain) { + // return undefined + // } + // if (isSmartContractWallet) { + // // fetch based on the connected network + // if (chain.id === chainPair.parentChainId) { + // return 'deposits' + // } + // if (chain.id === chainPair.childChainId) { + // return 'withdrawals' + // } + // return undefined + // } + // // EOA + // return isNetwork(chainPair.parentChainId).isTestnet === isTestnetMode + // ? 'all' + // : undefined + // }, + // [isSmartContractWallet, isLoadingAccountType, chain, isTestnetMode] + // ) + + // const cctpTransfersMainnet = useCctpFetching({ + // walletAddress: address, + // l1ChainId: ChainId.Ethereum, + // l2ChainId: ChainId.ArbitrumOne, + // pageNumber: 0, + // pageSize: cctpTypeToFetch({ + // parentChainId: ChainId.Ethereum, + // childChainId: ChainId.ArbitrumOne + // }) + // ? 1000 + // : 0, + // type: + // cctpTypeToFetch({ + // parentChainId: ChainId.Ethereum, + // childChainId: ChainId.ArbitrumOne + // }) ?? 'all' + // }) + + // const cctpTransfersTestnet = useCctpFetching({ + // walletAddress: address, + // l1ChainId: ChainId.Sepolia, + // l2ChainId: ChainId.ArbitrumSepolia, + // pageNumber: 0, + // pageSize: cctpTypeToFetch({ + // parentChainId: ChainId.Sepolia, + // childChainId: ChainId.ArbitrumSepolia + // }) + // ? 1000 + // : 0, + // type: + // cctpTypeToFetch({ + // parentChainId: ChainId.Sepolia, + // childChainId: ChainId.ArbitrumSepolia + // }) ?? 'all' + // }) + + // // TODO: Clean up this logic when introducing testnet/mainnet split + // const combinedCctpTransfers = [ + // ...(cctpTransfersMainnet.deposits?.completed || []), + // ...(cctpTransfersMainnet.withdrawals?.completed || []), + // ...(cctpTransfersTestnet.deposits?.completed || []), + // ...(cctpTransfersTestnet.withdrawals?.completed || []), + // ...(cctpTransfersMainnet.deposits?.pending || []), + // ...(cctpTransfersMainnet.withdrawals?.pending || []), + // ...(cctpTransfersTestnet.deposits?.pending || []), + // ...(cctpTransfersTestnet.withdrawals?.pending || []) + // ] + + // const cctpLoading = + // cctpTransfersMainnet.isLoadingDeposits || + // cctpTransfersMainnet.isLoadingWithdrawals || + // cctpTransfersTestnet.isLoadingDeposits || + // cctpTransfersTestnet.isLoadingWithdrawals + + const { data: erroredChains, mutate: addFailedChainPair } = useSWRImmutable< + ChainPair[] + >(address ? ['errored_chains', address] : null) const fetcher = useCallback( - (type: 'deposits' | 'withdrawals') => { + ({ + type, + fetchFor + }: { + type: 'deposits' | 'withdrawals' + fetchFor: 'sender' | 'receiver' + }) => { if (!chain) { return [] } + const fetchForSender = fetchFor === 'sender' + const fetcherFn = type === 'deposits' ? fetchDeposits : fetchWithdrawals return Promise.all( @@ -357,23 +381,6 @@ const useTransactionHistoryWithoutStatuses = (address: Address | undefined) => { ) }) .map(async chainPair => { - // SCW address is tied to a specific network - // that's why we need to limit shown txs either to sent or received funds - // otherwise we'd display funds for a different network, which could be someone else's account - const isConnectedToParentChain = - chainPair.parentChainId === chain.id - - const includeSentTxs = shouldIncludeSentTxs({ - type, - isSmartContractWallet, - isConnectedToParentChain - }) - - const includeReceivedTxs = shouldIncludeReceivedTxs({ - type, - isSmartContractWallet, - isConnectedToParentChain - }) try { // early check for fetching teleport if ( @@ -386,8 +393,8 @@ const useTransactionHistoryWithoutStatuses = (address: Address | undefined) => { if (type === 'withdrawals') return [] return await fetchTeleports({ - sender: includeSentTxs ? address : undefined, - receiver: includeReceivedTxs ? address : undefined, + sender: fetchForSender ? address : undefined, + receiver: fetchForSender ? undefined : address, parentChainProvider: getProviderForChainId( chainPair.parentChainId ), @@ -401,8 +408,8 @@ const useTransactionHistoryWithoutStatuses = (address: Address | undefined) => { // else, fetch deposits or withdrawals return await fetcherFn({ - sender: includeSentTxs ? address : undefined, - receiver: includeReceivedTxs ? address : undefined, + sender: fetchForSender ? address : undefined, + receiver: fetchForSender ? undefined : address, l1Provider: getProviderForChainId(chainPair.parentChainId), l2Provider: getProviderForChainId(chainPair.childChainId), pageNumber: 0, @@ -432,18 +439,20 @@ const useTransactionHistoryWithoutStatuses = (address: Address | undefined) => { }) ) }, - [address, isTestnetMode, addFailedChainPair, isSmartContractWallet, chain] + [addFailedChainPair, address, chain, isSmartContractWallet, isTestnetMode] ) - const shouldFetch = address && chain && !isLoadingAccountType + const shouldFetch = fetchStarted && address && chain && !isLoadingAccountType const { data: depositsData, error: depositsError, isLoading: depositsLoading } = useSWRImmutable( - shouldFetch ? ['tx_list', 'deposits', address, isTestnetMode] : null, - () => fetcher('deposits') + shouldFetch + ? ['tx_list', 'deposits', address, isTestnetMode, fetchFor] + : null, + () => fetcher({ type: 'deposits', fetchFor }) ) const { @@ -451,8 +460,10 @@ const useTransactionHistoryWithoutStatuses = (address: Address | undefined) => { error: withdrawalsError, isLoading: withdrawalsLoading } = useSWRImmutable( - shouldFetch ? ['tx_list', 'withdrawals', address, isTestnetMode] : null, - () => fetcher('withdrawals') + shouldFetch + ? ['tx_list', 'withdrawals', address, isTestnetMode, fetchFor] + : null, + () => fetcher({ type: 'withdrawals', fetchFor }) ) const deposits = (depositsData || []).flat() @@ -462,15 +473,16 @@ const useTransactionHistoryWithoutStatuses = (address: Address | undefined) => { // merge deposits and withdrawals and sort them by date const transactions = [ ...deposits, - ...withdrawals, - ...combinedCctpTransfers + ...withdrawals + // ...combinedCctpTransfers ].flat() return { - data: transactions, - loading: depositsLoading || withdrawalsLoading || cctpLoading, - error: depositsError ?? withdrawalsError, - failedChainPairs: failedChainPairs || [] + rawData: transactions, + rawDataLoading: depositsLoading || withdrawalsLoading, + // loading: depositsLoading || withdrawalsLoading || cctpLoading, + rawDataError: depositsError ?? withdrawalsError, + rawDataErroredChains: erroredChains || [] } } @@ -478,33 +490,57 @@ const useTransactionHistoryWithoutStatuses = (address: Address | undefined) => { * Maps additional info to previously fetches transaction history, starting with the earliest data. * This is done in small batches to safely meet RPC limits. */ -export const useTransactionHistory = ( - address: Address | undefined, - // TODO: look for a solution to this. It's used for now so that useEffect that handles pagination runs only a single instance. - { runFetcher = false } = {} -): UseTransactionHistoryResult => { - const [isTestnetMode] = useIsTestnetMode() - const { chain } = useNetwork() - const { isSmartContractWallet, isLoading: isLoadingAccountType } = - useAccountType() - const { connector } = useAccount() +const useMappedTransactionHistory = ({ + address, + fetchFor, + fetchStarted, + runFetcher = false +}: { + address: Address | undefined + fetchFor: 'sender' | 'receiver' + fetchStarted: boolean + runFetcher?: boolean +}) => { // max number of transactions mapped in parallel const MAX_BATCH_SIZE = 3 // Pause fetching after specified number of days. User can resume fetching to get another batch. const PAUSE_SIZE_DAYS = 30 + const { chain } = useNetwork() + const [isTestnetMode] = useIsTestnetMode() + const { isSmartContractWallet, isLoading: isLoadingAccountType } = + useAccountType() + const { connector } = useAccount() + const [fetching, setFetching] = useState(true) const [pauseCount, setPauseCount] = useState(0) - const { - data, - loading: isLoadingTxsWithoutStatus, - error, - failedChainPairs - } = useTransactionHistoryWithoutStatuses(address) + const { rawData, rawDataLoading, rawDataError, rawDataErroredChains } = + useRawTransactionHistory({ + address, + fetchFor, + fetchStarted + }) + + function pause() { + setFetching(false) + } + + function resume() { + setFetching(true) + setPage(prevPage => prevPage + 1) + } - const getCacheKey = useCallback( + const getSwrCacheKeyForSender = useCallback( (pageNumber: number, prevPageTxs: MergedTransaction[]) => { + if (fetchFor !== 'sender') { + return null + } + + if (!fetchStarted) { + return null + } + if (prevPageTxs) { if (prevPageTxs.length === 0) { // THIS is the last page @@ -512,13 +548,46 @@ export const useTransactionHistory = ( } } - return address && !isLoadingTxsWithoutStatus && !isLoadingAccountType - ? (['complete_tx_list', address, pageNumber, data] as const) + return address && !rawDataLoading && !isLoadingAccountType + ? ([ + 'mapped_transaction_history_for_sender', + address, + pageNumber, + rawData + ] as const) : null }, - [address, isLoadingTxsWithoutStatus, data, isLoadingAccountType] + [ + fetchFor, + fetchStarted, + address, + rawDataLoading, + isLoadingAccountType, + rawData + ] ) + const getSwrCacheKeyForReceiver = useCallback(() => { + if (fetchFor !== 'receiver') { + return null + } + + if (!fetchStarted) { + return null + } + + return address && !rawDataLoading && !isLoadingAccountType + ? (['mapped_transaction_history_for_receiver', address, rawData] as const) + : null + }, [ + address, + fetchFor, + fetchStarted, + isLoadingAccountType, + rawData, + rawDataLoading + ]) + const depositsFromCache = useMemo(() => { if (isLoadingAccountType || !chain) { return [] @@ -555,15 +624,15 @@ export const useTransactionHistory = ( ]) const { - data: txPages, - error: txPagesError, + data: senderTxPages, + error: senderTxPagesError, size: page, setSize: setPage, - mutate: mutateTxPages, - isValidating, - isLoading: isLoadingFirstPage + mutate: mutateSenderTxPages, + isValidating: isValidatingSenderTxPages, + isLoading: isLoadingSenderFirstPage } = useSWRInfinite( - getCacheKey, + getSwrCacheKeyForSender, ([, , _page, _data]) => { // we get cached data and dedupe here because we need to ensure _data never mutates // otherwise, if we added a new tx to cache, it would return a new reference and cause the SWR key to update, resulting in refetching @@ -597,17 +666,76 @@ export const useTransactionHistory = ( } ) - // based on an example from SWR - // https://swr.vercel.app/examples/infinite-loading - const isLoadingMore = - page > 0 && - typeof txPages !== 'undefined' && - typeof txPages[page - 1] === 'undefined' + const { + data: receiverTransactions, + error: receiverTransactionsError, + isLoading: isLoadingReceiverTransactions + } = useSWRImmutable( + getSwrCacheKeyForReceiver, + async ([, _address, _data]) => { + const _receiverTransactions = _data.filter(tx => { + if (isTransferTeleportFromSubgraph(tx)) { + return tx.sender.toLowerCase() !== _address?.toLowerCase() + } + + if (isDeposit(tx)) { + return ( + tx.destination && + tx.sender.toLowerCase() !== tx.destination.toLowerCase() + ) + } + + if (isWithdrawalFromSubgraph(tx)) { + return tx.sender.toLowerCase() !== tx.receiver.toLowerCase() + } + + if (isTokenWithdrawal(tx)) { + return tx._from.toLowerCase() !== tx._to.toLowerCase() + } + + return tx.caller.toLowerCase() !== tx.destination.toLowerCase() + }) - const completed = - !isLoadingFirstPage && - typeof txPages !== 'undefined' && - data.length === txPages.flat().length + const results = [] + for (const tx of _receiverTransactions) { + const transformedTx = await transformTransaction(tx) + results.push(transformedTx) + } + return results + } + ) + + useEffect(() => { + if (!runFetcher || !connector) { + return + } + connector.on('change', e => { + // reset state on account change + if (e.account) { + setPage(1) + setPauseCount(0) + setFetching(true) + } + }) + }, [connector, runFetcher, setPage]) + + useEffect(() => { + if (typeof rawDataError !== 'undefined') { + console.warn(rawDataError) + captureSentryErrorWithExtraData({ + error: rawDataError, + originFunction: 'useRawTransactionHistory' + }) + } + + if (typeof senderTxPagesError !== 'undefined') { + console.warn(senderTxPagesError) + captureSentryErrorWithExtraData({ + error: senderTxPagesError, + originFunction: 'useMappedTransactionHistory' + }) + } + }, [rawDataError, senderTxPagesError]) // transfers initiated by the user during the current session // we store it separately as there are a lot of side effects when mutating SWRInfinite @@ -616,34 +744,21 @@ export const useTransactionHistory = ( address ? ['new_tx_list', address] : null ) - const transactions: MergedTransaction[] = useMemo(() => { - const txs = [...(newTransactionsData || []), ...(txPages || [])].flat() - // make sure txs are for the current account, we can have a mismatch when switching accounts for a bit - return txs.filter(tx => - [tx.sender?.toLowerCase(), tx.destination?.toLowerCase()].includes( - address?.toLowerCase() + const senderTransactionsWithNewTransactions: MergedTransaction[] = + useMemo(() => { + const txs = [ + ...(newTransactionsData || []), + ...(senderTxPages || []) + ].flat() + // make sure txs are for the current account, we can have a mismatch when switching accounts for a bit + return txs.filter(tx => + [tx.sender?.toLowerCase(), tx.destination?.toLowerCase()].includes( + address?.toLowerCase() + ) ) - ) - }, [newTransactionsData, txPages, address]) + }, [newTransactionsData, senderTxPages, address]) - const addPendingTransaction = useCallback( - (tx: MergedTransaction) => { - if (!isTxPending(tx)) { - return - } - - mutateNewTransactionsData(currentNewTransactions => { - if (!currentNewTransactions) { - return [tx] - } - - return [tx, ...currentNewTransactions] - }) - }, - [mutateNewTransactionsData] - ) - - const updateCachedTransaction = useCallback( + const updateTransactionInSwrCache = useCallback( (newTx: MergedTransaction) => { // check if tx is a new transaction initiated by the user, and update it const foundInNewTransactions = @@ -663,7 +778,7 @@ export const useTransactionHistory = ( // tx not found in the new user initiated transaction list // look in the paginated historical data - mutateTxPages(prevTxPages => { + mutateSenderTxPages(prevTxPages => { if (!prevTxPages) { return } @@ -705,33 +820,50 @@ export const useTransactionHistory = ( return newTxPages }, false) }, - [mutateNewTransactionsData, mutateTxPages, newTransactionsData] + [mutateNewTransactionsData, mutateSenderTxPages, newTransactionsData] + ) + + const addPendingTransaction = useCallback( + (tx: MergedTransaction) => { + if (!isTxPending(tx)) { + return + } + + mutateNewTransactionsData(currentNewTransactions => { + if (!currentNewTransactions) { + return [tx] + } + + return [tx, ...currentNewTransactions] + }) + }, + [mutateNewTransactionsData] ) const updatePendingTransaction = useCallback( async (tx: MergedTransaction) => { if (!isTxPending(tx)) { // if not pending we don't need to check for status, we accept whatever status is passed in - updateCachedTransaction(tx) + updateTransactionInSwrCache(tx) return } if (isTeleportTx(tx)) { const updatedTeleportTransfer = await getUpdatedTeleportTransfer(tx) - updateCachedTransaction(updatedTeleportTransfer) + updateTransactionInSwrCache(updatedTeleportTransfer) return } if (tx.isCctp) { const updatedCctpTransfer = await getUpdatedCctpTransfer(tx) - updateCachedTransaction(updatedCctpTransfer) + updateTransactionInSwrCache(updatedCctpTransfer) return } // ETH or token withdrawal if (tx.isWithdrawal) { const updatedWithdrawal = await getUpdatedWithdrawal(tx) - updateCachedTransaction(updatedWithdrawal) + updateTransactionInSwrCache(updatedWithdrawal) return } @@ -740,38 +872,41 @@ export const useTransactionHistory = ( // ETH deposit to the same address if (tx.assetType === AssetType.ETH && !isDifferentDestinationAddress) { const updatedEthDeposit = await getUpdatedEthDeposit(tx) - updateCachedTransaction(updatedEthDeposit) + updateTransactionInSwrCache(updatedEthDeposit) return } // Token deposit or ETH deposit to a different destination address const updatedRetryableDeposit = await getUpdatedRetryableDeposit(tx) - updateCachedTransaction(updatedRetryableDeposit) + updateTransactionInSwrCache(updatedRetryableDeposit) }, - [updateCachedTransaction] + [updateTransactionInSwrCache] ) - useEffect(() => { - if (!runFetcher || !connector) { - return - } - connector.on('change', e => { - // reset state on account change - if (e.account) { - setPage(1) - setPauseCount(0) - setFetching(true) - } - }) - }, [connector, runFetcher, setPage]) + // based on an example from SWR + // https://swr.vercel.app/examples/infinite-loading + const isLoadingMore = + page > 0 && + typeof senderTxPages !== 'undefined' && + typeof senderTxPages[page - 1] === 'undefined' + + const senderCompleted = + !isLoadingSenderFirstPage && + typeof senderTxPages !== 'undefined' && + rawData.length === senderTxPages.flat().length useEffect(() => { - if (!txPages || !fetching || !runFetcher || isValidating) { + if ( + !senderTxPages || + !fetching || + !runFetcher || + isValidatingSenderTxPages + ) { return } - const firstPage = txPages[0] - const lastPage = txPages[txPages.length - 1] + const firstPage = senderTxPages[0] + const lastPage = senderTxPages[senderTxPages.length - 1] if (!firstPage || !lastPage) { return @@ -804,58 +939,90 @@ export const useTransactionHistory = ( } // make sure we don't over-fetch - if (page === txPages.length) { + if (page === senderTxPages.length) { setPage(prevPage => prevPage + 1) } - }, [txPages, setPage, page, pauseCount, fetching, runFetcher, isValidating]) - - useEffect(() => { - if (typeof error !== 'undefined') { - console.warn(error) - captureSentryErrorWithExtraData({ - error, - originFunction: 'useTransactionHistoryWithoutStatuses' - }) - } - - if (typeof txPagesError !== 'undefined') { - console.warn(txPagesError) - captureSentryErrorWithExtraData({ - error: txPagesError, - originFunction: 'useTransactionHistory' - }) - } - }, [error, txPagesError]) + }, [ + senderTxPages, + setPage, + page, + pauseCount, + fetching, + runFetcher, + isValidatingSenderTxPages + ]) - function pause() { - setFetching(false) + return { + transactions: + fetchFor === 'sender' + ? senderTransactionsWithNewTransactions || [] + : receiverTransactions || [], + error: senderTxPagesError || receiverTransactionsError, + completed: + senderCompleted || + (fetchFor === 'receiver' && !isLoadingReceiverTransactions), + loading: + rawDataLoading || + isLoadingSenderFirstPage || + isLoadingMore || + isLoadingReceiverTransactions, + pause, + resume, + addPendingTransaction, + updatePendingTransaction } +} - function resume() { - setFetching(true) - setPage(prevPage => prevPage + 1) - } +export const useTransactionHistory = ( + address: Address | undefined, + // TODO: look for a solution to this. It's used for now so that useEffect that handles pagination runs only a single instance. + { runFetcher = false } = {} +): UseTransactionHistoryResult => { + const { + transactions: senderTransactions, + loading: loadingForSender, + completed: completedForSender, + error: errorForSender, + pause, + resume, + addPendingTransaction, + updatePendingTransaction + } = useMappedTransactionHistory({ + address, + fetchFor: 'sender', + fetchStarted: true, + runFetcher + }) - if (isLoadingTxsWithoutStatus || error) { - return { - transactions: [], - loading: isLoadingTxsWithoutStatus, - error, - failedChainPairs: [], - completed: true, - pause, - resume, - addPendingTransaction, - updatePendingTransaction - } - } + const { + transactions: receiverTransactions, + loading: loadingForReceiver, + completed: completedForReceiver, + error: errorForReceiver + } = useMappedTransactionHistory({ + address, + fetchFor: 'receiver', + fetchStarted: !loadingForSender, + runFetcher + }) return { - transactions, - loading: isLoadingFirstPage || isLoadingMore, - completed, - error: txPagesError ?? error, - failedChainPairs, + transactions: [...senderTransactions, ...receiverTransactions].sort( + sortByTimestampDescending + ), + senderData: { + senderTransactions, + loadingForSender, + completedForSender, + errorForSender + }, + receiverData: { + receiverTransactions, + loadingForReceiver, + completedForReceiver, + errorForReceiver + }, + failedChainPairs: [], pause, resume, addPendingTransaction, From 86e6afc510363d68c2ecf771bc1d1396e65fd9a5 Mon Sep 17 00:00:00 2001 From: Bartek Date: Mon, 16 Dec 2024 12:04:01 +0100 Subject: [PATCH 02/15] fixes --- .../TransactionHistoryTable.tsx | 8 +- .../src/hooks/useTransactionHistory.ts | 237 ++++++++++-------- 2 files changed, 142 insertions(+), 103 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx index 5b4859b965..d29a88f753 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx @@ -142,7 +142,7 @@ export const TransactionHistoryTable = ( transactions, senderData, receiverData, - failedChainPairs, + rawDataErroredChains, resume, selectedTabIndex, oldestTxTimeAgoString @@ -219,7 +219,7 @@ export const TransactionHistoryTable = ( > {senderData.loadingForSender ? (
- +
) : ( @@ -237,7 +237,9 @@ export const TransactionHistoryTable = ( )} - + Showing {transactions.length}{' '} {isPendingTab ? 'pending' : 'settled'} transactions made in{' '} diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 9c4db9946d..3ecb6ce2a9 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -79,7 +79,7 @@ export type UseTransactionHistoryResult = { completedForReceiver: boolean errorForReceiver: unknown } - failedChainPairs: ChainPair[] + rawDataErroredChains: ChainPair[] pause: () => void resume: () => void addPendingTransaction: (tx: MergedTransaction) => void @@ -267,86 +267,7 @@ const useRawTransactionHistory = ({ const { isSmartContractWallet, isLoading: isLoadingAccountType } = useAccountType() - // // Check what type of CCTP (deposit, withdrawal or all) to fetch - // // We need this because of Smart Contract Wallets - // const cctpTypeToFetch = useCallback( - // (chainPair: ChainPair): 'deposits' | 'withdrawals' | 'all' | undefined => { - // if (isLoadingAccountType || !chain) { - // return undefined - // } - // if (isSmartContractWallet) { - // // fetch based on the connected network - // if (chain.id === chainPair.parentChainId) { - // return 'deposits' - // } - // if (chain.id === chainPair.childChainId) { - // return 'withdrawals' - // } - // return undefined - // } - // // EOA - // return isNetwork(chainPair.parentChainId).isTestnet === isTestnetMode - // ? 'all' - // : undefined - // }, - // [isSmartContractWallet, isLoadingAccountType, chain, isTestnetMode] - // ) - - // const cctpTransfersMainnet = useCctpFetching({ - // walletAddress: address, - // l1ChainId: ChainId.Ethereum, - // l2ChainId: ChainId.ArbitrumOne, - // pageNumber: 0, - // pageSize: cctpTypeToFetch({ - // parentChainId: ChainId.Ethereum, - // childChainId: ChainId.ArbitrumOne - // }) - // ? 1000 - // : 0, - // type: - // cctpTypeToFetch({ - // parentChainId: ChainId.Ethereum, - // childChainId: ChainId.ArbitrumOne - // }) ?? 'all' - // }) - - // const cctpTransfersTestnet = useCctpFetching({ - // walletAddress: address, - // l1ChainId: ChainId.Sepolia, - // l2ChainId: ChainId.ArbitrumSepolia, - // pageNumber: 0, - // pageSize: cctpTypeToFetch({ - // parentChainId: ChainId.Sepolia, - // childChainId: ChainId.ArbitrumSepolia - // }) - // ? 1000 - // : 0, - // type: - // cctpTypeToFetch({ - // parentChainId: ChainId.Sepolia, - // childChainId: ChainId.ArbitrumSepolia - // }) ?? 'all' - // }) - - // // TODO: Clean up this logic when introducing testnet/mainnet split - // const combinedCctpTransfers = [ - // ...(cctpTransfersMainnet.deposits?.completed || []), - // ...(cctpTransfersMainnet.withdrawals?.completed || []), - // ...(cctpTransfersTestnet.deposits?.completed || []), - // ...(cctpTransfersTestnet.withdrawals?.completed || []), - // ...(cctpTransfersMainnet.deposits?.pending || []), - // ...(cctpTransfersMainnet.withdrawals?.pending || []), - // ...(cctpTransfersTestnet.deposits?.pending || []), - // ...(cctpTransfersTestnet.withdrawals?.pending || []) - // ] - - // const cctpLoading = - // cctpTransfersMainnet.isLoadingDeposits || - // cctpTransfersMainnet.isLoadingWithdrawals || - // cctpTransfersTestnet.isLoadingDeposits || - // cctpTransfersTestnet.isLoadingWithdrawals - - const { data: erroredChains, mutate: addFailedChainPair } = useSWRImmutable< + const { data: erroredChains, mutate: addErroredChain } = useSWRImmutable< ChainPair[] >(address ? ['errored_chains', address] : null) @@ -362,8 +283,6 @@ const useRawTransactionHistory = ({ return [] } - const fetchForSender = fetchFor === 'sender' - const fetcherFn = type === 'deposits' ? fetchDeposits : fetchWithdrawals return Promise.all( @@ -381,6 +300,28 @@ const useRawTransactionHistory = ({ ) }) .map(async chainPair => { + // SCW address is tied to a specific network + // that's why we need to limit shown txs either to sent or received funds + // otherwise we'd display funds for a different network, which could be someone else's account + const isConnectedToParentChain = + chainPair.parentChainId === chain.id + + const includeSentTxs = shouldIncludeSentTxs({ + type, + isSmartContractWallet, + isConnectedToParentChain + }) + + const includeReceivedTxs = shouldIncludeReceivedTxs({ + type, + isSmartContractWallet, + isConnectedToParentChain + }) + + const fetchForSender = fetchFor === 'sender' && includeSentTxs + const fetchForReceiver = + fetchFor === 'receiver' && includeReceivedTxs + try { // early check for fetching teleport if ( @@ -394,7 +335,7 @@ const useRawTransactionHistory = ({ return await fetchTeleports({ sender: fetchForSender ? address : undefined, - receiver: fetchForSender ? undefined : address, + receiver: fetchForReceiver ? address : undefined, parentChainProvider: getProviderForChainId( chainPair.parentChainId ), @@ -409,29 +350,29 @@ const useRawTransactionHistory = ({ // else, fetch deposits or withdrawals return await fetcherFn({ sender: fetchForSender ? address : undefined, - receiver: fetchForSender ? undefined : address, + receiver: fetchForReceiver ? address : undefined, l1Provider: getProviderForChainId(chainPair.parentChainId), l2Provider: getProviderForChainId(chainPair.childChainId), pageNumber: 0, pageSize: 1000 }) } catch { - addFailedChainPair(prevFailedChainPairs => { - if (!prevFailedChainPairs) { + addErroredChain(prevErroredChains => { + if (!prevErroredChains) { return [chainPair] } if ( - typeof prevFailedChainPairs.find( - prevPair => - prevPair.parentChainId === chainPair.parentChainId && - prevPair.childChainId === chainPair.childChainId + typeof prevErroredChains.find( + prev => + prev.parentChainId === chainPair.parentChainId && + prev.childChainId === chainPair.childChainId ) !== 'undefined' ) { // already added - return prevFailedChainPairs + return prevErroredChains } - return [...prevFailedChainPairs, chainPair] + return [...prevErroredChains, chainPair] }) return [] @@ -439,7 +380,7 @@ const useRawTransactionHistory = ({ }) ) }, - [addFailedChainPair, address, chain, isSmartContractWallet, isTestnetMode] + [addErroredChain, address, chain, isSmartContractWallet, isTestnetMode] ) const shouldFetch = fetchStarted && address && chain && !isLoadingAccountType @@ -486,6 +427,94 @@ const useRawTransactionHistory = ({ } } +const useCctpTransactions = ({ address }: { address: Address | undefined }) => { + const { chain } = useNetwork() + const [isTestnetMode] = useIsTestnetMode() + const { isSmartContractWallet, isLoading: isLoadingAccountType } = + useAccountType() + + // Check what type of CCTP (deposit, withdrawal or all) to fetch + // We need this because of Smart Contract Wallets + const cctpTypeToFetch = useCallback( + (chainPair: ChainPair): 'deposits' | 'withdrawals' | 'all' | undefined => { + if (isLoadingAccountType || !chain) { + return undefined + } + if (isSmartContractWallet) { + // fetch based on the connected network + if (chain.id === chainPair.parentChainId) { + return 'deposits' + } + if (chain.id === chainPair.childChainId) { + return 'withdrawals' + } + return undefined + } + // EOA + return isNetwork(chainPair.parentChainId).isTestnet === isTestnetMode + ? 'all' + : undefined + }, + [isSmartContractWallet, isLoadingAccountType, chain, isTestnetMode] + ) + + const cctpTransfersMainnet = useCctpFetching({ + walletAddress: address, + l1ChainId: ChainId.Ethereum, + l2ChainId: ChainId.ArbitrumOne, + pageNumber: 0, + pageSize: cctpTypeToFetch({ + parentChainId: ChainId.Ethereum, + childChainId: ChainId.ArbitrumOne + }) + ? 1000 + : 0, + type: + cctpTypeToFetch({ + parentChainId: ChainId.Ethereum, + childChainId: ChainId.ArbitrumOne + }) ?? 'all' + }) + + const cctpTransfersTestnet = useCctpFetching({ + walletAddress: address, + l1ChainId: ChainId.Sepolia, + l2ChainId: ChainId.ArbitrumSepolia, + pageNumber: 0, + pageSize: cctpTypeToFetch({ + parentChainId: ChainId.Sepolia, + childChainId: ChainId.ArbitrumSepolia + }) + ? 1000 + : 0, + type: + cctpTypeToFetch({ + parentChainId: ChainId.Sepolia, + childChainId: ChainId.ArbitrumSepolia + }) ?? 'all' + }) + + // TODO: Clean up this logic when introducing testnet/mainnet split + const combinedCctpTransfers = [ + ...(cctpTransfersMainnet.deposits?.completed || []), + ...(cctpTransfersMainnet.withdrawals?.completed || []), + ...(cctpTransfersTestnet.deposits?.completed || []), + ...(cctpTransfersTestnet.withdrawals?.completed || []), + ...(cctpTransfersMainnet.deposits?.pending || []), + ...(cctpTransfersMainnet.withdrawals?.pending || []), + ...(cctpTransfersTestnet.deposits?.pending || []), + ...(cctpTransfersTestnet.withdrawals?.pending || []) + ] + + const cctpLoading = + cctpTransfersMainnet.isLoadingDeposits || + cctpTransfersMainnet.isLoadingWithdrawals || + cctpTransfersTestnet.isLoadingDeposits || + cctpTransfersTestnet.isLoadingWithdrawals + + return { cctpTransactions: combinedCctpTransfers, cctpLoading } +} + /** * Maps additional info to previously fetches transaction history, starting with the earliest data. * This is done in small batches to safely meet RPC limits. @@ -966,6 +995,7 @@ const useMappedTransactionHistory = ({ isLoadingSenderFirstPage || isLoadingMore || isLoadingReceiverTransactions, + rawDataErroredChains, pause, resume, addPendingTransaction, @@ -983,6 +1013,7 @@ export const useTransactionHistory = ( loading: loadingForSender, completed: completedForSender, error: errorForSender, + rawDataErroredChains: rawDataErroredChainsForSender, pause, resume, addPendingTransaction, @@ -998,7 +1029,8 @@ export const useTransactionHistory = ( transactions: receiverTransactions, loading: loadingForReceiver, completed: completedForReceiver, - error: errorForReceiver + error: errorForReceiver, + rawDataErroredChains: rawDataErroredChainsForReceiver } = useMappedTransactionHistory({ address, fetchFor: 'receiver', @@ -1006,13 +1038,17 @@ export const useTransactionHistory = ( runFetcher }) + const { cctpTransactions, cctpLoading } = useCctpTransactions({ address }) + return { - transactions: [...senderTransactions, ...receiverTransactions].sort( - sortByTimestampDescending - ), + transactions: [ + ...senderTransactions, + ...receiverTransactions, + ...cctpTransactions + ].sort(sortByTimestampDescending), senderData: { senderTransactions, - loadingForSender, + loadingForSender: loadingForSender || cctpLoading, completedForSender, errorForSender }, @@ -1022,7 +1058,8 @@ export const useTransactionHistory = ( completedForReceiver, errorForReceiver }, - failedChainPairs: [], + rawDataErroredChains: + rawDataErroredChainsForSender || rawDataErroredChainsForReceiver, pause, resume, addPendingTransaction, From 14fca0d3062ab2122d19e90bbb8118f71b1a0063 Mon Sep 17 00:00:00 2001 From: Bartek Date: Mon, 16 Dec 2024 13:59:56 +0100 Subject: [PATCH 03/15] clean up --- .../src/hooks/useTransactionHistory.ts | 354 ++++++++++-------- 1 file changed, 194 insertions(+), 160 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 3ecb6ce2a9..27cfe6e581 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -86,6 +86,15 @@ export type UseTransactionHistoryResult = { updatePendingTransaction: (tx: MergedTransaction) => Promise } +type UseMappedTransactionHistoryResult = { + transactions: MergedTransaction[] + error: unknown + completed: boolean + loading: boolean + rawDataErroredChains: ChainPair[] + updatePendingTransaction: (tx: MergedTransaction) => Promise +} + export type ChainPair = { parentChainId: ChainId; childChainId: ChainId } export type Deposit = Transaction @@ -250,17 +259,47 @@ function dedupeTransactions(txs: Transfer[]) { ) } +async function getUpdatedPendingTransaction(tx: MergedTransaction) { + if (!isTxPending(tx)) { + // if not pending we don't need to check for status, we accept whatever status is passed in + return tx + } + + if (isTeleportTx(tx)) { + return getUpdatedTeleportTransfer(tx) + } + + if (tx.isCctp) { + return getUpdatedCctpTransfer(tx) + } + + // ETH or token withdrawal + if (tx.isWithdrawal) { + return getUpdatedWithdrawal(tx) + } + + const isDifferentDestinationAddress = isCustomDestinationAddressTx(tx) + + // ETH deposit to the same address + if (tx.assetType === AssetType.ETH && !isDifferentDestinationAddress) { + return getUpdatedEthDeposit(tx) + } + + // Token deposit or ETH deposit to a different destination address + return getUpdatedRetryableDeposit(tx) +} + /** * Fetches transaction history only for deposits and withdrawals, without their statuses. */ const useRawTransactionHistory = ({ address, fetchFor, - fetchStarted + shouldStartToFetch }: { address: Address | undefined fetchFor: 'sender' | 'receiver' - fetchStarted: boolean + shouldStartToFetch: boolean }) => { const { chain } = useNetwork() const [isTestnetMode] = useIsTestnetMode() @@ -383,7 +422,8 @@ const useRawTransactionHistory = ({ [addErroredChain, address, chain, isSmartContractWallet, isTestnetMode] ) - const shouldFetch = fetchStarted && address && chain && !isLoadingAccountType + const shouldFetch = + shouldStartToFetch && address && chain && !isLoadingAccountType const { data: depositsData, @@ -408,20 +448,16 @@ const useRawTransactionHistory = ({ ) const deposits = (depositsData || []).flat() - const withdrawals = (withdrawalsData || []).flat() // merge deposits and withdrawals and sort them by date - const transactions = [ - ...deposits, - ...withdrawals - // ...combinedCctpTransfers - ].flat() + const transactions = [...deposits, ...withdrawals].sort( + sortByTimestampDescending + ) return { rawData: transactions, rawDataLoading: depositsLoading || withdrawalsLoading, - // loading: depositsLoading || withdrawalsLoading || cctpLoading, rawDataError: depositsError ?? withdrawalsError, rawDataErroredChains: erroredChains || [] } @@ -515,21 +551,20 @@ const useCctpTransactions = ({ address }: { address: Address | undefined }) => { return { cctpTransactions: combinedCctpTransfers, cctpLoading } } -/** - * Maps additional info to previously fetches transaction history, starting with the earliest data. - * This is done in small batches to safely meet RPC limits. - */ -const useMappedTransactionHistory = ({ +const useMappedSenderTransactionHistory = ({ address, - fetchFor, - fetchStarted, runFetcher = false }: { address: Address | undefined - fetchFor: 'sender' | 'receiver' - fetchStarted: boolean + // TODO: refactor runFetcher, make the method run without it + // https://linear.app/offchain-labs/issue/FS-1063/remove-runfetcher-in-usetransactionhistory runFetcher?: boolean -}) => { +}): UseMappedTransactionHistoryResult & { + firstPageLoaded: boolean + pause: () => void + resume: () => void + addPendingTransaction: (tx: MergedTransaction) => void +} => { // max number of transactions mapped in parallel const MAX_BATCH_SIZE = 3 // Pause fetching after specified number of days. User can resume fetching to get another batch. @@ -547,8 +582,8 @@ const useMappedTransactionHistory = ({ const { rawData, rawDataLoading, rawDataError, rawDataErroredChains } = useRawTransactionHistory({ address, - fetchFor, - fetchStarted + fetchFor: 'sender', + shouldStartToFetch: true }) function pause() { @@ -560,16 +595,8 @@ const useMappedTransactionHistory = ({ setPage(prevPage => prevPage + 1) } - const getSwrCacheKeyForSender = useCallback( + const getSwrCacheKey = useCallback( (pageNumber: number, prevPageTxs: MergedTransaction[]) => { - if (fetchFor !== 'sender') { - return null - } - - if (!fetchStarted) { - return null - } - if (prevPageTxs) { if (prevPageTxs.length === 0) { // THIS is the last page @@ -586,37 +613,9 @@ const useMappedTransactionHistory = ({ ] as const) : null }, - [ - fetchFor, - fetchStarted, - address, - rawDataLoading, - isLoadingAccountType, - rawData - ] + [address, rawDataLoading, isLoadingAccountType, rawData] ) - const getSwrCacheKeyForReceiver = useCallback(() => { - if (fetchFor !== 'receiver') { - return null - } - - if (!fetchStarted) { - return null - } - - return address && !rawDataLoading && !isLoadingAccountType - ? (['mapped_transaction_history_for_receiver', address, rawData] as const) - : null - }, [ - address, - fetchFor, - fetchStarted, - isLoadingAccountType, - rawData, - rawDataLoading - ]) - const depositsFromCache = useMemo(() => { if (isLoadingAccountType || !chain) { return [] @@ -661,7 +660,7 @@ const useMappedTransactionHistory = ({ isValidating: isValidatingSenderTxPages, isLoading: isLoadingSenderFirstPage } = useSWRInfinite( - getSwrCacheKeyForSender, + getSwrCacheKey, ([, , _page, _data]) => { // we get cached data and dedupe here because we need to ensure _data never mutates // otherwise, if we added a new tx to cache, it would return a new reference and cause the SWR key to update, resulting in refetching @@ -695,45 +694,6 @@ const useMappedTransactionHistory = ({ } ) - const { - data: receiverTransactions, - error: receiverTransactionsError, - isLoading: isLoadingReceiverTransactions - } = useSWRImmutable( - getSwrCacheKeyForReceiver, - async ([, _address, _data]) => { - const _receiverTransactions = _data.filter(tx => { - if (isTransferTeleportFromSubgraph(tx)) { - return tx.sender.toLowerCase() !== _address?.toLowerCase() - } - - if (isDeposit(tx)) { - return ( - tx.destination && - tx.sender.toLowerCase() !== tx.destination.toLowerCase() - ) - } - - if (isWithdrawalFromSubgraph(tx)) { - return tx.sender.toLowerCase() !== tx.receiver.toLowerCase() - } - - if (isTokenWithdrawal(tx)) { - return tx._from.toLowerCase() !== tx._to.toLowerCase() - } - - return tx.caller.toLowerCase() !== tx.destination.toLowerCase() - }) - - const results = [] - for (const tx of _receiverTransactions) { - const transformedTx = await transformTransaction(tx) - results.push(transformedTx) - } - return results - } - ) - useEffect(() => { if (!runFetcher || !connector) { return @@ -761,7 +721,7 @@ const useMappedTransactionHistory = ({ console.warn(senderTxPagesError) captureSentryErrorWithExtraData({ error: senderTxPagesError, - originFunction: 'useMappedTransactionHistory' + originFunction: 'useMappedSenderTransactionHistory' }) } }, [rawDataError, senderTxPagesError]) @@ -871,43 +831,8 @@ const useMappedTransactionHistory = ({ const updatePendingTransaction = useCallback( async (tx: MergedTransaction) => { - if (!isTxPending(tx)) { - // if not pending we don't need to check for status, we accept whatever status is passed in - updateTransactionInSwrCache(tx) - return - } - - if (isTeleportTx(tx)) { - const updatedTeleportTransfer = await getUpdatedTeleportTransfer(tx) - updateTransactionInSwrCache(updatedTeleportTransfer) - return - } - - if (tx.isCctp) { - const updatedCctpTransfer = await getUpdatedCctpTransfer(tx) - updateTransactionInSwrCache(updatedCctpTransfer) - return - } - - // ETH or token withdrawal - if (tx.isWithdrawal) { - const updatedWithdrawal = await getUpdatedWithdrawal(tx) - updateTransactionInSwrCache(updatedWithdrawal) - return - } - - const isDifferentDestinationAddress = isCustomDestinationAddressTx(tx) - - // ETH deposit to the same address - if (tx.assetType === AssetType.ETH && !isDifferentDestinationAddress) { - const updatedEthDeposit = await getUpdatedEthDeposit(tx) - updateTransactionInSwrCache(updatedEthDeposit) - return - } - - // Token deposit or ETH deposit to a different destination address - const updatedRetryableDeposit = await getUpdatedRetryableDeposit(tx) - updateTransactionInSwrCache(updatedRetryableDeposit) + const updatedPendingTransaction = await getUpdatedPendingTransaction(tx) + updateTransactionInSwrCache(updatedPendingTransaction) }, [updateTransactionInSwrCache] ) @@ -982,19 +907,11 @@ const useMappedTransactionHistory = ({ ]) return { - transactions: - fetchFor === 'sender' - ? senderTransactionsWithNewTransactions || [] - : receiverTransactions || [], - error: senderTxPagesError || receiverTransactionsError, - completed: - senderCompleted || - (fetchFor === 'receiver' && !isLoadingReceiverTransactions), - loading: - rawDataLoading || - isLoadingSenderFirstPage || - isLoadingMore || - isLoadingReceiverTransactions, + transactions: senderTransactionsWithNewTransactions || [], + error: senderTxPagesError, + completed: senderCompleted, + loading: rawDataLoading || isLoadingSenderFirstPage || isLoadingMore, + firstPageLoaded: !isLoadingSenderFirstPage, rawDataErroredChains, pause, resume, @@ -1003,6 +920,110 @@ const useMappedTransactionHistory = ({ } } +const useMappedReceiverTransactionHistory = ({ + address, + shouldStartToFetch +}: { + address: Address | undefined + shouldStartToFetch: boolean +}): UseMappedTransactionHistoryResult => { + const { isLoading: isLoadingAccountType } = useAccountType() + + const { rawData, rawDataLoading, rawDataErroredChains } = + useRawTransactionHistory({ + address, + fetchFor: 'receiver', + shouldStartToFetch + }) + + const getSwrCacheKey = useCallback(() => { + if (!shouldStartToFetch) { + return null + } + + return address && !rawDataLoading && !isLoadingAccountType + ? (['mapped_transaction_history_for_receiver', address, rawData] as const) + : null + }, [ + address, + shouldStartToFetch, + isLoadingAccountType, + rawData, + rawDataLoading + ]) + + const { + data: receiverTransactions, + error: receiverTransactionsError, + isLoading: isLoadingReceiverTransactions, + mutate: mutateReceiverTransactions + } = useSWRImmutable(getSwrCacheKey, async ([, _address, _data]) => { + const _receiverTransactions = _data.filter(tx => { + if (isTransferTeleportFromSubgraph(tx)) { + return tx.sender.toLowerCase() !== _address?.toLowerCase() + } + + if (isDeposit(tx)) { + return ( + tx.destination && + tx.sender.toLowerCase() !== tx.destination.toLowerCase() + ) + } + + if (isWithdrawalFromSubgraph(tx)) { + return tx.sender.toLowerCase() !== tx.receiver.toLowerCase() + } + + if (isTokenWithdrawal(tx)) { + return tx._from.toLowerCase() !== tx._to.toLowerCase() + } + + return tx.caller.toLowerCase() !== tx.destination.toLowerCase() + }) + + const results = [] + for (const tx of _receiverTransactions) { + const transformedTx = await transformTransaction(tx) + results.push(transformedTx) + } + return results + }) + + const updateTransactionInSwrCache = useCallback( + (newTx: MergedTransaction) => { + mutateReceiverTransactions(prevReceiverTransactions => { + return prevReceiverTransactions?.map(tx => { + if ( + tx.childChainId === newTx.childChainId && + tx.txId === newTx.txId + ) { + return newTx + } + return tx + }) + }) + }, + [mutateReceiverTransactions] + ) + + const updatePendingTransaction = useCallback( + async (tx: MergedTransaction) => { + const updatedPendingTransaction = await getUpdatedPendingTransaction(tx) + updateTransactionInSwrCache(updatedPendingTransaction) + }, + [updateTransactionInSwrCache] + ) + + return { + transactions: receiverTransactions || [], + error: receiverTransactionsError, + completed: !isLoadingReceiverTransactions, + loading: isLoadingReceiverTransactions, + rawDataErroredChains, + updatePendingTransaction + } +} + export const useTransactionHistory = ( address: Address | undefined, // TODO: look for a solution to this. It's used for now so that useEffect that handles pagination runs only a single instance. @@ -1014,14 +1035,13 @@ export const useTransactionHistory = ( completed: completedForSender, error: errorForSender, rawDataErroredChains: rawDataErroredChainsForSender, + firstPageLoaded: firstPageLoadedForSender, pause, resume, addPendingTransaction, - updatePendingTransaction - } = useMappedTransactionHistory({ + updatePendingTransaction: updatePendingSenderTransaction + } = useMappedSenderTransactionHistory({ address, - fetchFor: 'sender', - fetchStarted: true, runFetcher }) @@ -1030,16 +1050,30 @@ export const useTransactionHistory = ( loading: loadingForReceiver, completed: completedForReceiver, error: errorForReceiver, - rawDataErroredChains: rawDataErroredChainsForReceiver - } = useMappedTransactionHistory({ + rawDataErroredChains: rawDataErroredChainsForReceiver, + updatePendingTransaction: updatePendingReceiverTransaction + } = useMappedReceiverTransactionHistory({ address, - fetchFor: 'receiver', - fetchStarted: !loadingForSender, - runFetcher + shouldStartToFetch: firstPageLoadedForSender }) const { cctpTransactions, cctpLoading } = useCctpTransactions({ address }) + const updatePendingTransaction = useCallback( + async (tx: MergedTransaction) => { + if ( + tx.destination && + tx.sender && + tx.destination.toLowerCase() !== tx.sender.toLowerCase() + ) { + return updatePendingReceiverTransaction(tx) + } + + updatePendingSenderTransaction(tx) + }, + [updatePendingReceiverTransaction, updatePendingSenderTransaction] + ) + return { transactions: [ ...senderTransactions, From 5ac9013ceea8d104f540cb69b5b441a5d1cffe93 Mon Sep 17 00:00:00 2001 From: Bartek Date: Mon, 16 Dec 2024 14:08:05 +0100 Subject: [PATCH 04/15] add sentry error --- .../src/hooks/useTransactionHistory.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 27cfe6e581..11e335aea9 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -929,7 +929,7 @@ const useMappedReceiverTransactionHistory = ({ }): UseMappedTransactionHistoryResult => { const { isLoading: isLoadingAccountType } = useAccountType() - const { rawData, rawDataLoading, rawDataErroredChains } = + const { rawData, rawDataLoading, rawDataErroredChains, rawDataError } = useRawTransactionHistory({ address, fetchFor: 'receiver', @@ -1014,6 +1014,24 @@ const useMappedReceiverTransactionHistory = ({ [updateTransactionInSwrCache] ) + useEffect(() => { + if (typeof rawDataError !== 'undefined') { + console.warn(rawDataError) + captureSentryErrorWithExtraData({ + error: rawDataError, + originFunction: 'useRawTransactionHistory' + }) + } + + if (typeof receiverTransactionsError !== 'undefined') { + console.warn(receiverTransactionsError) + captureSentryErrorWithExtraData({ + error: receiverTransactionsError, + originFunction: 'useMappedReceiverTransactionHistory' + }) + } + }, [rawDataError, receiverTransactionsError]) + return { transactions: receiverTransactions || [], error: receiverTransactionsError, From 9cb22420bef7936af1937460f445264041910591 Mon Sep 17 00:00:00 2001 From: Bartek Date: Mon, 16 Dec 2024 15:58:39 +0100 Subject: [PATCH 05/15] fix --- .../src/hooks/useTransactionHistory.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 11e335aea9..22959115ff 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -1079,17 +1079,13 @@ export const useTransactionHistory = ( const updatePendingTransaction = useCallback( async (tx: MergedTransaction) => { - if ( - tx.destination && - tx.sender && - tx.destination.toLowerCase() !== tx.sender.toLowerCase() - ) { + if (address?.toLowerCase() !== tx.sender?.toLowerCase()) { return updatePendingReceiverTransaction(tx) } updatePendingSenderTransaction(tx) }, - [updatePendingReceiverTransaction, updatePendingSenderTransaction] + [address, updatePendingReceiverTransaction, updatePendingSenderTransaction] ) return { From a249cd328d77ca23380a1e7f1bf21794820ffb3f Mon Sep 17 00:00:00 2001 From: Bartek Date: Mon, 16 Dec 2024 17:36:56 +0100 Subject: [PATCH 06/15] record video --- packages/arb-token-bridge-ui/synpress.config.ts | 3 ++- packages/arb-token-bridge-ui/tests/e2e/specfiles.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/synpress.config.ts b/packages/arb-token-bridge-ui/synpress.config.ts index 84a332c1b0..cac18178d0 100644 --- a/packages/arb-token-bridge-ui/synpress.config.ts +++ b/packages/arb-token-bridge-ui/synpress.config.ts @@ -45,7 +45,8 @@ const isOrbitTest = [ process.env.E2E_ORBIT, process.env.E2E_ORBIT_CUSTOM_GAS_TOKEN ].includes('true') -const shouldRecordVideo = process.env.CYPRESS_RECORD_VIDEO === 'true' +// const shouldRecordVideo = process.env.CYPRESS_RECORD_VIDEO === 'true' +const shouldRecordVideo = true const l3Network = process.env.ORBIT_CUSTOM_GAS_TOKEN === 'true' diff --git a/packages/arb-token-bridge-ui/tests/e2e/specfiles.json b/packages/arb-token-bridge-ui/tests/e2e/specfiles.json index fd907011e2..382494be18 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specfiles.json +++ b/packages/arb-token-bridge-ui/tests/e2e/specfiles.json @@ -22,7 +22,7 @@ { "name": "Withdraw ERC20", "file": "tests/e2e/specs/**/withdrawERC20.cy.{js,jsx,ts,tsx}", - "recordVideo": "false" + "recordVideo": "true" }, { "name": "Batch deposit", From eb66c93dd56f420f4ea1279cbff745b3d90ad7ed Mon Sep 17 00:00:00 2001 From: Bartek Date: Mon, 16 Dec 2024 18:09:13 +0100 Subject: [PATCH 07/15] record video --- packages/arb-token-bridge-ui/tests/e2e/specfiles.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specfiles.json b/packages/arb-token-bridge-ui/tests/e2e/specfiles.json index 382494be18..2f5e7a8b91 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specfiles.json +++ b/packages/arb-token-bridge-ui/tests/e2e/specfiles.json @@ -12,7 +12,7 @@ { "name": "Withdraw native token", "file": "tests/e2e/specs/**/withdrawNativeToken.cy.{js,jsx,ts,tsx}", - "recordVideo": "false" + "recordVideo": "true" }, { "name": "Deposit ERC20", @@ -22,7 +22,7 @@ { "name": "Withdraw ERC20", "file": "tests/e2e/specs/**/withdrawERC20.cy.{js,jsx,ts,tsx}", - "recordVideo": "true" + "recordVideo": "false" }, { "name": "Batch deposit", From 37f42b8db3eb6d0eeaa3c2cd27e5ded686ad9759 Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 17 Dec 2024 13:17:57 +0100 Subject: [PATCH 08/15] logs --- .../TransactionHistory/TransactionHistoryTable.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx index d29a88f753..29b6d80ca9 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx @@ -106,6 +106,16 @@ const FailedChainPairsTooltip = ({ return null } + return ( +
+ {failedChainPairs.map(c => ( + + {getNetworkName(c.parentChainId)}-{getNetworkName(c.childChainId)} |{' '} + + ))} +
+ ) + return ( Date: Tue, 17 Dec 2024 14:32:08 +0100 Subject: [PATCH 09/15] fix --- .../TransactionHistory/TransactionHistoryTable.tsx | 10 ---------- .../src/hooks/useTransactionHistory.ts | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx index 29b6d80ca9..d29a88f753 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx @@ -106,16 +106,6 @@ const FailedChainPairsTooltip = ({ return null } - return ( -
- {failedChainPairs.map(c => ( - - {getNetworkName(c.parentChainId)}-{getNetworkName(c.childChainId)} |{' '} - - ))} -
- ) - return ( 0 || completedForSender }) const { cctpTransactions, cctpLoading } = useCctpTransactions({ address }) From 122a28387f9ad53f630ac2f0267abe68d04a827f Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 17 Dec 2024 14:55:22 +0100 Subject: [PATCH 10/15] fix --- packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 43903ce49f..86dcd59b67 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -1072,7 +1072,7 @@ export const useTransactionHistory = ( updatePendingTransaction: updatePendingReceiverTransaction } = useMappedReceiverTransactionHistory({ address, - shouldStartToFetch: senderTransactions.length > 0 || completedForSender + shouldStartToFetch: senderTransactions.length > 0 && !loadingForSender }) const { cctpTransactions, cctpLoading } = useCctpTransactions({ address }) From bb5879c14daa4f63f04e2fbb0d414e3fc03a506c Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 17 Dec 2024 16:03:43 +0100 Subject: [PATCH 11/15] fix --- .../arb-token-bridge-ui/src/hooks/useTransactionHistory.ts | 4 ++-- .../tests/e2e/specs/withdrawNativeToken.cy.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 86dcd59b67..0dd9af9640 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -1036,7 +1036,7 @@ const useMappedReceiverTransactionHistory = ({ transactions: receiverTransactions || [], error: receiverTransactionsError, completed: !isLoadingReceiverTransactions, - loading: isLoadingReceiverTransactions, + loading: isLoadingReceiverTransactions || rawDataLoading, rawDataErroredChains, updatePendingTransaction } @@ -1072,7 +1072,7 @@ export const useTransactionHistory = ( updatePendingTransaction: updatePendingReceiverTransaction } = useMappedReceiverTransactionHistory({ address, - shouldStartToFetch: senderTransactions.length > 0 && !loadingForSender + shouldStartToFetch: firstPageLoadedForSender }) const { cctpTransactions, cctpLoading } = useCctpTransactions({ address }) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts index 5de34a32de..a1b2a890cd 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts @@ -102,7 +102,7 @@ describe('Withdraw native token', () => { }) }) - it('should claim funds', { defaultCommandTimeout: 200_000 }, () => { + it('should claim funds', { defaultCommandTimeout: 300_000 }, () => { // increase the timeout for this test as claim button can take ~(20 blocks *10 blocks/sec) to activate cy.login({ networkType: 'parentChain' }) // login to L1 to claim the funds (otherwise would need to change network after clicking on claim) From e5f9d90d92fb2c410df3c288a67e6fc5f66be3c5 Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 17 Dec 2024 17:16:36 +0100 Subject: [PATCH 12/15] fix --- .../arb-token-bridge-ui/src/hooks/useTransactionHistory.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 0dd9af9640..bd0d65fb78 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -978,7 +978,8 @@ const useMappedReceiverTransactionHistory = ({ return tx._from.toLowerCase() !== tx._to.toLowerCase() } - return tx.caller.toLowerCase() !== tx.destination.toLowerCase() + // Native token withdrawals always fetch by receiver. We need to fetch for the sender here too. + return true }) const results = [] From cf45c6f9185659d5230e279205c97d0e56567755 Mon Sep 17 00:00:00 2001 From: Bartek Date: Wed, 18 Dec 2024 11:31:54 +0100 Subject: [PATCH 13/15] fix --- .../arb-token-bridge-ui/src/hooks/useTransactionHistory.ts | 4 ++-- .../arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts | 2 ++ .../tests/e2e/specs/withdrawNativeToken.cy.ts | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index bd0d65fb78..dc0b86f069 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -1079,12 +1079,12 @@ export const useTransactionHistory = ( const { cctpTransactions, cctpLoading } = useCctpTransactions({ address }) const updatePendingTransaction = useCallback( - async (tx: MergedTransaction) => { + (tx: MergedTransaction) => { if (address?.toLowerCase() !== tx.sender?.toLowerCase()) { return updatePendingReceiverTransaction(tx) } - updatePendingSenderTransaction(tx) + return updatePendingSenderTransaction(tx) }, [address, updatePendingReceiverTransaction, updatePendingSenderTransaction] ) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts index a1ac1fb75e..e02f47c0db 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts @@ -155,6 +155,8 @@ describe('Withdraw ERC20 Token', () => { cy.switchToTransactionHistoryTab('pending') + cy.wait(5_000) + cy.findClaimButton( formatAmount(ERC20AmountToSend, { symbol: testCase.symbol diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts index a1b2a890cd..1401ef2a42 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts @@ -108,6 +108,8 @@ describe('Withdraw native token', () => { cy.switchToTransactionHistoryTab('pending') + cy.wait(5_000) + cy.findClaimButton( formatAmount(ETHToWithdraw, { symbol: nativeTokenSymbol From 2740d95160bdafff10c1f0a94e67ee7f1b543093 Mon Sep 17 00:00:00 2001 From: Bartek Date: Wed, 18 Dec 2024 12:36:58 +0100 Subject: [PATCH 14/15] fix --- .../src/hooks/useTransactionHistory.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index dc0b86f069..e25a3caea5 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -831,10 +831,22 @@ const useMappedSenderTransactionHistory = ({ const updatePendingTransaction = useCallback( async (tx: MergedTransaction) => { + if (!senderTxPages) { + return + } + + const foundInSwrCache = senderTxPages + .flat() + .find(t => tx.txId === t.txId && tx.childChainId === t.childChainId) + + if (!foundInSwrCache) { + return + } + const updatedPendingTransaction = await getUpdatedPendingTransaction(tx) updateTransactionInSwrCache(updatedPendingTransaction) }, - [updateTransactionInSwrCache] + [senderTxPages, updateTransactionInSwrCache] ) // based on an example from SWR @@ -1009,10 +1021,22 @@ const useMappedReceiverTransactionHistory = ({ const updatePendingTransaction = useCallback( async (tx: MergedTransaction) => { + if (!receiverTransactions) { + return + } + + const foundInSwrCache = receiverTransactions + .flat() + .find(t => tx.txId === t.txId && tx.childChainId === t.childChainId) + + if (!foundInSwrCache) { + return + } + const updatedPendingTransaction = await getUpdatedPendingTransaction(tx) updateTransactionInSwrCache(updatedPendingTransaction) }, - [updateTransactionInSwrCache] + [receiverTransactions, updateTransactionInSwrCache] ) useEffect(() => { @@ -1079,14 +1103,11 @@ export const useTransactionHistory = ( const { cctpTransactions, cctpLoading } = useCctpTransactions({ address }) const updatePendingTransaction = useCallback( - (tx: MergedTransaction) => { - if (address?.toLowerCase() !== tx.sender?.toLowerCase()) { - return updatePendingReceiverTransaction(tx) - } - - return updatePendingSenderTransaction(tx) + async (tx: MergedTransaction) => { + await updatePendingSenderTransaction(tx) + await updatePendingReceiverTransaction(tx) }, - [address, updatePendingReceiverTransaction, updatePendingSenderTransaction] + [updatePendingReceiverTransaction, updatePendingSenderTransaction] ) return { From 8df6a573453b47171754a82c07aec7135e2ee6fb Mon Sep 17 00:00:00 2001 From: Bartek Date: Wed, 18 Dec 2024 13:12:25 +0100 Subject: [PATCH 15/15] fix --- .../src/hooks/useTransactionHistory.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index e25a3caea5..d3760f2be7 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -831,13 +831,9 @@ const useMappedSenderTransactionHistory = ({ const updatePendingTransaction = useCallback( async (tx: MergedTransaction) => { - if (!senderTxPages) { - return - } - - const foundInSwrCache = senderTxPages - .flat() - .find(t => tx.txId === t.txId && tx.childChainId === t.childChainId) + const foundInSwrCache = senderTransactionsWithNewTransactions.find( + t => tx.txId === t.txId && tx.childChainId === t.childChainId + ) if (!foundInSwrCache) { return @@ -846,7 +842,7 @@ const useMappedSenderTransactionHistory = ({ const updatedPendingTransaction = await getUpdatedPendingTransaction(tx) updateTransactionInSwrCache(updatedPendingTransaction) }, - [senderTxPages, updateTransactionInSwrCache] + [senderTransactionsWithNewTransactions, updateTransactionInSwrCache] ) // based on an example from SWR