diff --git a/packages/arb-token-bridge-ui/src/components/App/App.tsx b/packages/arb-token-bridge-ui/src/components/App/App.tsx index d23f339fb8..0e048de72d 100644 --- a/packages/arb-token-bridge-ui/src/components/App/App.tsx +++ b/packages/arb-token-bridge-ui/src/components/App/App.tsx @@ -41,7 +41,7 @@ import { HeaderConnectWalletButton } from '../common/HeaderConnectWalletButton' import { onDisconnectHandler } from '../../util/walletConnectUtils' import { addressIsSmartContract } from '../../util/AddressUtils' import { useSyncConnectedChainToAnalytics } from './useSyncConnectedChainToAnalytics' -import { isDepositMode } from '../../util/isDepositMode' +import { getTransferMode } from '../../util/getTransferMode' declare global { interface Window { @@ -105,15 +105,20 @@ const ArbTokenBridgeStoreSyncWrapper = (): JSX.Element | null => { l2NetworkChainId: childChain.id }) - if ( - isDepositMode({ + const { isDepositMode, isWithdrawalMode, isTeleportMode } = getTransferMode( + { sourceChainId: networks.sourceChain.id, destinationChainId: networks.destinationChain.id - }) - ) { + } + ) + + if (isDepositMode) { console.info('Deposit mode detected:') actions.app.setConnectionState(ConnectionState.L1_CONNECTED) - } else { + } else if (isTeleportMode) { + console.info('Teleport mode detected:') + actions.app.setConnectionState(ConnectionState.L1_CONNECTED) + } else if (isWithdrawalMode) { console.info('Withdrawal mode detected:') actions.app.setConnectionState(ConnectionState.L2_CONNECTED) } @@ -130,6 +135,7 @@ const ArbTokenBridgeStoreSyncWrapper = (): JSX.Element | null => { }) }, [ networks.sourceChain.id, + networks.destinationChain.id, parentChain.id, childChain.id, parentChain, diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts index cb3d25ddfc..5ed4c6c966 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts @@ -2,8 +2,7 @@ import { StaticJsonRpcProvider } from '@ethersproject/providers' import { useMemo } from 'react' import { Chain } from 'wagmi' import { UseNetworksState } from './useNetworks' -import { isDepositMode } from '../util/isDepositMode' -import { isValidTeleportChainPair } from '@/token-bridge-sdk/teleport' +import { getTransferMode } from '../util/getTransferMode' type UseNetworksRelationshipState = { childChain: Chain @@ -20,23 +19,18 @@ export function useNetworksRelationship({ destinationChainProvider }: UseNetworksState): UseNetworksRelationshipState { return useMemo(() => { - const _isDepositMode = isDepositMode({ + const { isDepositMode, isTeleportMode } = getTransferMode({ sourceChainId: sourceChain.id, destinationChainId: destinationChain.id }) - const isTeleportMode = isValidTeleportChainPair({ - sourceChainId: sourceChain.id, - destinationChainId: destinationChain.id - }) - - if (_isDepositMode) { + if (isDepositMode || isTeleportMode) { return { childChain: destinationChain, childChainProvider: destinationChainProvider, parentChain: sourceChain, parentChainProvider: sourceChainProvider, - isDepositMode: _isDepositMode, + isDepositMode, isTeleportMode } } @@ -46,7 +40,7 @@ export function useNetworksRelationship({ childChainProvider: sourceChainProvider, parentChain: destinationChain, parentChainProvider: destinationChainProvider, - isDepositMode: _isDepositMode, + isDepositMode, isTeleportMode } }, [ diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts index 54e7483bbf..671fd98eb9 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts @@ -1,7 +1,7 @@ import { BigNumber, Signer } from 'ethers' import { Provider, StaticJsonRpcProvider } from '@ethersproject/providers' -import { ChainId, isNetwork, rpcURLs } from '../util/networks' +import { ChainId, rpcURLs } from '../util/networks' import { BridgeTransferStarterPropsWithChainIds } from './BridgeTransferStarter' import { isValidTeleportChainPair } from './teleport' import { @@ -11,7 +11,7 @@ import { EthL1L3Bridger, getArbitrumNetwork } from '@arbitrum/sdk' -import { isDepositMode } from '../util/isDepositMode' +import { getTransferMode } from '../util/getTransferMode' export const getAddressFromSigner = async (signer: Signer) => { const address = await signer.getAddress() @@ -29,28 +29,11 @@ export const getBridgeTransferProperties = ( const sourceChainId = props.sourceChainId const destinationChainId = props.destinationChainId - const isDestinationChainEthereumMainnetOrTestnet = - isNetwork(destinationChainId).isEthereumMainnetOrTestnet - - const isSourceChainArbitrum = isNetwork(sourceChainId).isArbitrum - const isDestinationChainArbitrum = isNetwork(destinationChainId).isArbitrum - - const isSourceChainOrbit = isNetwork(sourceChainId).isOrbitChain - - const { isBase: isDestinationChainBase } = isNetwork(destinationChainId) - - const isDeposit = isDepositMode({ sourceChainId, destinationChainId }) - - const isWithdrawal = - (isSourceChainArbitrum && isDestinationChainEthereumMainnetOrTestnet) || // l2 arbitrum chains to l1 - (isSourceChainOrbit && isDestinationChainEthereumMainnetOrTestnet) || // l2 orbit chains to l1 - (isSourceChainOrbit && isDestinationChainArbitrum) || // l3 orbit chains to l1 - (isSourceChainOrbit && isDestinationChainBase) // l3 orbit chain to Base l2 - - const isTeleport = isValidTeleportChainPair({ - sourceChainId, - destinationChainId - }) + const { + isDepositMode: isDeposit, + isWithdrawalMode: isWithdrawal, + isTeleportMode: isTeleport + } = getTransferMode({ sourceChainId, destinationChainId }) const isNativeCurrencyTransfer = typeof props.sourceChainErc20Address === 'undefined' diff --git a/packages/arb-token-bridge-ui/src/util/__tests__/getTransferMode.test.ts b/packages/arb-token-bridge-ui/src/util/__tests__/getTransferMode.test.ts new file mode 100644 index 0000000000..377b18f5cd --- /dev/null +++ b/packages/arb-token-bridge-ui/src/util/__tests__/getTransferMode.test.ts @@ -0,0 +1,176 @@ +import { registerCustomArbitrumNetwork } from '@arbitrum/sdk' + +import { getTransferMode } from '../getTransferMode' +import { ChainId } from '../networks' +import { orbitMainnets } from '../orbitChainsList' + +beforeAll(() => { + const popApexChainId = 70700 + + const popApex = orbitMainnets[popApexChainId] + + if (!popApex) { + throw new Error(`Could not find Pop Apex in the Orbit chains list.`) + } + + registerCustomArbitrumNetwork(popApex) + + const rariMainnetChainId = 1380012617 + + const rariMainnet = orbitMainnets[rariMainnetChainId] + + if (!rariMainnet) { + throw new Error(`Could not find Rari Mainnet in the Orbit chains list.`) + } + + registerCustomArbitrumNetwork(rariMainnet) +}) + +describe('getTransferMode', () => { + it('should return correctly for L1 source chain and L2 destination chain', () => { + const result1 = getTransferMode({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) + expect(result1).toEqual({ + isDepositMode: true, + isWithdrawalMode: false, + isTeleportMode: false + }) + + const result2 = getTransferMode({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumNova + }) + + expect(result2).toEqual({ + isDepositMode: true, + isWithdrawalMode: false, + isTeleportMode: false + }) + }) + + it('should return correctly for L2 source chain and L3 destination chain', () => { + const result1 = getTransferMode({ + sourceChainId: ChainId.ArbitrumOne, + destinationChainId: 70700 // PopApex + }) + expect(result1).toEqual({ + isDepositMode: true, + isWithdrawalMode: false, + isTeleportMode: false + }) + + const result2 = getTransferMode({ + sourceChainId: ChainId.ArbitrumOne, + destinationChainId: 1380012617 // RARI mainnet + }) + + expect(result2).toEqual({ + isDepositMode: true, + isWithdrawalMode: false, + isTeleportMode: false + }) + }) + + it('should return correctly for L2 source chain and L1 destination chain', () => { + const result1 = getTransferMode({ + sourceChainId: ChainId.ArbitrumOne, + destinationChainId: ChainId.Ethereum + }) + expect(result1).toEqual({ + isDepositMode: false, + isWithdrawalMode: true, + isTeleportMode: false + }) + + const result2 = getTransferMode({ + sourceChainId: ChainId.ArbitrumNova, + destinationChainId: ChainId.Ethereum + }) + + expect(result2).toEqual({ + isDepositMode: false, + isWithdrawalMode: true, + isTeleportMode: false + }) + }) + + it('should return correctly for L3 source chain and L2 destination chain', () => { + const result1 = getTransferMode({ + sourceChainId: 1380012617, // RARI mainnet + destinationChainId: ChainId.ArbitrumOne + }) + expect(result1).toEqual({ + isDepositMode: false, + isWithdrawalMode: true, + isTeleportMode: false + }) + + const result2 = getTransferMode({ + sourceChainId: 70700, // PopApex + destinationChainId: ChainId.ArbitrumOne + }) + + expect(result2).toEqual({ + isDepositMode: false, + isWithdrawalMode: true, + isTeleportMode: false + }) + }) + + it('should return correctly for L1 source chain and L3 destination chain', () => { + const result1 = getTransferMode({ + sourceChainId: ChainId.Ethereum, + destinationChainId: 1380012617 // RARI mainnet + }) + expect(result1).toEqual({ + isDepositMode: false, + isWithdrawalMode: false, + isTeleportMode: true + }) + + const result2 = getTransferMode({ + sourceChainId: ChainId.Ethereum, + destinationChainId: 70700 // PopApex + }) + + expect(result2).toEqual({ + isDepositMode: false, + isWithdrawalMode: false, + isTeleportMode: true + }) + }) + + it('should return correctly for L3 source chain and L1 destination chain', () => { + expect(() => + getTransferMode({ + sourceChainId: 1380012617, // RARI mainnet + destinationChainId: ChainId.Ethereum + }) + ).toThrow(new Error('Unsupported source and destination chain pair.')) + + expect(() => + getTransferMode({ + sourceChainId: 70700, // PopApex + destinationChainId: ChainId.Ethereum + }) + ).toThrow(new Error('Unsupported source and destination chain pair.')) + }) + + it('should throw error for L2 source chain and L2 destination chain', () => { + expect(() => + getTransferMode({ + sourceChainId: ChainId.ArbitrumOne, + destinationChainId: ChainId.ArbitrumNova + }) + ).toThrow(new Error('Unsupported source and destination chain pair.')) + + expect(() => + getTransferMode({ + sourceChainId: ChainId.ArbitrumNova, + destinationChainId: ChainId.ArbitrumOne + }) + ).toThrow(new Error('Unsupported source and destination chain pair.')) + }) +}) diff --git a/packages/arb-token-bridge-ui/src/util/__tests__/isDepositMode.test.ts b/packages/arb-token-bridge-ui/src/util/__tests__/isDepositMode.test.ts deleted file mode 100644 index 17f0a92ea6..0000000000 --- a/packages/arb-token-bridge-ui/src/util/__tests__/isDepositMode.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { registerCustomArbitrumNetwork } from '@arbitrum/sdk' - -import { isDepositMode } from '../isDepositMode' -import { ChainId } from '../networks' -import { orbitMainnets } from '../orbitChainsList' - -beforeAll(() => { - const xaiMainnetChainId = 660279 - - const xaiMainnet = orbitMainnets[xaiMainnetChainId] - - if (!xaiMainnet) { - throw new Error(`Could not find Xai Mainnet in the Orbit chains list.`) - } - - registerCustomArbitrumNetwork(xaiMainnet) - - const rariMainnetChainId = 1380012617 - - const rariMainnet = orbitMainnets[rariMainnetChainId] - - if (!rariMainnet) { - throw new Error(`Could not find Rari Mainnet in the Orbit chains list.`) - } - - registerCustomArbitrumNetwork(rariMainnet) -}) - -describe('isDepositMode', () => { - it('should return true for L1 source chain and L2 destination chain', () => { - const result1 = isDepositMode({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - }) - expect(result1).toBe(true) - - const result2 = isDepositMode({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumNova - }) - - expect(result2).toBe(true) - }) - it('should return true for L2 source chain and L3 destination chain', () => { - const result1 = isDepositMode({ - sourceChainId: ChainId.ArbitrumOne, - destinationChainId: 660279 // Xai - }) - expect(result1).toBe(true) - - const result2 = isDepositMode({ - sourceChainId: ChainId.ArbitrumOne, - destinationChainId: 1380012617 // RARI mainnet - }) - - expect(result2).toBe(true) - }) - it('should return false for L2 source chain and L1 destination chain', () => { - const result1 = isDepositMode({ - sourceChainId: ChainId.ArbitrumOne, - destinationChainId: ChainId.Ethereum - }) - expect(result1).toBe(false) - - const result2 = isDepositMode({ - sourceChainId: ChainId.ArbitrumNova, - destinationChainId: ChainId.Ethereum - }) - - expect(result2).toBe(false) - }) - it('should return false for L3 source chain and L2 destination chain', () => { - const result1 = isDepositMode({ - sourceChainId: 1380012617, // RARI mainnet - destinationChainId: ChainId.ArbitrumOne - }) - expect(result1).toBe(false) - - const result2 = isDepositMode({ - sourceChainId: 660279, // Xai - destinationChainId: ChainId.ArbitrumOne - }) - - expect(result2).toBe(false) - }) - it('should return true for L1 source chain and L3 destination chain', () => { - const result1 = isDepositMode({ - sourceChainId: ChainId.Ethereum, - destinationChainId: 1380012617 // RARI mainnet - }) - expect(result1).toBe(true) - - const result2 = isDepositMode({ - sourceChainId: ChainId.Ethereum, - destinationChainId: 660279 // Xai - }) - - expect(result2).toBe(true) - }) - it('should return false for L3 source chain and L1 destination chain', () => { - const result1 = isDepositMode({ - sourceChainId: 1380012617, // RARI mainnet - destinationChainId: ChainId.Ethereum - }) - expect(result1).toBe(false) - - const result2 = isDepositMode({ - sourceChainId: 660279, // Xai - destinationChainId: ChainId.Ethereum - }) - - expect(result2).toBe(false) - }) - it('should throw error for L2 source chain and L2 destination chain', () => { - expect(() => - isDepositMode({ - sourceChainId: ChainId.ArbitrumOne, - destinationChainId: ChainId.ArbitrumNova - }) - ).toThrow(new Error('Arbitrum One to Arbitrum Nova is not supported.')) - - expect(() => - isDepositMode({ - sourceChainId: ChainId.ArbitrumNova, - destinationChainId: ChainId.ArbitrumOne - }) - ).toThrow(new Error('Arbitrum Nova to Arbitrum One is not supported.')) - }) -}) diff --git a/packages/arb-token-bridge-ui/src/util/getTransferMode.ts b/packages/arb-token-bridge-ui/src/util/getTransferMode.ts new file mode 100644 index 0000000000..1bbc3121b8 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/util/getTransferMode.ts @@ -0,0 +1,59 @@ +import { isValidTeleportChainPair } from '@/token-bridge-sdk/teleport' +import { getDestinationChainIds, isNetwork } from './networks' + +/** + * determines if the UI is in deposit mode or withdrawal mode or teleport mode + * + * @returns {Object} {isDepositMode, isWithdrawalMode, isTeleportMode} + */ +export function getTransferMode({ + sourceChainId, + destinationChainId +}: { + sourceChainId: number + destinationChainId: number +}) { + const { + isEthereumMainnetOrTestnet: isSourceChainEthereumMainnetOrTestnet, + isArbitrum: isSourceChainArbitrum, + isBase: isSourceChainBase, + isOrbitChain: isSourceChainOrbit + } = isNetwork(sourceChainId) + const { + isOrbitChain: isDestinationChainOrbit, + isArbitrum: isDestinationChainArbitrum, + isBase: isDestinationChainBase, + isEthereumMainnetOrTestnet: isDestinationChainEthereumMainnetOrTestnet + } = isNetwork(destinationChainId) + + const validDestinationChains = getDestinationChainIds(sourceChainId) + + if (!validDestinationChains.includes(destinationChainId)) { + throw new Error('Unsupported source and destination chain pair.') + } + + const isDepositMode = + (isSourceChainEthereumMainnetOrTestnet && !isDestinationChainOrbit) || + isSourceChainBase || + (isSourceChainArbitrum && isDestinationChainOrbit) + + const isWithdrawalMode = + (isSourceChainArbitrum && isDestinationChainEthereumMainnetOrTestnet) || // l2 arbitrum chains to l1 + (isSourceChainOrbit && isDestinationChainEthereumMainnetOrTestnet) || // l2 orbit chains to l1 + (isSourceChainOrbit && isDestinationChainArbitrum) || // l3 orbit chains to l1 + (isSourceChainOrbit && isDestinationChainBase) // l3 orbit chain to Base l2 + + const isTeleportMode = + isSourceChainEthereumMainnetOrTestnet && + isDestinationChainOrbit && + isValidTeleportChainPair({ + sourceChainId, + destinationChainId + }) + + return { + isDepositMode, + isWithdrawalMode, + isTeleportMode + } +} diff --git a/packages/arb-token-bridge-ui/src/util/isDepositMode.ts b/packages/arb-token-bridge-ui/src/util/isDepositMode.ts deleted file mode 100644 index ac9ed42078..0000000000 --- a/packages/arb-token-bridge-ui/src/util/isDepositMode.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { isNetwork } from '../util/networks' - -/** - * determines if the UI is in deposit mode or withdrawal mode - * - * @note this function classifies L1 -> L3 as deposit mode - * @returns boolean - */ -export function isDepositMode({ - sourceChainId, - destinationChainId -}: { - sourceChainId: number - destinationChainId: number -}) { - const { - isEthereumMainnetOrTestnet: isSourceChainEthereum, - isArbitrum: isSourceChainArbitrum, - isBase: isSourceChainBase, - isArbitrumOne: isSourceChainArbitrumOne, - isArbitrumNova: isSourceChainArbitrumNova - } = isNetwork(sourceChainId) - const { - isOrbitChain: isDestinationChainOrbit, - isArbitrumOne: isDestinationChainArbitrumOne, - isArbitrumNova: isDestinationChainArbitrumNova - } = isNetwork(destinationChainId) - - if (isSourceChainArbitrumOne && isDestinationChainArbitrumNova) { - throw new Error('Arbitrum One to Arbitrum Nova is not supported.') - } - if (isSourceChainArbitrumNova && isDestinationChainArbitrumOne) { - throw new Error('Arbitrum Nova to Arbitrum One is not supported.') - } - - const isDepositMode = - isSourceChainEthereum || - isSourceChainBase || - (isSourceChainArbitrum && isDestinationChainOrbit) - - return isDepositMode -}