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
-}