From 8669576a325491e4b3c2e344f1b2f74330b5bf69 Mon Sep 17 00:00:00 2001 From: Christophe Date: Fri, 17 Nov 2023 00:31:37 +0100 Subject: [PATCH 01/35] feat: Add useNetworks and useNetworksRelationship hooks --- packages/arb-token-bridge-ui/jest.config.js | 5 +- .../src/hooks/__tests__/useNetworks.test.ts | 26 ++++ .../src/hooks/useArbQueryParams.tsx | 24 +++ .../src/hooks/useNetworks.ts | 142 ++++++++++++++++++ .../src/hooks/useNetworksRelationship.ts | 46 ++++++ .../src/types/ChainQueryParam.ts | 92 ++++++++++++ .../util/wagmi/getPartnerChainsForChain.ts | 52 +++++++ .../src/util/wagmi/setup.ts | 16 +- 8 files changed, 397 insertions(+), 6 deletions(-) create mode 100644 packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts create mode 100644 packages/arb-token-bridge-ui/src/hooks/useNetworks.ts create mode 100644 packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts create mode 100644 packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts create mode 100644 packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts diff --git a/packages/arb-token-bridge-ui/jest.config.js b/packages/arb-token-bridge-ui/jest.config.js index 9688d58c04..607a5ce1f9 100644 --- a/packages/arb-token-bridge-ui/jest.config.js +++ b/packages/arb-token-bridge-ui/jest.config.js @@ -19,7 +19,10 @@ const transformNodeModules = [ // The following are dependencies for query-string (https://github.com/sindresorhus/query-string/blob/main/package.json) 'decode-uri-component', 'split-on-first', - 'filter-obj' + 'filter-obj', + // wagmi + '@wagmi', + 'wagmi' ] module.exports = async function () { diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts new file mode 100644 index 0000000000..707ca8872f --- /dev/null +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -0,0 +1,26 @@ +/** + * @jest-environment jsdom + */ +import { sanitizeQueryParams } from '../useNetworks' + +describe('sanitizeQueryParams', () => { + it('sets the default values for `from` and `to` when both `from` and `to` are `undefined`', () => { + const result = sanitizeQueryParams({ from: undefined, to: undefined }) + expect(result).toEqual({ from: 'ethereum', to: 'arbitrumOne' }) + }) + + it('sets the value for `to` to the partner chain of `from` when `to` is `undefined`', () => { + const result = sanitizeQueryParams({ from: 'ethereum', to: undefined }) + expect(result).toEqual({ from: 'ethereum', to: 'arbitrumOne' }) + }) + + it('sets the value for `from` to the partner chain of `to` when `from` is `undefined`', () => { + const result = sanitizeQueryParams({ from: undefined, to: 'arbitrumNova' }) + expect(result).toEqual({ from: 'ethereum', to: 'arbitrumNova' }) + }) + + it('sets the value for `to` to the partner chain of `from` when `to` is an invalid value', () => { + const result = sanitizeQueryParams({ from: 'goerli', to: 'arbitrumOne' }) + expect(result).toEqual({ from: 'goerli', to: 'arbitrumGoerli' }) + }) +}) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index f796256a2d..7fa1332f47 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -25,6 +25,11 @@ import { withDefault } from 'use-query-params' +import { + ChainQueryParam, + isValidChainQueryParam +} from '../types/ChainQueryParam' + export enum AmountQueryParamEnum { MAX = 'max' } @@ -37,6 +42,8 @@ export const useArbQueryParams = () => { ] */ return useQueryParams({ + from: ChainParam, + to: ChainParam, amount: withDefault(AmountQueryParam, ''), // amount which is filled in Transfer panel l2ChainId: NumberParam, // L2 chain-id with which we can initiaze (override) our networks/signer token: StringParam, // import a new token using a Dialog Box @@ -97,6 +104,23 @@ export const AmountQueryParam = { } } +export const ChainParam = { + encode: (value: string | (string | null)[] | null | undefined) => value, + decode: ( + value: string | (string | null)[] | null | undefined + ): ChainQueryParam | undefined => { + if (typeof value !== 'string') { + return undefined + } + + if (!isValidChainQueryParam(value)) { + return undefined + } + + return value + } +} + export function ArbQueryParamProvider({ children }: { diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts new file mode 100644 index 0000000000..54eb541098 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -0,0 +1,142 @@ +import { Chain } from 'wagmi' +import { StaticJsonRpcProvider } from '@ethersproject/providers' +import { useCallback, useMemo } from 'react' + +import { useArbQueryParams } from './useArbQueryParams' +import { + ChainQueryParam, + getChainForChainQueryParam, + getChainQueryParamForChain +} from '../types/ChainQueryParam' +import { ChainId, rpcURLs } from '../util/networks' +import { getPartnerChainsForChain } from '../util/wagmi/getPartnerChainsForChain' + +function getPartnerChainsQueryParams( + chainQueryParam: ChainQueryParam +): ChainQueryParam[] { + const chain = getChainForChainQueryParam(chainQueryParam) + const partnerChains = getPartnerChainsForChain(chain) + + return partnerChains.map(chain => getChainQueryParamForChain(chain.id)) +} + +const getProviderForChainCache: { + [rpcUrl: string]: StaticJsonRpcProvider +} = { + // start with empty cache +} + +function createProviderWithCache(rpcUrl: string, chainId: ChainId) { + const provider = new StaticJsonRpcProvider(rpcUrl, chainId) + getProviderForChainCache[rpcUrl] = provider + return provider +} + +function getProviderForChain(chain: Chain): StaticJsonRpcProvider { + const rpcUrl = rpcURLs[chain.id] + + if (typeof rpcUrl === 'undefined') { + throw new Error(`[getProviderForChain] Unexpected chain id: ${chain.id}`) + } + + const cachedProvider = getProviderForChainCache[rpcUrl] + + if (typeof cachedProvider !== 'undefined') { + return cachedProvider + } + + return createProviderWithCache(rpcUrl, chain.id) +} + +export function sanitizeQueryParams({ + from, + to +}: { + from: ChainQueryParam | undefined + to: ChainQueryParam | undefined +}): { + from: ChainQueryParam + to: ChainQueryParam +} { + // when both `from` and `to` are undefined, default to Ethereum and Arbitrum One + if (typeof from === 'undefined' && typeof to === 'undefined') { + return { from: 'ethereum', to: 'arbitrumOne' } + } + + // only `from` is undefined + if (typeof from === 'undefined' && typeof to !== 'undefined') { + // get the counter + const [defaultFrom] = getPartnerChainsQueryParams(to) + return { from: defaultFrom!, to } + } + + // only `to` is undefined + if (typeof from !== 'undefined' && typeof to === 'undefined') { + const [defaultTo] = getPartnerChainsQueryParams(from) + return { from, to: defaultTo as ChainQueryParam } + } + + // both values are defined, but `to` is an invalid partner chain + if (!getPartnerChainsQueryParams(from!).includes(to!)) { + const [defaultTo] = getPartnerChainsQueryParams(from!) + return { from: from!, to: defaultTo! } + } + + return { from: from!, to: to! } +} + +export type UseNetworksState = { + from: Chain + fromProvider: StaticJsonRpcProvider + to: Chain + toProvider: StaticJsonRpcProvider +} + +export type UseNetworksSetStateParams = { fromId: ChainId; toId?: ChainId } +export type UseNetworksSetState = (params: UseNetworksSetStateParams) => void + +export function useNetworks(): [UseNetworksState, UseNetworksSetState] { + const [{ from, to }, setQueryParams] = useArbQueryParams() + const { from: validFrom, to: validTo } = sanitizeQueryParams({ from, to }) + + const setState = useCallback( + (params: UseNetworksSetStateParams) => { + const fromQueryParam = getChainQueryParamForChain(params.fromId) + if (!params.toId) { + const [toQueryParam] = getPartnerChainsQueryParams(fromQueryParam) + setQueryParams( + sanitizeQueryParams({ from: fromQueryParam, to: toQueryParam }) + ) + return + } + + const toQueryParam = getChainQueryParamForChain(params.toId) + + setQueryParams( + sanitizeQueryParams({ from: fromQueryParam, to: toQueryParam }) + ) + }, + [setQueryParams] + ) + + if (from !== validFrom || to !== validTo) { + // On the first render, update query params with the sanitized values + setQueryParams({ from: validFrom, to: validTo }) + } + + // The return values of the hook will always be the sanitized values + return useMemo(() => { + const fromChain = getChainForChainQueryParam(validFrom) + const toChain = getChainForChainQueryParam(validTo) + + return [ + { + from: fromChain, + fromProvider: getProviderForChain(fromChain), + to: toChain, + toProvider: getProviderForChain(toChain) + }, + setState + ] + }, [validFrom, validTo, setState]) +} diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts new file mode 100644 index 0000000000..d189c0cf27 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts @@ -0,0 +1,46 @@ +import { StaticJsonRpcProvider } from '@ethersproject/providers' +import { useMemo } from 'react' +import { Chain } from 'wagmi' +import { isNetwork } from '../util/networks' +import { UseNetworksState } from './useNetworks' + +type UseNetworksRelationshipState = { + childProvider: StaticJsonRpcProvider + childChain: Chain + parentChain: Chain + parentProvider: StaticJsonRpcProvider +} +export function useNetworksRelationship({ + fromProvider, + from, + toProvider, + to +}: UseNetworksState): UseNetworksRelationshipState { + const fromNetwork = fromProvider.network + const toNetwork = toProvider.network + const { + isEthereumMainnet: isFromNetworkEthereum, + isArbitrum: isFromNetworkArbitrum + } = isNetwork(fromNetwork.chainId) + const { isOrbitChain: isToNetworkOrbitChain } = isNetwork(toNetwork.chainId) + const isFromNetworkParent = + isFromNetworkEthereum || (isFromNetworkArbitrum && isToNetworkOrbitChain) + + return useMemo(() => { + if (isFromNetworkParent) { + return { + parentChain: from, + parentProvider: fromProvider, + childProvider: toProvider, + childChain: to + } + } + + return { + parentChain: to, + parentProvider: toProvider, + childProvider: fromProvider, + childChain: from + } + }, [fromProvider, from, toProvider, to, isFromNetworkParent]) +} diff --git a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts new file mode 100644 index 0000000000..ccf734f486 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts @@ -0,0 +1,92 @@ +import { Chain } from 'wagmi' +import * as chains from 'wagmi/chains' + +import { ChainId } from '../util/networks' +import * as customChains from '../util/wagmi/wagmiAdditionalNetworks' + +const chainQueryParams = [ + 'ethereum', + 'goerli', + 'sepolia', + 'arbitrumOne', + 'arbitrumNova', + 'arbitrumGoerli', + 'arbitrumSepolia', + 'stylus-testnet', + 'xai-testnet' +] as const + +export type ChainQueryParam = (typeof chainQueryParams)[number] + +export function isValidChainQueryParam( + value: string +): value is ChainQueryParam { + return (chainQueryParams as readonly string[]).includes(value) +} + +export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam { + switch (chainId) { + case ChainId.Ethereum: + return 'ethereum' + + case ChainId.Goerli: + return 'goerli' + + case ChainId.ArbitrumOne: + return 'arbitrumOne' + + case ChainId.ArbitrumNova: + return 'arbitrumNova' + + case ChainId.ArbitrumGoerli: + return 'arbitrumGoerli' + + case ChainId.StylusTestnet: + return 'stylus-testnet' + + case ChainId.XaiTestnet: + return 'xai-testnet' + + case ChainId.Sepolia: + return 'sepolia' + + case ChainId.ArbitrumSepolia: + return 'arbitrumSepolia' + + default: + throw new Error( + `[getChainQueryParamForChain] Unexpected chain id: ${chainId}` + ) + } +} + +export function getChainForChainQueryParam(value: ChainQueryParam): Chain { + switch (value) { + case 'ethereum': + return chains.mainnet + + case 'goerli': + return chains.goerli + + case 'sepolia': + return chains.sepolia + + case 'arbitrumOne': + return chains.arbitrum + + case 'arbitrumNova': + return customChains.arbitrumNova + + case 'arbitrumGoerli': + return chains.arbitrumGoerli + + case 'arbitrumSepolia': + return customChains.arbitrumSepolia + + case 'stylus-testnet': + return customChains.stylusTestnet + + case 'xai-testnet': + return customChains.xaiTestnet + } +} diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts new file mode 100644 index 0000000000..c199105bbc --- /dev/null +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts @@ -0,0 +1,52 @@ +import { Chain } from 'wagmi' +import { + mainnet, + goerli, + sepolia, + arbitrum as arbitrumOne, + arbitrumGoerli +} from 'wagmi/chains' + +import { ChainId } from '../../util/networks' +import { + arbitrumNova, + arbitrumSepolia, + stylusTestnet, + xaiTestnet +} from './wagmiAdditionalNetworks' + +export function getPartnerChainsForChain(chain: Chain): Chain[] { + switch (chain.id) { + case ChainId.Ethereum: + return [arbitrumOne, arbitrumNova] + + case ChainId.Goerli: + return [arbitrumGoerli] + + case ChainId.Sepolia: + return [arbitrumSepolia] + + case ChainId.ArbitrumOne: + return [mainnet] + + case ChainId.ArbitrumNova: + return [mainnet] + + case ChainId.ArbitrumGoerli: + return [goerli, xaiTestnet, stylusTestnet] + + case ChainId.ArbitrumSepolia: + return [sepolia] + + case ChainId.StylusTestnet: + return [arbitrumGoerli] + + case ChainId.XaiTestnet: + return [arbitrumGoerli] + + default: + throw new Error( + `[getPartnerChainsForChain] Unexpected chain id: ${chain.id}` + ) + } +} 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 d9a8026ad1..fe75e601c9 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts @@ -3,7 +3,7 @@ import { mainnet, arbitrum, arbitrumGoerli } from '@wagmi/core/chains' import { publicProvider } from 'wagmi/providers/public' import { connectorsForWallets, getDefaultWallets } from '@rainbow-me/rainbowkit' import { trustWallet, ledgerWallet } from '@rainbow-me/rainbowkit/wallets' - +import { jsonRpcProvider } from '@wagmi/core/providers/jsonRpc' import { sepolia, arbitrumNova, @@ -15,7 +15,7 @@ import { chainToWagmiChain } from './wagmiAdditionalNetworks' import { isTestingEnvironment } from '../CommonUtils' -import { ChainId } from '../networks' +import { ChainId, rpcURLs } from '../networks' import { getCustomChainsFromLocalStorage } from '../networks' const customChains = getCustomChainsFromLocalStorage().map(chain => @@ -68,7 +68,7 @@ const appInfo = { } enum TargetChainKey { - Ethereum = 'mainnet', + Ethereum = 'ethereum', ArbitrumOne = 'arbitrum-one', ArbitrumNova = 'arbitrum-nova', Goerli = 'goerli', @@ -129,10 +129,16 @@ function getChains(targetChainKey: TargetChainKey) { export function getProps(targetChainKey: string | null) { const { chains, provider } = configureChains( // Wagmi selects the first chain as the one to target in WalletConnect, so it has to be the first in the array. - // // https://github.com/wagmi-dev/references/blob/main/packages/connectors/src/walletConnect.ts#L114 getChains(sanitizeTargetChainKey(targetChainKey)), - [publicProvider()] + [ + publicProvider(), + jsonRpcProvider({ + rpc: chain => ({ + http: rpcURLs[chain.id]! + }) + }) + ] ) const { wallets } = getDefaultWallets({ From 3db047bb0287f627481782b9897a4c8f563732a6 Mon Sep 17 00:00:00 2001 From: Christophe Deveaux Date: Mon, 20 Nov 2023 10:30:38 +0100 Subject: [PATCH 02/35] Update packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts Co-authored-by: spsjvc --- .../arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts index d189c0cf27..64569ac74f 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts @@ -5,10 +5,10 @@ import { isNetwork } from '../util/networks' import { UseNetworksState } from './useNetworks' type UseNetworksRelationshipState = { - childProvider: StaticJsonRpcProvider childChain: Chain + childChainProvider: StaticJsonRpcProvider parentChain: Chain - parentProvider: StaticJsonRpcProvider + parentChainProvider: StaticJsonRpcProvider } export function useNetworksRelationship({ fromProvider, From 995e18c32a91b795cc0d65683b6e96ab80281914 Mon Sep 17 00:00:00 2001 From: Christophe Deveaux Date: Mon, 20 Nov 2023 10:32:02 +0100 Subject: [PATCH 03/35] Update packages/arb-token-bridge-ui/src/hooks/useNetworks.ts Co-authored-by: spsjvc --- packages/arb-token-bridge-ui/src/hooks/useNetworks.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index 54eb541098..ce3f9eda96 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -86,10 +86,10 @@ export function sanitizeQueryParams({ } export type UseNetworksState = { - from: Chain - fromProvider: StaticJsonRpcProvider - to: Chain - toProvider: StaticJsonRpcProvider + sourceChain: Chain + sourceChainProvider: StaticJsonRpcProvider + destinationChain: Chain + destinationChainProvider: StaticJsonRpcProvider } export type UseNetworksSetStateParams = { fromId: ChainId; toId?: ChainId } From 8ac459ea9d98191dd23c3c68ecb2751f71949e25 Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 20 Nov 2023 12:30:28 +0100 Subject: [PATCH 04/35] Update hooks --- .../src/hooks/useNetworks.ts | 8 +-- .../src/hooks/useNetworksRelationship.ts | 53 +++++++++++-------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index ce3f9eda96..b82002ef2b 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -131,10 +131,10 @@ export function useNetworks(): [UseNetworksState, UseNetworksSetState] { return [ { - from: fromChain, - fromProvider: getProviderForChain(fromChain), - to: toChain, - toProvider: getProviderForChain(toChain) + sourceChain: fromChain, + sourceChainProvider: getProviderForChain(fromChain), + destinationChain: toChain, + destinationChainProvider: getProviderForChain(toChain) }, setState ] diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts index 64569ac74f..70b60a12fb 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts @@ -11,36 +11,45 @@ type UseNetworksRelationshipState = { parentChainProvider: StaticJsonRpcProvider } export function useNetworksRelationship({ - fromProvider, - from, - toProvider, - to + destinationChain, + destinationChainProvider, + sourceChain, + sourceChainProvider }: UseNetworksState): UseNetworksRelationshipState { - const fromNetwork = fromProvider.network - const toNetwork = toProvider.network + const sourceNetwork = sourceChainProvider.network + const destinationNetwork = destinationChainProvider.network const { - isEthereumMainnet: isFromNetworkEthereum, - isArbitrum: isFromNetworkArbitrum - } = isNetwork(fromNetwork.chainId) - const { isOrbitChain: isToNetworkOrbitChain } = isNetwork(toNetwork.chainId) - const isFromNetworkParent = - isFromNetworkEthereum || (isFromNetworkArbitrum && isToNetworkOrbitChain) + isEthereumMainnet: isSourceNetworkEthereum, + isArbitrum: isSourceNetworkArbitrum + } = isNetwork(sourceNetwork.chainId) + const { isOrbitChain: isDestinationNetworkOrbitChain } = isNetwork( + destinationNetwork.chainId + ) + const isSourceNetworkParent = + isSourceNetworkEthereum || + (isSourceNetworkArbitrum && isDestinationNetworkOrbitChain) return useMemo(() => { - if (isFromNetworkParent) { + if (isSourceNetworkParent) { return { - parentChain: from, - parentProvider: fromProvider, - childProvider: toProvider, - childChain: to + childChain: destinationChain, + childChainProvider: destinationChainProvider, + parentChain: sourceChain, + parentChainProvider: sourceChainProvider } } return { - parentChain: to, - parentProvider: toProvider, - childProvider: fromProvider, - childChain: from + childChain: sourceChain, + childChainProvider: sourceChainProvider, + parentChain: destinationChain, + parentChainProvider: destinationChainProvider } - }, [fromProvider, from, toProvider, to, isFromNetworkParent]) + }, [ + isSourceNetworkParent, + sourceChain, + destinationChain, + destinationChainProvider, + sourceChainProvider + ]) } From 510f47d7fd6022f09ef35dbee6c42ed235d123a4 Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 21 Nov 2023 13:36:43 +0100 Subject: [PATCH 05/35] use kebab case network names --- .../src/hooks/__tests__/useNetworks.test.ts | 8 +++---- .../src/hooks/useNetworks.ts | 2 +- .../src/types/ChainQueryParam.ts | 24 +++++++++---------- .../util/wagmi/getPartnerChainsForChain.ts | 6 ++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index 707ca8872f..cedcff1955 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -6,21 +6,21 @@ import { sanitizeQueryParams } from '../useNetworks' describe('sanitizeQueryParams', () => { it('sets the default values for `from` and `to` when both `from` and `to` are `undefined`', () => { const result = sanitizeQueryParams({ from: undefined, to: undefined }) - expect(result).toEqual({ from: 'ethereum', to: 'arbitrumOne' }) + expect(result).toEqual({ from: 'ethereum', to: 'arbitrum-one' }) }) it('sets the value for `to` to the partner chain of `from` when `to` is `undefined`', () => { const result = sanitizeQueryParams({ from: 'ethereum', to: undefined }) - expect(result).toEqual({ from: 'ethereum', to: 'arbitrumOne' }) + expect(result).toEqual({ from: 'ethereum', to: 'arbitrum-one' }) }) it('sets the value for `from` to the partner chain of `to` when `from` is `undefined`', () => { - const result = sanitizeQueryParams({ from: undefined, to: 'arbitrumNova' }) + const result = sanitizeQueryParams({ from: undefined, to: 'arbitrum-nova' }) expect(result).toEqual({ from: 'ethereum', to: 'arbitrumNova' }) }) it('sets the value for `to` to the partner chain of `from` when `to` is an invalid value', () => { - const result = sanitizeQueryParams({ from: 'goerli', to: 'arbitrumOne' }) + const result = sanitizeQueryParams({ from: 'goerli', to: 'arbitrum-one' }) expect(result).toEqual({ from: 'goerli', to: 'arbitrumGoerli' }) }) }) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index b82002ef2b..a8d0d1e5b6 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -60,7 +60,7 @@ export function sanitizeQueryParams({ } { // when both `from` and `to` are undefined, default to Ethereum and Arbitrum One if (typeof from === 'undefined' && typeof to === 'undefined') { - return { from: 'ethereum', to: 'arbitrumOne' } + return { from: 'ethereum', to: 'arbitrum-one' } } // only `from` is undefined diff --git a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts index ccf734f486..68cb4220d1 100644 --- a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts +++ b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts @@ -8,10 +8,10 @@ const chainQueryParams = [ 'ethereum', 'goerli', 'sepolia', - 'arbitrumOne', - 'arbitrumNova', - 'arbitrumGoerli', - 'arbitrumSepolia', + 'arbitrum-one', + 'arbitrum-nova', + 'arbitrum-goerli', + 'arbitrum-sepolia', 'stylus-testnet', 'xai-testnet' ] as const @@ -33,13 +33,13 @@ export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam { return 'goerli' case ChainId.ArbitrumOne: - return 'arbitrumOne' + return 'arbitrum-one' case ChainId.ArbitrumNova: - return 'arbitrumNova' + return 'arbitrum-nova' case ChainId.ArbitrumGoerli: - return 'arbitrumGoerli' + return 'arbitrum-goerli' case ChainId.StylusTestnet: return 'stylus-testnet' @@ -51,7 +51,7 @@ export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam { return 'sepolia' case ChainId.ArbitrumSepolia: - return 'arbitrumSepolia' + return 'arbitrum-sepolia' default: throw new Error( @@ -71,16 +71,16 @@ export function getChainForChainQueryParam(value: ChainQueryParam): Chain { case 'sepolia': return chains.sepolia - case 'arbitrumOne': + case 'arbitrum-one': return chains.arbitrum - case 'arbitrumNova': + case 'arbitrum-nova': return customChains.arbitrumNova - case 'arbitrumGoerli': + case 'arbitrum-goerli': return chains.arbitrumGoerli - case 'arbitrumSepolia': + case 'arbitrum-sepolia': return customChains.arbitrumSepolia case 'stylus-testnet': diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts index c199105bbc..54b728ee4d 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts @@ -33,13 +33,13 @@ export function getPartnerChainsForChain(chain: Chain): Chain[] { return [mainnet] case ChainId.ArbitrumGoerli: - return [goerli, xaiTestnet, stylusTestnet] + return [goerli, xaiTestnet] case ChainId.ArbitrumSepolia: - return [sepolia] + return [sepolia, stylusTestnet] case ChainId.StylusTestnet: - return [arbitrumGoerli] + return [arbitrumSepolia] case ChainId.XaiTestnet: return [arbitrumGoerli] From e7dd2dbea45f039623b8b6df73eb4f209c95af8a Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 22 Nov 2023 12:37:52 +0100 Subject: [PATCH 06/35] Add support for orbit chain --- .../src/hooks/__tests__/useNetworks.test.ts | 49 +++- .../src/hooks/useArbQueryParams.tsx | 42 +++- .../src/hooks/useNetworks.ts | 209 ++++++++++++------ .../src/types/ChainQueryParam.ts | 42 +++- ...Chain.ts => getPartnerChainsForChainId.ts} | 21 +- 5 files changed, 266 insertions(+), 97 deletions(-) rename packages/arb-token-bridge-ui/src/util/wagmi/{getPartnerChainsForChain.ts => getPartnerChainsForChainId.ts} (54%) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index cedcff1955..0fa5a62187 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -1,26 +1,51 @@ /** * @jest-environment jsdom */ +import { ChainId } from '../../util/networks' import { sanitizeQueryParams } from '../useNetworks' describe('sanitizeQueryParams', () => { - it('sets the default values for `from` and `to` when both `from` and `to` are `undefined`', () => { - const result = sanitizeQueryParams({ from: undefined, to: undefined }) - expect(result).toEqual({ from: 'ethereum', to: 'arbitrum-one' }) + it('sets the default values for `sourceChainId` and `destinationChainId` when both `sourceChainId` and `destinationChainId` are `undefined`', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) }) - it('sets the value for `to` to the partner chain of `from` when `to` is `undefined`', () => { - const result = sanitizeQueryParams({ from: 'ethereum', to: undefined }) - expect(result).toEqual({ from: 'ethereum', to: 'arbitrum-one' }) + it('sets the value for `destinationChainId` to the partner chain of `sourceChainId` when `destinationChainId` is `undefined`', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Ethereum, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) }) - it('sets the value for `from` to the partner chain of `to` when `from` is `undefined`', () => { - const result = sanitizeQueryParams({ from: undefined, to: 'arbitrum-nova' }) - expect(result).toEqual({ from: 'ethereum', to: 'arbitrumNova' }) + it('sets the value for `sourceChainId` to the partner chain of `destinationChainId` when `sourceChainId` is `undefined`', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: ChainId.ArbitrumNova + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumNova + }) }) - it('sets the value for `to` to the partner chain of `from` when `to` is an invalid value', () => { - const result = sanitizeQueryParams({ from: 'goerli', to: 'arbitrum-one' }) - expect(result).toEqual({ from: 'goerli', to: 'arbitrumGoerli' }) + it('sets the value for `destinationChainId` to the partner chain of `sourceChainId` when `destinationChainId` is an invalid value', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Goerli, + destinationChainId: ChainId.ArbitrumOne + }) + expect(result).toEqual({ + sourceChainId: ChainId.Goerli, + destinationChainId: ChainId.ArbitrumGoerli + }) }) }) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index 7fa1332f47..1380f5eab4 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -21,14 +21,17 @@ import { NumberParam, QueryParamProvider, StringParam, + decodeString, useQueryParams, withDefault } from 'use-query-params' import { - ChainQueryParam, + getChainForChainQueryParam, + getChainQueryParamForChain, isValidChainQueryParam } from '../types/ChainQueryParam' +import { ChainId } from '../util/networks' export enum AmountQueryParamEnum { MAX = 'max' @@ -42,8 +45,8 @@ export const useArbQueryParams = () => { ] */ return useQueryParams({ - from: ChainParam, - to: ChainParam, + sourceChain: ChainParam, + destinationChain: ChainParam, amount: withDefault(AmountQueryParam, ''), // amount which is filled in Transfer panel l2ChainId: NumberParam, // L2 chain-id with which we can initiaze (override) our networks/signer token: StringParam, // import a new token using a Dialog Box @@ -105,19 +108,40 @@ export const AmountQueryParam = { } export const ChainParam = { - encode: (value: string | (string | null)[] | null | undefined) => value, + // Parse chainId to ChainQueryParam or ChainId for orbit chain + // encode: (value: string | (string | null)[] | null | undefined) => { + encode: (chainId: number | null | undefined) => { + if (!chainId) { + return undefined + } + + try { + const chain = getChainQueryParamForChain(chainId) + return chain as string + } catch (e) { + return undefined + } + }, + // Parse ChainQueryParam/ChainId to ChainId decode: ( value: string | (string | null)[] | null | undefined - ): ChainQueryParam | undefined => { - if (typeof value !== 'string') { + ): ChainId | undefined => { + const valueStr = decodeString(value) + if (!valueStr) { return undefined } - if (!isValidChainQueryParam(value)) { - return undefined + const valueNum = parseInt(valueStr, 10) + if (!Number.isNaN(valueNum)) { + // TODO: Verify that chainId is of a supported network or orbit chain? + return valueNum + } + + if (isValidChainQueryParam(valueStr)) { + return getChainForChainQueryParam(valueStr).id } - return value + return undefined } } diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index a8d0d1e5b6..e413c75416 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -1,88 +1,134 @@ import { Chain } from 'wagmi' import { StaticJsonRpcProvider } from '@ethersproject/providers' import { useCallback, useMemo } from 'react' +import { mainnet, arbitrum, goerli, arbitrumGoerli } from '@wagmi/core/chains' import { useArbQueryParams } from './useArbQueryParams' -import { - ChainQueryParam, - getChainForChainQueryParam, - getChainQueryParamForChain -} from '../types/ChainQueryParam' import { ChainId, rpcURLs } from '../util/networks' -import { getPartnerChainsForChain } from '../util/wagmi/getPartnerChainsForChain' - -function getPartnerChainsQueryParams( - chainQueryParam: ChainQueryParam -): ChainQueryParam[] { - const chain = getChainForChainQueryParam(chainQueryParam) - const partnerChains = getPartnerChainsForChain(chain) +import { + sepolia, + arbitrumNova, + arbitrumSepolia, + xaiTestnet, + stylusTestnet, + localL1Network as local, + localL2Network as arbitrumLocal +} from '../util/wagmi/wagmiAdditionalNetworks' + +import { getPartnerChainsForChainId } from '../util/wagmi/getPartnerChainsForChainId' + +function getChainByChainId(chainId: ChainId): Chain { + const chain = { + // L1 + [mainnet.id]: mainnet, + // L1 Testnet + [goerli.id]: goerli, + [sepolia.id]: sepolia, + // L2 + [arbitrum.id]: arbitrum, + [arbitrumNova.id]: arbitrumNova, + // L2 Testnet + [arbitrumGoerli.id]: arbitrumGoerli, + [arbitrumSepolia.id]: arbitrumSepolia, + // L3 + [xaiTestnet.id]: xaiTestnet, + [stylusTestnet.id]: stylusTestnet, + // E2E + [local.id]: local, + [arbitrumLocal.id]: arbitrumLocal + }[chainId] + + return chain ?? mainnet +} - return partnerChains.map(chain => getChainQueryParamForChain(chain.id)) +function getPartnerChainsQueryParams(chainId: ChainId): ChainId[] { + const partnerChains = getPartnerChainsForChainId(chainId) + return partnerChains.map(chain => chain.id) } const getProviderForChainCache: { - [rpcUrl: string]: StaticJsonRpcProvider + [chainId: number]: StaticJsonRpcProvider } = { // start with empty cache } -function createProviderWithCache(rpcUrl: string, chainId: ChainId) { +function createProviderWithCache(chainId: ChainId) { + const chain = getChainByChainId(chainId) + const rpcUrl = rpcURLs[chainId] const provider = new StaticJsonRpcProvider(rpcUrl, chainId) - getProviderForChainCache[rpcUrl] = provider + getProviderForChainCache[chain.id] = provider return provider } -function getProviderForChain(chain: Chain): StaticJsonRpcProvider { - const rpcUrl = rpcURLs[chain.id] - - if (typeof rpcUrl === 'undefined') { - throw new Error(`[getProviderForChain] Unexpected chain id: ${chain.id}`) - } - - const cachedProvider = getProviderForChainCache[rpcUrl] +function getProviderForChainId(chainId: ChainId): StaticJsonRpcProvider { + const cachedProvider = getProviderForChainCache[chainId] if (typeof cachedProvider !== 'undefined') { return cachedProvider } - return createProviderWithCache(rpcUrl, chain.id) + return createProviderWithCache(chainId) } export function sanitizeQueryParams({ - from, - to + sourceChainId, + destinationChainId }: { - from: ChainQueryParam | undefined - to: ChainQueryParam | undefined + sourceChainId: ChainId | undefined + destinationChainId: ChainId | undefined }): { - from: ChainQueryParam - to: ChainQueryParam + sourceChainId: ChainId + destinationChainId: ChainId } { // when both `from` and `to` are undefined, default to Ethereum and Arbitrum One - if (typeof from === 'undefined' && typeof to === 'undefined') { - return { from: 'ethereum', to: 'arbitrum-one' } + if ( + typeof sourceChainId === 'undefined' && + typeof destinationChainId === 'undefined' + ) { + return { + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + } } // only `from` is undefined - if (typeof from === 'undefined' && typeof to !== 'undefined') { + if ( + typeof sourceChainId === 'undefined' && + typeof destinationChainId !== 'undefined' + ) { // get the counter - const [defaultFrom] = getPartnerChainsQueryParams(to) - return { from: defaultFrom!, to } + const [defaultSourceChainId] = + getPartnerChainsQueryParams(destinationChainId) + return { sourceChainId: defaultSourceChainId!, destinationChainId } } // only `to` is undefined - if (typeof from !== 'undefined' && typeof to === 'undefined') { - const [defaultTo] = getPartnerChainsQueryParams(from) - return { from, to: defaultTo as ChainQueryParam } + if ( + typeof sourceChainId !== 'undefined' && + typeof destinationChainId === 'undefined' + ) { + const [defaultDestinationChainId] = + getPartnerChainsQueryParams(sourceChainId) + return { sourceChainId, destinationChainId: defaultDestinationChainId! } } // both values are defined, but `to` is an invalid partner chain - if (!getPartnerChainsQueryParams(from!).includes(to!)) { - const [defaultTo] = getPartnerChainsQueryParams(from!) - return { from: from!, to: defaultTo! } + if ( + !getPartnerChainsQueryParams(sourceChainId!).includes(destinationChainId!) + ) { + const [defaultDestinationChainId] = getPartnerChainsQueryParams( + sourceChainId! + ) + return { + sourceChainId: sourceChainId!, + destinationChainId: defaultDestinationChainId! + } } - return { from: from!, to: to! } + return { + sourceChainId: sourceChainId!, + destinationChainId: destinationChainId! + } } export type UseNetworksState = { @@ -92,51 +138,84 @@ export type UseNetworksState = { destinationChainProvider: StaticJsonRpcProvider } -export type UseNetworksSetStateParams = { fromId: ChainId; toId?: ChainId } +export type UseNetworksSetStateParams = { + sourceChain: ChainId + destinationChain?: ChainId +} export type UseNetworksSetState = (params: UseNetworksSetStateParams) => void export function useNetworks(): [UseNetworksState, UseNetworksSetState] { - const [{ from, to }, setQueryParams] = useArbQueryParams() - const { from: validFrom, to: validTo } = sanitizeQueryParams({ from, to }) + const [ + { sourceChain: sourceChainId, destinationChain: destinationChainId }, + setQueryParams + ] = useArbQueryParams() + const { + sourceChainId: validSourceChainId, + destinationChainId: validDestinationChainId + } = sanitizeQueryParams({ + sourceChainId, + destinationChainId + }) const setState = useCallback( (params: UseNetworksSetStateParams) => { - const fromQueryParam = getChainQueryParamForChain(params.fromId) - if (!params.toId) { - const [toQueryParam] = getPartnerChainsQueryParams(fromQueryParam) - setQueryParams( - sanitizeQueryParams({ from: fromQueryParam, to: toQueryParam }) + if (!params.destinationChain) { + const [destinationChainId] = getPartnerChainsQueryParams( + params.sourceChain ) + const { + sourceChainId: sourceChain, + destinationChainId: destinationChain + } = sanitizeQueryParams({ + sourceChainId: params.sourceChain, + destinationChainId: destinationChainId + }) + setQueryParams({ + sourceChain, + destinationChain + }) return } - const toQueryParam = getChainQueryParamForChain(params.toId) - - setQueryParams( - sanitizeQueryParams({ from: fromQueryParam, to: toQueryParam }) - ) + const { + sourceChainId: sourceChain, + destinationChainId: destinationChain + } = sanitizeQueryParams({ + sourceChainId: params.sourceChain, + destinationChainId: params.destinationChain + }) + setQueryParams({ + sourceChain, + destinationChain + }) }, [setQueryParams] ) - if (from !== validFrom || to !== validTo) { + if ( + sourceChainId !== validSourceChainId || + destinationChainId !== validDestinationChainId + ) { // On the first render, update query params with the sanitized values - setQueryParams({ from: validFrom, to: validTo }) + setQueryParams({ + sourceChain: validSourceChainId, + destinationChain: validDestinationChainId + }) } // The return values of the hook will always be the sanitized values return useMemo(() => { - const fromChain = getChainForChainQueryParam(validFrom) - const toChain = getChainForChainQueryParam(validTo) + const sourceChain = getChainByChainId(validSourceChainId) + const destinationChain = getChainByChainId(validDestinationChainId) return [ { - sourceChain: fromChain, - sourceChainProvider: getProviderForChain(fromChain), - destinationChain: toChain, - destinationChainProvider: getProviderForChain(toChain) + sourceChain, + sourceChainProvider: getProviderForChainId(validSourceChainId), + destinationChain, + destinationChainProvider: getProviderForChainId(validDestinationChainId) }, setState ] - }, [validFrom, validTo, setState]) + }, [validSourceChainId, validDestinationChainId, setState]) } diff --git a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts index 68cb4220d1..9fdf4e854f 100644 --- a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts +++ b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts @@ -1,7 +1,7 @@ import { Chain } from 'wagmi' import * as chains from 'wagmi/chains' -import { ChainId } from '../util/networks' +import { ChainId, getCustomChainsFromLocalStorage } from '../util/networks' import * as customChains from '../util/wagmi/wagmiAdditionalNetworks' const chainQueryParams = [ @@ -13,7 +13,9 @@ const chainQueryParams = [ 'arbitrum-goerli', 'arbitrum-sepolia', 'stylus-testnet', - 'xai-testnet' + 'xai-testnet', + 'local', + 'arbitrum-local' ] as const export type ChainQueryParam = (typeof chainQueryParams)[number] @@ -24,7 +26,9 @@ export function isValidChainQueryParam( return (chainQueryParams as readonly string[]).includes(value) } -export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam { +export function getChainQueryParamForChain( + chainId: ChainId +): ChainQueryParam | ChainId { switch (chainId) { case ChainId.Ethereum: return 'ethereum' @@ -53,15 +57,32 @@ export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam { case ChainId.ArbitrumSepolia: return 'arbitrum-sepolia' + case ChainId.Local: + return 'local' + + case ChainId.ArbitrumLocal: + return 'arbitrum-local' + default: + const customChains = getCustomChainsFromLocalStorage() + const customChain = customChains.find( + customChain => customChain.chainID === chainId + ) + + if (customChain) { + return customChain.chainID + } + throw new Error( `[getChainQueryParamForChain] Unexpected chain id: ${chainId}` ) } } -export function getChainForChainQueryParam(value: ChainQueryParam): Chain { - switch (value) { +export function getChainForChainQueryParam( + chainQueryParam: ChainQueryParam +): Chain { + switch (chainQueryParam) { case 'ethereum': return chains.mainnet @@ -88,5 +109,16 @@ export function getChainForChainQueryParam(value: ChainQueryParam): Chain { case 'xai-testnet': return customChains.xaiTestnet + + case 'local': + return customChains.localL1Network + + case 'arbitrum-local': + return customChains.localL2Network + + default: + throw new Error( + `[getChainQueryParamForChain] Unexpected chainQueryParam: ${chainQueryParam}` + ) } } diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts similarity index 54% rename from packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts rename to packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts index 54b728ee4d..9cf2ed308d 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChain.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts @@ -7,16 +7,25 @@ import { arbitrumGoerli } from 'wagmi/chains' -import { ChainId } from '../../util/networks' +import { ChainId, getCustomChainsFromLocalStorage } from '../networks' import { arbitrumNova, arbitrumSepolia, + chainToWagmiChain, stylusTestnet, xaiTestnet } from './wagmiAdditionalNetworks' -export function getPartnerChainsForChain(chain: Chain): Chain[] { - switch (chain.id) { +const customWagmiChains = getCustomChainsFromLocalStorage() +const customArbitrumGoerliChains = customWagmiChains + .filter(chain => chain.partnerChainID === ChainId.ArbitrumGoerli) + .map(chain => chainToWagmiChain(chain)) +const customArbitrumSepoliaChains = customWagmiChains + .filter(chain => chain.partnerChainID === ChainId.ArbitrumSepolia) + .map(chain => chainToWagmiChain(chain)) + +export function getPartnerChainsForChainId(chainId: number): Chain[] { + switch (chainId) { case ChainId.Ethereum: return [arbitrumOne, arbitrumNova] @@ -33,10 +42,10 @@ export function getPartnerChainsForChain(chain: Chain): Chain[] { return [mainnet] case ChainId.ArbitrumGoerli: - return [goerli, xaiTestnet] + return [goerli, xaiTestnet, ...customArbitrumGoerliChains] case ChainId.ArbitrumSepolia: - return [sepolia, stylusTestnet] + return [sepolia, stylusTestnet, ...customArbitrumSepoliaChains] case ChainId.StylusTestnet: return [arbitrumSepolia] @@ -46,7 +55,7 @@ export function getPartnerChainsForChain(chain: Chain): Chain[] { default: throw new Error( - `[getPartnerChainsForChain] Unexpected chain id: ${chain.id}` + `[getPartnerChainsForChain] Unexpected chain id: ${chainId}` ) } } From c5535dfe08e0ec09f26be9d6d4ada26c5873cc74 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 22 Nov 2023 13:46:00 +0100 Subject: [PATCH 07/35] Add cases to handle invalid chainId in useNetwork --- .../src/hooks/useNetworks.ts | 71 ++++++++++++++++--- .../util/wagmi/getPartnerChainsForChainId.ts | 14 ++++ 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index e413c75416..b1ddbe424c 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -41,6 +41,26 @@ function getChainByChainId(chainId: ChainId): Chain { return chain ?? mainnet } +function isSupportedChainId(chainId: ChainId | undefined): boolean { + if (!chainId) { + return false + } + + return [ + mainnet.id, + goerli.id, + sepolia.id, + arbitrum.id, + arbitrumNova.id, + arbitrumGoerli.id, + arbitrumSepolia.id, + stylusTestnet.id, + xaiTestnet.id, + arbitrumLocal.id, + local.id + ].includes(chainId) +} + function getPartnerChainsQueryParams(chainId: ChainId): ChainId[] { const partnerChains = getPartnerChainsForChainId(chainId) return partnerChains.map(chain => chain.id) @@ -80,42 +100,71 @@ export function sanitizeQueryParams({ sourceChainId: ChainId destinationChainId: ChainId } { - // when both `from` and `to` are undefined, default to Ethereum and Arbitrum One + const defaultState = { + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + } + // when both `sourceChain` and `destinationChain` are undefined, default to Ethereum and Arbitrum One if ( typeof sourceChainId === 'undefined' && typeof destinationChainId === 'undefined' ) { - return { - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - } + return defaultState } - // only `from` is undefined + // only `destinationChainId` is defined if ( typeof sourceChainId === 'undefined' && typeof destinationChainId !== 'undefined' ) { - // get the counter + if (!isSupportedChainId(destinationChainId)) { + return defaultState + } + const [defaultSourceChainId] = getPartnerChainsQueryParams(destinationChainId) return { sourceChainId: defaultSourceChainId!, destinationChainId } } - // only `to` is undefined + // only `sourceChainId` is defined if ( typeof sourceChainId !== 'undefined' && typeof destinationChainId === 'undefined' ) { + if (!isSupportedChainId(sourceChainId)) { + return defaultState + } + const [defaultDestinationChainId] = getPartnerChainsQueryParams(sourceChainId) - return { sourceChainId, destinationChainId: defaultDestinationChainId! } + return { + sourceChainId: sourceChainId, + destinationChainId: defaultDestinationChainId! + } } - // both values are defined, but `to` is an invalid partner chain + // both values are defined if ( - !getPartnerChainsQueryParams(sourceChainId!).includes(destinationChainId!) + !isSupportedChainId(sourceChainId) && + !isSupportedChainId(destinationChainId) ) { + return defaultState + } + + // sourceChainId is invalid, destinationChainId is valid + if (!isSupportedChainId(sourceChainId)) { + // Set sourceChainId according to destinationChainId + const [defaultSourceChainId] = getPartnerChainsQueryParams( + destinationChainId! + ) + return { + sourceChainId: defaultSourceChainId!, + destinationChainId: destinationChainId! + } + } + + if (!isSupportedChainId(destinationChainId)) { + // Set destinationChainId according to sourceChainId const [defaultDestinationChainId] = getPartnerChainsQueryParams( sourceChainId! ) diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts index 9cf2ed308d..0626768b0d 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts @@ -20,9 +20,15 @@ const customWagmiChains = getCustomChainsFromLocalStorage() const customArbitrumGoerliChains = customWagmiChains .filter(chain => chain.partnerChainID === ChainId.ArbitrumGoerli) .map(chain => chainToWagmiChain(chain)) +const customArbitrumGoerliChainsIds = customArbitrumGoerliChains.map( + chain => chain.id +) const customArbitrumSepoliaChains = customWagmiChains .filter(chain => chain.partnerChainID === ChainId.ArbitrumSepolia) .map(chain => chainToWagmiChain(chain)) +const customArbitrumSepoliaChainsIds = customArbitrumGoerliChains.map( + chain => chain.id +) export function getPartnerChainsForChainId(chainId: number): Chain[] { switch (chainId) { @@ -54,6 +60,14 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { return [arbitrumGoerli] default: + // Orbit chains + if (customArbitrumGoerliChainsIds.includes(chainId)) { + return [arbitrumGoerli] + } + + if (customArbitrumSepoliaChainsIds.includes(chainId)) { + return [arbitrumSepolia] + } throw new Error( `[getPartnerChainsForChain] Unexpected chain id: ${chainId}` ) From de64492276198b78c1714618db5288eeb56fdb7f Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 22 Nov 2023 13:52:51 +0100 Subject: [PATCH 08/35] Add one check if chains are not partners --- .../arb-token-bridge-ui/src/hooks/useNetworks.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index b1ddbe424c..545cb101c5 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -143,7 +143,6 @@ export function sanitizeQueryParams({ } } - // both values are defined if ( !isSupportedChainId(sourceChainId) && !isSupportedChainId(destinationChainId) @@ -174,6 +173,18 @@ export function sanitizeQueryParams({ } } + if ( + !getPartnerChainsQueryParams(sourceChainId!).includes(destinationChainId!) + ) { + const [defaultDestinationChainId] = getPartnerChainsQueryParams( + sourceChainId! + ) + return { + sourceChainId: sourceChainId!, + destinationChainId: defaultDestinationChainId! + } + } + return { sourceChainId: sourceChainId!, destinationChainId: destinationChainId! From fa0376dcb4282341ccdca974b88de2b968277101 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 22 Nov 2023 14:14:03 +0100 Subject: [PATCH 09/35] Add customChain Ids to the list of supported chain --- .../arb-token-bridge-ui/src/hooks/useNetworks.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index 545cb101c5..14bf33d1bb 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -4,7 +4,11 @@ import { useCallback, useMemo } from 'react' import { mainnet, arbitrum, goerli, arbitrumGoerli } from '@wagmi/core/chains' import { useArbQueryParams } from './useArbQueryParams' -import { ChainId, rpcURLs } from '../util/networks' +import { + ChainId, + getCustomChainsFromLocalStorage, + rpcURLs +} from '../util/networks' import { sepolia, arbitrumNova, @@ -41,6 +45,9 @@ function getChainByChainId(chainId: ChainId): Chain { return chain ?? mainnet } +const customChainIds = getCustomChainsFromLocalStorage().map( + chain => chain.chainID +) function isSupportedChainId(chainId: ChainId | undefined): boolean { if (!chainId) { return false @@ -57,7 +64,8 @@ function isSupportedChainId(chainId: ChainId | undefined): boolean { stylusTestnet.id, xaiTestnet.id, arbitrumLocal.id, - local.id + local.id, + ...customChainIds ].includes(chainId) } From 6315da0acafe4805fe84851d7cdfb0678af4b2ce Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 30 Nov 2023 14:18:12 +0100 Subject: [PATCH 10/35] Allow undefined sourceChain in useNetworks --- .../src/hooks/useNetworks.ts | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index 14bf33d1bb..ea076acfe2 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -70,8 +70,12 @@ function isSupportedChainId(chainId: ChainId | undefined): boolean { } function getPartnerChainsQueryParams(chainId: ChainId): ChainId[] { - const partnerChains = getPartnerChainsForChainId(chainId) - return partnerChains.map(chain => chain.id) + try { + const partnerChains = getPartnerChainsForChainId(chainId) + return partnerChains.map(chain => chain.id) + } catch (e) { + return [] + } } const getProviderForChainCache: { @@ -206,10 +210,15 @@ export type UseNetworksState = { destinationChainProvider: StaticJsonRpcProvider } -export type UseNetworksSetStateParams = { - sourceChain: ChainId - destinationChain?: ChainId -} +export type UseNetworksSetStateParams = + | { + sourceChain: ChainId + destinationChain?: ChainId + } + | { + sourceChain?: ChainId + destinationChain: ChainId + } export type UseNetworksSetState = (params: UseNetworksSetStateParams) => void export function useNetworks(): [UseNetworksState, UseNetworksSetState] { @@ -227,10 +236,30 @@ export function useNetworks(): [UseNetworksState, UseNetworksSetState] { const setState = useCallback( (params: UseNetworksSetStateParams) => { + if (!params.sourceChain) { + const [sourceChainId] = getPartnerChainsQueryParams( + params.destinationChain + ) + + const { + sourceChainId: sourceChain, + destinationChainId: destinationChain + } = sanitizeQueryParams({ + sourceChainId: sourceChainId, + destinationChainId: params.destinationChain + }) + setQueryParams({ + sourceChain, + destinationChain + }) + return + } + if (!params.destinationChain) { const [destinationChainId] = getPartnerChainsQueryParams( params.sourceChain ) + const { sourceChainId: sourceChain, destinationChainId: destinationChain From a372977e7c0843c51bc1659d959ff264a4c76c83 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 30 Nov 2023 15:14:42 +0100 Subject: [PATCH 11/35] Add unit tests for chainParam encoder --- .../hooks/__tests__/useArbQueryParams.test.ts | 58 ++++++++++++++- .../src/hooks/useArbQueryParams.tsx | 70 +++++++++++-------- .../src/types/ChainQueryParam.ts | 17 +++-- 3 files changed, 109 insertions(+), 36 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts index 948c34cb6b..8a9c1803fe 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts @@ -1,7 +1,8 @@ /** * @jest-environment jsdom */ -import { AmountQueryParam } from '../useArbQueryParams' +import { ChainId, customChainLocalStorageKey } from '../../util/networks' +import { AmountQueryParam, ChainParam } from '../useArbQueryParams' describe('AmountQueryParam custom encoder and decoder', () => { describe('encode input field value to query param', () => { @@ -163,3 +164,58 @@ describe('AmountQueryParam custom encoder and decoder', () => { }) }) }) + +describe('ChainParam custom encoder and decoder', () => { + describe('encode chainId to chainId/ChainQueryParam', () => { + it('should return undefined if value is null or undefined', () => { + expect(ChainParam.encode(null)).toBeUndefined() + expect(ChainParam.encode(undefined)).toBeUndefined() + }) + + it('should return ChainQueryParam if value is a valid chainId', () => { + expect(ChainParam.encode(ChainId.Ethereum)).toBe('ethereum') + expect(ChainParam.encode(ChainId.ArbitrumOne)).toBe('arbitrum-one') + expect(ChainParam.encode(ChainId.Goerli)).toBe('goerli') + expect(ChainParam.encode(ChainId.ArbitrumGoerli)).toBe('arbitrum-goerli') + expect(ChainParam.encode(1234567890)).toBeUndefined() + localStorage.setItem( + customChainLocalStorageKey, + JSON.stringify([{ chainID: '1111111111' }]) + ) + expect(ChainParam.encode(1111111111)).toBe(1111111111) + localStorage.clear() + }) + }) + + describe('decode chainId/ChainQueryParam to chainId', () => { + it('should return undefined if value is null or undefined', () => { + expect(ChainParam.decode(null)).toBeUndefined() + expect(ChainParam.decode(undefined)).toBeUndefined() + }) + + it('should decode to ChainId if value is a valid ChainQueryParam', () => { + expect(ChainParam.decode('ethereum')).toBe(ChainId.Ethereum) + expect(ChainParam.decode('arbitrum-one')).toBe(ChainId.ArbitrumOne) + expect(ChainParam.decode('goerli')).toBe(ChainId.Goerli) + expect(ChainParam.decode('arbitrum-goerli')).toBe(ChainId.ArbitrumGoerli) + expect(ChainParam.decode('aaa123')).toBeUndefined() + }) + + it('should decode to ChainId if value is a valid chainId', () => { + function decodeChainId(value: ChainId) { + return ChainParam.decode(value.toString()) + } + expect(decodeChainId(ChainId.Ethereum)).toBe(ChainId.Ethereum) + expect(decodeChainId(ChainId.ArbitrumOne)).toBe(ChainId.ArbitrumOne) + expect(decodeChainId(ChainId.Goerli)).toBe(ChainId.Goerli) + expect(decodeChainId(ChainId.ArbitrumGoerli)).toBe(ChainId.ArbitrumGoerli) + expect(ChainParam.decode('1234567890')).toBeUndefined() + localStorage.setItem( + customChainLocalStorageKey, + JSON.stringify([{ chainID: '222222' }]) + ) + expect(ChainParam.decode('222222')).toBe(222222) + localStorage.clear() + }) + }) +}) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index 1380f5eab4..0d8f5b899c 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -27,6 +27,7 @@ import { } from 'use-query-params' import { + ChainQueryParam, getChainForChainQueryParam, getChainQueryParamForChain, isValidChainQueryParam @@ -107,42 +108,51 @@ export const AmountQueryParam = { } } -export const ChainParam = { - // Parse chainId to ChainQueryParam or ChainId for orbit chain - // encode: (value: string | (string | null)[] | null | undefined) => { - encode: (chainId: number | null | undefined) => { - if (!chainId) { - return undefined - } +// Parse chainId to ChainQueryParam or ChainId for orbit chain +function encodeChainQueryParam(chainId: number | null | undefined) { + if (!chainId) { + return undefined + } - try { - const chain = getChainQueryParamForChain(chainId) - return chain as string - } catch (e) { - return undefined - } - }, - // Parse ChainQueryParam/ChainId to ChainId - decode: ( - value: string | (string | null)[] | null | undefined - ): ChainId | undefined => { - const valueStr = decodeString(value) - if (!valueStr) { - return undefined - } + try { + const chain = getChainQueryParamForChain(chainId) + return chain + } catch (e) { + return undefined + } +} - const valueNum = parseInt(valueStr, 10) - if (!Number.isNaN(valueNum)) { - // TODO: Verify that chainId is of a supported network or orbit chain? - return valueNum - } +// Parse ChainQueryParam/ChainId to ChainId +// URL accept both chainId and chainQueryParam (string) +function decodeChainQueryParam( + value: string | (string | null)[] | null | undefined +): ChainId | undefined { + const valueStr = decodeString(value) + if (!valueStr) { + return undefined + } - if (isValidChainQueryParam(valueStr)) { - return getChainForChainQueryParam(valueStr).id + const valueNum = parseInt(valueStr, 10) + if (!Number.isNaN(valueNum)) { + if (isValidChainQueryParam(valueNum)) { + return valueNum } - return undefined } + + if (isValidChainQueryParam(valueStr)) { + return getChainForChainQueryParam(valueStr as ChainQueryParam).id + } + + return undefined +} + +export const ChainParam = { + encode: (chainId: number | null | undefined) => + encodeChainQueryParam(chainId), + + decode: (value: string | (string | null)[] | null | undefined) => + decodeChainQueryParam(value) } export function ArbQueryParamProvider({ diff --git a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts index 9fdf4e854f..d94ca00cf8 100644 --- a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts +++ b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts @@ -1,7 +1,11 @@ import { Chain } from 'wagmi' import * as chains from 'wagmi/chains' -import { ChainId, getCustomChainsFromLocalStorage } from '../util/networks' +import { + ChainId, + getCustomChainsFromLocalStorage, + getSupportedNetworks +} from '../util/networks' import * as customChains from '../util/wagmi/wagmiAdditionalNetworks' const chainQueryParams = [ @@ -20,10 +24,13 @@ const chainQueryParams = [ export type ChainQueryParam = (typeof chainQueryParams)[number] -export function isValidChainQueryParam( - value: string -): value is ChainQueryParam { - return (chainQueryParams as readonly string[]).includes(value) +export function isValidChainQueryParam(value: ChainId | string) { + if (typeof value === 'string') { + return (chainQueryParams as readonly string[]).includes(value) + } + + const supportedNetworkIds = getSupportedNetworks(value, true) + return supportedNetworkIds.includes(value) } export function getChainQueryParamForChain( From 15b4a2dc960e45f9a05f2c3d0e2887cc10aa1968 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 30 Nov 2023 15:25:57 +0100 Subject: [PATCH 12/35] Fix typing --- packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index 0d8f5b899c..432ff33285 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -109,7 +109,9 @@ export const AmountQueryParam = { } // Parse chainId to ChainQueryParam or ChainId for orbit chain -function encodeChainQueryParam(chainId: number | null | undefined) { +function encodeChainQueryParam( + chainId: number | null | undefined +): ChainId | ChainQueryParam | undefined { if (!chainId) { return undefined } From 8131db15aa3f87b373c9319aea5fc024e13e41ea Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 30 Nov 2023 15:31:54 +0100 Subject: [PATCH 13/35] Cast encodeChainQueryParam result to string --- .../src/hooks/__tests__/useArbQueryParams.test.ts | 2 +- .../arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts index 8a9c1803fe..f15c2f9fc4 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts @@ -182,7 +182,7 @@ describe('ChainParam custom encoder and decoder', () => { customChainLocalStorageKey, JSON.stringify([{ chainID: '1111111111' }]) ) - expect(ChainParam.encode(1111111111)).toBe(1111111111) + expect(ChainParam.encode(1111111111)).toBe('1111111111') localStorage.clear() }) }) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index 432ff33285..acbf759ddb 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -109,16 +109,14 @@ export const AmountQueryParam = { } // Parse chainId to ChainQueryParam or ChainId for orbit chain -function encodeChainQueryParam( - chainId: number | null | undefined -): ChainId | ChainQueryParam | undefined { +function encodeChainQueryParam(chainId: number | null | undefined) { if (!chainId) { return undefined } try { const chain = getChainQueryParamForChain(chainId) - return chain + return chain.toString() } catch (e) { return undefined } From f4f221bb2d86d7e389d2c93a2baec024c82558ca Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 30 Nov 2023 16:29:39 +0100 Subject: [PATCH 14/35] Add tests for custom orbit chain for sanitizeQueryParams --- .../hooks/__tests__/useArbQueryParams.test.ts | 6 +- .../src/hooks/__tests__/useNetworks.test.ts | 60 +++++++++++++++- .../src/hooks/useNetworks.ts | 71 ++----------------- .../util/wagmi/getPartnerChainsForChainId.ts | 29 ++++---- 4 files changed, 81 insertions(+), 85 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts index f15c2f9fc4..b01a72a74a 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts @@ -180,7 +180,9 @@ describe('ChainParam custom encoder and decoder', () => { expect(ChainParam.encode(1234567890)).toBeUndefined() localStorage.setItem( customChainLocalStorageKey, - JSON.stringify([{ chainID: '1111111111' }]) + JSON.stringify([ + { chainID: '1111111111', name: 'custom 1111111111 chain' } + ]) ) expect(ChainParam.encode(1111111111)).toBe('1111111111') localStorage.clear() @@ -212,7 +214,7 @@ describe('ChainParam custom encoder and decoder', () => { expect(ChainParam.decode('1234567890')).toBeUndefined() localStorage.setItem( customChainLocalStorageKey, - JSON.stringify([{ chainID: '222222' }]) + JSON.stringify([{ chainID: '222222', name: 'custom 222222 chain' }]) ) expect(ChainParam.decode('222222')).toBe(222222) localStorage.clear() diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index 0fa5a62187..08ec04955a 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -1,10 +1,32 @@ /** * @jest-environment jsdom */ -import { ChainId } from '../../util/networks' +import { ChainId, customChainLocalStorageKey } from '../../util/networks' import { sanitizeQueryParams } from '../useNetworks' describe('sanitizeQueryParams', () => { + beforeAll(() => { + localStorage.setItem( + customChainLocalStorageKey, + JSON.stringify([ + { + chainID: '1111', + partnerChainID: ChainId.ArbitrumGoerli, + name: 'custom 1111 chain' + }, + { + chainID: '2222', + partnerChainID: ChainId.ArbitrumSepolia, + name: 'custom 2222 chain' + } + ]) + ) + }) + + afterAll(() => { + localStorage.clear() + }) + it('sets the default values for `sourceChainId` and `destinationChainId` when both `sourceChainId` and `destinationChainId` are `undefined`', () => { const result = sanitizeQueryParams({ sourceChainId: undefined, @@ -25,6 +47,23 @@ describe('sanitizeQueryParams', () => { sourceChainId: ChainId.Ethereum, destinationChainId: ChainId.ArbitrumOne }) + + const resultWithArbitrumOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: undefined + }) + expect(resultWithArbitrumOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: undefined + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) }) it('sets the value for `sourceChainId` to the partner chain of `destinationChainId` when `sourceChainId` is `undefined`', () => { @@ -36,9 +75,26 @@ describe('sanitizeQueryParams', () => { sourceChainId: ChainId.Ethereum, destinationChainId: ChainId.ArbitrumNova }) + + const resultWithArbitrumOrbitChain = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 1111 + }) + expect(resultWithArbitrumOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumGoerli, + destinationChainId: 1111 + }) + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 2222 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumSepolia, + destinationChainId: 2222 + }) }) - it('sets the value for `destinationChainId` to the partner chain of `sourceChainId` when `destinationChainId` is an invalid value', () => { + it('sets the value for `destinationChainId` to the partner chain of `sourceChainId` when `destinationChainId` is not a partner chain of `sourceChainId', () => { const result = sanitizeQueryParams({ sourceChainId: ChainId.Goerli, destinationChainId: ChainId.ArbitrumOne diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index ea076acfe2..f3d02fcc0a 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -45,30 +45,6 @@ function getChainByChainId(chainId: ChainId): Chain { return chain ?? mainnet } -const customChainIds = getCustomChainsFromLocalStorage().map( - chain => chain.chainID -) -function isSupportedChainId(chainId: ChainId | undefined): boolean { - if (!chainId) { - return false - } - - return [ - mainnet.id, - goerli.id, - sepolia.id, - arbitrum.id, - arbitrumNova.id, - arbitrumGoerli.id, - arbitrumSepolia.id, - stylusTestnet.id, - xaiTestnet.id, - arbitrumLocal.id, - local.id, - ...customChainIds - ].includes(chainId) -} - function getPartnerChainsQueryParams(chainId: ChainId): ChainId[] { try { const partnerChains = getPartnerChainsForChainId(chainId) @@ -112,16 +88,15 @@ export function sanitizeQueryParams({ sourceChainId: ChainId destinationChainId: ChainId } { - const defaultState = { - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - } // when both `sourceChain` and `destinationChain` are undefined, default to Ethereum and Arbitrum One if ( typeof sourceChainId === 'undefined' && typeof destinationChainId === 'undefined' ) { - return defaultState + return { + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + } } // only `destinationChainId` is defined @@ -129,10 +104,6 @@ export function sanitizeQueryParams({ typeof sourceChainId === 'undefined' && typeof destinationChainId !== 'undefined' ) { - if (!isSupportedChainId(destinationChainId)) { - return defaultState - } - const [defaultSourceChainId] = getPartnerChainsQueryParams(destinationChainId) return { sourceChainId: defaultSourceChainId!, destinationChainId } @@ -143,10 +114,6 @@ export function sanitizeQueryParams({ typeof sourceChainId !== 'undefined' && typeof destinationChainId === 'undefined' ) { - if (!isSupportedChainId(sourceChainId)) { - return defaultState - } - const [defaultDestinationChainId] = getPartnerChainsQueryParams(sourceChainId) return { @@ -155,36 +122,6 @@ export function sanitizeQueryParams({ } } - if ( - !isSupportedChainId(sourceChainId) && - !isSupportedChainId(destinationChainId) - ) { - return defaultState - } - - // sourceChainId is invalid, destinationChainId is valid - if (!isSupportedChainId(sourceChainId)) { - // Set sourceChainId according to destinationChainId - const [defaultSourceChainId] = getPartnerChainsQueryParams( - destinationChainId! - ) - return { - sourceChainId: defaultSourceChainId!, - destinationChainId: destinationChainId! - } - } - - if (!isSupportedChainId(destinationChainId)) { - // Set destinationChainId according to sourceChainId - const [defaultDestinationChainId] = getPartnerChainsQueryParams( - sourceChainId! - ) - return { - sourceChainId: sourceChainId!, - destinationChainId: defaultDestinationChainId! - } - } - if ( !getPartnerChainsQueryParams(sourceChainId!).includes(destinationChainId!) ) { diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts index 0626768b0d..1ca2038469 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts @@ -16,21 +16,22 @@ import { xaiTestnet } from './wagmiAdditionalNetworks' -const customWagmiChains = getCustomChainsFromLocalStorage() -const customArbitrumGoerliChains = customWagmiChains - .filter(chain => chain.partnerChainID === ChainId.ArbitrumGoerli) - .map(chain => chainToWagmiChain(chain)) -const customArbitrumGoerliChainsIds = customArbitrumGoerliChains.map( - chain => chain.id -) -const customArbitrumSepoliaChains = customWagmiChains - .filter(chain => chain.partnerChainID === ChainId.ArbitrumSepolia) - .map(chain => chainToWagmiChain(chain)) -const customArbitrumSepoliaChainsIds = customArbitrumGoerliChains.map( - chain => chain.id -) - export function getPartnerChainsForChainId(chainId: number): Chain[] { + const customWagmiChains = getCustomChainsFromLocalStorage() + const customArbitrumGoerliChains = customWagmiChains + .filter(chain => chain.partnerChainID === ChainId.ArbitrumGoerli) + .map(chain => chainToWagmiChain(chain)) + + const customArbitrumGoerliChainsIds = customArbitrumGoerliChains.map( + chain => chain.id + ) + const customArbitrumSepoliaChains = customWagmiChains + .filter(chain => chain.partnerChainID === ChainId.ArbitrumSepolia) + .map(chain => chainToWagmiChain(chain)) + const customArbitrumSepoliaChainsIds = customArbitrumSepoliaChains.map( + chain => chain.id + ) + switch (chainId) { case ChainId.Ethereum: return [arbitrumOne, arbitrumNova] From 39deb037144bd7ce9780c417dfe27c7ffdade3c0 Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 12:20:17 +0100 Subject: [PATCH 15/35] Simplify useArbQueryParams --- .../arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index acbf759ddb..66abe5ad49 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -148,11 +148,8 @@ function decodeChainQueryParam( } export const ChainParam = { - encode: (chainId: number | null | undefined) => - encodeChainQueryParam(chainId), - - decode: (value: string | (string | null)[] | null | undefined) => - decodeChainQueryParam(value) + encode: encodeChainQueryParam, + decode: decodeChainQueryParam } export function ArbQueryParamProvider({ From 0a732e4dcbec409da6dc1e3ca2d085559c9ac8bb Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 12:20:26 +0100 Subject: [PATCH 16/35] Fix TS error in ExternalLink --- .../arb-token-bridge-ui/src/components/common/ExternalLink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx b/packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx index d05d43a081..562ed6189b 100644 --- a/packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx @@ -4,7 +4,7 @@ export function ExternalLink({ ...props }: React.AnchorHTMLAttributes) { if (!href) { - return children + return <>children } return ( From e40c4d786c90858cf8cb7df61ff1576a3bdd0a6c Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 14:18:59 +0100 Subject: [PATCH 17/35] Mock localstorage --- .../src/hooks/__tests__/useNetworks.test.ts | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index 08ec04955a..2cd53b17a7 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -6,25 +6,28 @@ import { sanitizeQueryParams } from '../useNetworks' describe('sanitizeQueryParams', () => { beforeAll(() => { - localStorage.setItem( - customChainLocalStorageKey, - JSON.stringify([ - { - chainID: '1111', - partnerChainID: ChainId.ArbitrumGoerli, - name: 'custom 1111 chain' - }, - { - chainID: '2222', - partnerChainID: ChainId.ArbitrumSepolia, - name: 'custom 2222 chain' - } - ]) - ) + global.Storage.prototype.getItem = jest.fn(key => { + if (key === customChainLocalStorageKey) { + return JSON.stringify([ + { + chainID: '1111', + partnerChainID: ChainId.ArbitrumGoerli, + name: 'custom 1111 chain' + }, + { + chainID: '2222', + partnerChainID: ChainId.ArbitrumSepolia, + name: 'custom 2222 chain' + } + ]) + } + return null + }) }) afterAll(() => { - localStorage.clear() + ;(global.Storage.prototype.setItem as jest.Mock).mockReset() + ;(global.Storage.prototype.setItem as jest.Mock).mockReset() }) it('sets the default values for `sourceChainId` and `destinationChainId` when both `sourceChainId` and `destinationChainId` are `undefined`', () => { @@ -49,6 +52,7 @@ describe('sanitizeQueryParams', () => { }) const resultWithArbitrumOrbitChain = sanitizeQueryParams({ + // @ts-expect-error Testing wrong chainId sourceChainId: 1111, destinationChainId: undefined }) @@ -57,6 +61,7 @@ describe('sanitizeQueryParams', () => { destinationChainId: ChainId.ArbitrumGoerli }) const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + // @ts-expect-error Testing wrong chainId sourceChainId: 2222, destinationChainId: undefined }) @@ -78,6 +83,7 @@ describe('sanitizeQueryParams', () => { const resultWithArbitrumOrbitChain = sanitizeQueryParams({ sourceChainId: undefined, + // @ts-expect-error Testing wrong chainId destinationChainId: 1111 }) expect(resultWithArbitrumOrbitChain).toEqual({ @@ -86,6 +92,7 @@ describe('sanitizeQueryParams', () => { }) const resultWithSepoliaOrbitChain = sanitizeQueryParams({ sourceChainId: undefined, + // @ts-expect-error Testing wrong chainId destinationChainId: 2222 }) expect(resultWithSepoliaOrbitChain).toEqual({ From 55adab1d73573629697f6c8ed7289ec07315935a Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 14:25:28 +0100 Subject: [PATCH 18/35] Fix mock --- .../src/hooks/__tests__/useNetworks.test.ts | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index 2cd53b17a7..9d82de6ee6 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -5,29 +5,32 @@ import { ChainId, customChainLocalStorageKey } from '../../util/networks' import { sanitizeQueryParams } from '../useNetworks' describe('sanitizeQueryParams', () => { + let localStorageGetItemMock: jest.Mock + beforeAll(() => { - global.Storage.prototype.getItem = jest.fn(key => { - if (key === customChainLocalStorageKey) { - return JSON.stringify([ - { - chainID: '1111', - partnerChainID: ChainId.ArbitrumGoerli, - name: 'custom 1111 chain' - }, - { - chainID: '2222', - partnerChainID: ChainId.ArbitrumSepolia, - name: 'custom 2222 chain' - } - ]) + localStorageGetItemMock = global.Storage.prototype.getItem = jest.fn( + key => { + if (key === customChainLocalStorageKey) { + return JSON.stringify([ + { + chainID: '1111', + partnerChainID: ChainId.ArbitrumGoerli, + name: 'custom 1111 chain' + }, + { + chainID: '2222', + partnerChainID: ChainId.ArbitrumSepolia, + name: 'custom 2222 chain' + } + ]) + } + return null } - return null - }) + ) }) afterAll(() => { - ;(global.Storage.prototype.setItem as jest.Mock).mockReset() - ;(global.Storage.prototype.setItem as jest.Mock).mockReset() + localStorageGetItemMock.mockReset() }) it('sets the default values for `sourceChainId` and `destinationChainId` when both `sourceChainId` and `destinationChainId` are `undefined`', () => { From 4c97724b0762a0f3f131ffb37b5e38070b556eff Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 21:31:31 +0100 Subject: [PATCH 19/35] Undo change in ExternalLink --- .../arb-token-bridge-ui/src/components/common/ExternalLink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx b/packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx index 562ed6189b..d05d43a081 100644 --- a/packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx @@ -4,7 +4,7 @@ export function ExternalLink({ ...props }: React.AnchorHTMLAttributes) { if (!href) { - return <>children + return children } return ( From 2726d24e7b37c1a7019c595b915ecb9fcb08a31a Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 21:31:41 +0100 Subject: [PATCH 20/35] Undo changes in wagmi setup --- packages/arb-token-bridge-ui/src/util/wagmi/setup.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) 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 fe75e601c9..750ea893b9 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts @@ -131,14 +131,7 @@ export function getProps(targetChainKey: string | null) { // Wagmi selects the first chain as the one to target in WalletConnect, so it has to be the first in the array. // https://github.com/wagmi-dev/references/blob/main/packages/connectors/src/walletConnect.ts#L114 getChains(sanitizeTargetChainKey(targetChainKey)), - [ - publicProvider(), - jsonRpcProvider({ - rpc: chain => ({ - http: rpcURLs[chain.id]! - }) - }) - ] + [publicProvider()] ) const { wallets } = getDefaultWallets({ From 2a6c5c5fb8d712da330f42a37552b2df92d95319 Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 21:32:01 +0100 Subject: [PATCH 21/35] Simplify getPartnerChainsForChainId --- .../src/util/wagmi/getPartnerChainsForChainId.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts index 1ca2038469..9306683215 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts @@ -21,16 +21,9 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { const customArbitrumGoerliChains = customWagmiChains .filter(chain => chain.partnerChainID === ChainId.ArbitrumGoerli) .map(chain => chainToWagmiChain(chain)) - - const customArbitrumGoerliChainsIds = customArbitrumGoerliChains.map( - chain => chain.id - ) const customArbitrumSepoliaChains = customWagmiChains .filter(chain => chain.partnerChainID === ChainId.ArbitrumSepolia) .map(chain => chainToWagmiChain(chain)) - const customArbitrumSepoliaChainsIds = customArbitrumSepoliaChains.map( - chain => chain.id - ) switch (chainId) { case ChainId.Ethereum: @@ -61,6 +54,13 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { return [arbitrumGoerli] default: + const customArbitrumGoerliChainsIds = customArbitrumGoerliChains.map( + chain => chain.id + ) + const customArbitrumSepoliaChainsIds = customArbitrumSepoliaChains.map( + chain => chain.id + ) + // Orbit chains if (customArbitrumGoerliChainsIds.includes(chainId)) { return [arbitrumGoerli] From bf7e0e825d06fb9964d10e891442165d6efbf5cf Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 21:32:36 +0100 Subject: [PATCH 22/35] Update type for ChainQueryParam --- .../src/hooks/useArbQueryParams.tsx | 26 ++++++++------- .../src/hooks/useNetworks.ts | 33 ++++++++----------- .../src/types/ChainQueryParam.ts | 15 ++++----- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index 66abe5ad49..679009de19 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -27,7 +27,7 @@ import { } from 'use-query-params' import { - ChainQueryParam, + ChainKeyQueryParam, getChainForChainQueryParam, getChainQueryParamForChain, isValidChainQueryParam @@ -122,26 +122,30 @@ function encodeChainQueryParam(chainId: number | null | undefined) { } } +function isValidNumber(value: number) { + return !Number.isNaN(value) +} + // Parse ChainQueryParam/ChainId to ChainId // URL accept both chainId and chainQueryParam (string) function decodeChainQueryParam( value: string | (string | null)[] | null | undefined ): ChainId | undefined { - const valueStr = decodeString(value) - if (!valueStr) { + const valueString = decodeString(value) + if (!valueString) { return undefined } - const valueNum = parseInt(valueStr, 10) - if (!Number.isNaN(valueNum)) { - if (isValidChainQueryParam(valueNum)) { - return valueNum - } - return undefined + const valueNumber = parseInt(valueString, 10) + if ( + isValidNumber(valueNumber) && + isValidChainQueryParam(valueNumber as ChainId) + ) { + return valueNumber } - if (isValidChainQueryParam(valueStr)) { - return getChainForChainQueryParam(valueStr as ChainQueryParam).id + if (isValidChainQueryParam(valueString)) { + return getChainForChainQueryParam(valueString as ChainKeyQueryParam).id } return undefined diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index f3d02fcc0a..5587ba65bb 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -4,11 +4,7 @@ import { useCallback, useMemo } from 'react' import { mainnet, arbitrum, goerli, arbitrumGoerli } from '@wagmi/core/chains' import { useArbQueryParams } from './useArbQueryParams' -import { - ChainId, - getCustomChainsFromLocalStorage, - rpcURLs -} from '../util/networks' +import { ChainId, rpcURLs } from '../util/networks' import { sepolia, arbitrumNova, @@ -24,25 +20,25 @@ import { getPartnerChainsForChainId } from '../util/wagmi/getPartnerChainsForCha function getChainByChainId(chainId: ChainId): Chain { const chain = { // L1 - [mainnet.id]: mainnet, + [ChainId.Ethereum]: mainnet, // L1 Testnet - [goerli.id]: goerli, - [sepolia.id]: sepolia, + [ChainId.Goerli]: goerli, + [ChainId.Sepolia]: sepolia, // L2 - [arbitrum.id]: arbitrum, - [arbitrumNova.id]: arbitrumNova, + [ChainId.ArbitrumOne]: arbitrum, + [ChainId.ArbitrumNova]: arbitrumNova, // L2 Testnet - [arbitrumGoerli.id]: arbitrumGoerli, - [arbitrumSepolia.id]: arbitrumSepolia, + [ChainId.ArbitrumGoerli]: arbitrumGoerli, + [ChainId.ArbitrumSepolia]: arbitrumSepolia, // L3 - [xaiTestnet.id]: xaiTestnet, - [stylusTestnet.id]: stylusTestnet, + [ChainId.XaiTestnet]: xaiTestnet, + [ChainId.StylusTestnet]: stylusTestnet, // E2E - [local.id]: local, - [arbitrumLocal.id]: arbitrumLocal + [ChainId.Local]: local, + [ChainId.ArbitrumLocal]: arbitrumLocal }[chainId] - return chain ?? mainnet + return chain } function getPartnerChainsQueryParams(chainId: ChainId): ChainId[] { @@ -61,10 +57,9 @@ const getProviderForChainCache: { } function createProviderWithCache(chainId: ChainId) { - const chain = getChainByChainId(chainId) const rpcUrl = rpcURLs[chainId] const provider = new StaticJsonRpcProvider(rpcUrl, chainId) - getProviderForChainCache[chain.id] = provider + getProviderForChainCache[chainId] = provider return provider } diff --git a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts index d94ca00cf8..f43790d6db 100644 --- a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts +++ b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts @@ -22,9 +22,10 @@ const chainQueryParams = [ 'arbitrum-local' ] as const -export type ChainQueryParam = (typeof chainQueryParams)[number] +export type ChainKeyQueryParam = (typeof chainQueryParams)[number] +export type ChainQueryParam = ChainKeyQueryParam | ChainId -export function isValidChainQueryParam(value: ChainId | string) { +export function isValidChainQueryParam(value: string | number): boolean { if (typeof value === 'string') { return (chainQueryParams as readonly string[]).includes(value) } @@ -33,9 +34,7 @@ export function isValidChainQueryParam(value: ChainId | string) { return supportedNetworkIds.includes(value) } -export function getChainQueryParamForChain( - chainId: ChainId -): ChainQueryParam | ChainId { +export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam { switch (chainId) { case ChainId.Ethereum: return 'ethereum' @@ -87,9 +86,9 @@ export function getChainQueryParamForChain( } export function getChainForChainQueryParam( - chainQueryParam: ChainQueryParam + chainKeyQueryParam: ChainKeyQueryParam ): Chain { - switch (chainQueryParam) { + switch (chainKeyQueryParam) { case 'ethereum': return chains.mainnet @@ -125,7 +124,7 @@ export function getChainForChainQueryParam( default: throw new Error( - `[getChainQueryParamForChain] Unexpected chainQueryParam: ${chainQueryParam}` + `[getChainQueryParamForChain] Unexpected chainKeyQueryParam: ${chainKeyQueryParam}` ) } } From 49de76ab74af8fb8cfd449e865f1912fc4f67fbb Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 4 Dec 2023 21:32:54 +0100 Subject: [PATCH 23/35] Remove network in useNetworksRelationShip --- .../src/hooks/useNetworksRelationship.ts | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts index 70b60a12fb..6e9c0c0d2a 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworksRelationship.ts @@ -11,26 +11,24 @@ type UseNetworksRelationshipState = { parentChainProvider: StaticJsonRpcProvider } export function useNetworksRelationship({ - destinationChain, - destinationChainProvider, sourceChain, - sourceChainProvider + sourceChainProvider, + destinationChain, + destinationChainProvider }: UseNetworksState): UseNetworksRelationshipState { - const sourceNetwork = sourceChainProvider.network - const destinationNetwork = destinationChainProvider.network - const { - isEthereumMainnet: isSourceNetworkEthereum, - isArbitrum: isSourceNetworkArbitrum - } = isNetwork(sourceNetwork.chainId) - const { isOrbitChain: isDestinationNetworkOrbitChain } = isNetwork( - destinationNetwork.chainId - ) - const isSourceNetworkParent = - isSourceNetworkEthereum || - (isSourceNetworkArbitrum && isDestinationNetworkOrbitChain) - return useMemo(() => { - if (isSourceNetworkParent) { + const { + isEthereumMainnetOrTestnet: isSourceChainEthereum, + isArbitrum: isSourceChainArbitrum + } = isNetwork(sourceChain.id) + const { isOrbitChain: isDestinationChainOrbit } = isNetwork( + destinationChain.id + ) + const isSourceChainParent = + isSourceChainEthereum || + (isSourceChainArbitrum && isDestinationChainOrbit) + + if (isSourceChainParent) { return { childChain: destinationChain, childChainProvider: destinationChainProvider, @@ -46,7 +44,6 @@ export function useNetworksRelationship({ parentChainProvider: destinationChainProvider } }, [ - isSourceNetworkParent, sourceChain, destinationChain, destinationChainProvider, From 5bbb6d5c97cede22686ec40eda31831dcf5a213b Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 5 Dec 2023 17:51:04 +0100 Subject: [PATCH 24/35] Simplify useNetworks --- .../src/hooks/useArbQueryParams.tsx | 4 +- .../src/hooks/useNetworks.ts | 50 +++---------------- .../src/types/ChainQueryParam.ts | 6 +-- .../src/util/wagmi/setup.ts | 3 +- 4 files changed, 12 insertions(+), 51 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index 679009de19..edd9efcff8 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -28,7 +28,7 @@ import { import { ChainKeyQueryParam, - getChainForChainQueryParam, + getChainForChainKeyQueryParam, getChainQueryParamForChain, isValidChainQueryParam } from '../types/ChainQueryParam' @@ -145,7 +145,7 @@ function decodeChainQueryParam( } if (isValidChainQueryParam(valueString)) { - return getChainForChainQueryParam(valueString as ChainKeyQueryParam).id + return getChainForChainKeyQueryParam(valueString as ChainKeyQueryParam).id } return undefined diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index 5587ba65bb..bc29a9a23e 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -167,55 +167,17 @@ export function useNetworks(): [UseNetworksState, UseNetworksSetState] { }) const setState = useCallback( - (params: UseNetworksSetStateParams) => { - if (!params.sourceChain) { - const [sourceChainId] = getPartnerChainsQueryParams( - params.destinationChain - ) - - const { - sourceChainId: sourceChain, - destinationChainId: destinationChain - } = sanitizeQueryParams({ - sourceChainId: sourceChainId, - destinationChainId: params.destinationChain - }) - setQueryParams({ - sourceChain, - destinationChain - }) - return - } - - if (!params.destinationChain) { - const [destinationChainId] = getPartnerChainsQueryParams( - params.sourceChain - ) - - const { - sourceChainId: sourceChain, - destinationChainId: destinationChain - } = sanitizeQueryParams({ - sourceChainId: params.sourceChain, - destinationChainId: destinationChainId - }) - setQueryParams({ - sourceChain, - destinationChain - }) - return - } - + ({ sourceChain, destinationChain }: UseNetworksSetStateParams) => { const { + sourceChainId: validSourceChainId, + destinationChainId: validDestinationChainId + } = sanitizeQueryParams({ sourceChainId: sourceChain, destinationChainId: destinationChain - } = sanitizeQueryParams({ - sourceChainId: params.sourceChain, - destinationChainId: params.destinationChain }) setQueryParams({ - sourceChain, - destinationChain + sourceChain: validSourceChainId, + destinationChain: validDestinationChainId }) }, [setQueryParams] diff --git a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts index f43790d6db..fc25cf4047 100644 --- a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts +++ b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts @@ -23,7 +23,7 @@ const chainQueryParams = [ ] as const export type ChainKeyQueryParam = (typeof chainQueryParams)[number] -export type ChainQueryParam = ChainKeyQueryParam | ChainId +export type ChainQueryParam = ChainKeyQueryParam | ChainId | number export function isValidChainQueryParam(value: string | number): boolean { if (typeof value === 'string') { @@ -85,7 +85,7 @@ export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam { } } -export function getChainForChainQueryParam( +export function getChainForChainKeyQueryParam( chainKeyQueryParam: ChainKeyQueryParam ): Chain { switch (chainKeyQueryParam) { @@ -124,7 +124,7 @@ export function getChainForChainQueryParam( default: throw new Error( - `[getChainQueryParamForChain] Unexpected chainKeyQueryParam: ${chainKeyQueryParam}` + `[getChainForChainKeyQueryParam] Unexpected chainKeyQueryParam: ${chainKeyQueryParam}` ) } } 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 750ea893b9..438c5d885c 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts @@ -3,7 +3,6 @@ import { mainnet, arbitrum, arbitrumGoerli } from '@wagmi/core/chains' import { publicProvider } from 'wagmi/providers/public' import { connectorsForWallets, getDefaultWallets } from '@rainbow-me/rainbowkit' import { trustWallet, ledgerWallet } from '@rainbow-me/rainbowkit/wallets' -import { jsonRpcProvider } from '@wagmi/core/providers/jsonRpc' import { sepolia, arbitrumNova, @@ -15,7 +14,7 @@ import { chainToWagmiChain } from './wagmiAdditionalNetworks' import { isTestingEnvironment } from '../CommonUtils' -import { ChainId, rpcURLs } from '../networks' +import { ChainId } from '../networks' import { getCustomChainsFromLocalStorage } from '../networks' const customChains = getCustomChainsFromLocalStorage().map(chain => From 1e99be847a83219bff90defbdf8e9954358ee43f Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 6 Dec 2023 15:12:22 +0100 Subject: [PATCH 25/35] Add number to decodeChainQueryParam return result --- packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index edd9efcff8..df1f75d74c 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -130,7 +130,8 @@ function isValidNumber(value: number) { // URL accept both chainId and chainQueryParam (string) function decodeChainQueryParam( value: string | (string | null)[] | null | undefined -): ChainId | undefined { + // ChainId type doesn't include custom orbit chain, we need to add number type +): ChainId | number | undefined { const valueString = decodeString(value) if (!valueString) { return undefined From 2fbaf614e686fd5eec800a1cbac5a54fb60fd2b8 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 6 Dec 2023 15:22:51 +0100 Subject: [PATCH 26/35] Undo change in wagmi setup --- packages/arb-token-bridge-ui/src/util/wagmi/setup.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 438c5d885c..d9a8026ad1 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts @@ -3,6 +3,7 @@ import { mainnet, arbitrum, arbitrumGoerli } from '@wagmi/core/chains' import { publicProvider } from 'wagmi/providers/public' import { connectorsForWallets, getDefaultWallets } from '@rainbow-me/rainbowkit' import { trustWallet, ledgerWallet } from '@rainbow-me/rainbowkit/wallets' + import { sepolia, arbitrumNova, @@ -67,7 +68,7 @@ const appInfo = { } enum TargetChainKey { - Ethereum = 'ethereum', + Ethereum = 'mainnet', ArbitrumOne = 'arbitrum-one', ArbitrumNova = 'arbitrum-nova', Goerli = 'goerli', @@ -128,6 +129,7 @@ function getChains(targetChainKey: TargetChainKey) { export function getProps(targetChainKey: string | null) { const { chains, provider } = configureChains( // Wagmi selects the first chain as the one to target in WalletConnect, so it has to be the first in the array. + // // https://github.com/wagmi-dev/references/blob/main/packages/connectors/src/walletConnect.ts#L114 getChains(sanitizeTargetChainKey(targetChainKey)), [publicProvider()] From 45d1a1983e09baea73f8bba01e0bad7ddc5399e0 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 6 Dec 2023 19:58:18 +0100 Subject: [PATCH 27/35] Rework useNetworks to handle invalid chain ids --- .../src/hooks/__tests__/useNetworks.test.ts | 250 +++++++++++++----- .../src/hooks/useArbQueryParams.tsx | 7 +- .../src/hooks/useNetworks.ts | 75 ++++-- 3 files changed, 234 insertions(+), 98 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index 9d82de6ee6..1fcfa09ddb 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -33,85 +33,195 @@ describe('sanitizeQueryParams', () => { localStorageGetItemMock.mockReset() }) - it('sets the default values for `sourceChainId` and `destinationChainId` when both `sourceChainId` and `destinationChainId` are `undefined`', () => { - const result = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: undefined - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - }) - }) + describe('when `destinationChainId` is valid', () => { + it('and `sourceChainId` is valid should not do anything', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) - it('sets the value for `destinationChainId` to the partner chain of `sourceChainId` when `destinationChainId` is `undefined`', () => { - const result = sanitizeQueryParams({ - sourceChainId: ChainId.Ethereum, - destinationChainId: undefined - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - }) + // Orbit chains + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) - const resultWithArbitrumOrbitChain = sanitizeQueryParams({ - // @ts-expect-error Testing wrong chainId - sourceChainId: 1111, - destinationChainId: undefined - }) - expect(resultWithArbitrumOrbitChain).toEqual({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli - }) - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - // @ts-expect-error Testing wrong chainId - sourceChainId: 2222, - destinationChainId: undefined - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + }) + it('and `sourceChainId` is invalid should set `sourceChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + + // Orbit chains + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 2222 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumSepolia, + destinationChainId: 2222 + }) + + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 1111 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumGoerli, + destinationChainId: 1111 + }) + }) + it('and `sourceChainId` is undefined should set `sourceChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: ChainId.ArbitrumNova + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumNova + }) + + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 1111 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumGoerli, + destinationChainId: 1111 + }) + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 2222 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumSepolia, + destinationChainId: 2222 + }) }) }) - it('sets the value for `sourceChainId` to the partner chain of `destinationChainId` when `sourceChainId` is `undefined`', () => { - const result = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: ChainId.ArbitrumNova - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumNova - }) + describe('when `destinationChainId` is invalid', () => { + it('and `sourceChainId` is valid should set `destinationChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) - const resultWithArbitrumOrbitChain = sanitizeQueryParams({ - sourceChainId: undefined, - // @ts-expect-error Testing wrong chainId - destinationChainId: 1111 - }) - expect(resultWithArbitrumOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumGoerli, - destinationChainId: 1111 - }) - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: undefined, - // @ts-expect-error Testing wrong chainId - destinationChainId: 2222 - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumSepolia, - destinationChainId: 2222 + // Orbit chains + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: 12345 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) + + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: 12345 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + }) + it('and `sourceChainId` is invalid should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) + }) + it('and `sourceChainId` is undefined should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) }) }) - it('sets the value for `destinationChainId` to the partner chain of `sourceChainId` when `destinationChainId` is not a partner chain of `sourceChainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: ChainId.Goerli, - destinationChainId: ChainId.ArbitrumOne - }) - expect(result).toEqual({ - sourceChainId: ChainId.Goerli, - destinationChainId: ChainId.ArbitrumGoerli + describe('when `destinationChainId` is undefined', () => { + it('and `sourceChainId` is valid should set `destinationChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + + // Orbit chains + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: undefined + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) + + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: undefined + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + }) + it('and `sourceChainId` is invalid should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) + }) + it('and `sourceChainId` is undefined should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) }) }) }) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index df1f75d74c..cd4e0e99ce 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -21,6 +21,7 @@ import { NumberParam, QueryParamProvider, StringParam, + decodeNumber, decodeString, useQueryParams, withDefault @@ -122,8 +123,8 @@ function encodeChainQueryParam(chainId: number | null | undefined) { } } -function isValidNumber(value: number) { - return !Number.isNaN(value) +function isValidNumber(value: number | null | undefined): value is number { + return value === 0 || !!value } // Parse ChainQueryParam/ChainId to ChainId @@ -137,7 +138,7 @@ function decodeChainQueryParam( return undefined } - const valueNumber = parseInt(valueString, 10) + const valueNumber = decodeNumber(value) if ( isValidNumber(valueNumber) && isValidChainQueryParam(valueNumber as ChainId) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index bc29a9a23e..ffb9bc8b57 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -4,7 +4,11 @@ import { useCallback, useMemo } from 'react' import { mainnet, arbitrum, goerli, arbitrumGoerli } from '@wagmi/core/chains' import { useArbQueryParams } from './useArbQueryParams' -import { ChainId, rpcURLs } from '../util/networks' +import { + ChainId, + getCustomChainsFromLocalStorage, + rpcURLs +} from '../util/networks' import { sepolia, arbitrumNova, @@ -41,7 +45,7 @@ function getChainByChainId(chainId: ChainId): Chain { return chain } -function getPartnerChainsQueryParams(chainId: ChainId): ChainId[] { +function getPartnerChainsIds(chainId: ChainId): ChainId[] { try { const partnerChains = getPartnerChainsForChainId(chainId) return partnerChains.map(chain => chain.id) @@ -73,20 +77,46 @@ function getProviderForChainId(chainId: ChainId): StaticJsonRpcProvider { return createProviderWithCache(chainId) } +function isSupportedChainId(chainId: ChainId | undefined): chainId is ChainId { + if (!chainId) { + return false + } + + const customChainIds = getCustomChainsFromLocalStorage().map( + chain => chain.chainID + ) + + return [ + mainnet.id, + goerli.id, + sepolia.id, + arbitrum.id, + arbitrumNova.id, + arbitrumGoerli.id, + arbitrumSepolia.id, + stylusTestnet.id, + xaiTestnet.id, + arbitrumLocal.id, + local.id, + ...customChainIds + ].includes(chainId) +} + export function sanitizeQueryParams({ sourceChainId, destinationChainId }: { - sourceChainId: ChainId | undefined - destinationChainId: ChainId | undefined + sourceChainId: ChainId | number | undefined + destinationChainId: ChainId | number | undefined }): { - sourceChainId: ChainId - destinationChainId: ChainId + sourceChainId: ChainId | number + destinationChainId: ChainId | number } { - // when both `sourceChain` and `destinationChain` are undefined, default to Ethereum and Arbitrum One + // when both `sourceChain` and `destinationChain` are undefined or invalid, default to Ethereum and Arbitrum One if ( - typeof sourceChainId === 'undefined' && - typeof destinationChainId === 'undefined' + (!sourceChainId && !destinationChainId) || + (!isSupportedChainId(sourceChainId) && + !isSupportedChainId(destinationChainId)) ) { return { sourceChainId: ChainId.Ethereum, @@ -94,35 +124,30 @@ export function sanitizeQueryParams({ } } - // only `destinationChainId` is defined + // destinationChainId is valid and sourceChainId is undefined if ( - typeof sourceChainId === 'undefined' && - typeof destinationChainId !== 'undefined' + !isSupportedChainId(sourceChainId) && + isSupportedChainId(destinationChainId) ) { - const [defaultSourceChainId] = - getPartnerChainsQueryParams(destinationChainId) + const [defaultSourceChainId] = getPartnerChainsIds(destinationChainId) return { sourceChainId: defaultSourceChainId!, destinationChainId } } - // only `sourceChainId` is defined + // sourceChainId is valid and destinationChainId is undefined if ( - typeof sourceChainId !== 'undefined' && - typeof destinationChainId === 'undefined' + isSupportedChainId(sourceChainId) && + !isSupportedChainId(destinationChainId) ) { - const [defaultDestinationChainId] = - getPartnerChainsQueryParams(sourceChainId) + const [defaultDestinationChainId] = getPartnerChainsIds(sourceChainId) return { sourceChainId: sourceChainId, destinationChainId: defaultDestinationChainId! } } - if ( - !getPartnerChainsQueryParams(sourceChainId!).includes(destinationChainId!) - ) { - const [defaultDestinationChainId] = getPartnerChainsQueryParams( - sourceChainId! - ) + // destinationChainId is not a partner of sourceChainId + if (!getPartnerChainsIds(sourceChainId!).includes(destinationChainId!)) { + const [defaultDestinationChainId] = getPartnerChainsIds(sourceChainId!) return { sourceChainId: sourceChainId!, destinationChainId: defaultDestinationChainId! From aabee9b002a958c43b949a580d51ea51f8bbb3d5 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 6 Dec 2023 20:06:23 +0100 Subject: [PATCH 28/35] Replace toBe with toEqual --- .../hooks/__tests__/useArbQueryParams.test.ts | 264 +++++++++--------- 1 file changed, 135 insertions(+), 129 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts index b01a72a74a..e4a47e98d4 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts @@ -12,80 +12,80 @@ describe('AmountQueryParam custom encoder and decoder', () => { const getEncodeResult = (value: string) => AmountQueryParam.encode(value) it('should return input field value after encoding', () => { - expect(getEncodeResult('10234')).toBe('10234') - expect(getEncodeResult('12')).toBe('12') - - expect(getEncodeResult('1.0234')).toBe('1.0234') - expect(getEncodeResult('0.0234')).toBe('0.0234') - expect(getEncodeResult('0.0')).toBe('0.0') - expect(getEncodeResult('0')).toBe('0') - expect(getEncodeResult('0.000')).toBe('0.000') - - expect(getEncodeResult('1,0234')).toBe('1.0234') - expect(getEncodeResult('0,0234')).toBe('0.0234') - expect(getEncodeResult('0,0')).toBe('0.0') - expect(getEncodeResult('0,000')).toBe('0.000') - - expect(getEncodeResult('1e1')).toBe('1e1') - expect(getEncodeResult('1.0234e4')).toBe('1.0234e4') - expect(getEncodeResult('1.0234e-4')).toBe('1.0234e-4') - expect(getEncodeResult('1,0234e4')).toBe('1.0234e4') - expect(getEncodeResult('1,0234e-4')).toBe('1.0234e-4') - - expect(getEncodeResult('max')).toBe('max') - expect(getEncodeResult('mAx')).toBe('max') - expect(getEncodeResult('MAX')).toBe('max') - expect(getEncodeResult('MAx')).toBe('max') + expect(getEncodeResult('10234')).toEqual('10234') + expect(getEncodeResult('12')).toEqual('12') + + expect(getEncodeResult('1.0234')).toEqual('1.0234') + expect(getEncodeResult('0.0234')).toEqual('0.0234') + expect(getEncodeResult('0.0')).toEqual('0.0') + expect(getEncodeResult('0')).toEqual('0') + expect(getEncodeResult('0.000')).toEqual('0.000') + + expect(getEncodeResult('1,0234')).toEqual('1.0234') + expect(getEncodeResult('0,0234')).toEqual('0.0234') + expect(getEncodeResult('0,0')).toEqual('0.0') + expect(getEncodeResult('0,000')).toEqual('0.000') + + expect(getEncodeResult('1e1')).toEqual('1e1') + expect(getEncodeResult('1.0234e4')).toEqual('1.0234e4') + expect(getEncodeResult('1.0234e-4')).toEqual('1.0234e-4') + expect(getEncodeResult('1,0234e4')).toEqual('1.0234e4') + expect(getEncodeResult('1,0234e-4')).toEqual('1.0234e-4') + + expect(getEncodeResult('max')).toEqual('max') + expect(getEncodeResult('mAx')).toEqual('max') + expect(getEncodeResult('MAX')).toEqual('max') + expect(getEncodeResult('MAx')).toEqual('max') }) it('should return the absolute positive value after encoding', () => { - expect(getEncodeResult('-0.234')).toBe('0.234') - expect(getEncodeResult('-0,234')).toBe('0.234') - expect(getEncodeResult('-0')).toBe('0') - expect(getEncodeResult('-0.123123')).toBe('0.123123') - expect(getEncodeResult('-0,123123')).toBe('0.123123') - expect(getEncodeResult('-1')).toBe('1') - expect(getEncodeResult('-10')).toBe('10') + expect(getEncodeResult('-0.234')).toEqual('0.234') + expect(getEncodeResult('-0,234')).toEqual('0.234') + expect(getEncodeResult('-0')).toEqual('0') + expect(getEncodeResult('-0.123123')).toEqual('0.123123') + expect(getEncodeResult('-0,123123')).toEqual('0.123123') + expect(getEncodeResult('-1')).toEqual('1') + expect(getEncodeResult('-10')).toEqual('10') }) it('should return an empty string after encoding', () => { // these should never come into encode from the input[type=number] // but the tests are here in case we change the input type in the future - expect(getEncodeResult('random')).toBe('') - expect(getEncodeResult('null')).toBe('') - expect(getEncodeResult('1dfk')).toBe('') - expect(getEncodeResult('da24')).toBe('') + expect(getEncodeResult('random')).toEqual('') + expect(getEncodeResult('null')).toEqual('') + expect(getEncodeResult('1dfk')).toEqual('') + expect(getEncodeResult('da24')).toEqual('') // these should never come into encode from the input[type=number] // but the tests are here in case we change the input type in the future - expect(getEncodeResult('1.23.0')).toBe('') - expect(getEncodeResult('1,23,0')).toBe('') - expect(getEncodeResult('0,null,123')).toBe('') - expect(getEncodeResult('some, text')).toBe('') + expect(getEncodeResult('1.23.0')).toEqual('') + expect(getEncodeResult('1,23,0')).toEqual('') + expect(getEncodeResult('0,null,123')).toEqual('') + expect(getEncodeResult('some, text')).toEqual('') // it's a quirk of the number field that these won't trigger a value change // although the function handles these, if these were input, // the value of the input will instantly become an empty string, at least it does on Chrome on Mac - expect(getEncodeResult('12--32123-32')).toBe('') - expect(getEncodeResult('--10.23')).toBe('') - expect(getEncodeResult('')).toBe('') + expect(getEncodeResult('12--32123-32')).toEqual('') + expect(getEncodeResult('--10.23')).toEqual('') + expect(getEncodeResult('')).toEqual('') }) it('should return formatted value after encoding', () => { - expect(getEncodeResult('00.001')).toBe('0.001') - expect(getEncodeResult('0000')).toBe('0') - expect(getEncodeResult('00.000')).toBe('0.000') - expect(getEncodeResult('.1')).toBe('0.1') - expect(getEncodeResult('00002.123')).toBe('2.123') - expect(getEncodeResult('.0234')).toBe('0.0234') - expect(getEncodeResult('123.123000')).toBe('123.123000') - - expect(getEncodeResult('00,001')).toBe('0.001') - expect(getEncodeResult('00,000')).toBe('0.000') - expect(getEncodeResult(',1')).toBe('0.1') - expect(getEncodeResult('00002,123')).toBe('2.123') - expect(getEncodeResult(',0234')).toBe('0.0234') - expect(getEncodeResult('123,123000')).toBe('123.123000') + expect(getEncodeResult('00.001')).toEqual('0.001') + expect(getEncodeResult('0000')).toEqual('0') + expect(getEncodeResult('00.000')).toEqual('0.000') + expect(getEncodeResult('.1')).toEqual('0.1') + expect(getEncodeResult('00002.123')).toEqual('2.123') + expect(getEncodeResult('.0234')).toEqual('0.0234') + expect(getEncodeResult('123.123000')).toEqual('123.123000') + + expect(getEncodeResult('00,001')).toEqual('0.001') + expect(getEncodeResult('00,000')).toEqual('0.000') + expect(getEncodeResult(',1')).toEqual('0.1') + expect(getEncodeResult('00002,123')).toEqual('2.123') + expect(getEncodeResult(',0234')).toEqual('0.0234') + expect(getEncodeResult('123,123000')).toEqual('123.123000') }) }) @@ -93,74 +93,74 @@ describe('AmountQueryParam custom encoder and decoder', () => { const getDecodeResult = (value: string) => AmountQueryParam.decode(value) it('should return the original value after decoding', () => { - expect(getDecodeResult('10234')).toBe('10234') - expect(getDecodeResult('12')).toBe('12') - - expect(getDecodeResult('1.0234')).toBe('1.0234') - expect(getDecodeResult('0.0234')).toBe('0.0234') - expect(getDecodeResult('0.0')).toBe('0.0') - expect(getDecodeResult('0')).toBe('0') - expect(getDecodeResult('0.000')).toBe('0.000') - - expect(getDecodeResult('1,0234')).toBe('1.0234') - expect(getDecodeResult('0,0234')).toBe('0.0234') - expect(getDecodeResult('0,0')).toBe('0.0') - expect(getDecodeResult('0,000')).toBe('0.000') - - expect(getDecodeResult('1e1')).toBe('1e1') - expect(getDecodeResult('1.0234e4')).toBe('1.0234e4') - expect(getDecodeResult('1.0234e-4')).toBe('1.0234e-4') - expect(getDecodeResult('1,0234e4')).toBe('1.0234e4') - expect(getDecodeResult('1,0234e-4')).toBe('1.0234e-4') - - expect(getDecodeResult('max')).toBe('max') - expect(getDecodeResult('mAx')).toBe('max') - expect(getDecodeResult('MAX')).toBe('max') - expect(getDecodeResult('MAx')).toBe('max') + expect(getDecodeResult('10234')).toEqual('10234') + expect(getDecodeResult('12')).toEqual('12') + + expect(getDecodeResult('1.0234')).toEqual('1.0234') + expect(getDecodeResult('0.0234')).toEqual('0.0234') + expect(getDecodeResult('0.0')).toEqual('0.0') + expect(getDecodeResult('0')).toEqual('0') + expect(getDecodeResult('0.000')).toEqual('0.000') + + expect(getDecodeResult('1,0234')).toEqual('1.0234') + expect(getDecodeResult('0,0234')).toEqual('0.0234') + expect(getDecodeResult('0,0')).toEqual('0.0') + expect(getDecodeResult('0,000')).toEqual('0.000') + + expect(getDecodeResult('1e1')).toEqual('1e1') + expect(getDecodeResult('1.0234e4')).toEqual('1.0234e4') + expect(getDecodeResult('1.0234e-4')).toEqual('1.0234e-4') + expect(getDecodeResult('1,0234e4')).toEqual('1.0234e4') + expect(getDecodeResult('1,0234e-4')).toEqual('1.0234e-4') + + expect(getDecodeResult('max')).toEqual('max') + expect(getDecodeResult('mAx')).toEqual('max') + expect(getDecodeResult('MAX')).toEqual('max') + expect(getDecodeResult('MAx')).toEqual('max') }) it('should return the absolute positive value after decoding', () => { - expect(getDecodeResult('-0.234')).toBe('0.234') - expect(getDecodeResult('-0')).toBe('0') - expect(getDecodeResult('-0.123123')).toBe('0.123123') - expect(getDecodeResult('-1')).toBe('1') - expect(getDecodeResult('-10')).toBe('10') - - expect(getDecodeResult('-0,234')).toBe('0.234') - expect(getDecodeResult('-0,123123')).toBe('0.123123') + expect(getDecodeResult('-0.234')).toEqual('0.234') + expect(getDecodeResult('-0')).toEqual('0') + expect(getDecodeResult('-0.123123')).toEqual('0.123123') + expect(getDecodeResult('-1')).toEqual('1') + expect(getDecodeResult('-10')).toEqual('10') + + expect(getDecodeResult('-0,234')).toEqual('0.234') + expect(getDecodeResult('-0,123123')).toEqual('0.123123') }) it('should return an empty string after decoding', () => { - expect(getDecodeResult('random')).toBe('') - expect(getDecodeResult('null')).toBe('') - expect(getDecodeResult('1dfk')).toBe('') - expect(getDecodeResult('da24')).toBe('') - - expect(getDecodeResult('1,23,0')).toBe('') - expect(getDecodeResult('1.23.0')).toBe('') - expect(getDecodeResult('0,null,123')).toBe('') - expect(getDecodeResult('some, text')).toBe('') - - expect(getDecodeResult('12--32123-32')).toBe('') - expect(getDecodeResult('--10.23')).toBe('') - expect(getDecodeResult('')).toBe('') + expect(getDecodeResult('random')).toEqual('') + expect(getDecodeResult('null')).toEqual('') + expect(getDecodeResult('1dfk')).toEqual('') + expect(getDecodeResult('da24')).toEqual('') + + expect(getDecodeResult('1,23,0')).toEqual('') + expect(getDecodeResult('1.23.0')).toEqual('') + expect(getDecodeResult('0,null,123')).toEqual('') + expect(getDecodeResult('some, text')).toEqual('') + + expect(getDecodeResult('12--32123-32')).toEqual('') + expect(getDecodeResult('--10.23')).toEqual('') + expect(getDecodeResult('')).toEqual('') }) it('should return formatted value after encoding', () => { - expect(getDecodeResult('00.001')).toBe('0.001') - expect(getDecodeResult('0000')).toBe('0') - expect(getDecodeResult('00.000')).toBe('0.000') - expect(getDecodeResult('.1')).toBe('0.1') - expect(getDecodeResult('00002.123')).toBe('2.123') - expect(getDecodeResult('.0234')).toBe('0.0234') - expect(getDecodeResult('123.123000')).toBe('123.123000') - - expect(getDecodeResult('00,001')).toBe('0.001') - expect(getDecodeResult('00,000')).toBe('0.000') - expect(getDecodeResult(',1')).toBe('0.1') - expect(getDecodeResult('00002,123')).toBe('2.123') - expect(getDecodeResult(',0234')).toBe('0.0234') - expect(getDecodeResult('123,123000')).toBe('123.123000') + expect(getDecodeResult('00.001')).toEqual('0.001') + expect(getDecodeResult('0000')).toEqual('0') + expect(getDecodeResult('00.000')).toEqual('0.000') + expect(getDecodeResult('.1')).toEqual('0.1') + expect(getDecodeResult('00002.123')).toEqual('2.123') + expect(getDecodeResult('.0234')).toEqual('0.0234') + expect(getDecodeResult('123.123000')).toEqual('123.123000') + + expect(getDecodeResult('00,001')).toEqual('0.001') + expect(getDecodeResult('00,000')).toEqual('0.000') + expect(getDecodeResult(',1')).toEqual('0.1') + expect(getDecodeResult('00002,123')).toEqual('2.123') + expect(getDecodeResult(',0234')).toEqual('0.0234') + expect(getDecodeResult('123,123000')).toEqual('123.123000') }) }) }) @@ -173,10 +173,12 @@ describe('ChainParam custom encoder and decoder', () => { }) it('should return ChainQueryParam if value is a valid chainId', () => { - expect(ChainParam.encode(ChainId.Ethereum)).toBe('ethereum') - expect(ChainParam.encode(ChainId.ArbitrumOne)).toBe('arbitrum-one') - expect(ChainParam.encode(ChainId.Goerli)).toBe('goerli') - expect(ChainParam.encode(ChainId.ArbitrumGoerli)).toBe('arbitrum-goerli') + expect(ChainParam.encode(ChainId.Ethereum)).toEqual('ethereum') + expect(ChainParam.encode(ChainId.ArbitrumOne)).toEqual('arbitrum-one') + expect(ChainParam.encode(ChainId.Goerli)).toEqual('goerli') + expect(ChainParam.encode(ChainId.ArbitrumGoerli)).toEqual( + 'arbitrum-goerli' + ) expect(ChainParam.encode(1234567890)).toBeUndefined() localStorage.setItem( customChainLocalStorageKey, @@ -184,7 +186,7 @@ describe('ChainParam custom encoder and decoder', () => { { chainID: '1111111111', name: 'custom 1111111111 chain' } ]) ) - expect(ChainParam.encode(1111111111)).toBe('1111111111') + expect(ChainParam.encode(1111111111)).toEqual('1111111111') localStorage.clear() }) }) @@ -196,10 +198,12 @@ describe('ChainParam custom encoder and decoder', () => { }) it('should decode to ChainId if value is a valid ChainQueryParam', () => { - expect(ChainParam.decode('ethereum')).toBe(ChainId.Ethereum) - expect(ChainParam.decode('arbitrum-one')).toBe(ChainId.ArbitrumOne) - expect(ChainParam.decode('goerli')).toBe(ChainId.Goerli) - expect(ChainParam.decode('arbitrum-goerli')).toBe(ChainId.ArbitrumGoerli) + expect(ChainParam.decode('ethereum')).toEqual(ChainId.Ethereum) + expect(ChainParam.decode('arbitrum-one')).toEqual(ChainId.ArbitrumOne) + expect(ChainParam.decode('goerli')).toEqual(ChainId.Goerli) + expect(ChainParam.decode('arbitrum-goerli')).toEqual( + ChainId.ArbitrumGoerli + ) expect(ChainParam.decode('aaa123')).toBeUndefined() }) @@ -207,16 +211,18 @@ describe('ChainParam custom encoder and decoder', () => { function decodeChainId(value: ChainId) { return ChainParam.decode(value.toString()) } - expect(decodeChainId(ChainId.Ethereum)).toBe(ChainId.Ethereum) - expect(decodeChainId(ChainId.ArbitrumOne)).toBe(ChainId.ArbitrumOne) - expect(decodeChainId(ChainId.Goerli)).toBe(ChainId.Goerli) - expect(decodeChainId(ChainId.ArbitrumGoerli)).toBe(ChainId.ArbitrumGoerli) + expect(decodeChainId(ChainId.Ethereum)).toEqual(ChainId.Ethereum) + expect(decodeChainId(ChainId.ArbitrumOne)).toEqual(ChainId.ArbitrumOne) + expect(decodeChainId(ChainId.Goerli)).toEqual(ChainId.Goerli) + expect(decodeChainId(ChainId.ArbitrumGoerli)).toEqual( + ChainId.ArbitrumGoerli + ) expect(ChainParam.decode('1234567890')).toBeUndefined() localStorage.setItem( customChainLocalStorageKey, JSON.stringify([{ chainID: '222222', name: 'custom 222222 chain' }]) ) - expect(ChainParam.decode('222222')).toBe(222222) + expect(ChainParam.decode('222222')).toEqual(222222) localStorage.clear() }) }) From a02ad3391dbe25fa24f54b48ae358e95f8cbc9e0 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 6 Dec 2023 20:07:13 +0100 Subject: [PATCH 29/35] Rename variables in useNetworks setter --- .../arb-token-bridge-ui/src/hooks/useNetworks.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index ffb9bc8b57..fd2259afd5 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -169,12 +169,12 @@ export type UseNetworksState = { export type UseNetworksSetStateParams = | { - sourceChain: ChainId - destinationChain?: ChainId + sourceChainId: ChainId + destinationChainId?: ChainId } | { - sourceChain?: ChainId - destinationChain: ChainId + sourceChainId?: ChainId + destinationChainId: ChainId } export type UseNetworksSetState = (params: UseNetworksSetStateParams) => void @@ -192,13 +192,13 @@ export function useNetworks(): [UseNetworksState, UseNetworksSetState] { }) const setState = useCallback( - ({ sourceChain, destinationChain }: UseNetworksSetStateParams) => { + ({ sourceChainId, destinationChainId }: UseNetworksSetStateParams) => { const { sourceChainId: validSourceChainId, destinationChainId: validDestinationChainId } = sanitizeQueryParams({ - sourceChainId: sourceChain, - destinationChainId: destinationChain + sourceChainId, + destinationChainId }) setQueryParams({ sourceChain: validSourceChainId, From c1df7153be12de5236c215fe07466bc02a9c9f66 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 7 Dec 2023 14:55:48 +0100 Subject: [PATCH 30/35] Update isValidNumber --- .../arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index cd4e0e99ce..a0b4cffa42 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -124,7 +124,11 @@ function encodeChainQueryParam(chainId: number | null | undefined) { } function isValidNumber(value: number | null | undefined): value is number { - return value === 0 || !!value + if (typeof value === 'undefined' || value === null) { + return false + } + + return !Number.isNaN(value) } // Parse ChainQueryParam/ChainId to ChainId From 41d7f86e9d698bd2592259e2f82ab86f11c8311a Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 7 Dec 2023 14:59:14 +0100 Subject: [PATCH 31/35] Add return type to encodeChainQueryParam --- packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index a0b4cffa42..248a203792 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -110,7 +110,9 @@ export const AmountQueryParam = { } // Parse chainId to ChainQueryParam or ChainId for orbit chain -function encodeChainQueryParam(chainId: number | null | undefined) { +function encodeChainQueryParam( + chainId: number | null | undefined +): string | undefined { if (!chainId) { return undefined } From 0ff42cec2e5d20869ff1d9857e723b5b29cbca85 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 7 Dec 2023 16:27:51 +0100 Subject: [PATCH 32/35] Add support for Nova --- .../src/util/wagmi/getPartnerChainsForChainId.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts index 9306683215..02d2d6f476 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts @@ -24,6 +24,9 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { const customArbitrumSepoliaChains = customWagmiChains .filter(chain => chain.partnerChainID === ChainId.ArbitrumSepolia) .map(chain => chainToWagmiChain(chain)) + const customArbitrumNovaChains = customWagmiChains + .filter(chain => chain.partnerChainID === ChainId.ArbitrumNova) + .map(chain => chainToWagmiChain(chain)) switch (chainId) { case ChainId.Ethereum: @@ -39,7 +42,7 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { return [mainnet] case ChainId.ArbitrumNova: - return [mainnet] + return [mainnet, ...customArbitrumNovaChains] case ChainId.ArbitrumGoerli: return [goerli, xaiTestnet, ...customArbitrumGoerliChains] @@ -60,6 +63,9 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { const customArbitrumSepoliaChainsIds = customArbitrumSepoliaChains.map( chain => chain.id ) + const customArbitrumNovaChainsIds = customArbitrumNovaChains.map( + chain => chain.id + ) // Orbit chains if (customArbitrumGoerliChainsIds.includes(chainId)) { @@ -69,6 +75,11 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { if (customArbitrumSepoliaChainsIds.includes(chainId)) { return [arbitrumSepolia] } + + if (customArbitrumNovaChainsIds.includes(chainId)) { + return [arbitrumNova] + } + throw new Error( `[getPartnerChainsForChain] Unexpected chain id: ${chainId}` ) From 593765103e0226cbcf4b77337dced15e84ade8ba Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 7 Dec 2023 16:37:35 +0100 Subject: [PATCH 33/35] Add support for ArbitrumOne --- .../src/util/wagmi/getPartnerChainsForChainId.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts index 02d2d6f476..fceed30d4c 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getPartnerChainsForChainId.ts @@ -24,6 +24,9 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { const customArbitrumSepoliaChains = customWagmiChains .filter(chain => chain.partnerChainID === ChainId.ArbitrumSepolia) .map(chain => chainToWagmiChain(chain)) + const customArbitrumOneChains = customWagmiChains + .filter(chain => chain.partnerChainID === ChainId.ArbitrumOne) + .map(chain => chainToWagmiChain(chain)) const customArbitrumNovaChains = customWagmiChains .filter(chain => chain.partnerChainID === ChainId.ArbitrumNova) .map(chain => chainToWagmiChain(chain)) @@ -39,7 +42,7 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { return [arbitrumSepolia] case ChainId.ArbitrumOne: - return [mainnet] + return [mainnet, ...customArbitrumOneChains] case ChainId.ArbitrumNova: return [mainnet, ...customArbitrumNovaChains] @@ -66,19 +69,23 @@ export function getPartnerChainsForChainId(chainId: number): Chain[] { const customArbitrumNovaChainsIds = customArbitrumNovaChains.map( chain => chain.id ) + const customArbitrumOneChainsIds = customArbitrumOneChains.map( + chain => chain.id + ) // Orbit chains if (customArbitrumGoerliChainsIds.includes(chainId)) { return [arbitrumGoerli] } - if (customArbitrumSepoliaChainsIds.includes(chainId)) { return [arbitrumSepolia] } - if (customArbitrumNovaChainsIds.includes(chainId)) { return [arbitrumNova] } + if (customArbitrumOneChainsIds.includes(chainId)) { + return [arbitrumOne] + } throw new Error( `[getPartnerChainsForChain] Unexpected chain id: ${chainId}` From caf06867855fc0606bca9318661620899497d717 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 7 Dec 2023 16:37:47 +0100 Subject: [PATCH 34/35] Update tests --- .../src/hooks/__tests__/useNetworks.test.ts | 406 ++++++++++-------- 1 file changed, 223 insertions(+), 183 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index 1fcfa09ddb..dbfb67e06f 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -21,6 +21,16 @@ describe('sanitizeQueryParams', () => { chainID: '2222', partnerChainID: ChainId.ArbitrumSepolia, name: 'custom 2222 chain' + }, + { + chainID: '3333', + partnerChainID: ChainId.ArbitrumOne, + name: 'custom 3333 chain' + }, + { + chainID: '4444', + partnerChainID: ChainId.ArbitrumNova, + name: 'custom 4444 chain' } ]) } @@ -33,195 +43,225 @@ describe('sanitizeQueryParams', () => { localStorageGetItemMock.mockReset() }) - describe('when `destinationChainId` is valid', () => { - it('and `sourceChainId` is valid should not do anything', () => { - const result = sanitizeQueryParams({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) - expect(result).toEqual({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) - - // Orbit chains - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia - }) - - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli - }) - }) - it('and `sourceChainId` is invalid should set `sourceChainId`', () => { - const result = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: ChainId.ArbitrumSepolia - }) - expect(result).toEqual({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) - - // Orbit chains - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: 2222 - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumSepolia, - destinationChainId: 2222 - }) - - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: 1111 - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumGoerli, - destinationChainId: 1111 - }) - }) - it('and `sourceChainId` is undefined should set `sourceChainId`', () => { - const result = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: ChainId.ArbitrumNova - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumNova - }) - - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: 1111 - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumGoerli, - destinationChainId: 1111 - }) - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: 2222 - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumSepolia, - destinationChainId: 2222 - }) + it('when `destinationChainId` is valid and `sourceChainId` is valid should not do anything', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + + // Orbit chains + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) + + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + }) + it('when `destinationChainId` is valid and `sourceChainId` is invalid should set `sourceChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + + // Orbit chains + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 1111 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumGoerli, + destinationChainId: 1111 + }) + + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 2222 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumSepolia, + destinationChainId: 2222 + }) + + const resultWithArbitrumOneChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 3333 + }) + expect(resultWithArbitrumOneChain).toEqual({ + sourceChainId: ChainId.ArbitrumOne, + destinationChainId: 3333 + }) + + const resultWithArbitrumNovaChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 4444 + }) + expect(resultWithArbitrumNovaChain).toEqual({ + sourceChainId: ChainId.ArbitrumNova, + destinationChainId: 4444 + }) + }) + it('when `destinationChainId` is valid and `sourceChainId` is undefined should set `sourceChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: ChainId.ArbitrumNova + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumNova + }) + + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 1111 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumGoerli, + destinationChainId: 1111 + }) + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 2222 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumSepolia, + destinationChainId: 2222 }) }) - describe('when `destinationChainId` is invalid', () => { - it('and `sourceChainId` is valid should set `destinationChainId`', () => { - const result = sanitizeQueryParams({ - sourceChainId: ChainId.Sepolia, - destinationChainId: 12345 - }) - expect(result).toEqual({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) - - // Orbit chains - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: 2222, - destinationChainId: 12345 - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia - }) - - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: 1111, - destinationChainId: 12345 - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli - }) - }) - it('and `sourceChainId` is invalid should set both chainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: 12345 - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - }) - }) - it('and `sourceChainId` is undefined should set both chainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: 12345 - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - }) + it('when `destinationChainId` is invalid and `sourceChainId` is valid should set `destinationChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + + // Orbit chains + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: 1234 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: 1234 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) + + const resultWithArbitrumOneChain = sanitizeQueryParams({ + sourceChainId: 3333, + destinationChainId: 1234 + }) + expect(resultWithArbitrumOneChain).toEqual({ + sourceChainId: 3333, + destinationChainId: ChainId.ArbitrumOne + }) + + const resultWithArbitrumNovaChain = sanitizeQueryParams({ + sourceChainId: 4444, + destinationChainId: 1234 + }) + expect(resultWithArbitrumNovaChain).toEqual({ + sourceChainId: 4444, + destinationChainId: ChainId.ArbitrumNova + }) + }) + it('when `destinationChainId` is invalid and `sourceChainId` is invalid should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne }) }) + it('when `destinationChainId` is invalid and `sourceChainId` is undefined should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) + }) + + it('when `destinationChainId` is undefined and `sourceChainId` is valid should set `destinationChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + + // Orbit chains + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: undefined + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) - describe('when `destinationChainId` is undefined', () => { - it('and `sourceChainId` is valid should set `destinationChainId`', () => { - const result = sanitizeQueryParams({ - sourceChainId: ChainId.Sepolia, - destinationChainId: undefined - }) - expect(result).toEqual({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) - - // Orbit chains - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: 2222, - destinationChainId: undefined - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia - }) - - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: 1111, - destinationChainId: undefined - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli - }) - }) - it('and `sourceChainId` is invalid should set both chainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: undefined - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - }) - }) - it('and `sourceChainId` is undefined should set both chainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: undefined - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne - }) + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: undefined + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + }) + it('when `destinationChainId` is undefined and `sourceChainId` is invalid should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) + }) + it('when `destinationChainId` is undefined and `sourceChainId` is undefined should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne }) }) }) From d53d41667933d65ea3a92abfcd701c6d3cfb12dd Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 7 Dec 2023 16:46:49 +0100 Subject: [PATCH 35/35] Group tests --- .../src/hooks/__tests__/useNetworks.test.ts | 394 +++++++++--------- 1 file changed, 206 insertions(+), 188 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index dbfb67e06f..635e5b7b8b 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -43,225 +43,243 @@ describe('sanitizeQueryParams', () => { localStorageGetItemMock.mockReset() }) - it('when `destinationChainId` is valid and `sourceChainId` is valid should not do anything', () => { - const result = sanitizeQueryParams({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) - expect(result).toEqual({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) + describe('when `destinationChainId` is valid and `sourceChainId` is valid', () => { + it('should not do anything', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) - // Orbit chains - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia - }) + // Orbit chains + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) }) }) - it('when `destinationChainId` is valid and `sourceChainId` is invalid should set `sourceChainId`', () => { - const result = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: ChainId.ArbitrumSepolia - }) - expect(result).toEqual({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) + describe('when `destinationChainId` is valid and `sourceChainId` is invalid', () => { + it('should set `sourceChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: ChainId.ArbitrumSepolia + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) - // Orbit chains - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: 1111 - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumGoerli, - destinationChainId: 1111 - }) + // Orbit chains + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 1111 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumGoerli, + destinationChainId: 1111 + }) - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: 2222 - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumSepolia, - destinationChainId: 2222 - }) + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 2222 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumSepolia, + destinationChainId: 2222 + }) - const resultWithArbitrumOneChain = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: 3333 - }) - expect(resultWithArbitrumOneChain).toEqual({ - sourceChainId: ChainId.ArbitrumOne, - destinationChainId: 3333 - }) + const resultWithArbitrumOneChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 3333 + }) + expect(resultWithArbitrumOneChain).toEqual({ + sourceChainId: ChainId.ArbitrumOne, + destinationChainId: 3333 + }) - const resultWithArbitrumNovaChain = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: 4444 - }) - expect(resultWithArbitrumNovaChain).toEqual({ - sourceChainId: ChainId.ArbitrumNova, - destinationChainId: 4444 + const resultWithArbitrumNovaChain = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 4444 + }) + expect(resultWithArbitrumNovaChain).toEqual({ + sourceChainId: ChainId.ArbitrumNova, + destinationChainId: 4444 + }) }) }) - it('when `destinationChainId` is valid and `sourceChainId` is undefined should set `sourceChainId`', () => { - const result = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: ChainId.ArbitrumNova - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumNova - }) + describe('when `destinationChainId` is valid and `sourceChainId` is undefined', () => { + it('should set `sourceChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: ChainId.ArbitrumNova + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumNova + }) - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: 1111 - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumGoerli, - destinationChainId: 1111 - }) - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: 2222 - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: ChainId.ArbitrumSepolia, - destinationChainId: 2222 + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 1111 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumGoerli, + destinationChainId: 1111 + }) + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 2222 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: ChainId.ArbitrumSepolia, + destinationChainId: 2222 + }) }) }) - it('when `destinationChainId` is invalid and `sourceChainId` is valid should set `destinationChainId`', () => { - const result = sanitizeQueryParams({ - sourceChainId: ChainId.Sepolia, - destinationChainId: 12345 - }) - expect(result).toEqual({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) + describe('when `destinationChainId` is invalid and `sourceChainId` is valid', () => { + it('should set `destinationChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) - // Orbit chains - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: 1111, - destinationChainId: 1234 - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli - }) + // Orbit chains + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: 1234 + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: 2222, - destinationChainId: 1234 - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia - }) + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: 1234 + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) - const resultWithArbitrumOneChain = sanitizeQueryParams({ - sourceChainId: 3333, - destinationChainId: 1234 - }) - expect(resultWithArbitrumOneChain).toEqual({ - sourceChainId: 3333, - destinationChainId: ChainId.ArbitrumOne - }) + const resultWithArbitrumOneChain = sanitizeQueryParams({ + sourceChainId: 3333, + destinationChainId: 1234 + }) + expect(resultWithArbitrumOneChain).toEqual({ + sourceChainId: 3333, + destinationChainId: ChainId.ArbitrumOne + }) - const resultWithArbitrumNovaChain = sanitizeQueryParams({ - sourceChainId: 4444, - destinationChainId: 1234 - }) - expect(resultWithArbitrumNovaChain).toEqual({ - sourceChainId: 4444, - destinationChainId: ChainId.ArbitrumNova + const resultWithArbitrumNovaChain = sanitizeQueryParams({ + sourceChainId: 4444, + destinationChainId: 1234 + }) + expect(resultWithArbitrumNovaChain).toEqual({ + sourceChainId: 4444, + destinationChainId: ChainId.ArbitrumNova + }) }) }) - it('when `destinationChainId` is invalid and `sourceChainId` is invalid should set both chainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: 12345 - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne + describe('when `destinationChainId` is invalid and `sourceChainId` is invalid', () => { + it('should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) }) }) - it('when `destinationChainId` is invalid and `sourceChainId` is undefined should set both chainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: 12345 - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne + describe('when `destinationChainId` is invalid and `sourceChainId` is undefined', () => { + it('should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: 12345 + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) }) }) - it('when `destinationChainId` is undefined and `sourceChainId` is valid should set `destinationChainId`', () => { - const result = sanitizeQueryParams({ - sourceChainId: ChainId.Sepolia, - destinationChainId: undefined - }) - expect(result).toEqual({ - sourceChainId: ChainId.Sepolia, - destinationChainId: ChainId.ArbitrumSepolia - }) + describe('when `destinationChainId` is undefined and `sourceChainId` is valid', () => { + it('should set `destinationChainId`', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Sepolia, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Sepolia, + destinationChainId: ChainId.ArbitrumSepolia + }) - // Orbit chains - const resultWithSepoliaOrbitChain = sanitizeQueryParams({ - sourceChainId: 2222, - destinationChainId: undefined - }) - expect(resultWithSepoliaOrbitChain).toEqual({ - sourceChainId: 2222, - destinationChainId: ChainId.ArbitrumSepolia - }) + // Orbit chains + const resultWithSepoliaOrbitChain = sanitizeQueryParams({ + sourceChainId: 2222, + destinationChainId: undefined + }) + expect(resultWithSepoliaOrbitChain).toEqual({ + sourceChainId: 2222, + destinationChainId: ChainId.ArbitrumSepolia + }) - const resultWithGoerliOrbitChain = sanitizeQueryParams({ - sourceChainId: 1111, - destinationChainId: undefined - }) - expect(resultWithGoerliOrbitChain).toEqual({ - sourceChainId: 1111, - destinationChainId: ChainId.ArbitrumGoerli + const resultWithGoerliOrbitChain = sanitizeQueryParams({ + sourceChainId: 1111, + destinationChainId: undefined + }) + expect(resultWithGoerliOrbitChain).toEqual({ + sourceChainId: 1111, + destinationChainId: ChainId.ArbitrumGoerli + }) }) }) - it('when `destinationChainId` is undefined and `sourceChainId` is invalid should set both chainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: 1234, - destinationChainId: undefined - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne + describe('when `destinationChainId` is undefined and `sourceChainId` is invalid', () => { + it('should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: 1234, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) }) }) - it('when `destinationChainId` is undefined and `sourceChainId` is undefined should set both chainId', () => { - const result = sanitizeQueryParams({ - sourceChainId: undefined, - destinationChainId: undefined - }) - expect(result).toEqual({ - sourceChainId: ChainId.Ethereum, - destinationChainId: ChainId.ArbitrumOne + describe('when`destinationChainId` is undefined and`sourceChainId` is undefined', () => { + it('should set both chainId', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) }) }) })