diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRowAction.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRowAction.tsx index 629aec8aaa..1b6bce2c75 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRowAction.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRowAction.tsx @@ -1,4 +1,6 @@ import { useCallback } from 'react' +import { useAccount, useNetwork } from 'wagmi' + import { GET_HELP_LINK } from '../../constants' import { useClaimWithdrawal } from '../../hooks/useClaimWithdrawal' import { useClaimCctp } from '../../state/cctpState' @@ -13,7 +15,6 @@ import { getNetworkName } from '../../util/networks' import { errorToast } from '../common/atoms/Toast' import { Button } from '../common/Button' import { useSwitchNetworkWithConfig } from '../../hooks/useSwitchNetworkWithConfig' -import { useNetwork } from 'wagmi' import { isDepositReadyToRedeem } from '../../state/app/utils' import { useRedeemRetryable } from '../../hooks/useRedeemRetryable' import { TransferCountdown } from '../common/TransferCountdown' @@ -23,6 +24,7 @@ import { useRedeemTeleporter } from '../../hooks/useRedeemTeleporter' import { sanitizeTokenSymbol } from '../../util/TokenUtils' import { formatAmount } from '../../util/NumberUtils' import { useTransactionHistoryAddressStore } from './TransactionHistorySearchBar' +import { Tooltip } from '../common/Tooltip' export function TransactionsTableRowAction({ tx, @@ -33,10 +35,17 @@ export function TransactionsTableRowAction({ isError: boolean type: 'deposits' | 'withdrawals' }) { + const { address: connectedAddress } = useAccount() const { chain } = useNetwork() const { switchNetworkAsync } = useSwitchNetworkWithConfig() const networkName = getNetworkName(chain?.id ?? 0) - const { sanitizedAddress } = useTransactionHistoryAddressStore() + const { sanitizedAddress: searchedAddress } = + useTransactionHistoryAddressStore() + + const isViewingAnotherAddress = + connectedAddress && + searchedAddress && + connectedAddress.toLowerCase() !== searchedAddress.toLowerCase() const tokenSymbol = sanitizeTokenSymbol(tx.asset, { erc20L1Address: tx.tokenAddress, @@ -47,10 +56,10 @@ export function TransactionsTableRowAction({ const { claim: claimCctp, isClaiming: isClaimingCctp } = useClaimCctp(tx) const { redeem, isRedeeming: isRetryableRedeeming } = useRedeemRetryable( tx, - sanitizedAddress + searchedAddress ) const { redeem: teleporterRedeem, isRedeeming: isTeleporterRedeeming } = - useRedeemTeleporter(tx, sanitizedAddress) + useRedeemTeleporter(tx, searchedAddress) const isRedeeming = isRetryableRedeeming || isTeleporterRedeeming @@ -162,16 +171,25 @@ export function TransactionsTableRowAction({ return isClaiming || isClaimingCctp ? ( Claiming... ) : ( - + + ) } diff --git a/packages/arb-token-bridge-ui/src/hooks/useClaimWithdrawal.ts b/packages/arb-token-bridge-ui/src/hooks/useClaimWithdrawal.ts index f174dc8fb3..be9b21769d 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useClaimWithdrawal.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useClaimWithdrawal.ts @@ -15,6 +15,7 @@ import { fetchErc20Data } from '../util/TokenUtils' import { fetchNativeCurrency } from './useNativeCurrency' import { getProviderForChainId } from '@/token-bridge-sdk/utils' import { captureSentryErrorWithExtraData } from '../util/SentryUtils' +import { useTransactionHistoryAddressStore } from '../components/TransactionHistory/TransactionHistorySearchBar' export type UseClaimWithdrawalResult = { claim: () => Promise @@ -28,8 +29,11 @@ export function useClaimWithdrawal( app: { arbTokenBridge } } = useAppState() const { address } = useAccount() + const { sanitizedAddress } = useTransactionHistoryAddressStore() const { data: signer } = useSigner({ chainId: tx.parentChainId }) - const { updatePendingTransaction } = useTransactionHistory(address) + const { updatePendingTransaction } = useTransactionHistory( + sanitizedAddress ?? address + ) const [isClaiming, setIsClaiming] = useState(false) const claim = useCallback(async () => { diff --git a/packages/arb-token-bridge-ui/src/util/networks.ts b/packages/arb-token-bridge-ui/src/util/networks.ts index ab8ce17bfb..ff4ca58a60 100644 --- a/packages/arb-token-bridge-ui/src/util/networks.ts +++ b/packages/arb-token-bridge-ui/src/util/networks.ts @@ -1,4 +1,4 @@ -import { StaticJsonRpcProvider } from '@ethersproject/providers' +import { Provider, StaticJsonRpcProvider } from '@ethersproject/providers' import { ArbitrumNetwork, getChildrenForNetwork, @@ -11,6 +11,7 @@ import { loadEnvironmentVariableWithFallback } from './index' import { getBridgeUiConfigForChain } from './bridgeUiConfig' import { chainIdToInfuraUrl } from './infura' import { fetchErc20Data } from './TokenUtils' +import { orbitChains } from './orbitChainsList' export enum ChainId { // L1 @@ -581,6 +582,16 @@ export function getSupportedChainIds({ }) } +export function isAlchemyChain(chainId: number) { + const chain = orbitChains[chainId] + + if (typeof chain === 'undefined') { + return false + } + + return chain.rpcUrl.toLowerCase().includes('alchemy.com') +} + export function mapCustomChainToNetworkData(chain: ChainWithRpcUrl) { // custom chain details need to be added to various objects to make it work with the UI // diff --git a/packages/arb-token-bridge-ui/src/util/orbitChainsData.json b/packages/arb-token-bridge-ui/src/util/orbitChainsData.json index 69ca450659..ba4db224dd 100644 --- a/packages/arb-token-bridge-ui/src/util/orbitChainsData.json +++ b/packages/arb-token-bridge-ui/src/util/orbitChainsData.json @@ -435,7 +435,8 @@ "name": "SX Network", "symbol": "SX", "logoUrl": "/images/sxTokenLogo.png" - } + }, + "fastWithdrawalTime": 21600000 } }, { diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts index 4b748a2080..d32c467a51 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts @@ -148,14 +148,13 @@ export function getProps(targetChainKey: string | null) { chains }) + wallets[0]?.wallets.push(okxWallet({ chains, projectId })) + const connectors = connectorsForWallets([ ...wallets, { groupName: 'More', - wallets: [ - trustWallet({ chains, projectId }), - okxWallet({ chains, projectId }) - ] + wallets: [trustWallet({ chains, projectId })] } ]) diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogsSequentially.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogsSequentially.ts index 5087788d3c..3e421ca077 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogsSequentially.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogsSequentially.ts @@ -1,41 +1,25 @@ import { constants } from 'ethers' import { Provider, BlockTag } from '@ethersproject/providers' -import { Erc20Bridger, getArbitrumNetwork } from '@arbitrum/sdk' +import { Erc20Bridger } from '@arbitrum/sdk' import { fetchTokenWithdrawalsFromEventLogs, FetchTokenWithdrawalsFromEventLogsParams } from './fetchTokenWithdrawalsFromEventLogs' -import { getNonce } from '../AddressUtils' -import { fetchL2Gateways } from '../fetchL2Gateways' -import { backOff, wait } from '../ExponentialBackoffUtils' -async function getGateways(provider: Provider): Promise<{ - standardGateway: string - wethGateway: string - customGateway: string - otherGateways: string[] -}> { - const network = await getArbitrumNetwork(provider) - - const standardGateway = network.tokenBridge?.childErc20Gateway - const customGateway = network.tokenBridge?.childCustomGateway - const wethGateway = network.tokenBridge?.childWethGateway - const otherGateways = await fetchL2Gateways(provider) - - return { - standardGateway: standardGateway ?? constants.AddressZero, - wethGateway: wethGateway ?? constants.AddressZero, - customGateway: customGateway ?? constants.AddressZero, - otherGateways - } -} +import { backOff, wait } from '../ExponentialBackoffUtils' -type TokenWithdrawalQuery = { +type FetchTokenWithdrawalsFromEventLogsQuery = { params: FetchTokenWithdrawalsFromEventLogsParams priority: number } +export type Query = { + sender?: string + receiver?: string + gateways?: string[] +} + export type FetchTokenWithdrawalsFromEventLogsSequentiallyParams = { sender?: string receiver?: string @@ -43,9 +27,10 @@ export type FetchTokenWithdrawalsFromEventLogsSequentiallyParams = { fromBlock?: BlockTag toBlock?: BlockTag /** - * How long to delay in-between queries of different priority. + * How long to delay in-between queries of different priority. Defaults to 0. */ delayMs?: number + queries: Query[] } export type FetchTokenWithdrawalsFromEventLogsSequentiallyResult = Awaited< @@ -53,28 +38,24 @@ export type FetchTokenWithdrawalsFromEventLogsSequentiallyResult = Awaited< > export async function fetchTokenWithdrawalsFromEventLogsSequentially({ - sender, - receiver, provider, fromBlock = 0, toBlock = 'latest', - delayMs = 2_000 + delayMs = 0, + queries: queriesProp }: FetchTokenWithdrawalsFromEventLogsSequentiallyParams): Promise { // keep track of priority; increment as queries are added let priority = 0 + // keep track of queries - const queries: TokenWithdrawalQuery[] = [] + const queries: FetchTokenWithdrawalsFromEventLogsQuery[] = [] // helper function to reuse common params function buildQueryParams({ sender, receiver, gateways = [] - }: { - sender?: string - receiver?: string - gateways?: string[] - }): TokenWithdrawalQuery['params'] { + }: Query): FetchTokenWithdrawalsFromEventLogsQuery['params'] { return { sender, receiver, @@ -86,7 +67,7 @@ export async function fetchTokenWithdrawalsFromEventLogsSequentially({ } // for sanitizing, adding queries and incrementing priority - function addQuery(params: TokenWithdrawalQuery['params']) { + function addQuery(params: FetchTokenWithdrawalsFromEventLogsQuery['params']) { const gateways = params.l2GatewayAddresses ?? [] const gatewaysSanitized = gateways.filter(g => g !== constants.AddressZero) @@ -100,22 +81,9 @@ export async function fetchTokenWithdrawalsFromEventLogsSequentially({ }) } - const gateways = await getGateways(provider) - const senderNonce = await backOff(() => getNonce(sender, { provider })) - - // sender queries; only add if nonce > 0 - if (senderNonce > 0) { - addQuery(buildQueryParams({ sender, gateways: [gateways.standardGateway] })) - addQuery(buildQueryParams({ sender, gateways: [gateways.wethGateway] })) - addQuery(buildQueryParams({ sender, gateways: [gateways.customGateway] })) - addQuery(buildQueryParams({ sender, gateways: gateways.otherGateways })) - } - - // receiver queries - addQuery(buildQueryParams({ receiver, gateways: [gateways.standardGateway] })) - addQuery(buildQueryParams({ receiver, gateways: [gateways.wethGateway] })) - addQuery(buildQueryParams({ receiver, gateways: [gateways.customGateway] })) - addQuery(buildQueryParams({ receiver, gateways: gateways.otherGateways })) + queriesProp.forEach(query => { + addQuery(buildQueryParams(query)) + }) // for iterating through all priorities in the while loop below let currentPriority = 1 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 9b8e1b6274..aba40e7207 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts @@ -11,8 +11,37 @@ import { fetchLatestSubgraphBlockNumber } from '../SubgraphUtils' import { Withdrawal } from '../../hooks/useTransactionHistory' import { attachTimestampToTokenWithdrawal } from './helpers' import { WithdrawalInitiated } from '../../hooks/arbTokenBridge.types' -import { fetchTokenWithdrawalsFromEventLogsSequentially } from './fetchTokenWithdrawalsFromEventLogsSequentially' +import { + Query, + fetchTokenWithdrawalsFromEventLogsSequentially +} from './fetchTokenWithdrawalsFromEventLogsSequentially' import { backOff, wait } from '../ExponentialBackoffUtils' +import { isAlchemyChain } from '../networks' +import { getArbitrumNetwork } from '@arbitrum/sdk' +import { fetchL2Gateways } from '../fetchL2Gateways' +import { constants } from 'ethers' +import { getNonce } from '../AddressUtils' + +async function getGateways(provider: Provider): Promise<{ + standardGateway: string + wethGateway: string + customGateway: string + otherGateways: string[] +}> { + const network = await getArbitrumNetwork(provider) + + const standardGateway = network.tokenBridge?.childErc20Gateway + const customGateway = network.tokenBridge?.childCustomGateway + const wethGateway = network.tokenBridge?.childWethGateway + const otherGateways = await fetchL2Gateways(provider) + + return { + standardGateway: standardGateway ?? constants.AddressZero, + wethGateway: wethGateway ?? constants.AddressZero, + customGateway: customGateway ?? constants.AddressZero, + otherGateways + } +} export type FetchWithdrawalsParams = { sender?: string @@ -84,6 +113,47 @@ export async function fetchWithdrawals({ console.log('Error fetching withdrawals from subgraph', error) } + const gateways = await getGateways(l2Provider) + const senderNonce = await getNonce(sender, { provider: l2Provider }) + + const queries: Query[] = [] + + // alchemy as a raas has a global rate limit across their chains, so we have to fetch sequentially and wait in-between requests to work around this + const isAlchemy = isAlchemyChain(l2ChainID) + const delayMs = isAlchemy ? 2_000 : 0 + + const allGateways = [ + gateways.standardGateway, + gateways.wethGateway, + gateways.customGateway, + ...gateways.otherGateways + ] + + // sender queries; only add if nonce > 0 + if (senderNonce > 0) { + if (isAlchemy) { + // for alchemy, fetch sequentially + queries.push({ sender, gateways: [gateways.standardGateway] }) + queries.push({ sender, gateways: [gateways.wethGateway] }) + queries.push({ sender, gateways: [gateways.customGateway] }) + queries.push({ sender, gateways: gateways.otherGateways }) + } else { + // for other chains, fetch in parallel + queries.push({ sender, gateways: allGateways }) + } + } + + if (isAlchemy) { + // for alchemy, fetch sequentially + queries.push({ receiver, gateways: [gateways.standardGateway] }) + queries.push({ receiver, gateways: [gateways.wethGateway] }) + queries.push({ receiver, gateways: [gateways.customGateway] }) + queries.push({ receiver, gateways: gateways.otherGateways }) + } else { + // for other chains, fetch in parallel + queries.push({ receiver, gateways: allGateways }) + } + const ethWithdrawalsFromEventLogs = await backOff(() => fetchETHWithdrawalsFromEventLogs({ receiver, @@ -95,7 +165,7 @@ export async function fetchWithdrawals({ }) ) - await wait(2_000) + await wait(delayMs) const tokenWithdrawalsFromEventLogs = await fetchTokenWithdrawalsFromEventLogsSequentially({ @@ -103,7 +173,8 @@ export async function fetchWithdrawals({ receiver, fromBlock: toBlock + 1, toBlock: 'latest', - provider: l2Provider + provider: l2Provider, + queries }) const mappedEthWithdrawalsFromEventLogs: Withdrawal[] =