diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index 82dbbc1518..99806c9a7f 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -3,12 +3,12 @@ import { useState, useMemo, useCallback } from 'react' import Tippy from '@tippyjs/react' import { constants, utils } from 'ethers' import { useLatest } from 'react-use' -import { useAccount, useChainId, useSigner } from 'wagmi' +import { useAccount, useNetwork, useSigner } from 'wagmi' import { TransactionResponse } from '@ethersproject/providers' import { twMerge } from 'tailwind-merge' import { useAppState } from '../../state' -import { getNetworkName, isNetwork } from '../../util/networks' +import { getNetworkName } from '../../util/networks' import { Button } from '../common/Button' import { TokenDepositCheckDialog, @@ -25,11 +25,9 @@ import { trackEvent } from '../../util/AnalyticsUtils' import { TransferPanelMain } from './TransferPanelMain' import { isGatewayRegistered } from '../../util/TokenUtils' import { useSwitchNetworkWithConfig } from '../../hooks/useSwitchNetworkWithConfig' -import { useIsConnectedToArbitrum } from '../../hooks/useIsConnectedToArbitrum' -import { useIsConnectedToOrbitChain } from '../../hooks/useIsConnectedToOrbitChain' import { errorToast, warningToast } from '../common/atoms/Toast' import { useAccountType } from '../../hooks/useAccountType' -import { DOCS_DOMAIN } from '../../constants' +import { DOCS_DOMAIN, GET_HELP_LINK } from '../../constants' import { AdvancedSettings, useDestinationAddressStore @@ -76,9 +74,24 @@ import { useIsBatchTransferSupported } from '../../hooks/TransferPanel/useIsBatc import { normalizeTimestamp } from '../../state/app/utils' import { useDestinationAddressError } from './hooks/useDestinationAddressError' import { useIsCctpTransfer } from './hooks/useIsCctpTransfer' +import { ExternalLink } from '../common/ExternalLink' import { isExperimentalFeatureEnabled } from '../../util' +import { useIsTransferAllowed } from './hooks/useIsTransferAllowed' const signerUndefinedError = 'Signer is undefined' +const transferNotAllowedError = 'Transfer not allowed' + +const networkConnectionWarningToast = () => + warningToast( + <> + Network connection issue. Please contact{' '} + + support + + . + , + { autoClose: false } + ) export function TransferPanel() { const { tokenFromSearchParams, setTokenQueryParam } = @@ -95,18 +108,18 @@ export function TransferPanel() { app: { connectionState, selectedToken, - arbTokenBridgeLoaded, - arbTokenBridge: { eth, token }, + arbTokenBridge: { token }, warningTokens } } = useAppState() const { layout } = useAppContextState() const { isTransferring } = layout - const { address: walletAddress, isConnected } = useAccount() + const { address: walletAddress } = useAccount() const { switchNetworkAsync } = useSwitchNetworkWithConfig({ isSwitchingNetworkBeforeTx: true }) - const chainId = useChainId() + // do not use `useChainId` because it won't detect chains outside of our wagmi config + const latestChain = useLatest(useNetwork()) const [networks] = useNetworks() const { childChain, @@ -133,10 +146,7 @@ export function TransferPanel() { const isCctpTransfer = useIsCctpTransfer() - const latestEth = useLatest(eth) - - const isConnectedToArbitrum = useLatest(useIsConnectedToArbitrum()) - const isConnectedToOrbitChain = useLatest(useIsConnectedToOrbitChain()) + const isTransferAllowed = useLatest(useIsTransferAllowed()) // Link the amount state directly to the amount in query params - no need of useState // Both `amount` getter and setter will internally be using `useArbQueryParams` functions @@ -338,17 +348,17 @@ export function TransferPanel() { if (!selectedToken) { return } - if (!walletAddress) { - return - } if (!signer) { - throw signerUndefinedError + throw new Error(signerUndefinedError) + } + if (!isTransferAllowed) { + throw new Error(transferNotAllowedError) } setTransferring(true) const childChainName = getNetworkName(childChain.id) const isConnectedToTheWrongChain = - chainId !== latestNetworks.current.sourceChain.id + latestChain.current?.chain?.id !== latestNetworks.current.sourceChain.id if (isConnectedToTheWrongChain) { trackEvent('Switch Network and Transfer', { @@ -534,35 +544,15 @@ export function TransferPanel() { } } - const isTransferAllowed = useMemo(() => { - if (!isConnected) { - return false - } - if (!walletAddress) { - return false - } - if (!!destinationAddressError) { - return false - } - return true - }, [destinationAddressError, isConnected, walletAddress]) - const transfer = async () => { - try { - setTransferring(true) - if (chainId !== networks.sourceChain.id) { - await switchNetworkAsync?.(networks.sourceChain.id) - } - } finally { - setTransferring(false) - } + const sourceChainId = latestNetworks.current.sourceChain.id if (!isTransferAllowed) { - return + throw new Error(transferNotAllowedError) } if (!signer) { - throw signerUndefinedError + throw new Error(signerUndefinedError) } // SC ETH transfers aren't enabled yet. Safety check, shouldn't be able to get here. @@ -598,65 +588,6 @@ export function TransferPanel() { return } - const depositRequiresChainSwitch = () => { - const isParentChainEthereum = isNetwork( - parentChain.id - ).isEthereumMainnetOrTestnet - - return ( - isDepositMode && - ((isParentChainEthereum && isConnectedToArbitrum.current) || - isConnectedToOrbitChain.current) - ) - } - - const withdrawalRequiresChainSwitch = () => { - const isConnectedToEthereum = - !isConnectedToArbitrum.current && !isConnectedToOrbitChain.current - - const { isOrbitChain: isSourceChainOrbit } = isNetwork(childChain.id) - - return ( - !isDepositMode && - (isConnectedToEthereum || - (isConnectedToArbitrum.current && isSourceChainOrbit)) - ) - } - - if (depositRequiresChainSwitch() || withdrawalRequiresChainSwitch()) { - trackEvent('Switch Network and Transfer', { - type: isTeleportMode - ? 'Teleport' - : isDepositMode - ? 'Deposit' - : 'Withdrawal', - tokenSymbol: selectedToken?.symbol, - assetType: selectedToken ? 'ERC-20' : 'ETH', - accountType: isSmartContractWallet ? 'Smart Contract' : 'EOA', - network: childChainName, - amount: Number(amount), - amount2: isBatchTransfer ? Number(amount2) : undefined, - version: 2 - }) - - const switchTargetChainId = latestNetworks.current.sourceChain.id - - await switchNetworkAsync?.(switchTargetChainId) - - // keep checking till we know the connected chain-pair are correct for transfer - while ( - depositRequiresChainSwitch() || - withdrawalRequiresChainSwitch() || - !latestEth.current || - !arbTokenBridgeLoaded - ) { - await new Promise(r => setTimeout(r, 100)) - } - - await new Promise(r => setTimeout(r, 3000)) - } - - const sourceChainId = latestNetworks.current.sourceChain.id const destinationChainId = latestNetworks.current.destinationChain.id const sourceChainErc20Address = isDepositMode @@ -987,6 +918,57 @@ export function TransferPanel() { isCustomDestinationTransfer ]) + const moveFundsButtonOnClick = async () => { + const isConnectedToTheWrongChain = + latestChain.current?.chain?.id !== latestNetworks.current.sourceChain.id + + const sourceChainId = latestNetworks.current.sourceChain.id + const childChainName = getNetworkName(childChain.id) + const isBatchTransfer = isBatchTransferSupported && Number(amount2) > 0 + + trackTransferButtonClick() + + try { + setTransferring(true) + if (isConnectedToTheWrongChain) { + trackEvent('Switch Network and Transfer', { + type: isTeleportMode + ? 'Teleport' + : isDepositMode + ? 'Deposit' + : 'Withdrawal', + tokenSymbol: selectedToken?.symbol, + assetType: selectedToken ? 'ERC-20' : 'ETH', + accountType: isSmartContractWallet ? 'Smart Contract' : 'EOA', + network: childChainName, + amount: Number(amount), + amount2: isBatchTransfer ? Number(amount2) : undefined, + version: 2 + }) + await switchNetworkAsync?.(sourceChainId) + } + } catch (error) { + if (isUserRejectedError(error)) { + return + } + return networkConnectionWarningToast() + } finally { + setTransferring(false) + } + + if (!isTransferAllowed) { + return networkConnectionWarningToast() + } + + if (isCctpTransfer) { + return transferCctp() + } + if (isDepositMode && selectedToken) { + return depositToken() + } + return transfer() + } + return ( <> { - trackTransferButtonClick() - - if (isCctpTransfer) { - transferCctp() - } else if (selectedToken) { - depositToken() - } else { - transfer() - } - }} + onClick={moveFundsButtonOnClick} style={{ borderColor: destinationChainUIcolor, backgroundColor: `${destinationChainUIcolor}66` @@ -1067,15 +1039,7 @@ export function TransferPanel() { variant="primary" loading={isTransferring} disabled={!transferReady.withdrawal} - onClick={() => { - trackTransferButtonClick() - - if (isCctpTransfer) { - transferCctp() - } else { - transfer() - } - }} + onClick={moveFundsButtonOnClick} style={{ borderColor: destinationChainUIcolor, backgroundColor: `${destinationChainUIcolor}66` diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useIsTransferAllowed.ts b/packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useIsTransferAllowed.ts new file mode 100644 index 0000000000..ce849ae6f8 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useIsTransferAllowed.ts @@ -0,0 +1,52 @@ +import { useMemo } from 'react' +import { useAccount, useNetwork } from 'wagmi' + +import { useAppState } from '../../../state' +import { useNetworks } from '../../../hooks/useNetworks' +import { useDestinationAddressError } from './useDestinationAddressError' + +export function useIsTransferAllowed() { + const { + app: { + arbTokenBridgeLoaded, + arbTokenBridge: { eth } + } + } = useAppState() + const { address: walletAddress, isConnected } = useAccount() + // do not use `useChainId` because it won't detect chains outside of our wagmi config + const { chain } = useNetwork() + const [networks] = useNetworks() + const { destinationAddressError } = useDestinationAddressError() + + return useMemo(() => { + const isConnectedToTheWrongChain = chain?.id !== networks.sourceChain.id + + if (!arbTokenBridgeLoaded) { + return false + } + if (!eth) { + return false + } + if (!isConnected) { + return false + } + if (!walletAddress) { + return false + } + if (isConnectedToTheWrongChain) { + return false + } + if (!!destinationAddressError) { + return false + } + return true + }, [ + arbTokenBridgeLoaded, + chain?.id, + destinationAddressError, + isConnected, + eth, + networks.sourceChain.id, + walletAddress + ]) +} diff --git a/packages/arb-token-bridge-ui/src/hooks/useIsConnectedToArbitrum.ts b/packages/arb-token-bridge-ui/src/hooks/useIsConnectedToArbitrum.ts deleted file mode 100644 index 25c98d5d35..0000000000 --- a/packages/arb-token-bridge-ui/src/hooks/useIsConnectedToArbitrum.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useNetwork } from 'wagmi' - -import { isNetwork } from '../util/networks' - -export function useIsConnectedToArbitrum() { - const { chain } = useNetwork() - - if (typeof chain === 'undefined') { - return undefined - } - - return isNetwork(chain.id).isArbitrum -} diff --git a/packages/arb-token-bridge-ui/src/hooks/useIsConnectedToOrbitChain.ts b/packages/arb-token-bridge-ui/src/hooks/useIsConnectedToOrbitChain.ts deleted file mode 100644 index 7d335ff832..0000000000 --- a/packages/arb-token-bridge-ui/src/hooks/useIsConnectedToOrbitChain.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useNetwork } from 'wagmi' - -import { isNetwork } from '../util/networks' - -export function useIsConnectedToOrbitChain() { - const { chain } = useNetwork() - - if (typeof chain === 'undefined') { - return undefined - } - - return isNetwork(chain.id).isOrbitChain -}