diff --git a/.github/ISSUE_TEMPLATE/add-layerleap-request.yml b/.github/ISSUE_TEMPLATE/add-layerleap-request.yml index 2889fea0b3..798b9fc7a9 100644 --- a/.github/ISSUE_TEMPLATE/add-layerleap-request.yml +++ b/.github/ISSUE_TEMPLATE/add-layerleap-request.yml @@ -1,7 +1,7 @@ name: Add Layer Leap Request description: File a request to have your Orbit chain support Layer Leap transfers title: "[feat]: enable Layer Leap for " -labels: ["feat", "triage"] +labels: ["Type: Add LayerLeap"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/add-orbit-chain-request.yml b/.github/ISSUE_TEMPLATE/add-orbit-chain-request.yml index eb3eb21fb6..75eebf1e8b 100644 --- a/.github/ISSUE_TEMPLATE/add-orbit-chain-request.yml +++ b/.github/ISSUE_TEMPLATE/add-orbit-chain-request.yml @@ -1,7 +1,7 @@ name: Add Orbit Chain Request description: File a request to have your Orbit chain added to the bridge title: "[feat]: Add Orbit chain " -labels: ["feat", "triage"] +labels: ["Type: Add Orbit Chain"] body: - type: markdown attributes: @@ -71,6 +71,8 @@ body: - "421614" - "11155111" - "17000" + - "8453" + - "84532" validations: required: true @@ -119,50 +121,20 @@ body: attributes: value: | Please fill out this section with your chain configuration details and contract addresses. As a reminder, you can get all contract addresses by running [getAllContracts](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/feat-add-verification-scripts/examples/verify-rollup/README.md#get-all-contracts). - - type: input - id: confirmPeriodBlocks - attributes: - label: confirmPeriodBlocks - placeholder: ex. 45818 - validations: - required: true - type: markdown attributes: value: | - ## Rollup contract addresses + ## Rollup contract address - type: markdown attributes: value: | You can find this information under the `"coreContracts" : {...}` key. - - type: input - id: bridge - attributes: - label: bridge - validations: - required: true - - type: input - id: inbox - attributes: - label: inbox - validations: - required: true - - type: input - id: outbox - attributes: - label: outbox - validations: - required: true - type: input id: rollup attributes: label: rollup - validations: - required: true - - type: input - id: sequencerInbox - attributes: - label: sequencerInbox + description: Please provide the address of the rollup contract. Other core contract addresses will be fetched automatically. validations: required: true @@ -170,10 +142,6 @@ body: attributes: value: | ## Parent chain token bridge contract addresses - - type: markdown - attributes: - value: | - You can find this information under the `"l2Contracts" : {...}` key. - type: input id: parent-custom-gateway attributes: @@ -222,10 +190,6 @@ body: attributes: value: | ## Orbit chain token bridge contract addresses - - type: markdown - attributes: - value: | - You can find this information under the `"l3Contracts" : {...}` key. - type: input id: child-custom-gateway attributes: diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 3d60b8d978..e99b5f4bb6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -153,7 +153,7 @@ jobs: - name: Install node_modules uses: OffchainLabs/actions/node-modules/install@main - + - name: Build run: yarn workspace scripts build diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9f465350ac..9b97671ee9 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -113,6 +113,7 @@ jobs: (matrix.test.type == 'orbit-custom-16dec' && 'yarn test:e2e:orbit:custom-gas-token --browser chrome') || (matrix.test.type == 'orbit-custom-18dec' && 'yarn test:e2e:orbit:custom-gas-token --browser chrome') || (matrix.test.type == 'orbit-custom-20dec' && 'yarn test:e2e:orbit:custom-gas-token --browser chrome') || + (matrix.test.type == 'cctp' && 'yarn test:e2e:cctp --browser chrome') || 'yarn test:e2e --browser chrome' }} wait-on: http://127.0.0.1:3000 diff --git a/README.md b/README.md index 73f9acd030..74739c0054 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,12 @@ It is important for any code change to pass both unit and end-to-end tests. This ./test-node.bash --init --no-simple --tokenbridge --l3node --l3-token-bridge ``` + To run with a custom fee token also include the following flags: + + ```bash + --l3-fee-token --l3-fee-token-decimals 18 + ``` + 2. When the Nitro test-node is up and running you should see logs like `sequencer_1` and `staker-unsafe_1` in the terminal. This can take up to 10 minutes. 2. At the root of the token bridge UI: diff --git a/audit-ci.jsonc b/audit-ci.jsonc index 52bdc29750..f5d48c759d 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -1,5 +1,13 @@ { "$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json", "low": true, - "allowlist": [] + "allowlist": [ + // https://github.com/advisories/GHSA-fc9h-whq2-v747 + // Valid ECDSA signatures erroneously rejected in Elliptic + // Legitimate transactions or communications may be incorrectly flagged as invalid. + // No patched version available yet + // from: arb-token-bridge-ui>@unstoppabledomains/resolution>elliptic + // from: arb-token-bridge-ui>ethers>@ethersproject/signing-key>elliptic + "GHSA-fc9h-whq2-v747" + ] } diff --git a/packages/arb-token-bridge-ui/.env.local.sample b/packages/arb-token-bridge-ui/.env.local.sample index 9ccb08c10a..9e0d6cfb67 100644 --- a/packages/arb-token-bridge-ui/.env.local.sample +++ b/packages/arb-token-bridge-ui/.env.local.sample @@ -8,8 +8,10 @@ NEXT_PUBLIC_INFURA_KEY_SEPOLIA= # L2 NEXT_PUBLIC_INFURA_KEY_ARBITRUM_ONE= +NEXT_PUBLIC_INFURA_KEY_BASE= # L2 Testnet NEXT_PUBLIC_INFURA_KEY_ARBITRUM_SEPOLIA= +NEXT_PUBLIC_INFURA_KEY_BASE_SEPOLIA= NEXT_PUBLIC_SENTRY_DSN= diff --git a/packages/arb-token-bridge-ui/public/images/ApeChainLogo.svg b/packages/arb-token-bridge-ui/public/images/ApeChainLogo.svg new file mode 100644 index 0000000000..b5e6448444 --- /dev/null +++ b/packages/arb-token-bridge-ui/public/images/ApeChainLogo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/arb-token-bridge-ui/public/images/ApeTokenLogo.svg b/packages/arb-token-bridge-ui/public/images/ApeTokenLogo.svg new file mode 100644 index 0000000000..fd9175bb9d --- /dev/null +++ b/packages/arb-token-bridge-ui/public/images/ApeTokenLogo.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/arb-token-bridge-ui/public/images/AtlasLogo.png b/packages/arb-token-bridge-ui/public/images/AtlasLogo.png deleted file mode 100644 index 6d256c9ab6..0000000000 Binary files a/packages/arb-token-bridge-ui/public/images/AtlasLogo.png and /dev/null differ diff --git a/packages/arb-token-bridge-ui/public/images/BaseWhite.svg b/packages/arb-token-bridge-ui/public/images/BaseWhite.svg new file mode 100644 index 0000000000..72e52ac2f8 --- /dev/null +++ b/packages/arb-token-bridge-ui/public/images/BaseWhite.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/arb-token-bridge-ui/public/images/GravityAlpha_Logo.png b/packages/arb-token-bridge-ui/public/images/GravityAlpha_Logo.png new file mode 100644 index 0000000000..891aec4023 Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/GravityAlpha_Logo.png differ diff --git a/packages/arb-token-bridge-ui/public/images/GravityAlpha_NativeTokenLogo.png b/packages/arb-token-bridge-ui/public/images/GravityAlpha_NativeTokenLogo.png new file mode 100644 index 0000000000..891aec4023 Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/GravityAlpha_NativeTokenLogo.png differ diff --git a/packages/arb-token-bridge-ui/public/images/PolterTestnetLogo.png b/packages/arb-token-bridge-ui/public/images/PolterTestnetLogo.png new file mode 100644 index 0000000000..70ee5c61fb Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/PolterTestnetLogo.png differ diff --git a/packages/arb-token-bridge-ui/public/images/ghst.png b/packages/arb-token-bridge-ui/public/images/ghst.png new file mode 100644 index 0000000000..b571b7eef9 Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/ghst.png differ diff --git a/packages/arb-token-bridge-ui/src/components/App/App.tsx b/packages/arb-token-bridge-ui/src/components/App/App.tsx index 65d3f60ccf..d23f339fb8 100644 --- a/packages/arb-token-bridge-ui/src/components/App/App.tsx +++ b/packages/arb-token-bridge-ui/src/components/App/App.tsx @@ -25,7 +25,7 @@ import { BalanceUpdater } from '../syncers/BalanceUpdater' import { TokenListSyncer } from '../syncers/TokenListSyncer' import { Header } from '../common/Header' import { HeaderAccountPopover } from '../common/HeaderAccountPopover' -import { getNetworkName, isNetwork } from '../../util/networks' +import { getNetworkName } from '../../util/networks' import { ArbQueryParamProvider, useArbQueryParams @@ -41,6 +41,7 @@ import { HeaderConnectWalletButton } from '../common/HeaderConnectWalletButton' import { onDisconnectHandler } from '../../util/walletConnectUtils' import { addressIsSmartContract } from '../../util/AddressUtils' import { useSyncConnectedChainToAnalytics } from './useSyncConnectedChainToAnalytics' +import { isDepositMode } from '../../util/isDepositMode' declare global { interface Window { @@ -98,15 +99,6 @@ const ArbTokenBridgeStoreSyncWrapper = (): JSX.Element | null => { // Any time one of those changes setTokenBridgeParams(null) actions.app.setConnectionState(ConnectionState.LOADING) - - const { - isArbitrum: isConnectedToArbitrum, - isOrbitChain: isConnectedToOrbitChain - } = isNetwork(networks.sourceChain.id) - const isParentChainEthereum = isNetwork( - parentChain.id - ).isEthereumMainnetOrTestnet - actions.app.reset(networks.sourceChain.id) actions.app.setChainIds({ l1NetworkChainId: parentChain.id, @@ -114,14 +106,16 @@ const ArbTokenBridgeStoreSyncWrapper = (): JSX.Element | null => { }) if ( - (isParentChainEthereum && isConnectedToArbitrum) || - isConnectedToOrbitChain + isDepositMode({ + sourceChainId: networks.sourceChain.id, + destinationChainId: networks.destinationChain.id + }) ) { - console.info('Withdrawal mode detected:') - actions.app.setConnectionState(ConnectionState.L2_CONNECTED) - } else { console.info('Deposit mode detected:') actions.app.setConnectionState(ConnectionState.L1_CONNECTED) + } else { + console.info('Withdrawal mode detected:') + actions.app.setConnectionState(ConnectionState.L2_CONNECTED) } setTokenBridgeParams({ diff --git a/packages/arb-token-bridge-ui/src/components/common/NetworkSelectionContainer.tsx b/packages/arb-token-bridge-ui/src/components/common/NetworkSelectionContainer.tsx index 95994fa261..522a69e6cb 100644 --- a/packages/arb-token-bridge-ui/src/components/common/NetworkSelectionContainer.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/NetworkSelectionContainer.tsx @@ -32,10 +32,11 @@ import { useActions } from '../../state' import { useChainIdsForNetworkSelection } from '../../hooks/TransferPanel/useChainIdsForNetworkSelection' import { useAccountType } from '../../hooks/useAccountType' -type NetworkType = 'core' | 'orbit' +type NetworkType = 'core' | 'other' | 'orbit' enum ChainGroupName { core = 'CORE CHAINS', + other = 'OTHER CHAINS', orbit = 'ORBIT CHAINS' } @@ -48,6 +49,19 @@ const chainGroupInfo: { [key in NetworkType]: ChainGroupInfo } = { core: { name: ChainGroupName.core }, + other: { + name: ChainGroupName.other, + description: ( +

+ + + Independent projects using non-Arbitrum technology. These chains have + varying degrees of decentralization.{' '} + Bridge at your own risk. + +

+ ) + }, orbit: { name: ChainGroupName.orbit, description: ( @@ -71,7 +85,7 @@ function ChainTypeInfoRow({ style: CSSProperties }) { const { name, description } = chainGroup - const isCoreGroup = chainGroup.name === ChainGroupName.core + const isOrbitGroup = chainGroup.name === ChainGroupName.orbit return (
@@ -236,7 +250,11 @@ function NetworksPanel({ } const coreNetworks = chainIds.filter( - chainId => !isNetwork(chainId).isOrbitChain + chainId => isNetwork(chainId).isCoreChain + ) + const otherNetworks = chainIds.filter( + chainId => + !isNetwork(chainId).isCoreChain && !isNetwork(chainId).isOrbitChain ) const orbitNetworks = chainIds.filter( chainId => isNetwork(chainId).isOrbitChain @@ -244,6 +262,7 @@ function NetworksPanel({ return { core: coreNetworks, + other: otherNetworks, orbit: orbitNetworks } }, [debouncedNetworkSearched, chainIds]) @@ -262,6 +281,10 @@ function NetworksPanel({ groupedNetworks.push(ChainGroupName.core, ...networksToShow.core) } + if (networksToShow.other.length > 0) { + groupedNetworks.push(ChainGroupName.other, ...networksToShow.other) + } + if (networksToShow.orbit.length > 0) { groupedNetworks.push(ChainGroupName.orbit, ...networksToShow.orbit) } @@ -302,6 +325,12 @@ function NetworksPanel({ ) } + if (networkOrChainTypeName === ChainGroupName.other) { + return ( + + ) + } + if (networkOrChainTypeName === ChainGroupName.orbit) { return ( 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 80bf5705ee..ec03fd1f9c 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 @@ -143,6 +143,18 @@ describe('sanitizeQueryParams', () => { }) }) }) + describe('when `destinationChainId` is valid but has no paired source chains and `sourceChainId` is undefined', () => { + it('should set `sourceChainId` to Ethereum and `destinationChainId` to Arbitrum One', () => { + const result = sanitizeQueryParams({ + sourceChainId: undefined, + destinationChainId: ChainId.Holesky + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + }) + }) + }) describe('when `destinationChainId` is invalid and `sourceChainId` is valid', () => { it('should set `destinationChainId` based on `sourceChainId`', () => { @@ -207,4 +219,16 @@ describe('sanitizeQueryParams', () => { }) }) }) + describe('when `destinationChainId` is undefined and `sourceChainId` is valid but has no paired destination chains', () => { + it('should set `sourceChainId` to Ethereum and `destinationChainId` to Arbitrum One', () => { + const result = sanitizeQueryParams({ + sourceChainId: ChainId.Holesky, + destinationChainId: undefined + }) + expect(result).toEqual({ + sourceChainId: ChainId.Ethereum, + 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 64a394d3fb..b141f2d144 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -12,7 +12,9 @@ import { arbitrumSepolia, localL1Network as local, localL2Network as arbitrumLocal, - localL3Network as l3Local + localL3Network as l3Local, + base, + baseSepolia } from '../util/wagmi/wagmiAdditionalNetworks' import { getDestinationChainIds } from '../util/networks' @@ -37,7 +39,9 @@ export function isSupportedChainId( holesky.id, arbitrum.id, arbitrumNova.id, + base.id, arbitrumSepolia.id, + baseSepolia.id, arbitrumLocal.id, l3Local.id, local.id, @@ -74,7 +78,15 @@ export function sanitizeQueryParams({ isSupportedChainId(destinationChainId) ) { const [defaultSourceChainId] = getDestinationChainIds(destinationChainId) - return { sourceChainId: defaultSourceChainId!, destinationChainId } + + if (typeof defaultSourceChainId === 'undefined') { + return { + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + } + } + + return { sourceChainId: defaultSourceChainId, destinationChainId } } // sourceChainId is valid and destinationChainId is undefined @@ -83,9 +95,17 @@ export function sanitizeQueryParams({ !isSupportedChainId(destinationChainId) ) { const [defaultDestinationChainId] = getDestinationChainIds(sourceChainId) + + if (typeof defaultDestinationChainId === 'undefined') { + return { + sourceChainId: ChainId.Ethereum, + destinationChainId: ChainId.ArbitrumOne + } + } + return { sourceChainId: sourceChainId, - destinationChainId: defaultDestinationChainId! + destinationChainId: defaultDestinationChainId } } diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts index 7dbf694b29..54e7483bbf 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts @@ -11,6 +11,7 @@ import { EthL1L3Bridger, getArbitrumNetwork } from '@arbitrum/sdk' +import { isDepositMode } from '../util/isDepositMode' export const getAddressFromSigner = async (signer: Signer) => { const address = await signer.getAddress() @@ -28,8 +29,6 @@ export const getBridgeTransferProperties = ( const sourceChainId = props.sourceChainId const destinationChainId = props.destinationChainId - const isSourceChainEthereumMainnetOrTestnet = - isNetwork(sourceChainId).isEthereumMainnetOrTestnet const isDestinationChainEthereumMainnetOrTestnet = isNetwork(destinationChainId).isEthereumMainnetOrTestnet @@ -37,16 +36,16 @@ export const getBridgeTransferProperties = ( const isDestinationChainArbitrum = isNetwork(destinationChainId).isArbitrum const isSourceChainOrbit = isNetwork(sourceChainId).isOrbitChain - const isDestinationChainOrbit = isNetwork(destinationChainId).isOrbitChain - const isDeposit = - isSourceChainEthereumMainnetOrTestnet || - (isSourceChainArbitrum && isDestinationChainOrbit) + const { isBase: isDestinationChainBase } = isNetwork(destinationChainId) + + const isDeposit = isDepositMode({ sourceChainId, destinationChainId }) const isWithdrawal = (isSourceChainArbitrum && isDestinationChainEthereumMainnetOrTestnet) || // l2 arbitrum chains to l1 (isSourceChainOrbit && isDestinationChainEthereumMainnetOrTestnet) || // l2 orbit chains to l1 - (isSourceChainOrbit && isDestinationChainArbitrum) // l3 orbit chains to l1 + (isSourceChainOrbit && isDestinationChainArbitrum) || // l3 orbit chains to l1 + (isSourceChainOrbit && isDestinationChainBase) // l3 orbit chain to Base l2 const isTeleport = isValidTeleportChainPair({ sourceChainId, diff --git a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts index 6a9dde793a..77abdf0b20 100644 --- a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts +++ b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts @@ -13,9 +13,12 @@ import { chainToWagmiChain } from '../util/wagmi/wagmiAdditionalNetworks' const chainQueryParams = [ 'ethereum', 'sepolia', + 'holesky', 'arbitrum-one', 'arbitrum-nova', + 'base', 'arbitrum-sepolia', + 'base-sepolia', 'custom-localhost', 'arbitrum-localhost', 'l3-localhost' @@ -50,12 +53,21 @@ export function getChainQueryParamForChain(chainId: ChainId): ChainQueryParam { case ChainId.ArbitrumNova: return 'arbitrum-nova' + case ChainId.Base: + return 'base' + + case ChainId.Holesky: + return 'holesky' + case ChainId.Sepolia: return 'sepolia' case ChainId.ArbitrumSepolia: return 'arbitrum-sepolia' + case ChainId.BaseSepolia: + return 'base-sepolia' + case ChainId.Local: return 'custom-localhost' @@ -94,15 +106,24 @@ export function getChainForChainKeyQueryParam( case 'sepolia': return chains.sepolia + case 'holesky': + return customChains.holesky + case 'arbitrum-one': return chains.arbitrum case 'arbitrum-nova': return customChains.arbitrumNova + case 'base': + return customChains.base + case 'arbitrum-sepolia': return customChains.arbitrumSepolia + case 'base-sepolia': + return customChains.baseSepolia + case 'custom-localhost': return customChains.localL1Network diff --git a/packages/arb-token-bridge-ui/src/util/TokenUtils.ts b/packages/arb-token-bridge-ui/src/util/TokenUtils.ts index 3b27c16633..7c79df9893 100644 --- a/packages/arb-token-bridge-ui/src/util/TokenUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/TokenUtils.ts @@ -66,6 +66,13 @@ function getErc20DataCache(params: GetErc20DataCacheParams): Erc20Data | null function getErc20DataCache( params?: GetErc20DataCacheParams ): Erc20DataCache | (Erc20Data | null) { + if ( + typeof window === 'undefined' || + typeof window.localStorage === 'undefined' + ) { + return null + } + const cache: Erc20DataCache = JSON.parse( // intentionally using || instead of ?? for it to work with an empty string localStorage.getItem(erc20DataCacheLocalStorageKey) || '{}' diff --git a/packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts b/packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts index cf9f7d8aca..f08da0b4df 100644 --- a/packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts @@ -205,6 +205,12 @@ export const withdrawOnlyTokens: { [chainId: number]: WithdrawOnlyToken[] } = { l2CustomAddr: '0x6985884c4392d348587b19cb9eaaf157f13271cd', l1Address: '0x6985884c4392d348587b19cb9eaaf157f13271cd', l2Address: '0xd99f14023f6bde3142d339b6c069b2b711da7e37' + }, + { + symbol: 'G3', + l2CustomAddr: '0xc24A365A870821EB83Fd216c9596eDD89479d8d7', + l1Address: '0xCF67815ccE72E682Eb4429eCa46843bed81Ca739', + l2Address: '0x34fb4148fdc1ab3054ac85d32de887c58538bb57' } ], [ChainId.ArbitrumNova]: [] diff --git a/packages/arb-token-bridge-ui/src/util/__tests__/networks.test.ts b/packages/arb-token-bridge-ui/src/util/__tests__/networks.test.ts index 554663bf91..2dbeb6f3cb 100644 --- a/packages/arb-token-bridge-ui/src/util/__tests__/networks.test.ts +++ b/packages/arb-token-bridge-ui/src/util/__tests__/networks.test.ts @@ -52,6 +52,15 @@ beforeAll(() => { }) registerCustomArbitrumNetwork(xaiTestnet) + + const polterTestnetChainId = 631571 + const polterTestnet = orbitTestnets[polterTestnetChainId] + + if (!polterTestnet) { + throw new Error(`Could not find Polter Testnet in the Orbit chains list.`) + } + + registerCustomArbitrumNetwork(polterTestnet) }) describe('getBaseChainIdByChainId', () => { @@ -254,4 +263,20 @@ describe('getDestinationChainIds', () => { expect(defaultChainId).toBe(ChainId.Sepolia) expect(isAscending(nonDefaultChainIds)).toBe(true) }) + + it('should return a sorted list for Base Sepolia', () => { + const destinationChainIds = getDestinationChainIds(ChainId.BaseSepolia) + const defaultChainId = destinationChainIds[0] + const nonDefaultChainIds = destinationChainIds.slice(1) + + expect(defaultChainId).toBe(631571) + expect(isAscending(nonDefaultChainIds)).toBe(true) + }) + + // Enable when there are Orbit Chains on Base + it('should not return a list for Base', () => { + const destinationChainIds = getDestinationChainIds(ChainId.Base) + + expect(destinationChainIds).toHaveLength(0) + }) }) diff --git a/packages/arb-token-bridge-ui/src/util/bridgeUiConfig.ts b/packages/arb-token-bridge-ui/src/util/bridgeUiConfig.ts index d68e0a1719..d7d4461918 100644 --- a/packages/arb-token-bridge-ui/src/util/bridgeUiConfig.ts +++ b/packages/arb-token-bridge-ui/src/util/bridgeUiConfig.ts @@ -104,6 +104,25 @@ export function getBridgeUiConfigForChain(chainId: number): BridgeUiConfig { 'AnyTrust protocol. Low fees for high-volume transactions. Secured by a trust-minimized Data Availability Committee (DAC).' } } + case ChainId.Base: + return { + color: '#0052ff', + network: { + name: 'Base', + logo: '/images/BaseWhite.svg', + description: + 'Base is an Optimistic Rollup built by Coinbase with the OP Stack.' + } + } + case ChainId.BaseSepolia: + return { + color: '#0052ff', + network: { + name: 'Base Sepolia', + logo: '/images/BaseWhite.svg', + description: 'Base Sepolia is an Ethereum L2 testnet by Coinbase.' + } + } default: { // added Orbit chains const orbitChain = orbitChains[chainId] diff --git a/packages/arb-token-bridge-ui/src/util/infura.ts b/packages/arb-token-bridge-ui/src/util/infura.ts index 02124bb907..20fa95514d 100644 --- a/packages/arb-token-bridge-ui/src/util/infura.ts +++ b/packages/arb-token-bridge-ui/src/util/infura.ts @@ -61,10 +61,14 @@ export function chainIdToInfuraKey(chainId: ChainId) { return process.env.NEXT_PUBLIC_INFURA_KEY_SEPOLIA || defaultInfuraKey case ChainId.ArbitrumOne: return process.env.NEXT_PUBLIC_INFURA_KEY_ARBITRUM_ONE || defaultInfuraKey + case ChainId.Base: + return process.env.NEXT_PUBLIC_INFURA_KEY_BASE || defaultInfuraKey case ChainId.ArbitrumSepolia: return ( process.env.NEXT_PUBLIC_INFURA_KEY_ARBITRUM_SEPOLIA || defaultInfuraKey ) + case ChainId.BaseSepolia: + return process.env.NEXT_PUBLIC_INFURA_KEY_BASE_SEPOLIA || defaultInfuraKey default: return defaultInfuraKey @@ -81,8 +85,12 @@ export function chainIdToInfuraUrl(chainId: ChainId) { return `https://sepolia.infura.io/v3/${infuraKey}` case ChainId.ArbitrumOne: return `https://arbitrum-mainnet.infura.io/v3/${infuraKey}` + case ChainId.Base: + return `https://base-mainnet.infura.io/v3/${infuraKey}` case ChainId.ArbitrumSepolia: return `https://arbitrum-sepolia.infura.io/v3/${infuraKey}` + case ChainId.BaseSepolia: + return `https://base-sepolia.infura.io/v3/${infuraKey}` default: return undefined } diff --git a/packages/arb-token-bridge-ui/src/util/isDepositMode.ts b/packages/arb-token-bridge-ui/src/util/isDepositMode.ts index 86f49391ed..dd22cceb7c 100644 --- a/packages/arb-token-bridge-ui/src/util/isDepositMode.ts +++ b/packages/arb-token-bridge-ui/src/util/isDepositMode.ts @@ -9,13 +9,16 @@ export function isDepositMode({ }) { const { isEthereumMainnetOrTestnet: isSourceChainEthereum, - isArbitrum: isSourceChainArbitrum + isArbitrum: isSourceChainArbitrum, + isBase: isSourceChainBase } = isNetwork(sourceChainId) const { isOrbitChain: isDestinationChainOrbit } = isNetwork(destinationChainId) const isDepositMode = - isSourceChainEthereum || (isSourceChainArbitrum && isDestinationChainOrbit) + isSourceChainEthereum || + isSourceChainBase || + (isSourceChainArbitrum && isDestinationChainOrbit) return isDepositMode } diff --git a/packages/arb-token-bridge-ui/src/util/networks.ts b/packages/arb-token-bridge-ui/src/util/networks.ts index d234bee411..02d26cdeb3 100644 --- a/packages/arb-token-bridge-ui/src/util/networks.ts +++ b/packages/arb-token-bridge-ui/src/util/networks.ts @@ -22,20 +22,23 @@ export enum ChainId { // L2 ArbitrumOne = 42161, ArbitrumNova = 42170, + Base = 8453, // L2 Testnets ArbitrumSepolia = 421614, ArbitrumLocal = 412346, + BaseSepolia = 84532, // L3 Testnets L3Local = 333333 } -type L1Network = { +/** The network that you reference when calling `block.number` in solidity */ +type BlockNumberReferenceNetwork = { chainId: ChainId blockTime: number isTestnet: boolean } -const l1Networks: { [chainId: number]: L1Network } = { +const l1Networks: { [chainId: number]: BlockNumberReferenceNetwork } = { [ChainId.Ethereum]: { chainId: ChainId.Ethereum, blockTime: 12, @@ -58,12 +61,32 @@ const l1Networks: { [chainId: number]: L1Network } = { } } +const baseNetworks: { [chainId: number]: BlockNumberReferenceNetwork } = { + [ChainId.Base]: { + chainId: ChainId.Base, + blockTime: 2, + isTestnet: false + }, + [ChainId.BaseSepolia]: { + chainId: ChainId.BaseSepolia, + blockTime: 2, + isTestnet: true + } +} + export const getChains = () => { - const chains = [...Object.values(l1Networks), ...getArbitrumNetworks()] + const chains: (BlockNumberReferenceNetwork | ArbitrumNetwork)[] = [ + ...Object.values(l1Networks), + ...Object.values(baseNetworks), + ...getArbitrumNetworks() + ] return chains.filter(chain => { - // exclude L1 chains with no child chains - if (isL1Chain(chain) && getChildrenForNetwork(chain.chainId).length === 0) { + // exclude L1 chains or Base Chains with no child chains + if ( + isBlockNumberReferenceNetwork(chain) && + getChildrenForNetwork(chain.chainId).length === 0 + ) { return false } @@ -88,12 +111,12 @@ export function getBaseChainIdByChainId({ }: { chainId: number }): number { - // the chain provided is an L1 chain, so we can return early - if (isL1Chain({ chainId })) { + // the chain provided is an L1 chain or Base chain, so we can return early + if (isBlockNumberReferenceNetwork({ chainId })) { return chainId } - let currentParentChain: L1Network | ArbitrumNetwork + let currentParentChain: BlockNumberReferenceNetwork | ArbitrumNetwork try { currentParentChain = getArbitrumNetwork(chainId) @@ -101,9 +124,9 @@ export function getBaseChainIdByChainId({ return chainId } - // keep following the parent chains until we find the L1 chain + // keep following the parent chains until we find the L1/Base chain while (true) { - if (isL1Chain(currentParentChain)) { + if (isBlockNumberReferenceNetwork(currentParentChain)) { return currentParentChain.chainId } @@ -186,10 +209,12 @@ export function removeCustomChainFromLocalStorage(chainId: number) { ) } +// Only support testnet chains export const supportedCustomOrbitParentChains = [ ChainId.Sepolia, ChainId.Holesky, - ChainId.ArbitrumSepolia + ChainId.ArbitrumSepolia, + ChainId.BaseSepolia ] export const rpcURLs: { [chainId: number]: string } = { @@ -210,10 +235,18 @@ export const rpcURLs: { [chainId: number]: string } = { fallback: 'https://arb1.arbitrum.io/rpc' }), [ChainId.ArbitrumNova]: 'https://nova.arbitrum.io/rpc', + [ChainId.Base]: loadEnvironmentVariableWithFallback({ + env: chainIdToInfuraUrl(ChainId.Base), + fallback: 'https://mainnet.base.org' + }), // L2 Testnets [ChainId.ArbitrumSepolia]: loadEnvironmentVariableWithFallback({ env: chainIdToInfuraUrl(ChainId.ArbitrumSepolia), fallback: 'https://sepolia-rollup.arbitrum.io/rpc' + }), + [ChainId.BaseSepolia]: loadEnvironmentVariableWithFallback({ + env: chainIdToInfuraUrl(ChainId.BaseSepolia), + fallback: 'https://sepolia.base.org' }) } @@ -226,8 +259,10 @@ export const explorerUrls: { [chainId: number]: string } = { // L2 [ChainId.ArbitrumNova]: 'https://nova.arbiscan.io', [ChainId.ArbitrumOne]: 'https://arbiscan.io', + [ChainId.Base]: 'https://basescan.org', // L2 Testnets - [ChainId.ArbitrumSepolia]: 'https://sepolia.arbiscan.io' + [ChainId.ArbitrumSepolia]: 'https://sepolia.arbiscan.io', + [ChainId.BaseSepolia]: 'https://sepolia.basescan.org' } export const getExplorerUrl = (chainId: ChainId) => { @@ -238,7 +273,7 @@ export const getExplorerUrl = (chainId: ChainId) => { export const getL1BlockTime = (chainId: number) => { const chain = getChainByChainId(getBaseChainIdByChainId({ chainId })) - if (!chain || !isL1Chain(chain)) { + if (!chain || !isBlockNumberReferenceNetwork(chain)) { throw new Error(`Couldn't get block time. Unexpected chain ID: ${chainId}`) } @@ -246,6 +281,11 @@ export const getL1BlockTime = (chainId: number) => { } export const getConfirmPeriodBlocks = (chainId: ChainId) => { + // Base is not an Arbitrum chain so it doesn't work in the same way, and we don't support deposits from L1, or withdrawals from Base chains + if (isNetwork(chainId).isBase) { + return 0 + } + return getArbitrumNetwork(chainId).confirmPeriodBlocks } @@ -271,7 +311,7 @@ export const l2MoonGatewayAddresses: { [chainId: number]: string } = { [ChainId.ArbitrumNova]: '0xA430a792c14d3E49d9D00FD7B4BA343F516fbB81' } -const defaultL1Network: L1Network = { +const defaultL1Network: BlockNumberReferenceNetwork = { blockTime: 10, chainId: 1337, isTestnet: true @@ -396,7 +436,7 @@ export const localL3NetworkRpcUrl = loadEnvironmentVariableWithFallback({ fallback: 'http://127.0.0.1:3347' }) -export async function registerLocalNetwork(isLocalCustomNativeToken?: boolean) { +export async function registerLocalNetwork() { try { rpcURLs[defaultL1Network.chainId] = localL1NetworkRpcUrl rpcURLs[defaultL2Network.chainId] = localL2NetworkRpcUrl @@ -404,19 +444,19 @@ export async function registerLocalNetwork(isLocalCustomNativeToken?: boolean) { registerCustomArbitrumNetwork(defaultL2Network) - if (typeof isLocalCustomNativeToken === 'undefined') { - try { - const data = await fetchErc20Data({ - address: defaultL3CustomGasTokenNetwork.nativeToken!, - provider: new StaticJsonRpcProvider(localL2NetworkRpcUrl) - }) - if (data.symbol === 'TN') { - isLocalCustomNativeToken = true - } - } catch (e) { - // not the native token - isLocalCustomNativeToken = false + let isLocalCustomNativeToken = false + + try { + const data = await fetchErc20Data({ + address: defaultL3CustomGasTokenNetwork.nativeToken!, + provider: new StaticJsonRpcProvider(localL2NetworkRpcUrl) + }) + if (data.symbol === 'TN') { + isLocalCustomNativeToken = true } + } catch (e) { + // not the native token + isLocalCustomNativeToken = false } registerCustomArbitrumNetwork( @@ -435,6 +475,11 @@ function isTestnetChain(chainId: ChainId) { return l1Network.isTestnet } + const baseNetwork = baseNetworks[chainId] + if (baseNetwork) { + return baseNetwork.isTestnet + } + try { return getArbitrumNetwork(chainId).isTestnet } catch { @@ -443,6 +488,14 @@ function isTestnetChain(chainId: ChainId) { } } +function getIsArbitrumChain(chainId: ChainId) { + try { + return !!getArbitrumNetwork(chainId).parentChainId + } catch (error) { + return false + } +} + export function isNetwork(chainId: ChainId) { const isEthereumMainnet = chainId === ChainId.Ethereum @@ -455,14 +508,19 @@ export function isNetwork(chainId: ChainId) { const isArbitrumSepolia = chainId === ChainId.ArbitrumSepolia const isArbitrumLocal = chainId === ChainId.ArbitrumLocal + const isBaseMainnet = chainId === ChainId.Base + const isBaseSepolia = chainId === ChainId.BaseSepolia + const isEthereumMainnetOrTestnet = isEthereumMainnet || isSepolia || isHolesky || isLocal const isArbitrum = isArbitrumOne || isArbitrumNova || isArbitrumLocal || isArbitrumSepolia + const isBase = isBaseMainnet || isBaseSepolia + const isCoreChain = isEthereumMainnetOrTestnet || isArbitrum - const isOrbitChain = !isCoreChain + const isOrbitChain = getIsArbitrumChain(chainId) && !isCoreChain return { // L1 @@ -474,8 +532,11 @@ export function isNetwork(chainId: ChainId) { isArbitrum, isArbitrumOne, isArbitrumNova, + isBase, + isBaseMainnet, // L2 Testnets isArbitrumSepolia, + isBaseSepolia, // Orbit chains isOrbitChain, // General @@ -522,22 +583,29 @@ export function mapCustomChainToNetworkData(chain: ChainWithRpcUrl) { explorerUrls[chain.chainId] = chain.explorerUrl } -function isL1Chain(chain: { chainId: number }): chain is L1Network { - return typeof l1Networks[chain.chainId] !== 'undefined' -} - function isArbitrumChain( - chain: L1Network | ArbitrumNetwork + chain: BlockNumberReferenceNetwork | ArbitrumNetwork ): chain is ArbitrumNetwork { return typeof (chain as ArbitrumNetwork).parentChainId !== 'undefined' } +function isBlockNumberReferenceNetwork(chain: { + chainId: number +}): chain is BlockNumberReferenceNetwork { + return ( + typeof l1Networks[chain.chainId] !== 'undefined' || + typeof baseNetworks[chain.chainId] !== 'undefined' + ) +} + export const TELEPORT_ALLOWLIST: { [id: number]: number[] } = { [ChainId.Ethereum]: [1380012617, 70700, 70701], // Rari, PopApex and PopBoss [ChainId.Sepolia]: [1918988905] // RARI Testnet } -export function getChildChainIds(chain: ArbitrumNetwork | L1Network) { +export function getChildChainIds( + chain: ArbitrumNetwork | BlockNumberReferenceNetwork +) { const childChainIds = [ ...getChildrenForNetwork(chain.chainId).map(chain => chain.chainId), ...(TELEPORT_ALLOWLIST[chain.chainId] ?? []) // for considering teleport (L1-L3 transfers) we will get the L3 children of the chain, if present diff --git a/packages/arb-token-bridge-ui/src/util/orbitChainsData.json b/packages/arb-token-bridge-ui/src/util/orbitChainsData.json index 01673e0071..6e7879dd77 100644 --- a/packages/arb-token-bridge-ui/src/util/orbitChainsData.json +++ b/packages/arb-token-bridge-ui/src/util/orbitChainsData.json @@ -491,6 +491,107 @@ "logoUrl": "/images/sxTokenLogo.png" } } + }, + { + "chainId": 33139, + "confirmPeriodBlocks": 45818, + "ethBridge": { + "bridge": "0x6B71AFb4b7725227ab944c96FE018AB9dc0434b8", + "inbox": "0x1B98e4ED82Ee1a91A65a38C690e2266364064D15", + "outbox": "0x4F405BA65291063d8A524c2bDf55d4e67405c2aF", + "rollup": "0x374de579AE15aD59eD0519aeAf1A23F348Df259c", + "sequencerInbox": "0xE6a92Ae29E24C343eE66A2B3D3ECB783d65E4a3C" + }, + "explorerUrl": "https://apescan.io", + "nativeToken": "0x7f9FBf9bDd3F4105C478b996B648FE6e828a1e98", + "rpcUrl": "https://apechain.calderachain.xyz/http", + "isCustom": true, + "isTestnet": false, + "name": "ApeChain", + "slug": "apechain", + "parentChainId": 42161, + "retryableLifetimeSeconds": 604800, + "tokenBridge": { + "parentCustomGateway": "0xEd543dA6fE33cceE50DC024b78C27959235D0ab0", + "parentErc20Gateway": "0xB603a1C07A11945bFe4855347c88583e31b8ddB0", + "parentGatewayRouter": "0xD57Df5C2Dc2D60307a74944191f2aA5B7BD4a700", + "parentMultiCall": "0x411f8A148e448bBe75382d4FFABee0796484f3c6", + "parentProxyAdmin": "0x1E5f8ff72895aEa53DD62b590dA51E92dC75b507", + "parentWeth": "0x0000000000000000000000000000000000000000", + "parentWethGateway": "0x0000000000000000000000000000000000000000", + "childCustomGateway": "0x49607221AF038229cEc6c85DB37a652E00226D29", + "childErc20Gateway": "0xf9710169D2f3524E7Bf068F6DDF6b2cc65D38a14", + "childGatewayRouter": "0x31eFB847d3f7A0C9Ba1364a6423aEa0a7a60BF3C", + "childMultiCall": "0x350186513FB2C148A50439311533656EF8096D9C", + "childProxyAdmin": "0x965a8220D41A031b1F71d94feFBf13dE4f686B33", + "childWeth": "0x0000000000000000000000000000000000000000", + "childWethGateway": "0x0000000000000000000000000000000000000000" + }, + "bridgeUiConfig": { + "color": "#0054FA", + "network": { + "name": "ApeChain", + "logo": "/images/ApeChainLogo.svg", + "description": "ApeChain is a dedicated infrastructure layer to power the ApeCoin ecosystem." + }, + "nativeTokenData": { + "name": "ApeCoin", + "symbol": "APE", + "decimals": 18, + "logoUrl": "/images/ApeTokenLogo.svg" + } + } + }, + { + "chainId": 1625, + "confirmPeriodBlocks": 40320, + "ethBridge": { + "bridge": "0x7983403dDA368AA7d67145a9b81c5c517F364c42", + "inbox": "0x7AD2a94BefF3294a31894cFb5ba4206957a53c19", + "outbox": "0x1153a1e4B1523DFf36f77d696bd6eBF2B0e7DAbF", + "rollup": "0xf993AF239770932A0EDaB88B6A5ba3708Bd58239", + "sequencerInbox": "0x8D99372612e8cFE7163B1a453831Bc40eAeb3cF3" + }, + "nativeToken": "0x9C7BEBa8F6eF6643aBd725e45a4E8387eF260649", + "explorerUrl": "https://explorer.gravity.xyz", + "rpcUrl": "https://rpc.gravity.xyz", + "isArbitrum": true, + "isCustom": true, + "isTestnet": false, + "name": "Gravity Alpha", + "slug": "gravity-alpha", + "parentChainId": 1, + "retryableLifetimeSeconds": 604800, + "tokenBridge": { + "parentCustomGateway": "0xa26Fd1c23634870303e42311E114D5cc8301Ed1E", + "parentErc20Gateway": "0xb23988D9728EF147EAa02D602D7e067B6131A1bB", + "parentGatewayRouter": "0x8713569d016f981D956715e9EE2795382168b5c0", + "parentMultiCall": "0x7cdCB0Cc61f47B8Dd8f47C5A29edaDd84a1BDf5e", + "parentProxyAdmin": "0xBbc3872E30C91ef69336937838c2a283F79f7E68", + "parentWeth": "0x0000000000000000000000000000000000000000", + "parentWethGateway": "0x0000000000000000000000000000000000000000", + "childCustomGateway": "0xC18EADE2B2CdA6AcFAc4fd2226C724a1008b02Ab", + "childErc20Gateway": "0xD330E617270F375Bd476896f3A8AE9041264E13d", + "childGatewayRouter": "0xf1cA401FB474520EbaBb285670891dEbd7C505Bc", + "childMultiCall": "0xABF31e3A13528082cE5bb05D6E88749556DAFD5F", + "childProxyAdmin": "0xB881cf085a78491AaA71Bf22bc87E67865a4409a", + "childWeth": "0x0000000000000000000000000000000000000000", + "childWethGateway": "0x0000000000000000000000000000000000000000" + }, + "bridgeUiConfig": { + "color": "#FFAC43", + "network": { + "name": "Gravity Alpha", + "logo": "/images/GravityAlpha_Logo.png", + "description": "The Magnet Chain for Web3." + }, + "nativeTokenData": { + "name": "G", + "symbol": "G", + "decimals": 18, + "logoUrl": "/images/GravityAlpha_NativeTokenLogo.png" + } + } } ], "testnet": [ @@ -680,56 +781,6 @@ } } }, - { - "chainId": 1183, - "confirmPeriodBlocks": 150, - "ethBridge": { - "bridge": "0xCAeCF7c5c2769e250Ef69592dD470D40BC61D1f7", - "inbox": "0x44199D537Aa690bE3B4446602e0c66F2B992D51e", - "outbox": "0xAD6A8df458dBD6b440F50313C5d53E3C5b4734e9", - "rollup": "0x357717a0F9Ac8714A5995109Fba3BAd3b5f0954F", - "sequencerInbox": "0x146bCf344F949ad4c9Fcf339eb4415981aE21dF9" - }, - "nativeToken": "0x566f8345F7bF45358FaB2802C19c60D691dE04e4", - "explorerUrl": "https://testnet.theatlas.tech", - "rpcUrl": "https://theatlas.tech", - "isCustom": true, - "isTestnet": true, - "name": "Atlas Testnet", - "slug": "atlas-testnet", - "parentChainId": 421614, - "retryableLifetimeSeconds": 604800, - "tokenBridge": { - "parentCustomGateway": "0xE5A8943e36f93491f97A521447772fCF529533B5", - "parentErc20Gateway": "0xBc89C0A2CCA8A0e1583AA6d4e22B36E26661C78E", - "parentGatewayRouter": "0xc93fDF8327e04776fd184188bB4EC8521E5E96D7", - "parentMultiCall": "0xce1CAd780c529e66e3aa6D952a1ED9A6447791c1", - "parentProxyAdmin": "0x0000000000000000000000000000000000000000", - "parentWeth": "0x0000000000000000000000000000000000000000", - "parentWethGateway": "0x0000000000000000000000000000000000000000", - "childCustomGateway": "0x2c2Be954C9B79d9cA2CF145d9d6363F3c3784615", - "childErc20Gateway": "0xDfCD9f6154BEA2E98e8aE493f3Fec2E22De52D6a", - "childGatewayRouter": "0xBc63Ec54dA9Ee3B2b3D4cA5DE3622a3F85e0F219", - "childMultiCall": "0x54AFaf5Fd025A99708e73e52c317454bD52B1e77", - "childProxyAdmin": "0xE1C9a27C47bb9691bA5b4E35eb7617B39F6098B9", - "childWeth": "0x0000000000000000000000000000000000000000", - "childWethGateway": "0x0000000000000000000000000000000000000000" - }, - "bridgeUiConfig": { - "color": "#889B6F", - "network": { - "name": "Atlas Testnet", - "logo": "/images/AtlasLogo.png", - "description": "Aarc is the first modular layer for unified borderless dapps." - }, - "nativeTokenData": { - "name": "Aarc", - "symbol": "AARC", - "decimals": 18, - "logoUrl": "/images/AtlasLogo.png" - } - } - }, { "chainId": 2730, "confirmPeriodBlocks": 64, @@ -779,6 +830,56 @@ "logoUrl": "/images/xrTokenLogo.png" } } + }, + { + "chainId": 631571, + "confirmPeriodBlocks": 900, + "ethBridge": { + "bridge": "0x133634FA8F372e59422744759a67796F01428BDD", + "inbox": "0x81Bf51dEC736adA7E256ed6C092CAe24D62a1ca7", + "outbox": "0x020e1cb41cF68999D988c20Ef40E2C58e1947db2", + "rollup": "0xAFcb6cF53AB01ac39577A592E141F614171c9371", + "sequencerInbox": "0x2453eb0d6F58C01Adb87505A4348fB5EEceB007D" + }, + "nativeToken": "0xe97f36a00058AA7DfC4E85d23532C3f70453a7aE", + "explorerUrl": "https://polter-testnet.explorer.alchemy.com", + "rpcUrl": "https://geist-polter.g.alchemy.com/public", + "isCustom": true, + "isTestnet": true, + "name": "Polter Testnet", + "slug": "polter-testnet", + "parentChainId": 84532, + "retryableLifetimeSeconds": 604800, + "tokenBridge": { + "parentCustomGateway": "0x24e1e6375f0f0Eecd38266357cADE57cA3A348F3", + "parentErc20Gateway": "0x9014E7244116b965F0D1059f80e2bD8169957117", + "parentGatewayRouter": "0xE38553a7989feD9e240DE8B0f5ed166BF75a088A", + "parentMultiCall": "0xE0753Df74d86D6B25aCd2d049389c7E52e2dd728", + "parentProxyAdmin": "0x0000000000000000000000000000000000000000", + "parentWeth": "0x0000000000000000000000000000000000000000", + "parentWethGateway": "0x0000000000000000000000000000000000000000", + "childCustomGateway": "0x7965162499751ABf89b8e3C640f8229AD8EeB022", + "childErc20Gateway": "0xA8E8b5D957e4ad9335E504442e877E513BD50729", + "childGatewayRouter": "0xF654086B1118fE98ea045De8D6dC0F1C53Da5B76", + "childMultiCall": "0xb97c5bC7DB7532471726550480A855484a408d00", + "childProxyAdmin": "0xda11A4ecaAcA51b1f2e2109a2EEFaC438409b68D", + "childWeth": "0x0000000000000000000000000000000000000000", + "childWethGateway": "0x0000000000000000000000000000000000000000" + }, + "bridgeUiConfig": { + "color": "#03AB2A", + "network": { + "name": "Polter Testnet", + "logo": "/images/PolterTestnetLogo.png", + "description": "A gaming testnet for Aavegotchi's Geist Mainnet." + }, + "nativeTokenData": { + "name": "Aavegotchi GHST Token", + "symbol": "GHST", + "decimals": 18, + "logoUrl": "/images/ghst.png" + } + } } ] } diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts index bd6774c656..66de1b1b0f 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts @@ -9,7 +9,9 @@ import { arbitrumSepolia, localL1Network, localL2Network, - localL3Network + localL3Network, + baseSepolia, + base } from './wagmiAdditionalNetworks' import { ChainId } from '../networks' import { getCustomChainFromLocalStorageById } from '../networks' @@ -37,6 +39,9 @@ export function getWagmiChain(chainId: number): Chain { case ChainId.ArbitrumNova: return arbitrumNova + case ChainId.Base: + return base + // Testnets case ChainId.Sepolia: return sepolia @@ -47,6 +52,9 @@ export function getWagmiChain(chainId: number): Chain { case ChainId.ArbitrumSepolia: return arbitrumSepolia + case ChainId.BaseSepolia: + return baseSepolia + // Local networks case ChainId.Local: return localL1Network 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 0023574b53..fac6d8c971 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts @@ -11,7 +11,9 @@ import { localL1Network as local, localL2Network as arbitrumLocal, localL3Network as l3Local, - holesky + holesky, + base, + baseSepolia } from './wagmiAdditionalNetworks' import { isTestingEnvironment } from '../CommonUtils' import { getCustomChainsFromLocalStorage, ChainId } from '../networks' @@ -26,16 +28,22 @@ const wagmiOrbitChains = getOrbitChains().map(chain => getWagmiChain(chain.chainId) ) +const defaultChains = [ + // mainnet, arb1, & arb nova are for network switch tests + mainnet, + arbitrum, + arbitrumNova, + base, + // sepolia & arb sepolia are for tx history panel tests + sepolia, + arbitrumSepolia, + baseSepolia, + holesky +] + const chainList = isTestingEnvironment ? [ - // mainnet, arb1, & arb nova are for network switch tests - mainnet, - arbitrum, - arbitrumNova, - // sepolia & arb sepolia are for tx history panel tests - sepolia, - arbitrumSepolia, - holesky, + ...defaultChains, // Orbit chains ...wagmiOrbitChains, // add local environments during testing @@ -45,16 +53,7 @@ const chainList = isTestingEnvironment // user-added custom chains ...customChains ] - : [ - mainnet, - arbitrum, - arbitrumNova, - sepolia, - arbitrumSepolia, - holesky, - ...wagmiOrbitChains, - ...customChains - ] + : [...defaultChains, ...wagmiOrbitChains, ...customChains] const projectId = process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID! @@ -71,8 +70,10 @@ enum TargetChainKey { Ethereum = 'mainnet', ArbitrumOne = 'arbitrum-one', ArbitrumNova = 'arbitrum-nova', + Base = 'base', Sepolia = 'sepolia', - ArbitrumSepolia = 'arbitrum-sepolia' + ArbitrumSepolia = 'arbitrum-sepolia', + BaseSepolia = 'base-sepolia' } function sanitizeTargetChainKey(targetChainKey: string | null): TargetChainKey { @@ -100,11 +101,17 @@ function getChainId(targetChainKey: TargetChainKey): number { case TargetChainKey.ArbitrumNova: return ChainId.ArbitrumNova + case TargetChainKey.Base: + return ChainId.Base + case TargetChainKey.Sepolia: return ChainId.Sepolia case TargetChainKey.ArbitrumSepolia: return ChainId.ArbitrumSepolia + + case TargetChainKey.BaseSepolia: + return ChainId.BaseSepolia } } diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/wagmiAdditionalNetworks.ts b/packages/arb-token-bridge-ui/src/util/wagmi/wagmiAdditionalNetworks.ts index 6a915e10e0..a241bcc182 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/wagmiAdditionalNetworks.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/wagmiAdditionalNetworks.ts @@ -95,6 +95,28 @@ export const arbitrumSepolia: Chain = { } } +export const baseSepolia: Chain = { + id: ChainId.BaseSepolia, + name: 'Base Sepolia', + network: 'base-sepolia', + nativeCurrency: ether, + rpcUrls: { + default: { + http: [rpcURLs[ChainId.BaseSepolia]!] + }, + public: { + http: [rpcURLs[ChainId.BaseSepolia]!] + } + }, + blockExplorers: { + etherscan: { + name: 'Basescan', + url: explorerUrls[ChainId.BaseSepolia]! + }, + default: { name: 'Basescan', url: explorerUrls[ChainId.BaseSepolia]! } + } +} + export const arbitrumNova: Chain = { id: ChainId.ArbitrumNova, name: 'Arbitrum Nova', @@ -114,6 +136,25 @@ export const arbitrumNova: Chain = { } } +export const base: Chain = { + id: ChainId.Base, + name: 'Base', + network: 'base', + nativeCurrency: ether, + rpcUrls: { + default: { + http: [rpcURLs[ChainId.Base]!] + }, + public: { + http: [rpcURLs[ChainId.Base]!] + } + }, + blockExplorers: { + etherscan: { name: 'Basescan', url: explorerUrls[ChainId.Base]! }, + default: { name: 'Basescan', url: explorerUrls[ChainId.Base]! } + } +} + /** * For e2e testing */ diff --git a/packages/arb-token-bridge-ui/synpress.config.ts b/packages/arb-token-bridge-ui/synpress.config.ts index d42707dbaa..be4a2e51c5 100644 --- a/packages/arb-token-bridge-ui/synpress.config.ts +++ b/packages/arb-token-bridge-ui/synpress.config.ts @@ -69,9 +69,8 @@ export default defineConfig({ e2e: { async setupNodeEvents(on, config) { logsPrinter(on) - await registerLocalNetwork( - process.env.E2E_ORBIT_CUSTOM_GAS_TOKEN === 'true' - ) + + await registerLocalNetwork() if (!ethRpcUrl && !isOrbitTest) { throw new Error('NEXT_PUBLIC_LOCAL_ETHEREUM_RPC_URL variable missing.') diff --git a/packages/arb-token-bridge-ui/tests/e2e/specfiles.json b/packages/arb-token-bridge-ui/tests/e2e/specfiles.json index 8e740d8962..fd907011e2 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specfiles.json +++ b/packages/arb-token-bridge-ui/tests/e2e/specfiles.json @@ -5,13 +5,13 @@ "recordVideo": "false" }, { - "name": "Deposit ETH", - "file": "tests/e2e/specs/**/depositETH.cy.{js,jsx,ts,tsx}", + "name": "Deposit native token", + "file": "tests/e2e/specs/**/depositNativeToken.cy.{js,jsx,ts,tsx}", "recordVideo": "false" }, { - "name": "Withdraw ETH", - "file": "tests/e2e/specs/**/withdrawETH.cy.{js,jsx,ts,tsx}", + "name": "Withdraw native token", + "file": "tests/e2e/specs/**/withdrawNativeToken.cy.{js,jsx,ts,tsx}", "recordVideo": "false" }, { diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/approveToken.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/approveToken.cy.ts index e338075cf4..c96ceb903c 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/approveToken.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/approveToken.cy.ts @@ -2,7 +2,7 @@ import { importTokenThroughUI, ERC20TokenName, ERC20TokenSymbol, - getZeroToLessThanOneNativeToken, + getZeroToLessThanOneToken, getL1NetworkName, getL2NetworkName } from '../../support/common' @@ -11,8 +11,8 @@ const ERC20TokenAddressL1 = Cypress.env('ERC20_TOKEN_ADDRESS_PARENT_CHAIN') describe('Approve token for deposit', () => { // log in to metamask - const zeroToLessThanOneEth = getZeroToLessThanOneNativeToken('ETH') - const zeroToLessThanOneNativeToken = getZeroToLessThanOneNativeToken( + const zeroToLessThanOneEth = getZeroToLessThanOneToken('ETH') + const zeroToLessThanOneNativeToken = getZeroToLessThanOneToken( Cypress.env('NATIVE_TOKEN_SYMBOL') ) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts index a3d4a52b5a..6f002c0385 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts @@ -6,7 +6,7 @@ import { getL1NetworkName, getL2NetworkConfig, getL2NetworkName, - getZeroToLessThanOneNativeToken + getZeroToLessThanOneToken } from '../../support/common' import { formatAmount } from '../../../src/util/NumberUtils' @@ -17,9 +17,9 @@ describe('Batch Deposit', () => { childErc20Balance: string const nativeTokenSymbol = Cypress.env('NATIVE_TOKEN_SYMBOL') - const zeroToLessThanOneEth = getZeroToLessThanOneNativeToken('ETH') + const zeroToLessThanOneEth = getZeroToLessThanOneToken('ETH') const zeroToLessThanOneNativeToken = - getZeroToLessThanOneNativeToken(nativeTokenSymbol) + getZeroToLessThanOneToken(nativeTokenSymbol) beforeEach(() => { getInitialERC20Balance({ diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts index 81432d54ae..f655a787ce 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts @@ -2,9 +2,8 @@ * When user wants to bridge USDC through CCTP from L1 to L2 */ -import { getZeroToLessThanOneNativeToken } from '../../support/common' +import { getZeroToLessThanOneToken } from '../../support/common' import { CommonAddress } from '../../../src/util/CommonAddressUtils' -import { formatAmount } from 'packages/arb-token-bridge-ui/src/util/NumberUtils' // common function for this cctp deposit const confirmAndApproveCctpDeposit = () => { @@ -65,9 +64,7 @@ const confirmAndApproveCctpDeposit = () => { describe('Deposit USDC through CCTP', () => { // Happy Path const USDCAmountToSend = 0.0001 - const zeroToLessThanOneNativeToken = getZeroToLessThanOneNativeToken( - Cypress.env('NATIVE_TOKEN_SYMBOL') - ) + const zeroToLessThanOneETH = getZeroToLessThanOneToken('ETH') beforeEach(() => { cy.login({ networkType: 'parentChain', networkName: 'sepolia' }) @@ -81,8 +78,8 @@ describe('Deposit USDC through CCTP', () => { }) cy.typeAmount(USDCAmountToSend) - cy.findGasFeeSummary(zeroToLessThanOneNativeToken) - cy.findGasFeeForChain('Sepolia', zeroToLessThanOneNativeToken) + cy.findGasFeeSummary(zeroToLessThanOneETH) + cy.findGasFeeForChain('Sepolia', zeroToLessThanOneETH) cy.findGasFeeForChain( /You'll have to pay Arbitrum Sepolia gas fee upon claiming./i ) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts index 8b214f4c55..1cd9ac5c13 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts @@ -6,7 +6,7 @@ import { formatAmount } from '../../../src/util/NumberUtils' import { getInitialERC20Balance, getL1NetworkConfig, - getZeroToLessThanOneNativeToken, + getZeroToLessThanOneToken, moreThanZeroBalance, getL1NetworkName, getL2NetworkName, @@ -34,9 +34,9 @@ describe('Deposit Token', () => { const isOrbitTest = Cypress.env('ORBIT_TEST') == '1' const depositTime = isOrbitTest ? 'Less than a minute' : '9 minutes' const nativeTokenSymbol = Cypress.env('NATIVE_TOKEN_SYMBOL') - const zeroToLessThanOneEth = getZeroToLessThanOneNativeToken('ETH') + const zeroToLessThanOneEth = getZeroToLessThanOneToken('ETH') const zeroToLessThanOneNativeToken = - getZeroToLessThanOneNativeToken(nativeTokenSymbol) + getZeroToLessThanOneToken(nativeTokenSymbol) // Happy Path Object.keys(depositTestCases).forEach(tokenType => { diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/depositETH.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/depositNativeToken.cy.ts similarity index 91% rename from packages/arb-token-bridge-ui/tests/e2e/specs/depositETH.cy.ts rename to packages/arb-token-bridge-ui/tests/e2e/specs/depositNativeToken.cy.ts index eb4886ac9a..7e674337b4 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/depositETH.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/depositNativeToken.cy.ts @@ -5,15 +5,15 @@ import { getL1NetworkName, getL2NetworkName, - getZeroToLessThanOneNativeToken + getZeroToLessThanOneToken } from '../../support/common' describe('Deposit native token', () => { const ETHAmountToDeposit = 0.0001 const nativeTokenSymbol = Cypress.env('NATIVE_TOKEN_SYMBOL') - const zeroToLessThanOneEth = getZeroToLessThanOneNativeToken('ETH') + const zeroToLessThanOneEth = getZeroToLessThanOneToken('ETH') const zeroToLessThanOneNativeToken = - getZeroToLessThanOneNativeToken(nativeTokenSymbol) + getZeroToLessThanOneToken(nativeTokenSymbol) const isOrbitTest = Cypress.env('ORBIT_TEST') == '1' const depositTime = isOrbitTest ? 'Less than a minute' : '9 minutes' diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts index 3ee878e90b..bbc0ff696f 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts @@ -9,7 +9,7 @@ import { getL2NetworkConfig, getL1NetworkName, getL2NetworkName, - getZeroToLessThanOneNativeToken, + getZeroToLessThanOneToken, ERC20TokenSymbol } from '../../support/common' @@ -29,7 +29,7 @@ const withdrawalTestCases = { describe('Withdraw ERC20 Token', () => { const nativeTokenSymbol = Cypress.env('NATIVE_TOKEN_SYMBOL') const zeroToLessThanOneNativeToken = - getZeroToLessThanOneNativeToken(nativeTokenSymbol) + getZeroToLessThanOneToken(nativeTokenSymbol) let ERC20AmountToSend = Number((Math.random() * 0.001).toFixed(5)) // randomize the amount to be sure that previous transactions are not checked in e2e // when all of our tests need to run in a logged-in state // we have to make sure we preserve a healthy LocalStorage state diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawETH.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts similarity index 98% rename from packages/arb-token-bridge-ui/tests/e2e/specs/withdrawETH.cy.ts rename to packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts index da9ad51646..bee9061c24 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawETH.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts @@ -6,14 +6,14 @@ import { getInitialETHBalance, getL1NetworkName, getL2NetworkName, - getZeroToLessThanOneNativeToken + getZeroToLessThanOneToken } from '../../support/common' import { formatAmount } from '../../../src/util/NumberUtils' describe('Withdraw native token', () => { const nativeTokenSymbol = Cypress.env('NATIVE_TOKEN_SYMBOL') const zeroToLessThanOneNativeToken = - getZeroToLessThanOneNativeToken(nativeTokenSymbol) + getZeroToLessThanOneToken(nativeTokenSymbol) let ETHToWithdraw = Number((Math.random() * 0.001).toFixed(5)) // randomize the amount to be sure that previous transactions are not checked in e2e let l1EthBal: string diff --git a/packages/arb-token-bridge-ui/tests/support/commands.ts b/packages/arb-token-bridge-ui/tests/support/commands.ts index fd17e9ace8..a3ae2a0dea 100644 --- a/packages/arb-token-bridge-ui/tests/support/commands.ts +++ b/packages/arb-token-bridge-ui/tests/support/commands.ts @@ -141,7 +141,7 @@ export const searchAndSelectToken = ({ tokenAddress: string }) => { // Click on the native token dropdown (Select token button) - cy.findSelectTokenButton(Cypress.env('NATIVE_TOKEN_SYMBOL')).click() + cy.findSelectTokenButton(Cypress.env('NATIVE_TOKEN_SYMBOL') ?? 'ETH').click() // open the Select Token popup cy.findByPlaceholderText(/Search by token name/i) diff --git a/packages/arb-token-bridge-ui/tests/support/common.ts b/packages/arb-token-bridge-ui/tests/support/common.ts index e9ca75c4a2..4ddfe2f3b0 100644 --- a/packages/arb-token-bridge-ui/tests/support/common.ts +++ b/packages/arb-token-bridge-ui/tests/support/common.ts @@ -58,7 +58,7 @@ export const getL1NetworkConfig = (): NetworkConfig => { export const getL2NetworkConfig = (): NetworkConfig => { const isOrbitTest = Cypress.env('ORBIT_TEST') == '1' - const nativeTokenSymbol = Cypress.env('NATIVE_TOKEN_SYMBOL') + const nativeTokenSymbol = Cypress.env('NATIVE_TOKEN_SYMBOL') ?? 'ETH' const isCustomFeeToken = nativeTokenSymbol !== 'ETH' const l3Network = isCustomFeeToken @@ -106,7 +106,7 @@ export const invalidTokenAddress = utils.computeAddress(utils.randomBytes(32)) export const moreThanZeroBalance = /0(\.\d+)/ -export function getZeroToLessThanOneNativeToken(symbol: string) { +export function getZeroToLessThanOneToken(symbol: string) { return new RegExp(`0(\\.\\d+)*( ${symbol})`) } diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 0c53feef3e..e9d310eb63 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -17,6 +17,7 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", + "@arbitrum/sdk": "^4.0.1", "@octokit/rest": "^21.0.2", "axios": "^1.7.7", "commander": "^12.1.0", diff --git a/packages/scripts/src/addOrbitChain/schemas.ts b/packages/scripts/src/addOrbitChain/schemas.ts index 87ff3072de..34a3768518 100644 --- a/packages/scripts/src/addOrbitChain/schemas.ts +++ b/packages/scripts/src/addOrbitChain/schemas.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { constants, ethers } from "ethers"; import { getOctokit } from "@actions/github"; -export const TESTNET_PARENT_CHAIN_IDS = [11155111, 421614, 17000]; +export const TESTNET_PARENT_CHAIN_IDS = [11155111, 421614, 17000, 84532]; const ZERO_ADDRESS = constants.AddressZero; export const isValidAddress = (address: string): boolean => { @@ -128,6 +128,20 @@ export const chainSchema = z chainId: 17000, name: "Holesky", }; + case 8453: // Base + return { + rpcUrl: "https://mainnet.base.org", + blockExplorer: "https://basescan.io", + chainId: 8453, + name: "Base", + }; + case 84532: // Base Sepolia + return { + rpcUrl: "https://sepolia.base.org", + blockExplorer: "https://sepolia.basescan.io", + chainId: 84532, + name: "Base Sepolia", + }; default: throw new Error(`Unsupported parent chain ID: ${parentChainId}`); } diff --git a/packages/scripts/src/addOrbitChain/tests/__mocks__/chainDataMocks.ts b/packages/scripts/src/addOrbitChain/tests/__mocks__/chainDataMocks.ts index 564357146f..6b47b03366 100644 --- a/packages/scripts/src/addOrbitChain/tests/__mocks__/chainDataMocks.ts +++ b/packages/scripts/src/addOrbitChain/tests/__mocks__/chainDataMocks.ts @@ -180,7 +180,7 @@ export const mockIncomingChainData: IncomingChainData = { description: "This is a test chain.", chainLogo: "https://example.com/testchain.png", color: "#FF0000", - rpcUrl: "https://testrpc.com", + rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc", explorerUrl: "https://testexplorer.com", parentChainId: "421614", confirmPeriodBlocks: "150", @@ -191,7 +191,7 @@ export const mockIncomingChainData: IncomingChainData = { bridge: "0x0000000000000000000000000000000000000001", inbox: "0x0000000000000000000000000000000000000002", outbox: "0x0000000000000000000000000000000000000003", - rollup: "0x0000000000000000000000000000000000000004", + rollup: "0xeedE9367Df91913ab149e828BDd6bE336df2c892", sequencerInbox: "0x0000000000000000000000000000000000000005", parentGatewayRouter: "0x0000000000000000000000000000000000000009", childGatewayRouter: "0x0000000000000000000000000000000000000016", diff --git a/packages/scripts/src/addOrbitChain/tests/__snapshots__/transforms.test.ts.snap b/packages/scripts/src/addOrbitChain/tests/__snapshots__/transforms.test.ts.snap index 70e1a3d876..84d87c2798 100644 --- a/packages/scripts/src/addOrbitChain/tests/__snapshots__/transforms.test.ts.snap +++ b/packages/scripts/src/addOrbitChain/tests/__snapshots__/transforms.test.ts.snap @@ -56,11 +56,11 @@ exports[`Transforms > transformIncomingDataToOrbitChain > should transform incom "chainId": 1234567890, "confirmPeriodBlocks": 150, "ethBridge": { - "bridge": "0x0000000000000000000000000000000000000001", - "inbox": "0x0000000000000000000000000000000000000002", - "outbox": "0x0000000000000000000000000000000000000003", - "rollup": "0x0000000000000000000000000000000000000004", - "sequencerInbox": "0x0000000000000000000000000000000000000005", + "bridge": "0x6c7FAC4edC72E86B3388B48979eF37Ecca5027e6", + "inbox": "0x6396825803B720bc6A43c63caa1DcD7B31EB4dd0", + "outbox": "0xc7491a559b416540427f9f112C5c98b1412c5d51", + "rollup": "0xeedE9367Df91913ab149e828BDd6bE336df2c892", + "sequencerInbox": "0x529a2061A1973be80D315770bA9469F3Da40D938", }, "explorerUrl": "https://testexplorer.com", "isArbitrum": true, @@ -70,7 +70,7 @@ exports[`Transforms > transformIncomingDataToOrbitChain > should transform incom "nativeToken": "0x0000000000000000000000000000000000000006", "parentChainId": 421614, "retryableLifetimeSeconds": 604800, - "rpcUrl": "https://testrpc.com", + "rpcUrl": "https://sepolia-rollup.arbitrum.io/rpc", "slug": "test-chain", "tokenBridge": { "childCustomGateway": "0x0000000000000000000000000000000000000014", diff --git a/packages/scripts/src/addOrbitChain/tests/transforms.test.ts b/packages/scripts/src/addOrbitChain/tests/transforms.test.ts index c7f0690106..d0c078f157 100644 --- a/packages/scripts/src/addOrbitChain/tests/transforms.test.ts +++ b/packages/scripts/src/addOrbitChain/tests/transforms.test.ts @@ -27,11 +27,11 @@ describe("Transforms", () => { }); describe("transformIncomingDataToOrbitChain", () => { - it("should transform incoming chain data to OrbitChain format", () => { + it("should transform incoming chain data to OrbitChain format", async () => { const chainLogoPath = "/images/mockChain_Logo.png"; const nativeTokenLogoPath = "/images/mockChain_NativeTokenLogo.png"; - const result = transformIncomingDataToOrbitChain( + const result = await transformIncomingDataToOrbitChain( mockIncomingChainData as IncomingChainData, chainLogoPath, nativeTokenLogoPath diff --git a/packages/scripts/src/addOrbitChain/transforms.ts b/packages/scripts/src/addOrbitChain/transforms.ts index e34dbf45a2..b90ee3893e 100644 --- a/packages/scripts/src/addOrbitChain/transforms.ts +++ b/packages/scripts/src/addOrbitChain/transforms.ts @@ -3,6 +3,8 @@ import * as core from "@actions/core"; import { warning } from "@actions/core"; +import { getArbitrumNetworkInformationFromRollup } from "@arbitrum/sdk"; +import { JsonRpcProvider } from "@ethersproject/providers"; import axios from "axios"; import * as fs from "fs"; import sharp from "sharp"; @@ -118,7 +120,7 @@ export const createAndValidateOrbitChain = async ( ) => { core.startGroup("Orbit Chain Creation and Validation"); console.log("Creating OrbitChain object..."); - const orbitChain = transformIncomingDataToOrbitChain( + const orbitChain = await transformIncomingDataToOrbitChain( validatedIncomingData, chainLogoPath, nativeTokenLogoPath @@ -131,7 +133,7 @@ export const createAndValidateOrbitChain = async ( }; export const updateAndValidateOrbitChainsList = async ( - orbitChain: ReturnType, + orbitChain: OrbitChain, targetJsonPath: string ) => { core.startGroup("Orbit ChainsList Update and Validation"); @@ -153,7 +155,7 @@ export const commitChangesAndCreatePR = async ( branchName: string, targetJsonPath: string, updatedOrbitChainsList: ReturnType, - orbitChain: ReturnType + orbitChain: OrbitChain ) => { core.startGroup("Commit Changes and Create Pull Request"); console.log("Preparing to commit changes..."); @@ -181,7 +183,7 @@ export const commitChangesAndCreatePR = async ( export const setOutputs = ( branchName: string, - orbitChain: ReturnType, + orbitChain: OrbitChain, targetJsonPath: string ) => { core.startGroup("Set Outputs"); @@ -334,23 +336,28 @@ export const fetchAndSaveImage = async ( return `/${imageSavePath}`; }; -export const transformIncomingDataToOrbitChain = ( +export const transformIncomingDataToOrbitChain = async ( chainData: IncomingChainData, chainLogoPath: string, nativeTokenLogoPath?: string -): OrbitChain => { +): Promise => { const parentChainId = parseInt(chainData.parentChainId, 10); const isTestnet = TESTNET_PARENT_CHAIN_IDS.includes(parentChainId); + const provider = new JsonRpcProvider(chainData.rpcUrl); + const rollupData = await getArbitrumNetworkInformationFromRollup( + chainData.rollup, + provider + ); return { chainId: parseInt(chainData.chainId, 10), - confirmPeriodBlocks: parseInt(chainData.confirmPeriodBlocks, 10), + confirmPeriodBlocks: rollupData.confirmPeriodBlocks, ethBridge: { - bridge: chainData.bridge, - inbox: chainData.inbox, - outbox: chainData.outbox, + bridge: rollupData.ethBridge.bridge, + inbox: rollupData.ethBridge.inbox, + outbox: rollupData.ethBridge.outbox, rollup: chainData.rollup, - sequencerInbox: chainData.sequencerInbox, + sequencerInbox: rollupData.ethBridge.sequencerInbox, }, nativeToken: chainData.nativeTokenAddress, explorerUrl: chainData.explorerUrl,