diff --git a/packages/arb-token-bridge-ui/src/components/App/AppContext.tsx b/packages/arb-token-bridge-ui/src/components/App/AppContext.tsx index dfe1716202..5dcb36c0e1 100644 --- a/packages/arb-token-bridge-ui/src/components/App/AppContext.tsx +++ b/packages/arb-token-bridge-ui/src/components/App/AppContext.tsx @@ -16,7 +16,6 @@ type AppContextState = { isTransferPanelVisible: boolean isTransferring: boolean isTransactionHistoryPanelVisible: boolean - isTransactionHistoryShowingSentTx: boolean isTransactionHistoryShowingCctpDeposits: boolean transactionHistorySelectedTab: TransactionHistoryTab } @@ -27,7 +26,6 @@ const initialState: AppContextState = { isTransferPanelVisible: true, isTransferring: false, isTransactionHistoryPanelVisible: false, - isTransactionHistoryShowingSentTx: true, isTransactionHistoryShowingCctpDeposits: true, transactionHistorySelectedTab: TransactionHistoryTab.DEPOSITS } @@ -42,7 +40,6 @@ type Action = | { type: 'layout.set_is_transfer_panel_visible'; payload: boolean } | { type: 'layout.set_is_transferring'; payload: boolean } | { type: 'layout.set_txhistory_panel_visible'; payload: boolean } - | { type: 'layout.set_txhistory_show_sent_tx'; payload: boolean } | { type: 'layout.set_txhistory_show_cctp_deposits'; payload: boolean } | { type: 'layout.set_txhistory_tab'; payload: TransactionHistoryTab } @@ -63,15 +60,6 @@ function reducer(state: AppContextState, action: Action) { } } - case 'layout.set_txhistory_show_sent_tx': - return { - ...state, - layout: { - ...state.layout, - isTransactionHistoryShowingSentTx: action.payload - } - } - case 'layout.set_txhistory_show_cctp_deposits': return { ...state, @@ -145,14 +133,6 @@ export const useAppContextActions = (dispatchOverride?: Dispatch) => { }) }, [dispatch]) - const showSentTransactions = useCallback(() => { - dispatch({ type: 'layout.set_txhistory_show_sent_tx', payload: true }) - }, [dispatch]) - - const showReceivedTransactions = useCallback(() => { - dispatch({ type: 'layout.set_txhistory_show_sent_tx', payload: false }) - }, [dispatch]) - const closeTransactionHistoryPanel = () => { dispatch({ type: 'layout.set_txhistory_panel_visible', payload: false }) } @@ -168,8 +148,6 @@ export const useAppContextActions = (dispatchOverride?: Dispatch) => { setTransferring, openTransactionHistoryPanel, closeTransactionHistoryPanel, - showSentTransactions, - showReceivedTransactions, showCctpDepositsTransactions, showCctpWithdrawalsTransactions, setTransactionHistoryTab diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx index 020c18a26e..1b6c18b7a8 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx @@ -62,8 +62,6 @@ export const TransactionHistory = ({ const { l1, l2 } = useNetworksAndSigners() const { isSmartContractWallet } = useAccountType() const { - showSentTransactions, - showReceivedTransactions, showCctpDepositsTransactions, showCctpWithdrawalsTransactions, setTransactionHistoryTab @@ -147,31 +145,10 @@ export const TransactionHistory = ({ if (!isSmartContractWallet || !chain) { return } - const isDepositsTab = index === TransactionHistoryTab.DEPOSITS - const isWithdrawalsTab = index === TransactionHistoryTab.WITHDRAWALS + const isCctpTab = index === TransactionHistoryTab.CCTP const isConnectedToArbitrum = isNetwork(chain.id).isArbitrum - // SCW address is tied to a specific network, so we must ensure that: - if (isDepositsTab) { - // if showing deposits, we always show: - if (isConnectedToArbitrum) { - // - received txs if connected to L2 - showReceivedTransactions() - } else { - // - sent txs if connected to L1 - showSentTransactions() - } - } else if (isWithdrawalsTab) { - // Withdrawal tab - // if showing withdrawals, we always show: - if (isConnectedToArbitrum) { - // - sent txs if connected to L2 - showSentTransactions() - } else { - // - received txs if connected to L1 - showReceivedTransactions() - } - } else { - // Cctp tab + + if (isCctpTab) { if (isConnectedToArbitrum) { showCctpDepositsTransactions() } else { @@ -183,9 +160,7 @@ export const TransactionHistory = ({ chain, isSmartContractWallet, showCctpDepositsTransactions, - showCctpWithdrawalsTransactions, - showReceivedTransactions, - showSentTransactions + showCctpWithdrawalsTransactions ] ) @@ -300,7 +275,6 @@ export const TransactionHistory = ({ pageParams={depositsPageParams} setPageParams={setDepositsPageParams} transactions={depositsData.transformedDeposits} - isSmartContractWallet={isSmartContractWallet} loading={depositsLoading} error={depositsError} /> @@ -317,7 +291,6 @@ export const TransactionHistory = ({ pageParams={withdrawalsPageParams} setPageParams={setWithdrawalsPageParams} transactions={withdrawalsData.transformedWithdrawals} - isSmartContractWallet={isSmartContractWallet} loading={withdrawalsLoading} error={withdrawalsError} /> diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTable/TableSentOrReceivedFundsSwitch.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTable/TableSentOrReceivedFundsSwitch.tsx deleted file mode 100644 index 4039c91bbd..0000000000 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTable/TableSentOrReceivedFundsSwitch.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useMemo } from 'react' - -import { useAppContextActions, useAppContextState } from '../../App/AppContext' -import { TransactionsTableSwitch } from './TransactionsTableSwitch' - -export const TableSentOrReceivedFundsSwitch = () => { - const { - layout: { isTransactionHistoryShowingSentTx } - } = useAppContextState() - const { showSentTransactions, showReceivedTransactions } = - useAppContextActions() - - const tabs = useMemo(() => { - return [ - { - handleClick: showSentTransactions, - text: 'Funds Sent', - isActive: isTransactionHistoryShowingSentTx - }, - { - handleClick: showReceivedTransactions, - text: 'Funds Received', - isActive: !isTransactionHistoryShowingSentTx - } - ] - }, [ - isTransactionHistoryShowingSentTx, - showReceivedTransactions, - showSentTransactions - ]) - - return -} diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTable/TransactionsTable.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTable/TransactionsTable.tsx index 8cf5273e24..5d8bc52bff 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTable/TransactionsTable.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTable/TransactionsTable.tsx @@ -16,9 +16,7 @@ import { NoDataOverlay } from './NoDataOverlay' import { TableBodyLoading } from './TableBodyLoading' import { TableBodyError } from './TableBodyError' import { TableActionHeader } from './TableActionHeader' -import { TableSentOrReceivedFundsSwitch } from './TableSentOrReceivedFundsSwitch' import { useAppState } from '../../../state' -import { useAppContextState } from '../../App/AppContext' import { useNetworksAndSigners } from '../../../hooks/useNetworksAndSigners' import { ExternalLink } from '../../common/ExternalLink' import { getExplorerUrl } from '../../../util/networks' @@ -166,7 +164,6 @@ export type TransactionsTableProps = { pageParams: PageParams setPageParams: Dispatch> transactions: MergedTransaction[] - isSmartContractWallet?: boolean loading: boolean error: boolean } @@ -176,17 +173,12 @@ export function TransactionsTable({ pageParams, setPageParams, transactions, - isSmartContractWallet, loading, error }: TransactionsTableProps) { const { app: { mergedTransactions: locallyStoredTransactions } } = useAppState() - const { - layout: { isTransactionHistoryShowingSentTx } - } = useAppContextState() - const { address } = useAccount() // don't want to update hooks on useAppState reference change. Just the exact value of localTransactions const localTransactionsKey = JSON.stringify(locallyStoredTransactions || []) @@ -235,18 +227,6 @@ export function TransactionsTable({ return [...newerTransactions.reverse(), ...subgraphTransactions] }, [transactions, localTransactionsKey]) - const transactionsBySentOrReceivedFunds = useMemo(() => { - if (!address) return [] - // both sent and received PENDING txs are stored together - // here we make sure we display a correct tx (sent or received) - return _transactions.filter(tx => { - if (isTransactionHistoryShowingSentTx) { - return tx.sender?.toLowerCase() === address.toLowerCase() - } - return tx.sender?.toLowerCase() !== address.toLowerCase() - }) - }, [_transactions, address]) - const locallyStoredTransactionsMap = useMemo(() => { // map of all the locally-stored transactions (pending + recently executed) // so that tx rows can easily subscribe to live-local status without refetching table data @@ -271,14 +251,12 @@ export function TransactionsTable({ return ( <> - {!isSmartContractWallet && } - {/* search and pagination buttons */} @@ -299,7 +277,7 @@ export function TransactionsTable({ {/* when there are no transactions present */} {status === TableStatus.SUCCESS && !noSearchResults && - !transactionsBySentOrReceivedFunds.length && ( + _transactions.length === 0 && ( No transactions @@ -308,9 +286,8 @@ export function TransactionsTable({ {/* finally, when transactions are present, show rows */} {status === TableStatus.SUCCESS && !noSearchResults && - transactionsBySentOrReceivedFunds.map((tx, index) => { - const isLastRow = - index === transactionsBySentOrReceivedFunds.length - 1 + _transactions.map((tx, index) => { + const isLastRow = index === _transactions.length - 1 // if transaction is present in local (pending + recently executed) transactions, subscribe to that in this row, // this will make sure the row updates with any updates in the local app state diff --git a/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts b/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts index 66dfc386a5..19f99fb25e 100644 --- a/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts +++ b/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts @@ -95,6 +95,7 @@ export type L2ToL1EventResultPlus = L2ToL1EventResult & { export type WithdrawalInitiated = EventArgs & { txHash: string + timestamp?: BigNumber } export interface PendingWithdrawalsMap { diff --git a/packages/arb-token-bridge-ui/src/hooks/useDeposits.ts b/packages/arb-token-bridge-ui/src/hooks/useDeposits.ts index b2934796b6..15d479fa4c 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useDeposits.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useDeposits.ts @@ -1,9 +1,8 @@ import { useMemo } from 'react' import useSWRImmutable from 'swr/immutable' -import { useAccount } from 'wagmi' +import { useAccount, useChainId } from 'wagmi' import { PageParams } from '../components/TransactionHistory/TransactionsTable/TransactionsTable' -import { useAppContextState } from '../components/App/AppContext' import { MergedTransaction } from '../state/app/state' import { isPending, transformDeposits } from '../state/app/utils' import { @@ -12,9 +11,10 @@ import { } from '../util/deposits/fetchDeposits' import { useNetworksAndSigners } from './useNetworksAndSigners' import { Transaction } from './useTransactions' +import { useAccountType } from './useAccountType' import { - getQueryParamsForFetchingReceivedFunds, - getQueryParamsForFetchingSentFunds + shouldIncludeSentTxs, + shouldIncludeReceivedTxs } from '../util/SubgraphUtils' export type CompleteDepositData = { @@ -46,6 +46,11 @@ export const fetchCompleteDepositData = async ( export const useDeposits = (depositPageParams: PageParams) => { const { l1, l2 } = useNetworksAndSigners() + const { isSmartContractWallet, isLoading: isAccountTypeLoading } = + useAccountType() + const chainId = useChainId() + + const isConnectedToParentChain = l1.network.id === chainId // only change l1-l2 providers (and hence, reload deposits) when the connected chain id changes // otherwise tx-history unnecessarily reloads on l1<->l2 network switch as well (#847) @@ -53,9 +58,25 @@ export const useDeposits = (depositPageParams: PageParams) => { const l2Provider = useMemo(() => l2.provider, [l2.network.id]) const { address: walletAddress } = useAccount() - const { - layout: { isTransactionHistoryShowingSentTx } - } = useAppContextState() + + // 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 includeSentTxs = isAccountTypeLoading + ? false + : shouldIncludeSentTxs({ + type: 'deposit', + isSmartContractWallet, + isConnectedToParentChain + }) + + const includeReceivedTxs = isAccountTypeLoading + ? false + : shouldIncludeReceivedTxs({ + type: 'deposit', + isSmartContractWallet, + isConnectedToParentChain + }) /* return the cached response for the complete pending transactions */ return useSWRImmutable( @@ -65,10 +86,10 @@ export const useDeposits = (depositPageParams: PageParams) => { walletAddress, l1Provider, l2Provider, - isTransactionHistoryShowingSentTx, depositPageParams.pageNumber, depositPageParams.pageSize, - depositPageParams.searchString + depositPageParams.searchString, + isAccountTypeLoading ] : null, ([ @@ -76,20 +97,18 @@ export const useDeposits = (depositPageParams: PageParams) => { _walletAddress, _l1Provider, _l2Provider, - _isTransactionHistoryShowingSentTx, _pageNumber, _pageSize, _searchString ]) => fetchCompleteDepositData({ + sender: includeSentTxs ? _walletAddress : undefined, + receiver: includeReceivedTxs ? _walletAddress : undefined, l1Provider: _l1Provider, l2Provider: _l2Provider, pageNumber: _pageNumber, pageSize: _pageSize, - searchString: _searchString, - ...(_isTransactionHistoryShowingSentTx - ? getQueryParamsForFetchingSentFunds(_walletAddress) - : getQueryParamsForFetchingReceivedFunds(_walletAddress)) + searchString: _searchString }) ) } diff --git a/packages/arb-token-bridge-ui/src/hooks/useWithdrawals.ts b/packages/arb-token-bridge-ui/src/hooks/useWithdrawals.ts index abdd820816..10a5e6852a 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useWithdrawals.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useWithdrawals.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' import useSWRImmutable from 'swr/immutable' -import { useAccount } from 'wagmi' +import { useAccount, useChainId } from 'wagmi' import { PageParams } from '../components/TransactionHistory/TransactionsTable/TransactionsTable' import { MergedTransaction } from '../state/app/state' @@ -11,10 +11,10 @@ import { } from '../util/withdrawals/fetchWithdrawals' import { L2ToL1EventResultPlus } from './arbTokenBridge.types' import { useNetworksAndSigners } from './useNetworksAndSigners' -import { useAppContextState } from '../components/App/AppContext' +import { useAccountType } from './useAccountType' import { - getQueryParamsForFetchingReceivedFunds, - getQueryParamsForFetchingSentFunds + shouldIncludeSentTxs, + shouldIncludeReceivedTxs } from '../util/SubgraphUtils' export type CompleteWithdrawalData = { @@ -55,18 +55,38 @@ const fetchCompleteWithdrawalData = async ( export const useWithdrawals = (withdrawalPageParams: PageParams) => { const { l1, l2 } = useNetworksAndSigners() + const { isSmartContractWallet, isLoading: isAccountTypeLoading } = + useAccountType() + const chainId = useChainId() + + const isConnectedToParentChain = l1.network.id === chainId // only change l1-l2 providers (and hence, reload withdrawals) when the connected chain id changes // otherwise tx-history unnecessarily reloads on l1<->l2 network switch as well (#847) const l1Provider = useMemo(() => l1.provider, [l1.network.id]) const l2Provider = useMemo(() => l2.provider, [l2.network.id]) - const { - layout: { isTransactionHistoryShowingSentTx } - } = useAppContextState() - const { address: walletAddress } = useAccount() + // 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 includeSentTxs = isAccountTypeLoading + ? false + : shouldIncludeSentTxs({ + type: 'withdrawal', + isSmartContractWallet, + isConnectedToParentChain + }) + + const includeReceivedTxs = isAccountTypeLoading + ? false + : shouldIncludeReceivedTxs({ + type: 'withdrawal', + isSmartContractWallet, + isConnectedToParentChain + }) + /* return the cached response for the complete pending transactions */ return useSWRImmutable( // `walletAddress` can actually be `undefined`, so the type is wrong @@ -77,10 +97,10 @@ export const useWithdrawals = (withdrawalPageParams: PageParams) => { walletAddress, l1Provider, l2Provider, - isTransactionHistoryShowingSentTx, withdrawalPageParams.pageNumber, withdrawalPageParams.pageSize, - withdrawalPageParams.searchString + withdrawalPageParams.searchString, + isAccountTypeLoading ] : null, ([ @@ -88,20 +108,18 @@ export const useWithdrawals = (withdrawalPageParams: PageParams) => { _walletAddress, _l1Provider, _l2Provider, - _isTransactionHistoryShowingSentTx, _pageNumber, _pageSize, _searchString ]) => fetchCompleteWithdrawalData({ + sender: includeSentTxs ? _walletAddress : undefined, + receiver: includeReceivedTxs ? _walletAddress : undefined, l1Provider: _l1Provider, l2Provider: _l2Provider, pageNumber: _pageNumber, pageSize: _pageSize, - searchString: _searchString, - ...(_isTransactionHistoryShowingSentTx - ? getQueryParamsForFetchingSentFunds(_walletAddress) - : getQueryParamsForFetchingReceivedFunds(_walletAddress)) + searchString: _searchString }) ) } diff --git a/packages/arb-token-bridge-ui/src/pages/api/deposits.ts b/packages/arb-token-bridge-ui/src/pages/api/deposits.ts index bc5f6dfd28..c1dc4124d2 100644 --- a/packages/arb-token-bridge-ui/src/pages/api/deposits.ts +++ b/packages/arb-token-bridge-ui/src/pages/api/deposits.ts @@ -6,12 +6,10 @@ import { getL1SubgraphClient } from '../../util/SubgraphUtils' // Extending the standard NextJs request with Deposit-params type NextApiRequestWithDepositParams = NextApiRequest & { query: { - l2ChainId: string - search?: string sender?: string - senderNot?: string receiver?: string - receiverNot?: string + l2ChainId: string + search?: string page?: string pageSize?: string fromBlock?: string @@ -30,12 +28,10 @@ export default async function handler( ) { try { const { - search = '', - l2ChainId, sender, - senderNot, receiver, - receiverNot, + search = '', + l2ChainId, page = '0', pageSize = '10', fromBlock, @@ -63,25 +59,31 @@ export default async function handler( }) } + const additionalFilters = `${ + typeof fromBlock !== 'undefined' + ? `blockCreatedAt_gte: ${Number(fromBlock)},` + : '' + } + ${ + typeof toBlock !== 'undefined' + ? `blockCreatedAt_lte: ${Number(toBlock)},` + : '' + } + ${search ? `transactionHash_contains: "${search}"` : ''} + ` + const subgraphResult = await getL1SubgraphClient(Number(l2ChainId)).query({ query: gql(`{ deposits( where: { - ${sender ? `sender: "${sender}",` : ''} - ${senderNot ? `sender_not: "${senderNot}",` : ''} - ${receiver ? `receiver: "${receiver}",` : ''} - ${receiverNot ? `receiver_not: "${receiverNot}",` : ''} - ${ - typeof fromBlock !== 'undefined' - ? `blockCreatedAt_gte: ${Number(fromBlock)}` - : '' - } - ${ - typeof toBlock !== 'undefined' - ? `blockCreatedAt_lte: ${Number(toBlock)}` - : '' - } - ${search ? `transactionHash_contains: "${search}"` : ''} + or: [ + ${sender ? `{ sender: "${sender}", ${additionalFilters} },` : ''} + ${ + receiver + ? `{ receiver: "${receiver}", ${additionalFilters} },` + : '' + } + ] } orderBy: blockCreatedAt orderDirection: desc diff --git a/packages/arb-token-bridge-ui/src/pages/api/withdrawals.ts b/packages/arb-token-bridge-ui/src/pages/api/withdrawals.ts index d11597dcac..732848a9ee 100644 --- a/packages/arb-token-bridge-ui/src/pages/api/withdrawals.ts +++ b/packages/arb-token-bridge-ui/src/pages/api/withdrawals.ts @@ -6,12 +6,10 @@ import { getL2SubgraphClient } from '../../util/SubgraphUtils' // Extending the standard NextJs request with Withdrawal-params type NextApiRequestWithWithdrawalParams = NextApiRequest & { query: { - l2ChainId: string - search?: string sender?: string - senderNot?: string receiver?: string - receiverNot?: string + l2ChainId: string + search?: string page?: string pageSize?: string fromBlock?: string @@ -30,12 +28,10 @@ export default async function handler( ) { try { const { - search = '', - l2ChainId, sender, - senderNot, receiver, - receiverNot, + search = '', + l2ChainId, page = '0', pageSize = '10', fromBlock, @@ -63,46 +59,52 @@ export default async function handler( }) } + const additionalFilters = `${ + typeof fromBlock !== 'undefined' + ? `l2BlockNum_gte: ${Number(fromBlock)},` + : '' + } + ${ + typeof toBlock !== 'undefined' + ? `l2BlockNum_lte: ${Number(toBlock)},` + : '' + } + ${search ? `l2TxHash_contains: "${search}"` : ''} + ` + const subgraphResult = await getL2SubgraphClient(Number(l2ChainId)).query({ query: gql`{ - withdrawals( - where: { - ${sender ? `sender: "${sender}",` : ''} - ${senderNot ? `sender_not: "${senderNot}",` : ''} - ${receiver ? `receiver: "${receiver}",` : ''} - ${receiverNot ? `receiver_not: "${receiverNot}",` : ''} - ${ - typeof fromBlock !== 'undefined' - ? `l2BlockNum_gte: ${Number(fromBlock)}` - : '' - } - ${ - typeof toBlock !== 'undefined' - ? `l2BlockNum_lte: ${Number(toBlock)}` - : '' - } - ${search ? `l2TxHash_contains: "${search}"` : ''} - } - orderBy: l2BlockTimestamp - orderDirection: desc - first: ${Number(pageSize)}, - skip: ${Number(page) * Number(pageSize)} - ) { - id, - type, - sender, - receiver, - ethValue, - l1Token { - id - }, - tokenAmount, - isClassic, - l2BlockTimestamp, - l2TxHash, - l2BlockNum - } - }` + withdrawals( + where: { + or: [ + ${sender ? `{ sender: "${sender}", ${additionalFilters} },` : ''} + ${ + receiver + ? `{ receiver: "${receiver}", ${additionalFilters} },` + : '' + } + ] + } + orderBy: l2BlockTimestamp + orderDirection: desc + first: ${Number(pageSize)}, + skip: ${Number(page) * Number(pageSize)} + ) { + id, + type, + sender, + receiver, + ethValue, + l1Token { + id + }, + tokenAmount, + isClassic, + l2BlockTimestamp, + l2TxHash, + l2BlockNum + } + }` }) const transactions: FetchWithdrawalsFromSubgraphResult[] = diff --git a/packages/arb-token-bridge-ui/src/util/SubgraphUtils.ts b/packages/arb-token-bridge-ui/src/util/SubgraphUtils.ts index 38fe544e83..e139566318 100644 --- a/packages/arb-token-bridge-ui/src/util/SubgraphUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/SubgraphUtils.ts @@ -1,7 +1,5 @@ import fetch from 'cross-fetch' import { ApolloClient, HttpLink, InMemoryCache, gql } from '@apollo/client' -import { FetchDepositParams } from './deposits/fetchDeposits' -import { FetchWithdrawalsParams } from './withdrawals/fetchWithdrawals' const L1SubgraphClient = { ArbitrumOne: new ApolloClient({ @@ -121,24 +119,40 @@ export const tryFetchLatestSubgraphBlockNumber = async ( } } -type AdditionalSubgraphQueryParams = Pick< - FetchDepositParams | FetchWithdrawalsParams, - 'sender' | 'senderNot' | 'receiver' | 'receiverNot' -> - -export function getQueryParamsForFetchingSentFunds( - address: string -): AdditionalSubgraphQueryParams { - return { - sender: address +export const shouldIncludeSentTxs = ({ + type, + isSmartContractWallet, + isConnectedToParentChain +}: { + type: 'deposit' | 'withdrawal' + isSmartContractWallet: boolean + isConnectedToParentChain: boolean +}) => { + if (isSmartContractWallet) { + // show txs sent from this account for: + // 1. deposits if we are connected to the parent chain, or + // 2. withdrawals if we are connected to the child chain + return isConnectedToParentChain ? type === 'deposit' : type === 'withdrawal' } + // always show for EOA + return true } -export function getQueryParamsForFetchingReceivedFunds( - address: string -): AdditionalSubgraphQueryParams { - return { - senderNot: address, - receiver: address +export const shouldIncludeReceivedTxs = ({ + type, + isSmartContractWallet, + isConnectedToParentChain +}: { + type: 'deposit' | 'withdrawal' + isSmartContractWallet: boolean + isConnectedToParentChain: boolean +}) => { + if (isSmartContractWallet) { + // show txs sent to this account for: + // 1. withdrawals if we are connected to the parent chain, or + // 2. deposits if we are connected to the child chain + return isConnectedToParentChain ? type === 'withdrawal' : type === 'deposit' } + // always show for EOA + return true } diff --git a/packages/arb-token-bridge-ui/src/util/deposits/fetchDeposits.ts b/packages/arb-token-bridge-ui/src/util/deposits/fetchDeposits.ts index 51a616970f..233ebb0a4e 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/fetchDeposits.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/fetchDeposits.ts @@ -11,9 +11,7 @@ import { Transaction } from '../../hooks/useTransactions' export type FetchDepositParams = { sender?: string - senderNot?: string receiver?: string - receiverNot?: string fromBlock?: number toBlock?: number l1Provider: Provider @@ -28,9 +26,7 @@ export type FetchDepositParams = { /* TODO : Add event logs as well */ export const fetchDeposits = async ({ sender, - senderNot, receiver, - receiverNot, fromBlock, toBlock, l1Provider, @@ -39,7 +35,8 @@ export const fetchDeposits = async ({ pageNumber = 0, searchString = '' }: FetchDepositParams): Promise => { - if (!sender && !receiver) return [] + if (typeof sender === 'undefined' && typeof receiver === 'undefined') + return [] if (!l1Provider || !l2Provider) return [] const l1ChainId = (await l1Provider.getNetwork()).chainId @@ -68,9 +65,7 @@ export const fetchDeposits = async ({ const depositsFromSubgraph = await fetchDepositsFromSubgraph({ sender, - senderNot, receiver, - receiverNot, fromBlock, toBlock, l2ChainId, diff --git a/packages/arb-token-bridge-ui/src/util/deposits/fetchDepositsFromSubgraph.ts b/packages/arb-token-bridge-ui/src/util/deposits/fetchDepositsFromSubgraph.ts index ce767f7bb6..e2fa0164b8 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/fetchDepositsFromSubgraph.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/fetchDepositsFromSubgraph.ts @@ -25,7 +25,8 @@ export type FetchDepositsFromSubgraphResult = { * Fetches initiated deposits (ETH + Tokens) from subgraph in range of [fromBlock, toBlock] and pageParams. * * @param query Query params - * @param query.address Account address + * @param query.sender Address that initiated the withdrawal + * @param query.receiver Address that received the funds * @param query.fromBlock Start at this block number (including) * @param query.toBlock Stop at this block number (including) * @param query.l2ChainId Chain id for the L2 network @@ -36,9 +37,7 @@ export type FetchDepositsFromSubgraphResult = { export const fetchDepositsFromSubgraph = async ({ sender, - senderNot, receiver, - receiverNot, fromBlock, toBlock, l2ChainId, @@ -47,9 +46,7 @@ export const fetchDepositsFromSubgraph = async ({ searchString = '' }: { sender?: string - senderNot?: string receiver?: string - receiverNot?: string fromBlock: number toBlock: number l2ChainId: number @@ -65,9 +62,7 @@ export const fetchDepositsFromSubgraph = async ({ const urlParams = new URLSearchParams( sanitizeQueryParams({ sender, - senderNot, receiver, - receiverNot, fromBlock, toBlock, l2ChainId, diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchETHWithdrawalsTestHelpers.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchETHWithdrawalsTestHelpers.ts index 5bef08ae95..0d6ea02aee 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchETHWithdrawalsTestHelpers.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchETHWithdrawalsTestHelpers.ts @@ -1,10 +1,10 @@ import { StaticJsonRpcProvider } from '@ethersproject/providers' -const toAddress = '0xd898275e8b9428429155752f89fe0899ce232830' +const receiver = '0xd898275e8b9428429155752f89fe0899ce232830' const l2Provider = new StaticJsonRpcProvider('https://arb1.arbitrum.io/rpc') const baseQuery = { - toAddress, + receiver, l2Provider } diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchTokenWithdrawalsFromEventLogs.test.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchTokenWithdrawalsFromEventLogs.test.ts index d1c60ca13e..ddde9186c3 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchTokenWithdrawalsFromEventLogs.test.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchTokenWithdrawalsFromEventLogs.test.ts @@ -10,22 +10,14 @@ describe('fetchTokenWithdrawalsFromEventLogs', () => { // TODO: This is a temporary fix, when Event Logs are enabled for custom address // we will be able to use the same properties, and remove the need to assign sender to address const query = getQueryCoveringClassicOnlyWithoutResults() - const fromAddress = query.sender - const result = await fetchTokenWithdrawalsFromEventLogs({ - ...query, - fromAddress - }) + const result = await fetchTokenWithdrawalsFromEventLogs(query) expect(result).toHaveLength(0) }) it('fetches some token withdrawals from event logs pre-nitro', async () => { const query = getQueryCoveringClassicOnlyWithResults() - const fromAddress = query.sender - const result = await fetchTokenWithdrawalsFromEventLogs({ - ...query, - fromAddress - }) + const result = await fetchTokenWithdrawalsFromEventLogs(query) expect(result).toHaveLength(1) expect(result).toEqual( @@ -40,11 +32,7 @@ describe('fetchTokenWithdrawalsFromEventLogs', () => { it('fetches some token withdrawals from event logs pre-nitro and post-nitro', async () => { const query = getQueryCoveringClassicAndNitroWithResults() - const fromAddress = query.sender - const result = await fetchTokenWithdrawalsFromEventLogs({ - ...query, - fromAddress - }) + const result = await fetchTokenWithdrawalsFromEventLogs(query) expect(result).toHaveLength(2) expect(result).toEqual( diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts index 6d1db68015..ad8dff4bd0 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts @@ -5,26 +5,30 @@ import { L2ToL1MessageReader } from '@arbitrum/sdk' * Fetches initiated ETH withdrawals from event logs in range of [fromBlock, toBlock]. * * @param query Query params - * @param query.toAddress Address that will receive the funds + * @param query.receiver Address that will receive the funds * @param query.fromBlock Start at this block number (including) * @param query.toBlock Stop at this block number (including) * @param query.l2Provider Provider for the L2 network */ export function fetchETHWithdrawalsFromEventLogs({ - toAddress, + receiver, fromBlock, toBlock, l2Provider }: { - toAddress?: string + receiver?: string fromBlock: BlockTag toBlock: BlockTag l2Provider: Provider }) { + if (typeof receiver === 'undefined') { + return [] + } + // funds sent by this address return L2ToL1MessageReader.getL2ToL1Events( l2Provider, { fromBlock, toBlock }, undefined, - toAddress + receiver ) } diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts index e40ae53d91..0831ed38e2 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts @@ -1,46 +1,75 @@ import { Provider, BlockTag } from '@ethersproject/providers' import { Erc20Bridger } from '@arbitrum/sdk' +import { EventArgs } from '@arbitrum/sdk/dist/lib/dataEntities/event' +import { WithdrawalInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L2ArbitrumGateway' + +function dedupeEvents( + events: (EventArgs & { + txHash: string + })[] +) { + return [...new Map(events.map(item => [item.txHash, item])).values()] +} /** * Fetches initiated token withdrawals from event logs in range of [fromBlock, toBlock]. * * @param query Query params - * @param query.fromAddress Address that initiated the withdrawal - * @param query.toAddress Address that will receive the funds + * @param query.sender Address that initiated the withdrawal + * @param query.receiver Address that received the funds * @param query.fromBlock Start at this block number (including) * @param query.toBlock Stop at this block number (including) * @param query.l2Provider Provider for the L2 network * @param query.l2GatewayAddresses L2 gateway addresses to use */ export async function fetchTokenWithdrawalsFromEventLogs({ - fromAddress, - toAddress, + sender, + receiver, fromBlock, toBlock, l2Provider, l2GatewayAddresses = [] }: { - fromAddress?: string - toAddress?: string + sender?: string + receiver?: string fromBlock: BlockTag toBlock: BlockTag l2Provider: Provider l2GatewayAddresses?: string[] }) { const erc20Bridger = await Erc20Bridger.fromProvider(l2Provider) + const promises: ReturnType[] = [] + + l2GatewayAddresses.forEach(gatewayAddress => { + // funds sent by this address + if (sender) { + promises.push( + erc20Bridger.getL2WithdrawalEvents( + l2Provider, + gatewayAddress, + { fromBlock, toBlock }, + undefined, + sender, + undefined + ) + ) + } - return ( - await Promise.all( - l2GatewayAddresses.map(gatewayAddress => + // funds received by this address + if (receiver) { + promises.push( erc20Bridger.getL2WithdrawalEvents( l2Provider, gatewayAddress, { fromBlock, toBlock }, undefined, - fromAddress, - toAddress + undefined, + receiver ) ) - ) - ).flat() + } + }) + + // when getting funds received by this address we will also get duplicate txs returned in 'funds sent by this address' + return dedupeEvents((await Promise.all(promises)).flat()) } diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts index 21c66a6fbb..bf2f631eaf 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts @@ -1,9 +1,12 @@ +import { BigNumber, constants } from 'ethers' import { Provider } from '@ethersproject/providers' -import { constants as arbitrumConstants } from '@arbitrum/sdk' import { fetchETHWithdrawalsFromEventLogs } from './fetchETHWithdrawalsFromEventLogs' import { + EthWithdrawal, + attachTimestampToTokenWithdrawal, + isTokenWithdrawal, mapETHWithdrawalToL2ToL1EventResult, mapTokenWithdrawalFromEventLogsToL2ToL1EventResult, mapWithdrawalToL2ToL1EventResult, @@ -12,14 +15,15 @@ import { import { fetchWithdrawalsFromSubgraph } from './fetchWithdrawalsFromSubgraph' import { tryFetchLatestSubgraphBlockNumber } from '../SubgraphUtils' import { fetchTokenWithdrawalsFromEventLogs } from './fetchTokenWithdrawalsFromEventLogs' -import { L2ToL1EventResultPlus } from '../../hooks/arbTokenBridge.types' +import { + L2ToL1EventResultPlus, + WithdrawalInitiated +} from '../../hooks/arbTokenBridge.types' import { fetchL2Gateways } from '../fetchL2Gateways' export type FetchWithdrawalsParams = { sender?: string - senderNot?: string receiver?: string - receiverNot?: string fromBlock?: number toBlock?: number l1Provider: Provider @@ -29,13 +33,22 @@ export type FetchWithdrawalsParams = { searchString?: string } +type Timestamped = { + timestamp?: BigNumber +} + +function sortByTimestampDescending(a: Timestamped, b: Timestamped) { + const aTimestamp = a.timestamp ?? constants.Zero + const bTimestamp = b.timestamp ?? constants.Zero + + return aTimestamp.gt(bTimestamp) ? -1 : 1 +} + /* Fetch complete withdrawals - both ETH and Token withdrawals from subgraph and event logs into one list */ /* Also fills in any additional data required per transaction for our UI logic to work well */ export const fetchWithdrawals = async ({ sender, - senderNot, receiver, - receiverNot, l1Provider, l2Provider, pageNumber = 0, @@ -68,31 +81,6 @@ export const fetchWithdrawals = async ({ toBlock = latestSubgraphBlockNumber } - // todo: update when eth withdrawals to a custom destination address are enabled (requires https://github.com/OffchainLabs/arbitrum-sdk/issues/325) - function getETHWithdrawalsFromEventLogsQuery() { - // give me all the eth withdrawals that someone else initiated, but the funds are sent to me - if (receiver) { - // since we don't do eth withdrawals to a custom destination address, we don't expect any results here - // however, if we pass in `undefined`, it will skip the filter and give us everything, which is why we use the node interface address that should never return any results - return { toAddress: arbitrumConstants.NODE_INTERFACE_ADDRESS } - } - - // give me all the eth withdrawals that i initiated - // since we don't do eth withdrawals to a custom destination address, we set `toAddress` to be the sender, giving us all withdrawals sent to the same address - return { toAddress: sender } - } - - function getTokenWithdrawalsFromEventLogsQuery() { - // give me all the token withdrawals that someone else initiated, but the funds are sent to me - // because we can't exclude withdrawals that were sent from the same address, we have to filter them out later, see `mappedTokenWithdrawalsFromEventLogs` - if (receiver) { - return { fromAddress: undefined, toAddress: receiver } - } - - // give me all the token withdrawals that i initiated - return { fromAddress: sender, toAddress: undefined } - } - const [ withdrawalsFromSubgraph, ethWithdrawalsFromEventLogs, @@ -100,24 +88,23 @@ export const fetchWithdrawals = async ({ ] = await Promise.all([ fetchWithdrawalsFromSubgraph({ sender, - senderNot, receiver, - receiverNot, - fromBlock: fromBlock, - toBlock: toBlock, + fromBlock, + toBlock, l2ChainId: l2ChainID, pageNumber, pageSize, searchString }), fetchETHWithdrawalsFromEventLogs({ - ...getETHWithdrawalsFromEventLogsQuery(), + receiver, fromBlock: toBlock + 1, toBlock: 'latest', l2Provider: l2Provider }), fetchTokenWithdrawalsFromEventLogs({ - ...getTokenWithdrawalsFromEventLogsQuery(), + sender, + receiver, fromBlock: toBlock + 1, toBlock: 'latest', l2Provider: l2Provider, @@ -129,36 +116,42 @@ export const fetchWithdrawals = async ({ const currentPageStart = pageNumber * pageSize const currentPageEnd = currentPageStart + pageSize - const partialEthWithdrawalsFromEventLogs = [...ethWithdrawalsFromEventLogs] - // event logs start from the earliest, we need to reverse them - .reverse() - .slice(currentPageStart, currentPageEnd) - const partialTokenWithdrawalsFromEventLogs = [ - ...tokenWithdrawalsFromEventLogs + // we need timestamps to sort token withdrawals along ETH withdrawals + const tokenWithdrawalsFromEventLogsWithTimestamp = await Promise.all( + tokenWithdrawalsFromEventLogs.map(withdrawal => + attachTimestampToTokenWithdrawal({ withdrawal, l2Provider }) + ) + ) + + // get ETH and token withdrawals that will be displayed on the current page + const paginatedWithdrawalsFromEventLogs = [ + ...ethWithdrawalsFromEventLogs, + ...tokenWithdrawalsFromEventLogsWithTimestamp ] - .reverse() + .sort(sortByTimestampDescending) .slice(currentPageStart, currentPageEnd) - const mappedTokenWithdrawalsFromEventLogs = ( - await Promise.all( - partialTokenWithdrawalsFromEventLogs.map(withdrawal => - mapTokenWithdrawalFromEventLogsToL2ToL1EventResult( - withdrawal, - l1Provider, - l2Provider, - l2ChainID - ) + const paginatedTokenWithdrawalsFromEventLogs: WithdrawalInitiated[] = [] + const paginatedEthWithdrawalsFromEventLogs: EthWithdrawal[] = [] + + for (const withdrawal of paginatedWithdrawalsFromEventLogs) { + if (isTokenWithdrawal(withdrawal)) { + paginatedTokenWithdrawalsFromEventLogs.push(withdrawal) + } else { + paginatedEthWithdrawalsFromEventLogs.push(withdrawal) + } + } + + const mappedTokenWithdrawalsFromEventLogs = await Promise.all( + paginatedTokenWithdrawalsFromEventLogs.map(withdrawal => + mapTokenWithdrawalFromEventLogsToL2ToL1EventResult( + withdrawal, + l1Provider, + l2Provider, + l2ChainID ) ) ) - // when viewing received funds, we don't want to see funds sent from the same address, so we filter them out - .filter(withdrawal => { - if (senderNot && receiver && withdrawal) { - return withdrawal.sender?.toLowerCase() !== senderNot.toLowerCase() - } - - return true - }) const l2ToL1Txns = [ ...(await Promise.all([ @@ -170,7 +163,7 @@ export const fetchWithdrawals = async ({ l2ChainID ) ), - ...partialEthWithdrawalsFromEventLogs.map(withdrawal => + ...paginatedEthWithdrawalsFromEventLogs.map(withdrawal => mapETHWithdrawalToL2ToL1EventResult( withdrawal, l1Provider, @@ -182,7 +175,7 @@ export const fetchWithdrawals = async ({ ...mappedTokenWithdrawalsFromEventLogs ] .filter((msg): msg is L2ToL1EventResultPlus => typeof msg !== 'undefined') - .sort((msgA, msgB) => +msgA.timestamp - +msgB.timestamp) + .sort(sortByTimestampDescending) const finalL2ToL1Txns: L2ToL1EventResultPlus[] = await Promise.all( l2ToL1Txns.map(withdrawal => diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawalsFromSubgraph.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawalsFromSubgraph.ts index 2ed36eb318..98e8d7ce59 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawalsFromSubgraph.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawalsFromSubgraph.ts @@ -20,7 +20,8 @@ export type FetchWithdrawalsFromSubgraphResult = { * Fetches initiated withdrawals (ETH + Token) from subgraph in range of [fromBlock, toBlock] and pageParams. * * @param query Query params - * @param query.address Account address + * @param query.sender Address that initiated the withdrawal + * @param query.receiver Address that received the funds * @param query.fromBlock Start at this block number (including) * @param query.toBlock Stop at this block number (including) * @param query.l2ChainId ChainId for the L2 network @@ -30,9 +31,7 @@ export type FetchWithdrawalsFromSubgraphResult = { */ export async function fetchWithdrawalsFromSubgraph({ sender, - senderNot, receiver, - receiverNot, fromBlock, toBlock, l2ChainId, @@ -41,9 +40,7 @@ export async function fetchWithdrawalsFromSubgraph({ searchString = '' }: { sender?: string - senderNot?: string receiver?: string - receiverNot?: string fromBlock: number toBlock: number l2ChainId: number @@ -59,9 +56,7 @@ export async function fetchWithdrawalsFromSubgraph({ const urlParams = new URLSearchParams( sanitizeQueryParams({ sender, - senderNot, receiver, - receiverNot, fromBlock, toBlock, l2ChainId, diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/helpers.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/helpers.ts index 77b3bfe7a7..30718852fb 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/helpers.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/helpers.ts @@ -15,6 +15,15 @@ import { } from '../../hooks/arbTokenBridge.types' import { getExecutedMessagesCacheKey } from '../../hooks/useArbTokenBridge' +/** + * `l2TxHash` exists on result from subgraph + * `transactionHash` exists on result from event logs + */ +export type EthWithdrawal = L2ToL1EventResult & { + l2TxHash?: string + transactionHash?: string +} + export const updateAdditionalWithdrawalData = async ( withdrawalTx: L2ToL1EventResultPlus, l1Provider: Provider, @@ -29,10 +38,25 @@ export const updateAdditionalWithdrawalData = async ( return l2toL1TxWithDeadline } +export async function attachTimestampToTokenWithdrawal({ + withdrawal, + l2Provider +}: { + withdrawal: WithdrawalInitiated + l2Provider: Provider +}) { + const txReceipt = await l2Provider.getTransactionReceipt(withdrawal.txHash) + const l2TxReceipt = new L2TransactionReceipt(txReceipt) + const [event] = l2TxReceipt.getL2ToL1Events() + + return { + ...withdrawal, + timestamp: event?.timestamp + } +} + export async function mapETHWithdrawalToL2ToL1EventResult( - // `l2TxHash` exists on result from subgraph - // `transactionHash` exists on result from event logs - event: L2ToL1EventResult & { l2TxHash?: string; transactionHash?: string }, + event: EthWithdrawal, l1Provider: Provider, l2Provider: Provider, l2ChainId: number @@ -134,6 +158,12 @@ export async function attachNodeBlockDeadlineToEvent( } } +export function isTokenWithdrawal( + withdrawal: WithdrawalInitiated | EthWithdrawal +): withdrawal is WithdrawalInitiated { + return typeof (withdrawal as WithdrawalInitiated).l1Token !== 'undefined' +} + export async function mapTokenWithdrawalFromEventLogsToL2ToL1EventResult( result: WithdrawalInitiated, l1Provider: Provider, @@ -211,8 +241,6 @@ export async function mapTokenWithdrawalFromEventLogsToL2ToL1EventResult( } export async function mapWithdrawalToL2ToL1EventResult( - // `l2TxHash` exists on result from subgraph - // `transactionHash` exists on result from event logs withdrawal: FetchWithdrawalsFromSubgraphResult, l1Provider: Provider, l2Provider: Provider,