├── .nvmrc ├── patches └── .gitkeep ├── packages ├── arb-token-bridge-ui │ ├── public │ │ ├── _redirects │ │ ├── _headers │ │ ├── images │ │ │ ├── __auto-generated │ │ │ │ └── open-graph │ │ │ │ │ └── .gitkeep │ │ │ ├── l1.gif │ │ │ ├── l2.gif │ │ │ ├── ghst.png │ │ │ ├── sxLogo.png │ │ │ ├── xrLogo.png │ │ │ ├── DODOchain.png │ │ │ ├── SankoLogo.png │ │ │ ├── XCHAINLogo.png │ │ │ ├── bridge │ │ │ │ ├── hop.png │ │ │ │ ├── celer.png │ │ │ │ ├── lifi.webp │ │ │ │ ├── across.png │ │ │ │ ├── connext.png │ │ │ │ ├── router.webp │ │ │ │ ├── stargate.png │ │ │ │ └── synapse.png │ │ │ ├── explorer.gif │ │ │ ├── lists │ │ │ │ ├── cmc.png │ │ │ │ ├── uniswap.png │ │ │ │ └── ArbitrumLogo.png │ │ │ ├── re.al_Logo.png │ │ │ ├── EduChainLogo.png │ │ │ ├── PopApexLogo.webp │ │ │ ├── sxTokenLogo.png │ │ │ ├── xrTokenLogo.png │ │ │ ├── ArbitrumFaded.webp │ │ │ ├── MindChainLogo.webp │ │ │ ├── SXToronto_Logo.png │ │ │ ├── eclipse_bottom.png │ │ │ ├── CheeseChain_Logo.png │ │ │ ├── EduChainTokenLogo.png │ │ │ ├── GravityAlpha_Logo.png │ │ │ ├── PlumeDevnet_Logo.png │ │ │ ├── AlephZeroEVM_Logo.webp │ │ │ ├── DataLakeMainnet_Logo.png │ │ │ ├── PolterTestnetLogo.webp │ │ │ ├── SocialMainnet_Logo.webp │ │ │ ├── SocialTestnetLogo.webp │ │ │ ├── projects │ │ │ │ ├── banxa-logo.webp │ │ │ │ ├── bybit-logo.webp │ │ │ │ ├── okex-logo.webp │ │ │ │ ├── wirex-logo.webp │ │ │ │ ├── binance-logo.webp │ │ │ │ ├── bitget-logo.webp │ │ │ │ ├── fluidfi-logo.webp │ │ │ │ ├── kucoin-logo.webp │ │ │ │ ├── simplex-logo.webp │ │ │ │ ├── transak-logo.webp │ │ │ │ ├── crypto-com-logo.webp │ │ │ │ ├── mt-pelerin-logo.webp │ │ │ │ ├── ramp-network-logo.webp │ │ │ │ ├── cryptorefills-logo.webp │ │ │ │ └── mexc-exchange-logo.webp │ │ │ ├── unite-mainnet_Logo.png │ │ │ ├── unite-testnet_Logo.png │ │ │ ├── re.al_NativeTokenLogo.png │ │ │ ├── SXToronto_NativeTokenLogo.png │ │ │ ├── TransparentEthereumLogo.webp │ │ │ ├── CheeseChain_NativeTokenLogo.jpg │ │ │ ├── HeaderArbitrumLogoTestnet.webp │ │ │ ├── arbinaut-fixing-spaceship.webp │ │ │ ├── explore-arbitrum │ │ │ │ ├── defi │ │ │ │ │ ├── aave.webp │ │ │ │ │ ├── gmx.webp │ │ │ │ │ ├── 1inch.webp │ │ │ │ │ ├── curve.webp │ │ │ │ │ ├── dopex.webp │ │ │ │ │ ├── sperax.webp │ │ │ │ │ ├── uniswap.webp │ │ │ │ │ ├── balancer.webp │ │ │ │ │ ├── jonesdao.webp │ │ │ │ │ ├── kyberswap.webp │ │ │ │ │ ├── sushiswap.webp │ │ │ │ │ ├── treasuredao.webp │ │ │ │ │ ├── beefy-finance.webp │ │ │ │ │ └── yearn-finance.webp │ │ │ │ └── nft │ │ │ │ │ ├── realm.webp │ │ │ │ │ ├── arbibots.png │ │ │ │ │ ├── arbidudes.png │ │ │ │ │ ├── farmland.webp │ │ │ │ │ ├── mithical.webp │ │ │ │ │ ├── opensea.webp │ │ │ │ │ ├── battlefly.webp │ │ │ │ │ ├── castledao.webp │ │ │ │ │ ├── city-clash.webp │ │ │ │ │ ├── smithydao.webp │ │ │ │ │ ├── toadstoolz.webp │ │ │ │ │ ├── bridgeworld.webp │ │ │ │ │ ├── diamond-pepes.webp │ │ │ │ │ ├── randomwalknft.webp │ │ │ │ │ ├── smol-bodies.webp │ │ │ │ │ ├── smol-brains.webp │ │ │ │ │ ├── gmx-blueberry-club.webp │ │ │ │ │ ├── tales-of-elleria.webp │ │ │ │ │ └── the-lost-donkeys.webp │ │ │ ├── GravityAlpha_NativeTokenLogo.png │ │ │ ├── DataLakeMainnet_NativeTokenLogo.png │ │ │ ├── XaiLogo.svg │ │ │ ├── RoundedTab.svg │ │ │ ├── BaseWhite.svg │ │ │ ├── GeistMainnetLogo.svg │ │ │ ├── copy.svg │ │ │ ├── XMTPLogo.svg │ │ │ ├── EthereumLogo.svg │ │ │ ├── SuperpositionLogo.svg │ │ │ ├── LightningIcon.svg │ │ │ ├── sidebar │ │ │ │ ├── gethelp.svg │ │ │ │ ├── missions.svg │ │ │ │ ├── bridge.svg │ │ │ │ ├── tools.svg │ │ │ │ ├── projects.svg │ │ │ │ └── home.svg │ │ │ ├── RARIMainnetLogo.svg │ │ │ ├── ApeChainLogo.svg │ │ │ ├── GoogleCalendar.svg │ │ │ ├── arrows.svg │ │ │ ├── UsdcLogo.svg │ │ │ ├── header │ │ │ │ ├── headerLogo_governance.svg │ │ │ │ └── headerLogo_help.svg │ │ │ └── ArbitrumOneLogo.svg │ │ ├── robots.txt │ │ ├── logo.png │ │ ├── og-image.jpg │ │ ├── icons │ │ │ ├── discord.webp │ │ │ ├── twitter.webp │ │ │ └── wallet.svg │ │ ├── manifest.json │ │ └── tokenLists │ │ │ └── 660279_default.json │ ├── src │ │ ├── defaults.ts │ │ ├── font │ │ │ ├── Unica77LL-Medium.otf │ │ │ ├── Unica77LL-Regular.otf │ │ │ ├── Unica77LLWeb-Light.woff2 │ │ │ ├── Unica77LLWeb-Medium.woff2 │ │ │ └── Unica77LLWeb-Regular.woff2 │ │ ├── state │ │ │ ├── app │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── components │ │ │ ├── common │ │ │ │ ├── ExternalLink.tsx │ │ │ │ ├── CustomBoringAvatar.tsx │ │ │ │ ├── SearchPanel │ │ │ │ │ └── SearchPanelUtils.ts │ │ │ │ ├── Tooltip.tsx │ │ │ │ ├── Tab.tsx │ │ │ │ ├── atoms │ │ │ │ │ ├── Loader.tsx │ │ │ │ │ └── Toast.tsx │ │ │ │ ├── HeaderConnectWalletButton.tsx │ │ │ │ ├── StatusBadge.tsx │ │ │ │ ├── TestnetToggle.tsx │ │ │ │ ├── NetworkImage.tsx │ │ │ │ ├── Checkbox.tsx │ │ │ │ ├── NoteBox.tsx │ │ │ │ ├── Notifications.tsx │ │ │ │ ├── SafeImage.tsx │ │ │ │ └── Transition.tsx │ │ │ ├── TransactionHistory │ │ │ │ ├── CustomMessageWarning.tsx │ │ │ │ ├── TransactionHistory.tsx │ │ │ │ ├── TransactionsTableExternalLink.tsx │ │ │ │ └── EmptyTransactionHistory.tsx │ │ │ ├── TransferPanel │ │ │ │ ├── TransferPanelMain │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── useNativeCurrencyBalances.ts │ │ │ │ ├── SecurityLabels.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── useAmountBigNumber.ts │ │ │ │ │ ├── useSelectedTokenIsWithdrawOnly.ts │ │ │ │ │ ├── useIsCctpTransfer.ts │ │ │ │ │ └── useIsTransferAllowed.ts │ │ │ │ ├── TransferPanelUtils.ts │ │ │ │ ├── NativeCurrencyPrice.tsx │ │ │ │ ├── useTransferReadinessUtils.ts │ │ │ │ └── CctpTabContent.tsx │ │ │ ├── Sidebar │ │ │ │ ├── AppSidebar.tsx │ │ │ │ ├── AppMobileSidebar.tsx │ │ │ │ └── AccountMenuItem.tsx │ │ │ ├── syncers │ │ │ │ ├── ArbTokenBridgeStoreSync.tsx │ │ │ │ ├── useBalanceUpdater.tsx │ │ │ │ └── TokenListSyncer.tsx │ │ │ ├── App │ │ │ │ └── BlockedDialog.tsx │ │ │ └── TopNavBar.tsx │ │ ├── util │ │ │ ├── ExponentialBackoffUtils.ts │ │ │ ├── deposits │ │ │ │ └── __tests__ │ │ │ │ │ ├── fetchEthDepositsToCustomDestinationTestHelpers.ts │ │ │ │ │ ├── fetchDepositsTestHelpers.ts │ │ │ │ │ └── fetchEthDepositsToCustomDestinationFromSubgraph.test.ts │ │ │ ├── walletConnectUtils.ts │ │ │ ├── SentryUtils.ts │ │ │ ├── isUserRejectedError.ts │ │ │ ├── CommonUtils.ts │ │ │ ├── isDepositMode.ts │ │ │ ├── cctp │ │ │ │ └── getAttestationHashAndMessageFromReceipt.ts │ │ │ ├── withdrawals │ │ │ │ ├── __tests__ │ │ │ │ │ ├── fetchETHWithdrawalsTestHelpers.ts │ │ │ │ │ └── fetchWithdrawalsTestHelpers.ts │ │ │ │ └── fetchETHWithdrawalsFromEventLogs.ts │ │ │ ├── TokenApprovalUtils.ts │ │ │ ├── TokenTransferDisabledUtils.ts │ │ │ ├── CommonAddressUtils.ts │ │ │ ├── xErc20Utils.ts │ │ │ ├── AddressUtils.ts │ │ │ ├── TokenTeleportEnabledUtils.ts │ │ │ ├── fetchL2Gateways.ts │ │ │ ├── wagmi │ │ │ │ └── getWagmiChain.ts │ │ │ └── L2NativeUtils.ts │ │ ├── pages │ │ │ ├── _document.tsx │ │ │ ├── restricted.tsx │ │ │ ├── 404.tsx │ │ │ └── api │ │ │ │ ├── status.ts │ │ │ │ └── denylist.ts │ │ ├── hooks │ │ │ ├── TransferPanel │ │ │ │ ├── useSelectedTokenDecimals.ts │ │ │ │ ├── useIsBatchTransferSupported.ts │ │ │ │ ├── useImportTokenModal.ts │ │ │ │ ├── useSetInputAmount.ts │ │ │ │ └── useChainIdsForNetworkSelection.ts │ │ │ ├── useNewFeatureIndicator.ts │ │ │ ├── useIsTestnetMode.ts │ │ │ ├── useChainId.ts │ │ │ ├── useTokenDecimals.ts │ │ │ ├── useSourceChainNativeCurrencyDecimals.ts │ │ │ ├── CCTP │ │ │ │ └── useCCTPIsBlocked.ts │ │ │ ├── __tests__ │ │ │ │ └── helpers.ts │ │ │ ├── useGasPrice.ts │ │ │ ├── useTheme.ts │ │ │ ├── useDestinationChainStyle.ts │ │ │ ├── useETHPrice.ts │ │ │ ├── useERC20L1Address.ts │ │ │ └── useAccountType.ts │ │ └── constants.ts │ ├── setupTests.ts │ ├── postcss.config.js │ ├── prettier.config.js │ ├── tests │ │ ├── e2e │ │ │ ├── cctp.json │ │ │ ├── tsconfig.json │ │ │ ├── getCommonSynpressConfig.ts │ │ │ ├── .eslintrc.js │ │ │ ├── README.md │ │ │ ├── specs │ │ │ │ ├── switchNetworks.cy.ts │ │ │ │ └── txHistory.cy.ts │ │ │ └── specfiles.json │ │ └── support │ │ │ └── index.ts │ ├── additional.d.ts │ ├── tsconfig.json │ ├── scripts │ │ ├── tsconfig.json │ │ ├── generateOrbitChainsToMonitor.ts │ │ ├── utils.ts │ │ └── generateCoreChainsToMonitor.ts │ ├── .gitignore │ ├── .env.local.sample │ ├── .e2e.env.sample │ ├── jest.config.js │ └── next.config.js └── scripts │ ├── .gitignore │ ├── src │ ├── addOrbitChain │ │ ├── tests │ │ │ ├── __mocks__ │ │ │ │ ├── test-image.jpg │ │ │ │ └── mockChains.json │ │ │ └── setup.ts │ │ ├── github.ts │ │ ├── provider.ts │ │ └── index.ts │ └── index.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── package.json ├── .editorconfig ├── .prettierignore ├── LICENSE ├── .gitignore ├── audit-ci.jsonc ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── feature-request.yml ├── actions │ ├── restore-build-artifacts │ │ └── action.yml │ └── check-files │ │ └── action.yml ├── workflows │ ├── assertion-monitor.yml │ ├── batch-poster-monitor.yml │ ├── retryable-monitor.yml │ ├── pr-title-check.yml │ ├── run-cctp-tests.yml │ ├── monitor-config.json │ ├── formatSpecfiles.js │ └── build.yml └── PULL_REQUEST_TEMPLATE.md ├── .eslintignore ├── vercel.json ├── tsconfig.base.json ├── tsconfig.eslint.json ├── .clabot ├── .eslintrc.js └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /patches/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/_headers: -------------------------------------------------------------------------------- 1 | /* 2 | Access-Control-Allow-Origin: * -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/__auto-generated/open-graph/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | node_modules 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/defaults.ts: -------------------------------------------------------------------------------- 1 | export const defaultErc20Decimals = 0 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/.next 2 | **/out 3 | **/build 4 | **/dist 5 | .github/ 6 | **/src/styles/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/setupTests.ts: -------------------------------------------------------------------------------- 1 | import 'cross-fetch/polyfill' 2 | 3 | jest.setTimeout(25000) 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | .idea 4 | .DS_Store 5 | tsconfig.tsbuildinfo 6 | __auto-generated-denylist.json 7 | 8 | -------------------------------------------------------------------------------- /audit-ci.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json", 3 | "low": true, 4 | "allowlist": [] 5 | } 6 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/l1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/l1.gif -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/l2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/l2.gif -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/og-image.jpg -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/ghst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/ghst.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/icons/discord.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/icons/discord.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/icons/twitter.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/icons/twitter.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/sxLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/sxLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/xrLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/xrLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/DODOchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/DODOchain.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/SankoLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/SankoLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/XCHAINLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/XCHAINLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/bridge/hop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/bridge/hop.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explorer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explorer.gif -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/lists/cmc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/lists/cmc.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/re.al_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/re.al_Logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/EduChainLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/EduChainLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/PopApexLogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/PopApexLogo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/bridge/celer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/bridge/celer.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/bridge/lifi.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/bridge/lifi.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/sxTokenLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/sxTokenLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/xrTokenLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/xrTokenLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/font/Unica77LL-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/src/font/Unica77LL-Medium.otf -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/font/Unica77LL-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/src/font/Unica77LL-Regular.otf -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/ArbitrumFaded.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/ArbitrumFaded.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/MindChainLogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/MindChainLogo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/SXToronto_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/SXToronto_Logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/bridge/across.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/bridge/across.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/bridge/connext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/bridge/connext.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/bridge/router.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/bridge/router.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/bridge/stargate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/bridge/stargate.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/bridge/synapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/bridge/synapse.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/eclipse_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/eclipse_bottom.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/lists/uniswap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/lists/uniswap.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/font/Unica77LLWeb-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/src/font/Unica77LLWeb-Light.woff2 -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/CheeseChain_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/CheeseChain_Logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/EduChainTokenLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/EduChainTokenLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/GravityAlpha_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/GravityAlpha_Logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/PlumeDevnet_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/PlumeDevnet_Logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/font/Unica77LLWeb-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/src/font/Unica77LLWeb-Medium.woff2 -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/font/Unica77LLWeb-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/src/font/Unica77LLWeb-Regular.woff2 -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/AlephZeroEVM_Logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/AlephZeroEVM_Logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/DataLakeMainnet_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/DataLakeMainnet_Logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/PolterTestnetLogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/PolterTestnetLogo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/SocialMainnet_Logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/SocialMainnet_Logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/SocialTestnetLogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/SocialTestnetLogo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/lists/ArbitrumLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/lists/ArbitrumLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/banxa-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/banxa-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/bybit-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/bybit-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/okex-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/okex-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/wirex-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/wirex-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/unite-mainnet_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/unite-mainnet_Logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/unite-testnet_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/unite-testnet_Logo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/state/app/index.ts: -------------------------------------------------------------------------------- 1 | import * as actions from './actions' 2 | import { state } from './state' 3 | 4 | export const config = { 5 | state, 6 | actions 7 | } 8 | -------------------------------------------------------------------------------- /packages/scripts/src/addOrbitChain/tests/__mocks__/test-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/scripts/src/addOrbitChain/tests/__mocks__/test-image.jpg -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/binance-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/binance-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/bitget-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/bitget-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/fluidfi-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/fluidfi-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/kucoin-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/kucoin-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/simplex-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/simplex-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/transak-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/transak-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/re.al_NativeTokenLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/re.al_NativeTokenLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/SXToronto_NativeTokenLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/SXToronto_NativeTokenLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/TransparentEthereumLogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/TransparentEthereumLogo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/crypto-com-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/crypto-com-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/mt-pelerin-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/mt-pelerin-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/CheeseChain_NativeTokenLogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/CheeseChain_NativeTokenLogo.jpg -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/HeaderArbitrumLogoTestnet.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/HeaderArbitrumLogoTestnet.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/arbinaut-fixing-spaceship.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/arbinaut-fixing-spaceship.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/aave.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/aave.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/gmx.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/gmx.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/realm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/realm.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/ramp-network-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/ramp-network-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/GravityAlpha_NativeTokenLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/GravityAlpha_NativeTokenLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/1inch.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/1inch.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/curve.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/curve.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/dopex.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/dopex.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/sperax.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/sperax.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/uniswap.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/uniswap.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/arbibots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/arbibots.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/arbidudes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/arbidudes.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/farmland.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/farmland.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/mithical.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/mithical.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/opensea.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/opensea.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/cryptorefills-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/cryptorefills-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/projects/mexc-exchange-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/projects/mexc-exchange-logo.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/DataLakeMainnet_NativeTokenLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/DataLakeMainnet_NativeTokenLogo.png -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/balancer.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/balancer.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/jonesdao.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/jonesdao.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/kyberswap.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/kyberswap.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/sushiswap.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/sushiswap.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/battlefly.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/battlefly.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/castledao.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/castledao.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/city-clash.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/city-clash.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/smithydao.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/smithydao.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/toadstoolz.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/toadstoolz.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/treasuredao.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/treasuredao.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/bridgeworld.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/bridgeworld.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/diamond-pepes.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/diamond-pepes.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/randomwalknft.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/randomwalknft.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/smol-bodies.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/smol-bodies.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/smol-brains.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/smol-brains.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/beefy-finance.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/beefy-finance.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/yearn-finance.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/defi/yearn-finance.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/gmx-blueberry-club.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/gmx-blueberry-club.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/tales-of-elleria.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/tales-of-elleria.webp -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/the-lost-donkeys.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShumCr/arbitrum-token-bridge/HEAD/packages/arb-token-bridge-ui/public/images/explore-arbitrum/nft/the-lost-donkeys.webp -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Arbitrum Bridge UI Support 4 | url: https://support.arbitrum.io/hc/en-gb/requests/new?ticket_form_id=18155929976987 5 | about: Please ask your specific questions here. 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | synpress.config.ts 4 | tailwind.config.js 5 | 6 | packages/arb-token-bridge/craco.config.js 7 | packages/arb-token-bridge/prettier.config.js 8 | packages/arb-token-bridge/build/static/js/*.js 9 | packages/arb-token-bridge/tests/**/*.ts 10 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/XaiLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/scripts/src/addOrbitChain/tests/setup.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | // Load environment variables for tests 4 | dotenv.config(); 5 | 6 | // Set GITHUB_TOKEN to "test" if it's not already set 7 | const githubToken = process.env.GITHUB_TOKEN || "test"; 8 | process.env.GITHUB_TOKEN = githubToken; 9 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/RoundedTab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 2, 3 | useTabs: false, 4 | semi: false, 5 | singleQuote: true, 6 | bracketSpacing: true, 7 | arrowParens: 'avoid', 8 | trailingComma: 'none', 9 | 10 | // Plugins 11 | plugins: [require('prettier-plugin-tailwindcss')] 12 | } 13 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/e2e/cctp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Deposit Cctp", 4 | "file": "tests/e2e/specs/**/depositCctp.cy.{js,jsx,ts,tsx}", 5 | "recordVideo": false 6 | }, 7 | { 8 | "name": "Withdraw Cctp", 9 | "file": "tests/e2e/specs/**/withdrawCctp.cy.{js,jsx,ts,tsx}", 10 | "recordVideo": false 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/BaseWhite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | export function ExternalLink({ 2 | children, 3 | href, 4 | ...props 5 | }: React.AnchorHTMLAttributes) { 6 | if (!href) { 7 | return children 8 | } 9 | 10 | return ( 11 | 12 | {children} 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransactionHistory/CustomMessageWarning.tsx: -------------------------------------------------------------------------------- 1 | export const CustomMessageWarning = ({ 2 | children 3 | }: { 4 | children: React.ReactNode 5 | }) => { 6 | return ( 7 |
8 | {children} 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Arb Bridge", 3 | "name": "Arbitrum Token Bridge", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.github/actions/restore-build-artifacts/action.yml: -------------------------------------------------------------------------------- 1 | name: Restore build artifacts 2 | description: Restore build artifacts 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Restore build artifacts 8 | uses: actions/cache/restore@v4 9 | with: 10 | path: | 11 | ./packages/arb-token-bridge-ui/build 12 | key: build-artifacts-${{ github.run_id }} 13 | fail-on-cache-miss: true 14 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/utils.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '../../../util/networks' 2 | 3 | export enum NetworkType { 4 | parentChain = 'parentChain', 5 | childChain = 'childChain' 6 | } 7 | 8 | export function shouldOpenOneNovaDialog(selectedChainIds: number[]) { 9 | return [ChainId.ArbitrumOne, ChainId.ArbitrumNova].every(chainId => 10 | selectedChainIds.includes(chainId) 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/GeistMainnetLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "../../../../", 4 | "types": [ 5 | "cypress.d.ts", 6 | "@synthetixio/synpress/support", 7 | "cypress-wait-until", 8 | "@testing-library/cypress" 9 | ], 10 | "outDir": "./output", 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true 13 | }, 14 | "include": ["**/*.*"], 15 | "exclude": ["node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/scripts/src/addOrbitChain/github.ts: -------------------------------------------------------------------------------- 1 | import { context, getOctokit } from "@actions/github"; 2 | import { Issue } from "./schemas"; 3 | 4 | const github = getOctokit(process.env.GITHUB_TOKEN || ""); 5 | 6 | export const getIssue = async (issueNumber: string): Promise => { 7 | const response = await github.rest.issues.get({ 8 | ...context.repo, 9 | issue_number: Number(issueNumber), 10 | }); 11 | return response.data as Issue; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/CustomBoringAvatar.tsx: -------------------------------------------------------------------------------- 1 | import BoringAvatar from 'boring-avatars' 2 | 3 | export function CustomBoringAvatar({ 4 | size, 5 | name 6 | }: { 7 | size: number 8 | name?: string 9 | }) { 10 | return ( 11 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/ExponentialBackoffUtils.ts: -------------------------------------------------------------------------------- 1 | import { backOff as _backOff, BackoffOptions } from 'exponential-backoff' 2 | 3 | const backoffOptions: BackoffOptions = { 4 | startingDelay: 1_000, 5 | timeMultiple: 1.5 6 | } 7 | 8 | export function backOff(request: () => Promise): Promise { 9 | return _backOff(request, backoffOptions) 10 | } 11 | 12 | export function wait(ms: number) { 13 | return new Promise(resolve => setTimeout(resolve, ms)) 14 | } 15 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/scripts/src/addOrbitChain/tests/__mocks__/mockChains.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainnet": [ 3 | { 4 | "chainId": 42161, 5 | "name": "Arbitrum One", 6 | "explorerUrl": "https://arbiscan.io", 7 | "rpcUrl": "https://arb1.arbitrum.io/rpc" 8 | } 9 | ], 10 | "testnet": [ 11 | { 12 | "chainId": 421613, 13 | "name": "Arbitrum Goerli", 14 | "explorerUrl": "https://goerli.arbiscan.io", 15 | "rpcUrl": "https://goerli-rollup.arbitrum.io/rpc" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "src": "/token-list-42161.json", 5 | "status": 308, 6 | "headers": { 7 | "Location": "https://tokenlist.arbitrum.io/ArbTokenLists/arbed_arb_whitelist_era.json", 8 | "Access-Control-Allow-Credentials": "true", 9 | "Access-Control-Allow-Headers": "*", 10 | "Access-Control-Allow-Origin": "*", 11 | "Access-Control-Allow-Methods": "GET", 12 | "Referrer-Policy": "origin-when-cross-origin" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchEthDepositsToCustomDestinationTestHelpers.ts: -------------------------------------------------------------------------------- 1 | const sender = '0xEF733aDA13D6598bC7340852Bc8fd7E04d9EAc55' 2 | 3 | const baseQuery = { 4 | sender, 5 | l2ChainId: 421614, 6 | pageSize: 100 7 | } 8 | 9 | export function getQueryCoveringNitroWithoutResults() { 10 | return { ...baseQuery, fromBlock: 6799500, toBlock: 6800000 } 11 | } 12 | 13 | export function getQueryCoveringNitroWithResults() { 14 | return { ...baseQuery, fromBlock: 6800000, toBlock: 6800220 } 15 | } 16 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/walletConnectUtils.ts: -------------------------------------------------------------------------------- 1 | export function onDisconnectHandler() { 2 | if (typeof indexedDB === 'undefined') { 3 | return 4 | } 5 | 6 | if (typeof localStorage === 'undefined') { 7 | return 8 | } 9 | 10 | const isWalletConnect = 11 | localStorage.getItem('wagmi.wallet') === '"walletConnect"' 12 | 13 | if (!isWalletConnect) { 14 | return 15 | } 16 | 17 | indexedDB.deleteDatabase('WALLET_CONNECT_V2_INDEXED_DB') 18 | 19 | setTimeout(() => window.location.reload(), 100) 20 | } 21 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/TransferPanel/useSelectedTokenDecimals.ts: -------------------------------------------------------------------------------- 1 | import { useAppState } from '../../state' 2 | import { useSourceChainNativeCurrencyDecimals } from '../useSourceChainNativeCurrencyDecimals' 3 | 4 | export function useSelectedTokenDecimals() { 5 | const { 6 | app: { selectedToken } 7 | } = useAppState() 8 | const nativeCurrencyDecimalsOnSourceChain = 9 | useSourceChainNativeCurrencyDecimals() 10 | 11 | return selectedToken 12 | ? selectedToken.decimals 13 | : nativeCurrencyDecimalsOnSourceChain 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/assertion-monitor.yml: -------------------------------------------------------------------------------- 1 | name: Assertion Monitor 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 */6 * * *" # Run every 6 hours 7 | 8 | jobs: 9 | run-monitoring: 10 | name: Assertion Monitor (${{ matrix.chain == 'orbit' && 'Orbit' || matrix.chain == 'core' && 'Core' || matrix.chain }}) 11 | strategy: 12 | matrix: 13 | chain: [core, orbit] 14 | uses: ./.github/workflows/monitoring.yml 15 | with: 16 | chain: ${{ matrix.chain }} 17 | monitor: assertion 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/batch-poster-monitor.yml: -------------------------------------------------------------------------------- 1 | name: Batch Poster Monitor 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 */6 * * *" # Run every 6 hours 7 | 8 | jobs: 9 | run-monitoring: 10 | name: Batch Poster Monitor (${{ matrix.chain == 'orbit' && 'Orbit' || matrix.chain == 'core' && 'Core' || matrix.chain }}) 11 | strategy: 12 | matrix: 13 | chain: [core, orbit] 14 | uses: ./.github/workflows/monitoring.yml 15 | with: 16 | chain: ${{ matrix.chain }} 17 | monitor: batch-poster 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /packages/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "lib": ["ES2020"], 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "outDir": "./dist", 12 | "rootDir": "./src", 13 | "resolveJsonModule": true, 14 | "types": ["node", "vitest/globals"] 15 | }, 16 | "include": ["src/**/*", "vitest.config.ts"], 17 | "exclude": ["node_modules", "**/*.test.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/retryable-monitor.yml: -------------------------------------------------------------------------------- 1 | name: Retryable Monitor 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "3 8 * * *" # Run once a day at 08:03am GMT 7 | 8 | jobs: 9 | run-retryable-monitoring: 10 | name: Retryable Monitor (${{ matrix.chain == 'orbit' && 'Orbit' || matrix.chain == 'core' && 'Core' || matrix.chain }}) 11 | strategy: 12 | matrix: 13 | chain: [core, orbit] 14 | uses: ./.github/workflows/monitoring.yml 15 | with: 16 | chain: ${{ matrix.chain }} 17 | monitor: retryable 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/pr-title-check.yml: -------------------------------------------------------------------------------- 1 | name: "PR Title Check" 2 | # PR title is checked according to https://www.conventionalcommits.org/en/v1.0.0/ 3 | 4 | on: 5 | pull_request_target: 6 | types: 7 | - opened 8 | - edited 9 | - synchronize 10 | merge_group: 11 | 12 | jobs: 13 | main: 14 | name: Validate PR title 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: amannn/action-semantic-pull-request@v5 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | subjectPattern: '^.{0,50}$' 22 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/SentryUtils.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/react' 2 | 3 | export function captureSentryErrorWithExtraData({ 4 | error, 5 | originFunction, 6 | additionalData 7 | }: { 8 | error: unknown 9 | originFunction: string 10 | additionalData?: Record 11 | }) { 12 | // tags only allow primitive values 13 | Sentry.getCurrentScope().setTag('origin function', originFunction) 14 | 15 | if (additionalData) { 16 | Sentry.getCurrentScope().setTags(additionalData) 17 | } 18 | Sentry.captureException(error) 19 | } 20 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/pages/restricted.tsx: -------------------------------------------------------------------------------- 1 | export default function RestrictedPage() { 2 | return ( 3 |
4 | 5 | Cannot connect from your location 6 | 7 |

8 | We apologize, this page is not available in certain jurisdictions due to 9 | legal restrictions. 10 |

11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noUncheckedIndexedAccess": true, 17 | "jsx": "react-jsx" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/state/index.ts: -------------------------------------------------------------------------------- 1 | import { IContext } from 'overmind' 2 | import { createActionsHook, createStateHook } from 'overmind-react' 3 | import { namespaced } from 'overmind/config' 4 | 5 | import * as app from './app' 6 | 7 | export const config = namespaced({ 8 | app: app.config 9 | }) 10 | 11 | export type Context = IContext<{ 12 | state: typeof config.state 13 | actions: typeof config.actions 14 | effects: typeof config.effects 15 | }> 16 | 17 | export const useAppState = createStateHook() 18 | export const useActions = createActionsHook() 19 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/SearchPanel/SearchPanelUtils.ts: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge' 2 | 3 | export const panelWrapperClassnames = twMerge( 4 | 'h-screen w-full bg-bg-gray-1 text-white border-gray-dark border w-screen', 5 | 'sm:h-auto sm:w-auto sm:min-w-[448px] sm:gap-3 sm:rounded sm:shadow-modal' 6 | ) 7 | 8 | export function onPopoverClose() { 9 | document.body.classList.remove('overflow-hidden', 'sm:overflow-visible') 10 | } 11 | 12 | export function onPopoverButtonClick() { 13 | document.body.classList.add('overflow-hidden', 'sm:overflow-visible') 14 | } 15 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/additional.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | import Image from 'next/image' 3 | const content: Image['src'] 4 | export default content 5 | } 6 | 7 | // the following list is for ci yarn lint to pass 8 | declare module '*.png' { 9 | import Image from 'next/image' 10 | const content: Image['src'] 11 | export default content 12 | } 13 | 14 | declare module '*.webp' { 15 | import Image from 'next/image' 16 | const content: Image['src'] 17 | export default content 18 | } 19 | 20 | declare module '*.json' { 21 | const value: any 22 | export default value 23 | } 24 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/isUserRejectedError.ts: -------------------------------------------------------------------------------- 1 | import { UserRejectedRequestError } from 'wagmi' 2 | 3 | /** 4 | * This should only be used to conditionally act on errors, 5 | * to display an error toast for example. 6 | * 7 | * Filtering of userRejectedError sent to sentry is done in _app.tsx 8 | */ 9 | function isUserRejectedError(error: any) { 10 | return ( 11 | error?.code === 4001 || 12 | error?.code === 'ACTION_REJECTED' || 13 | error?.message?.match(/User Cancelled/) || 14 | error instanceof UserRejectedRequestError 15 | ) 16 | } 17 | export { isUserRejectedError } 18 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useNewFeatureIndicator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Helpful when you want to nudge users towards a new UI feature using a tooltip or persistent banner. 3 | Sets a flag in local-storage for the `feature-key`. 4 | If the user checks out the new feature in UI, then our business logic can clear this update this flag to mark the feature as viewed once. 5 | */ 6 | 7 | import { useLocalStorage } from 'react-use' 8 | 9 | export const useNewFeatureIndicator = (featureKey: string) => { 10 | return useLocalStorage(`arbitrum:new:${featureKey}`) //eg. arbitrum:new:tx-history 11 | } 12 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/CommonUtils.ts: -------------------------------------------------------------------------------- 1 | export function shortenAddress(address: string) { 2 | const addressLength = address.length 3 | 4 | return `${address.substring(0, 5)}...${address.substring( 5 | addressLength - 4, 6 | addressLength 7 | )}` 8 | } 9 | 10 | export function shortenTxHash(txHash: string) { 11 | const txHashLength = txHash.length 12 | 13 | return `${txHash.substring(0, 7)}...${txHash.substring( 14 | txHashLength - 4, 15 | txHashLength 16 | )}` 17 | } 18 | 19 | export const isTestingEnvironment = 20 | !!window.Cypress || process.env.NODE_ENV !== 'production' 21 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import FixingSpaceship from '@/images/arbinaut-fixing-spaceship.webp' 3 | 4 | export default function Custom404Page() { 5 | return ( 6 |
7 | 404 8 |

Page not found in this solar system

9 | Arbinaut fixing a spaceship 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useIsTestnetMode.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | import { useNetworks } from './useNetworks' 3 | import { ChainId, isNetwork } from '../util/networks' 4 | 5 | export const useIsTestnetMode = () => { 6 | const [networks, setNetworks] = useNetworks() 7 | 8 | const isTestnetMode = isNetwork(networks.sourceChain.id).isTestnet 9 | 10 | const toggleTestnetMode = useCallback(() => { 11 | setNetworks({ 12 | sourceChainId: isTestnetMode ? ChainId.Ethereum : ChainId.Sepolia 13 | }) 14 | }, [isTestnetMode, setNetworks]) 15 | 16 | return [isTestnetMode, toggleTestnetMode] as const 17 | } 18 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/Sidebar/AppSidebar.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import dynamic from 'next/dynamic' 3 | import { usePostHog } from 'posthog-js/react' 4 | 5 | // Dynamically import the Sidebar component with SSR disabled 6 | const DynamicSidebar = dynamic( 7 | () => import('@offchainlabs/cobalt').then(mod => ({ default: mod.Sidebar })), 8 | { ssr: false } 9 | ) 10 | 11 | export const AppSidebar = () => { 12 | const posthog = usePostHog() 13 | return ( 14 |
15 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | // extend your base config to share compilerOptions, etc 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | // ensure that nobody can accidentally use this config for a build 6 | "noEmit": true 7 | }, 8 | "include": [ 9 | // whatever paths you intend to lint 10 | // adding eslintrc here fixes eslint from throwing an error 11 | // https://stackoverflow.com/questions/63118405/how-to-fix-eslintrc-the-file-does-not-match-your-project-config 12 | ".eslintrc.js", 13 | "./packages/arb-token-bridge-ui/tests/**/*.ts", 14 | "./packages/arb-token-bridge-ui/synpress*.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts: -------------------------------------------------------------------------------- 1 | const sender = '0x5d64a0fd6af0d76a7ed189d4061ffa6823fbf97e' 2 | 3 | const baseQuery = { 4 | sender, 5 | l2ChainId: 42161, 6 | pageSize: 100 7 | } 8 | 9 | export function getQueryCoveringClassicOnlyWithoutResults() { 10 | return { ...baseQuery, fromBlock: 0, toBlock: 14309825 } 11 | } 12 | 13 | export function getQueryCoveringClassicOnlyWithResults() { 14 | return { ...baseQuery, fromBlock: 14309825, toBlock: 14428639 } 15 | } 16 | 17 | export function getQueryCoveringClassicAndNitroWithResults() { 18 | return { ...baseQuery, fromBlock: 15362737, toBlock: 15517648 } 19 | } 20 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useChainId.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { providers } from 'ethers' 3 | 4 | export function useChainId({ provider }: { provider: providers.Provider }) { 5 | const [chainId, setChainId] = useState(undefined) 6 | 7 | useEffect(() => { 8 | let cancelled = false 9 | 10 | async function updateChainId() { 11 | const network = await provider.getNetwork() 12 | if (!cancelled) { 13 | setChainId(network.chainId) 14 | } 15 | } 16 | 17 | updateChainId() 18 | 19 | return () => { 20 | cancelled = true 21 | } 22 | }, [provider]) 23 | 24 | return chainId 25 | } 26 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useTokenDecimals.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | 3 | import { ArbTokenBridge } from './arbTokenBridge.types' 4 | import { defaultErc20Decimals } from '../defaults' 5 | 6 | const useTokenDecimals = ( 7 | bridgeTokens: ArbTokenBridge['bridgeTokens'], 8 | tokenAddress: string | null 9 | ) => { 10 | return useMemo(() => { 11 | if (typeof bridgeTokens === 'undefined') { 12 | return defaultErc20Decimals 13 | } 14 | 15 | if (!tokenAddress) { 16 | return defaultErc20Decimals 17 | } 18 | 19 | return bridgeTokens[tokenAddress]?.decimals ?? defaultErc20Decimals 20 | }, [bridgeTokens, tokenAddress]) 21 | } 22 | 23 | export { useTokenDecimals } 24 | -------------------------------------------------------------------------------- /packages/scripts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { resolve } from "path"; 3 | 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: resolve(__dirname, "src/index.ts"), 8 | formats: ["cjs", "es"], 9 | fileName: (format) => `scripts.${format}.js`, 10 | }, 11 | rollupOptions: { 12 | external: [ 13 | "@actions/core", 14 | "@actions/github", 15 | "axios", 16 | "fs", 17 | "commander", 18 | "sharp", 19 | "path", 20 | ], 21 | }, 22 | }, 23 | optimizeDeps: { 24 | exclude: ["sharp"], 25 | }, 26 | resolve: { 27 | alias: { 28 | path: "path", 29 | }, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": [ 4 | "src", 5 | "additional.d.ts", 6 | "next.config.js", 7 | "next-env.d.ts", 8 | ".next/types/**/*.ts", 9 | "build/types/**/*.ts" 10 | ], 11 | "compilerOptions": { 12 | "noEmit": true, 13 | "incremental": true, 14 | "jsx": "preserve", 15 | "paths": { 16 | "@/images/*": ["./public/images/*"], 17 | "@/icons/*": ["./public/icons/*"], 18 | "@/token-bridge-sdk/*": ["./src/token-bridge-sdk/*"] 19 | }, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "strictNullChecks": true 26 | }, 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/isDepositMode.ts: -------------------------------------------------------------------------------- 1 | import { isNetwork } from '../util/networks' 2 | 3 | export function isDepositMode({ 4 | sourceChainId, 5 | destinationChainId 6 | }: { 7 | sourceChainId: number 8 | destinationChainId: number 9 | }) { 10 | const { 11 | isEthereumMainnetOrTestnet: isSourceChainEthereum, 12 | isArbitrum: isSourceChainArbitrum, 13 | isBase: isSourceChainBase 14 | } = isNetwork(sourceChainId) 15 | const { isOrbitChain: isDestinationChainOrbit } = 16 | isNetwork(destinationChainId) 17 | 18 | const isDepositMode = 19 | isSourceChainEthereum || 20 | isSourceChainBase || 21 | (isSourceChainArbitrum && isDestinationChainOrbit) 22 | 23 | return isDepositMode 24 | } 25 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import Tippy from '@tippyjs/react' 2 | 3 | export type TooltipProps = { 4 | show?: boolean 5 | children: React.ReactNode 6 | content?: React.ReactNode 7 | wrapperClassName?: string 8 | theme?: 'light' | 'dark' 9 | } 10 | 11 | export function Tooltip({ 12 | show = true, 13 | content, 14 | wrapperClassName = 'w-max', 15 | theme = 'light', 16 | children 17 | }: TooltipProps): JSX.Element | null { 18 | if (!content) { 19 | return null 20 | } 21 | 22 | if (!show) { 23 | return <>{children} 24 | } 25 | 26 | return ( 27 | 28 |
{children}
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/e2e/getCommonSynpressConfig.ts: -------------------------------------------------------------------------------- 1 | export function getCommonSynpressConfig(shouldRecordVideo: boolean) { 2 | return { 3 | userAgent: 'synpress', 4 | retries: shouldRecordVideo ? 0 : 2, 5 | screenshotsFolder: 'cypress/screenshots', 6 | videosFolder: 'cypress/videos', 7 | video: shouldRecordVideo, 8 | screenshotOnRunFailure: true, 9 | chromeWebSecurity: true, 10 | modifyObstructiveCode: false, 11 | scrollBehavior: false, 12 | viewportWidth: 1366, 13 | viewportHeight: 850, 14 | env: { 15 | coverage: false 16 | }, 17 | defaultCommandTimeout: 30000, 18 | pageLoadTimeout: 30000, 19 | requestTimeout: 30000 20 | } as const satisfies Cypress.ConfigOptions 21 | } 22 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path') 3 | const synpressPath = path.join( 4 | process.cwd(), 5 | '/node_modules/@synthetixio/synpress' 6 | ) 7 | 8 | module.exports = { 9 | extends: `${synpressPath}/.eslintrc.js`, 10 | parserOptions: { 11 | project: path.resolve( 12 | './packages/arb-token-bridge-ui/tests/e2e/tsconfig.json' 13 | ) 14 | }, 15 | rules: { 16 | 'jest/expect-expect': [ 17 | 'off', 18 | { 19 | assertFunctionNames: ['expect'] 20 | } 21 | ], 22 | // Cypress awaiting by default 23 | 'testing-library/await-async-query': 'off', 24 | '@typescript-eslint/no-empty-function': 'off' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/syncers/ArbTokenBridgeStoreSync.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { 3 | useArbTokenBridge, 4 | TokenBridgeParams 5 | } from '../../hooks/useArbTokenBridge' 6 | 7 | import { useActions } from '../../state' 8 | 9 | // Syncs the arbTokenBridge data with the global store, so we dont have to drill with props but use store hooks to get data 10 | export function ArbTokenBridgeStoreSync({ 11 | tokenBridgeParams 12 | }: { 13 | tokenBridgeParams: TokenBridgeParams 14 | }): JSX.Element { 15 | const actions = useActions() 16 | const arbTokenBridge = useArbTokenBridge(tokenBridgeParams) 17 | 18 | useEffect(() => { 19 | actions.app.setArbTokenBridge(arbTokenBridge) 20 | }, [arbTokenBridge]) 21 | 22 | return <> 23 | } 24 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useSourceChainNativeCurrencyDecimals.ts: -------------------------------------------------------------------------------- 1 | import { isNetwork } from '../util/networks' 2 | import { useNativeCurrency } from './useNativeCurrency' 3 | import { useNetworks } from './useNetworks' 4 | import { useNetworksRelationship } from './useNetworksRelationship' 5 | 6 | export const useSourceChainNativeCurrencyDecimals = () => { 7 | const [networks] = useNetworks() 8 | const { childChainProvider } = useNetworksRelationship(networks) 9 | const nativeCurrency = useNativeCurrency({ 10 | provider: childChainProvider 11 | }) 12 | const { isOrbitChain: isSourceChainOrbit } = isNetwork( 13 | networks.sourceChain.id 14 | ) 15 | 16 | if (isSourceChainOrbit) { 17 | return 18 18 | } 19 | 20 | return nativeCurrency.decimals 21 | } 22 | -------------------------------------------------------------------------------- /.clabot: -------------------------------------------------------------------------------- 1 | { 2 | "contributors": "https://api.github.com/repos/OffchainLabs/clabot-config/contents/apache-contributors.json", 3 | "message": "We require contributors to sign our Contributor License Agreement. In order for us to review and merge your code, please sign one of the linked documents below to get yourself added. If you're an independent Individual please sign this form: https://na3.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=1353a816-a9c1-47ba-847e-ec79f0f23d31&env=na3&acct=6e152afc-6284-44af-a4c1-d8ef291db402&v=2. If you're with a company (corporate) please sign this form: https://na3.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=2b5fe8ba-51d4-4980-b4ee-605d66e675d4&env=na3&acct=6e152afc-6284-44af-a4c1-d8ef291db402&v=2. To agree to the CLA license, please fill out one of the attached forms." 4 | } 5 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/Tab.tsx: -------------------------------------------------------------------------------- 1 | import { Tab } from '@headlessui/react' 2 | import { forwardRef, PropsWithChildren } from 'react' 3 | 4 | export type TabButtonProps = PropsWithChildren< 5 | React.ButtonHTMLAttributes 6 | > 7 | 8 | export const TabButton = forwardRef( 9 | (props, ref) => { 10 | const tabButtonClassName = 11 | 'text-white px-3 mr-2 pb-1 ui-selected:border-b-4 ui-selected:border-white ui-not-selected:text-white/80 arb-hover' 12 | 13 | return ( 14 | 19 | {props.children} 20 | 21 | ) 22 | } 23 | ) 24 | 25 | TabButton.displayName = 'TabButton' 26 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/CCTP/useCCTPIsBlocked.ts: -------------------------------------------------------------------------------- 1 | import useSWRImmutable from 'swr/immutable' 2 | import { ChainId } from '../../util/networks' 3 | import { getCctpUtils } from '@/token-bridge-sdk/cctp' 4 | 5 | export function useCCTPIsBlocked() { 6 | const { fetchAttestation } = getCctpUtils({ sourceChainId: ChainId.Ethereum }) 7 | return useSWRImmutable(['cctp-check'], async () => { 8 | // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#checking_that_the_fetch_was_successful 9 | // Circle API returns 403 with Cors error for unauthorized users which throws instantly. 10 | // All successful calls are assumed to be authorized users 11 | try { 12 | await fetchAttestation('0x') 13 | return false 14 | } catch (_) { 15 | return true 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This is an alias to @tsconfig/node16: https://github.com/tsconfig/bases 3 | "extends": "ts-node/node16/tsconfig.json", 4 | // Most ts-node options can be specified here using their programmatic names. 5 | "ts-node": { 6 | // It is faster to skip typechecking. 7 | // Remove if you want ts-node to do typechecking. 8 | "transpileOnly": true, 9 | "files": true, 10 | "compilerOptions": { 11 | // compilerOptions specified here will override those declared below, 12 | // but *only* in ts-node. Useful if you want ts-node and tsc to use 13 | // different options with a single tsconfig.json. 14 | } 15 | }, 16 | "compilerOptions": { 17 | // typescript options here 18 | "moduleResolution": "NodeNext", 19 | "jsx": "react" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/cctp/getAttestationHashAndMessageFromReceipt.ts: -------------------------------------------------------------------------------- 1 | import { TransactionReceipt } from '@ethersproject/providers' 2 | import { utils } from 'ethers' 3 | import { Address } from '../AddressUtils' 4 | 5 | export function getAttestationHashAndMessageFromReceipt( 6 | txReceipt: TransactionReceipt 7 | ) { 8 | const eventTopic = utils.keccak256(utils.toUtf8Bytes('MessageSent(bytes)')) 9 | const log = txReceipt.logs.find(l => l.topics[0] === eventTopic) 10 | 11 | if (!log) 12 | return { 13 | messageBytes: null, 14 | attestationHash: null 15 | } 16 | 17 | const messageBytes = utils.defaultAbiCoder.decode( 18 | ['bytes'], 19 | log.data 20 | )[0] as Address 21 | 22 | return { 23 | messageBytes, 24 | attestationHash: utils.keccak256(messageBytes) as Address 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | /cypress 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | .e2e.env 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pre-commit-config.yaml 27 | 28 | # built hooks files 29 | /dist 30 | .env 31 | 32 | # Local Netlify folder 33 | .netlify 34 | 35 | /src/styles/tailwind.output.css 36 | 37 | # Local network config file generated by @arbitrum/sdk 38 | /src/util/localNetwork.json 39 | 40 | # Next.js 41 | .next 42 | next-env.d.ts 43 | 44 | # auto-generated images 45 | public/images/__auto-generated 46 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/atoms/Loader.tsx: -------------------------------------------------------------------------------- 1 | import { TailSpin } from 'react-loader-spinner' 2 | import { BaseProps } from 'react-loader-spinner/dist/type' 3 | 4 | export type LoaderProps = BaseProps & { 5 | size: 'small' | 'medium' | 'large' | number 6 | } 7 | 8 | const getSizeByLoaderProps = ( 9 | loaderSize: LoaderProps['size'] | number | undefined 10 | ) => { 11 | if (typeof loaderSize === 'number') { 12 | return loaderSize 13 | } 14 | 15 | switch (loaderSize) { 16 | case 'small': 17 | return 16 18 | 19 | case 'medium': 20 | return 32 21 | 22 | case 'large': 23 | return 44 24 | } 25 | } 26 | 27 | export const Loader = ({ size, color, ...rest }: LoaderProps) => { 28 | const sizeInPx = getSizeByLoaderProps(size) 29 | 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/SecurityLabels.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | CheckCircleIcon, 3 | ExclamationTriangleIcon 4 | } from '@heroicons/react/24/outline' 5 | 6 | export function SecurityGuaranteed() { 7 | return ( 8 |
9 |
10 | 11 | Security guaranteed by Ethereum 12 |
13 |
14 | ) 15 | } 16 | 17 | export function SecurityNotGuaranteed() { 18 | return ( 19 |
20 |
21 | 22 | Security not guaranteed by Arbitrum 23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/syncers/useBalanceUpdater.tsx: -------------------------------------------------------------------------------- 1 | import { useInterval, useLatest } from 'react-use' 2 | import { useAccount } from 'wagmi' 3 | 4 | import { useAppState } from '../../state' 5 | import { useUpdateUSDCBalances } from '../../hooks/CCTP/useUpdateUSDCBalances' 6 | 7 | // Updates all balances periodically 8 | export function useBalanceUpdater() { 9 | const { 10 | app: { arbTokenBridge, selectedToken } 11 | } = useAppState() 12 | const { address: walletAddress } = useAccount() 13 | const latestTokenBridge = useLatest(arbTokenBridge) 14 | 15 | const { updateUSDCBalances } = useUpdateUSDCBalances({ 16 | walletAddress 17 | }) 18 | 19 | useInterval(() => { 20 | updateUSDCBalances() 21 | 22 | if (selectedToken) { 23 | latestTokenBridge?.current?.token?.updateTokenData(selectedToken.address) 24 | } 25 | }, 10000) 26 | } 27 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/XMTPLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/__tests__/helpers.ts: -------------------------------------------------------------------------------- 1 | import { getArbitrumNetwork } from '@arbitrum/sdk' 2 | import { ChainWithRpcUrl } from '../../util/networks' 3 | 4 | export function createMockOrbitChain({ 5 | chainId, 6 | parentChainId 7 | }: { 8 | chainId: number 9 | parentChainId: number 10 | }): ChainWithRpcUrl { 11 | const isTestnet = 12 | parentChainId === 1 ? false : getArbitrumNetwork(parentChainId).isTestnet 13 | 14 | return { 15 | chainId: chainId, 16 | confirmPeriodBlocks: 45818, 17 | ethBridge: { 18 | bridge: '', 19 | inbox: '', 20 | outbox: '', 21 | rollup: '', 22 | sequencerInbox: '' 23 | }, 24 | nativeToken: '', 25 | explorerUrl: '', 26 | rpcUrl: '', 27 | isCustom: true, 28 | isTestnet, 29 | name: `Mocked Orbit Chain ${chainId}`, 30 | slug: `mocked-orbit-chain-${chainId}`, 31 | parentChainId 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/EthereumLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/tokenLists/660279_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XAI Default List", 3 | "timestamp": "2024-06-28T09:16:36.457Z", 4 | "tokens": [ 5 | { 6 | "chainId": 660279, 7 | "address": "0x89c49a3fa372920ac23ce757a029e6936c0b8e02", 8 | "name": "Crypto Unicorns", 9 | "symbol": "CU", 10 | "decimals": 18, 11 | "logoURI": "https://s2.coinmarketcap.com/static/img/coins/64x64/31074.png", 12 | "extensions": { 13 | "bridgeInfo": { 14 | "42161": { 15 | "tokenAddress": "0x89c49a3fa372920ac23ce757a029e6936c0b8e02", 16 | "originBridgeAddress": "0x96551194230725c72ACF8E9573B1382CCBC70635", 17 | "destBridgeAddress": "0xb15A0826d65bE4c2fDd961b72636168ee70Af030" 18 | } 19 | } 20 | } 21 | } 22 | ], 23 | "version": { 24 | "major": 1, 25 | "minor": 0, 26 | "patch": 0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useGasPrice.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { BigNumber, constants, providers } from 'ethers' 3 | import useSWR from 'swr' 4 | 5 | import { useChainId } from './useChainId' 6 | 7 | export function useGasPrice({ 8 | provider 9 | }: { 10 | provider: providers.Provider 11 | }): BigNumber { 12 | const chainId = useChainId({ provider }) 13 | 14 | const queryKey = useMemo(() => { 15 | if (typeof chainId === 'undefined') { 16 | // Don't fetch 17 | return null 18 | } 19 | 20 | return ['gasPrice', chainId] 21 | }, [chainId]) 22 | 23 | const { data: gasPrice = constants.Zero } = useSWR( 24 | queryKey, 25 | () => provider.getGasPrice(), 26 | { 27 | refreshInterval: 30_000, 28 | shouldRetryOnError: true, 29 | errorRetryCount: 2, 30 | errorRetryInterval: 5_000 31 | } 32 | ) 33 | 34 | return gasPrice 35 | } 36 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/.env.local.sample: -------------------------------------------------------------------------------- 1 | # Default Infura key. If no network-specific keys are set, it will fallback to this key. 2 | NEXT_PUBLIC_INFURA_KEY= 3 | 4 | # L1 5 | NEXT_PUBLIC_INFURA_KEY_ETHEREUM= 6 | # L1 Testnet 7 | NEXT_PUBLIC_INFURA_KEY_SEPOLIA= 8 | NEXT_PUBLIC_INFURA_KEY_HOLESKY= 9 | 10 | # L2 11 | NEXT_PUBLIC_INFURA_KEY_ARBITRUM_ONE= 12 | NEXT_PUBLIC_INFURA_KEY_BASE= 13 | # L2 Testnet 14 | NEXT_PUBLIC_INFURA_KEY_ARBITRUM_SEPOLIA= 15 | NEXT_PUBLIC_INFURA_KEY_BASE_SEPOLIA= 16 | 17 | NEXT_PUBLIC_SENTRY_DSN= 18 | 19 | NEXT_PUBLIC_ETHEREUM_RPC_URL= 20 | NEXT_PUBLIC_SEPOLIA_RPC_URL= 21 | 22 | NEXT_PUBLIC_LOCAL_ETHEREUM_RPC_URL="http://localhost:8545" 23 | NEXT_PUBLIC_LOCAL_ARBITRUM_RPC_URL="http://localhost:8547" 24 | 25 | NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID= 26 | 27 | THE_GRAPH_NETWORK_API_KEY= 28 | SELF_HOSTED_SUBGRAPH_API_KEY= 29 | 30 | NEXT_PUBLIC_SCREENING_API_ENDPOINT= 31 | 32 | NEXT_PUBLIC_POSTHOG_KEY= 33 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchETHWithdrawalsTestHelpers.ts: -------------------------------------------------------------------------------- 1 | import { StaticJsonRpcProvider } from '@ethersproject/providers' 2 | 3 | const receiver = '0xd898275e8b9428429155752f89fe0899ce232830' 4 | const l2Provider = new StaticJsonRpcProvider('https://arb1.arbitrum.io/rpc') 5 | 6 | const baseQuery = { 7 | receiver, 8 | l2Provider 9 | } 10 | 11 | export function getQueryCoveringClassicOnlyWithoutResults() { 12 | // keeping the block range low (not fetching from 0) to make sure we don't run into event-log deadline-exceeded error #904 13 | return { ...baseQuery, fromBlock: 20780771, toBlock: 20785771 } 14 | } 15 | 16 | export function getQueryCoveringClassicOnlyWithResults() { 17 | return { ...baseQuery, fromBlock: 20785772, toBlock: 22207816 } 18 | } 19 | 20 | export function getQueryCoveringClassicAndNitroWithResults() { 21 | return { ...baseQuery, fromBlock: 20785772, toBlock: 24905369 } 22 | } 23 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/atoms/Toast.tsx: -------------------------------------------------------------------------------- 1 | import { ToastContainer, toast } from 'react-toastify' 2 | 3 | export const errorToast = ( 4 | message: React.ReactNode, 5 | { 6 | autoClose 7 | }: { 8 | autoClose?: number | false | undefined 9 | } = { autoClose: 5000 } 10 | ) => { 11 | toast.error(message, { autoClose }) 12 | } 13 | 14 | export const warningToast = ( 15 | message: React.ReactNode, 16 | { 17 | autoClose 18 | }: { 19 | autoClose?: number | false | undefined 20 | } = { autoClose: 5000 } 21 | ) => { 22 | toast.warning(message, { autoClose }) 23 | } 24 | 25 | export const Toast = () => { 26 | return ( 27 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts: -------------------------------------------------------------------------------- 1 | import { useAppState } from '../../state' 2 | import { isTokenNativeUSDC } from '../../util/TokenUtils' 3 | import { useNetworks } from '../useNetworks' 4 | import { useNetworksRelationship } from '../useNetworksRelationship' 5 | 6 | export const useIsBatchTransferSupported = () => { 7 | const [networks] = useNetworks() 8 | const { isDepositMode, isTeleportMode } = useNetworksRelationship(networks) 9 | const { 10 | app: { selectedToken } 11 | } = useAppState() 12 | 13 | if (!selectedToken) { 14 | return false 15 | } 16 | if (!isDepositMode) { 17 | return false 18 | } 19 | if (isTokenNativeUSDC(selectedToken.address)) { 20 | return false 21 | } 22 | // TODO: teleport is disabled for now but it needs to be looked into more to check whether it is or can be supported 23 | if (isTeleportMode) { 24 | return false 25 | } 26 | 27 | return true 28 | } 29 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/.e2e.env.sample: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_INFURA_KEY= 2 | NETWORK_NAME=localhost 3 | 4 | NEXT_PUBLIC_LOCAL_ETHEREUM_RPC_URL=http://127.0.0.1:8545 5 | NEXT_PUBLIC_LOCAL_ARBITRUM_RPC_URL=http://127.0.0.1:8547 6 | NEXT_PUBLIC_LOCAL_L3_RPC_URL=http://127.0.0.1:3347 7 | 8 | CYPRESS_RECORD_VIDEO=false 9 | 10 | # We don't use PRIVATE_KEY because Synpress is looking for that to set up MetaMask 11 | # Instead we generate the private key in runtime before tests start 12 | # Below key is only used to fund the newly created wallet 13 | PRIVATE_KEY_CUSTOM=b6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 14 | 15 | PRIVATE_KEY_CCTP= 16 | 17 | # We set up MetaMask ourselves 18 | SKIP_METAMASK_SETUP=true 19 | 20 | # Private key of the user wallet with enough deposits and withdrawals on Sepolia to pass the tx history tests 21 | # Also funded by E2E_PRIVATE_KEY wallet and used for all the tests 22 | PRIVATE_KEY_USER=c791f745783e3545f83e7b4f0adcc0c2f591c5c8bc796e042c639824ec5d7bf0 23 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/SuperpositionLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/TokenApprovalUtils.ts: -------------------------------------------------------------------------------- 1 | import { ERC20__factory } from '@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory' 2 | import { MaxUint256 } from '@ethersproject/constants' 3 | import { Provider } from '@ethersproject/providers' 4 | import { fetchErc20ParentChainGatewayAddress } from './TokenUtils' 5 | 6 | export const approveTokenEstimateGas = async ({ 7 | erc20L1Address, 8 | address, 9 | l1Provider, 10 | l2Provider 11 | }: { 12 | erc20L1Address: string 13 | address: string 14 | l1Provider: Provider 15 | l2Provider: Provider 16 | }) => { 17 | const l1GatewayAddress = await fetchErc20ParentChainGatewayAddress({ 18 | erc20ParentChainAddress: erc20L1Address, 19 | parentChainProvider: l1Provider, 20 | childChainProvider: l2Provider 21 | }) 22 | 23 | const contract = ERC20__factory.connect(erc20L1Address, l1Provider) 24 | 25 | return contract.estimateGas.approve(l1GatewayAddress, MaxUint256, { 26 | from: address 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/TokenTransferDisabledUtils.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '../util/networks' 2 | 3 | export type TransferDisabledToken = { 4 | symbol: string 5 | l1Address: string 6 | l2Address: string 7 | } 8 | 9 | const transferDisabledTokens: { [chainId: number]: TransferDisabledToken[] } = { 10 | [ChainId.ArbitrumOne]: [ 11 | { 12 | symbol: 'rDPX', 13 | l1Address: '0x0ff5A8451A839f5F0BB3562689D9A44089738D11', 14 | l2Address: '0x32Eb7902D4134bf98A28b963D26de779AF92A212' 15 | }, 16 | { 17 | symbol: 'FU', 18 | l1Address: '0x43df01681966d5339702e96ef039e481b9da20c1', 19 | l2Address: '0x9aee3C99934C88832399D6C6E08ad802112eBEab' 20 | } 21 | ] 22 | } 23 | 24 | export function isTransferDisabledToken( 25 | erc20L1Address: string, 26 | childChainId: number 27 | ) { 28 | return (transferDisabledTokens[childChainId] ?? []) 29 | .map(token => token.l1Address.toLowerCase()) 30 | .includes(erc20L1Address.toLowerCase()) 31 | } 32 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/scripts/generateOrbitChainsToMonitor.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { getOrbitChains } from '../src/util/orbitChainsList' 3 | import { getChainToMonitor } from './utils' 4 | 5 | async function generateOrbitChainsToMonitor() { 6 | const orbitChains = getOrbitChains({ mainnet: true, testnet: false }) 7 | 8 | // make the orbit chain data compatible with the orbit-data required by the retryable-monitoring script 9 | const orbitChainsToMonitor = orbitChains.map(orbitChain => { 10 | return getChainToMonitor({ 11 | chain: orbitChain, 12 | rpcUrl: orbitChain.rpcUrl 13 | }) 14 | }) 15 | 16 | // write to orbit-chains.json, we will use this json as an input to the retryable-monitoring script 17 | const resultsJson = JSON.stringify( 18 | { 19 | childChains: orbitChainsToMonitor 20 | }, 21 | null, 22 | 2 23 | ) 24 | fs.writeFileSync('./public/__auto-generated-orbit-chains.json', resultsJson) 25 | } 26 | 27 | generateOrbitChainsToMonitor() 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ### Summary 13 | 14 | 20 | 21 | ### Steps to test 22 | 23 | 28 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/TransferPanel/useImportTokenModal.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { ImportTokenModalStatus } from '../../components/TransferPanel/TransferPanelUtils' 3 | import { ConnectionState } from '../../util' 4 | import { useTokenImportDialogStore } from '../../components/TransferPanel/TokenImportDialog' 5 | 6 | export function useImportTokenModal({ 7 | importTokenModalStatus, 8 | connectionState 9 | }: { 10 | importTokenModalStatus: ImportTokenModalStatus 11 | connectionState: number 12 | }) { 13 | const { openDialog: openTokenImportDialog } = useTokenImportDialogStore() 14 | useEffect(() => { 15 | if (importTokenModalStatus !== ImportTokenModalStatus.IDLE) { 16 | return 17 | } 18 | 19 | if ( 20 | connectionState === ConnectionState.L1_CONNECTED || 21 | connectionState === ConnectionState.L2_CONNECTED 22 | ) { 23 | openTokenImportDialog() 24 | } 25 | }, [connectionState, importTokenModalStatus, openTokenImportDialog]) 26 | } 27 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/e2e/README.md: -------------------------------------------------------------------------------- 1 | # End-to-end tests 2 | 3 | ## Folder structure 4 | 5 | These folders hold end-to-end tests and supporting files for the Cypress Test Runner. 6 | 7 | - [fixtures](fixtures) holds optional JSON data for mocking, [read more](https://on.cypress.io/fixture) 8 | - [integration](integration) holds the actual test files, [read more](https://on.cypress.io/writing-and-organizing-tests) 9 | - [support](support) file runs before all tests and is a great place to write or load additional custom commands, [read more](https://on.cypress.io/writing-and-organizing-tests#Support-file) 10 | 11 | ## `cypress.json` file 12 | 13 | You can configure project options in the [../cypress.json](../cypress.json) file, see [Cypress configuration doc](https://on.cypress.io/configuration). 14 | 15 | ## More information 16 | 17 | - [https://github.com/cypress.io/cypress](https://github.com/cypress.io/cypress) 18 | - [https://docs.cypress.io/](https://docs.cypress.io/) 19 | - [Writing your first Cypress test](http://on.cypress.io/intro) 20 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/TransferPanel/useSetInputAmount.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | 3 | import { truncateExtraDecimals } from '../../util/NumberUtils' 4 | import { useArbQueryParams } from '../useArbQueryParams' 5 | import { useSelectedTokenDecimals } from './useSelectedTokenDecimals' 6 | 7 | export function useSetInputAmount() { 8 | const [, setQueryParams] = useArbQueryParams() 9 | const decimals = useSelectedTokenDecimals() 10 | 11 | const setAmount = useCallback( 12 | (newAmount: string) => { 13 | const correctDecimalsAmount = truncateExtraDecimals(newAmount, decimals) 14 | 15 | setQueryParams({ amount: correctDecimalsAmount }) 16 | }, 17 | [decimals, setQueryParams] 18 | ) 19 | 20 | const setAmount2 = useCallback( 21 | (newAmount: string) => { 22 | const correctDecimalsAmount = truncateExtraDecimals(newAmount, 18) 23 | 24 | setQueryParams({ amount2: correctDecimalsAmount }) 25 | }, 26 | [setQueryParams] 27 | ) 28 | 29 | return { setAmount, setAmount2 } 30 | } 31 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/HeaderConnectWalletButton.tsx: -------------------------------------------------------------------------------- 1 | import { PlusCircleIcon } from '@heroicons/react/24/outline' 2 | import { ConnectButton } from '@rainbow-me/rainbowkit' 3 | import { twMerge } from 'tailwind-merge' 4 | 5 | export function HeaderConnectWalletButton() { 6 | return ( 7 | 8 | {({ openConnectModal }) => ( 9 |
10 | 21 |
22 | )} 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/StatusBadge.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export type StatusBadgeProps = React.HTMLAttributes & { 5 | variant?: 'blue' | 'yellow' | 'green' | 'red' | 'gray' 6 | } 7 | 8 | const variants: Record = { 9 | blue: 'bg-cyan text-cyan-dark', 10 | yellow: 'bg-orange text-orange-dark border-orange-dark border', 11 | green: 'bg-lime text-lime-dark', 12 | red: 'bg-brick text-brick-dark border border-brick-dark', 13 | gray: 'bg-gray-3 text-gray-dark' 14 | } 15 | 16 | export function StatusBadge({ 17 | variant = 'blue', 18 | children, 19 | className, 20 | ...props 21 | }: StatusBadgeProps): JSX.Element { 22 | return ( 23 |
31 | {children} 32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/LightningIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/e2e/specs/switchNetworks.cy.ts: -------------------------------------------------------------------------------- 1 | import { getL1NetworkName, getL2NetworkName } from '../../support/common' 2 | 3 | describe('Switch Networks', () => { 4 | context('User is on test network L1', () => { 5 | it('should show L1 and L2 chains correctly', () => { 6 | cy.login({ networkType: 'parentChain' }) 7 | cy.findSourceChainButton(getL1NetworkName()) 8 | cy.findDestinationChainButton(getL2NetworkName()) 9 | }) 10 | 11 | context( 12 | 'User is connected to Ethereum, source chain is Ethereum and destination chain is Arbitrum', 13 | () => { 14 | it('should switch "from: Ethereum" to "from: Arbitrum" successfully', () => { 15 | cy.login({ networkType: 'parentChain' }) 16 | cy.findSourceChainButton(getL1NetworkName()) 17 | 18 | cy.findByRole('button', { name: /Switch Networks/i }) 19 | .should('be.visible') 20 | .click() 21 | 22 | cy.findSourceChainButton(getL2NetworkName()) 23 | }) 24 | } 25 | ) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/TestnetToggle.tsx: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge' 2 | 3 | import { useIsTestnetMode } from '../../hooks/useIsTestnetMode' 4 | 5 | import { Switch } from './atoms/Switch' 6 | 7 | export const TestnetToggle = ({ 8 | className, 9 | label, 10 | description, 11 | includeToggleStateOnLabel 12 | }: { 13 | className?: { 14 | wrapper?: string 15 | switch?: string 16 | } 17 | label: string 18 | description?: string 19 | includeToggleStateOnLabel?: boolean 20 | }) => { 21 | const [isTestnetMode, toggleTestnetMode] = useIsTestnetMode() 22 | 23 | const labelText = includeToggleStateOnLabel 24 | ? `${label} ${isTestnetMode ? 'ON' : 'OFF'}` 25 | : label 26 | 27 | return ( 28 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/sidebar/gethelp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import useLocalStorage from '@rehooks/local-storage' 3 | 4 | export const themeLocalStorageKey = 'arbitrum:bridge:preferences:theme' 5 | 6 | export const classicThemeKey = 'arbitrum-classic-theme' 7 | 8 | export const THEME_CONFIG = [ 9 | { 10 | id: 'space', 11 | label: 'Space', 12 | description: 13 | 'A dark, space-themed UI with a sleek and futuristic aesthetic, featuring Arbinaut on a backdrop of shining stars and moon.' 14 | }, 15 | { 16 | id: classicThemeKey, 17 | label: 'Arbitrum Classic', 18 | description: 19 | 'Arbitrum before it was cool: A reminiscent of the pre-nitro era, with simple, solid buttons, a minimal purple layout and chunky fonts.' 20 | } 21 | ] 22 | 23 | export const useTheme = () => { 24 | const [theme, setTheme] = useLocalStorage(themeLocalStorageKey) 25 | 26 | useEffect(() => { 27 | if (!theme) return 28 | document.body.className = theme 29 | }, [theme]) 30 | 31 | return [theme, setTheme] as const 32 | } 33 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/CommonAddressUtils.ts: -------------------------------------------------------------------------------- 1 | export const CommonAddress = { 2 | Ethereum: { 3 | USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 4 | tokenMessengerContractAddress: '0xbd3fa81b58ba92a82136038b25adec7066af3155' 5 | }, 6 | ArbitrumOne: { 7 | USDC: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', 8 | 'USDC.e': '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', 9 | tokenMessengerContractAddress: '0x19330d10d9cc8751218eaf51e8885d058642e08a', 10 | 11 | CU: '0x89c49a3fa372920ac23ce757a029e6936c0b8e02' 12 | }, 13 | // Xai Mainnet 14 | 660279: { 15 | CU: '0x89c49a3fa372920ac23ce757a029e6936c0b8e02' 16 | }, 17 | Sepolia: { 18 | USDC: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238', 19 | tokenMessengerContractAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5' 20 | }, 21 | ArbitrumSepolia: { 22 | USDC: '0x75faf114eafb1bdbe2f0316df893fd58ce46aa4d', 23 | 'USDC.e': '0x119f0e6303bec7021b295ecab27a4a1a5b37ecf0', 24 | tokenMessengerContractAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5' 25 | } 26 | } as const 27 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useDestinationChainStyle.ts: -------------------------------------------------------------------------------- 1 | import { getOrbitChains } from '../util/orbitChainsList' 2 | import { getBridgeUiConfigForChain } from '../util/bridgeUiConfig' 3 | import { useNetworks } from './useNetworks' 4 | 5 | type DestinationChainStyle = 6 | | { 7 | borderColor: `#${string}` 8 | backgroundColor: string 9 | } 10 | | Record 11 | 12 | export const useDestinationChainStyle = (): DestinationChainStyle => { 13 | const [networks] = useNetworks() 14 | const orbitChains = getOrbitChains({ mainnet: true, testnet: false }) 15 | const orbitChain = orbitChains.find( 16 | orbitChain => orbitChain.chainId === networks.destinationChain.id 17 | ) 18 | 19 | // early return if the orbit chain is not found 20 | if (!orbitChain) return {} 21 | 22 | // styles for the orbit chain 23 | const orbitChainColor = getBridgeUiConfigForChain(orbitChain.chainId).color 24 | const orbitStyles = { 25 | borderColor: orbitChainColor, 26 | backgroundColor: `${orbitChainColor}40` 27 | } 28 | 29 | return orbitStyles 30 | } 31 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useAmountBigNumber.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { useArbQueryParams } from '../../../hooks/useArbQueryParams' 3 | import { useAppState } from '../../../state' 4 | import { constants, utils } from 'ethers' 5 | import { useSourceChainNativeCurrencyDecimals } from '../../../hooks/useSourceChainNativeCurrencyDecimals' 6 | 7 | export function useAmountBigNumber() { 8 | const { 9 | app: { selectedToken } 10 | } = useAppState() 11 | const [{ amount }] = useArbQueryParams() 12 | const nativeCurrencyDecimalsOnSourceChain = 13 | useSourceChainNativeCurrencyDecimals() 14 | 15 | return useMemo(() => { 16 | try { 17 | const amountSafe = amount || '0' 18 | 19 | if (selectedToken) { 20 | return utils.parseUnits(amountSafe, selectedToken.decimals) 21 | } 22 | 23 | return utils.parseUnits(amountSafe, nativeCurrencyDecimalsOnSourceChain) 24 | } catch (error) { 25 | return constants.Zero 26 | } 27 | }, [amount, selectedToken, nativeCurrencyDecimalsOnSourceChain]) 28 | } 29 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/NetworkImage.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | import { getNetworkName } from '../../util/networks' 5 | import { getBridgeUiConfigForChain } from '../../util/bridgeUiConfig' 6 | 7 | export const NetworkImage = ({ 8 | chainId, 9 | className, 10 | size 11 | }: { 12 | chainId: number 13 | className?: string 14 | size?: number 15 | }) => { 16 | const imageSize = size ?? 16 17 | const { network, color } = getBridgeUiConfigForChain(chainId) 18 | const networkName = getNetworkName(chainId) 19 | 20 | return ( 21 |
30 | {`${networkName} 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useETHPrice.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | import axios from 'axios' 3 | import useSWR, { KeyedMutator } from 'swr' 4 | 5 | export type UseETHPriceResult = { 6 | ethPrice: number 7 | ethToUSD: (etherValue: number) => number 8 | error?: Error 9 | isValidating: boolean 10 | mutate: KeyedMutator 11 | } 12 | 13 | export function useETHPrice(): UseETHPriceResult { 14 | const { data, error, isValidating, mutate } = useSWR( 15 | 'https://api.coinbase.com/v2/prices/ETH-USD/spot', 16 | url => axios.get(url).then(res => res.data.data.amount), 17 | { 18 | refreshInterval: 300_000, // 5 minutes 19 | shouldRetryOnError: true, 20 | errorRetryCount: 2, 21 | errorRetryInterval: 3_000 22 | } 23 | ) 24 | 25 | const ethToUSD = useCallback( 26 | (etherValue: number) => { 27 | const safeETHPrice = data && !isNaN(data) ? Number(data) : 0 28 | return etherValue * safeETHPrice 29 | }, 30 | [data] 31 | ) 32 | 33 | return { ethPrice: data ?? 0, ethToUSD, error, isValidating, mutate } 34 | } 35 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useERC20L1Address.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@ethersproject/providers' 2 | import useSWRImmutable from 'swr/immutable' 3 | import { getL1ERC20Address } from '../util/TokenUtils' 4 | 5 | /** 6 | * Returns L1 address 7 | * 8 | * @param eitherL1OrL2Address string Token address (on L1 or L2) 9 | * @param l2Provider L2 Provider 10 | * @returns 11 | */ 12 | export const useERC20L1Address = ({ 13 | eitherL1OrL2Address, 14 | l2Provider 15 | }: { 16 | eitherL1OrL2Address: string 17 | l2Provider: Provider 18 | }) => { 19 | const { data = null, isValidating } = useSWRImmutable( 20 | ['useERC20L1Address', eitherL1OrL2Address], 21 | async () => { 22 | const address = 23 | (await getL1ERC20Address({ 24 | erc20L2Address: eitherL1OrL2Address, 25 | l2Provider 26 | })) ?? eitherL1OrL2Address 27 | return address.toLowerCase() 28 | }, 29 | { 30 | shouldRetryOnError: true, 31 | errorRetryCount: 2, 32 | errorRetryInterval: 1_000 33 | } 34 | ) 35 | 36 | return { data, isLoading: isValidating } 37 | } 38 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/sidebar/missions.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/TransferPanel/useChainIdsForNetworkSelection.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChainId, 3 | getDestinationChainIds, 4 | getSupportedChainIds 5 | } from '../../util/networks' 6 | import { useIsTestnetMode } from '../useIsTestnetMode' 7 | import { useNetworks } from '../useNetworks' 8 | 9 | export function useChainIdsForNetworkSelection({ 10 | isSource 11 | }: { 12 | isSource: boolean 13 | }) { 14 | const [networks] = useNetworks() 15 | const [isTestnetMode] = useIsTestnetMode() 16 | 17 | if (isSource) { 18 | return getSupportedChainIds({ 19 | includeMainnets: !isTestnetMode, 20 | includeTestnets: isTestnetMode 21 | }) 22 | } 23 | 24 | const destinationChainIds = getDestinationChainIds(networks.sourceChain.id) 25 | 26 | // if source chain is Arbitrum One, add Arbitrum Nova to destination 27 | if (networks.sourceChain.id === ChainId.ArbitrumOne) { 28 | destinationChainIds.push(ChainId.ArbitrumNova) 29 | } 30 | 31 | if (networks.sourceChain.id === ChainId.ArbitrumNova) { 32 | destinationChainIds.push(ChainId.ArbitrumOne) 33 | } 34 | 35 | return destinationChainIds 36 | } 37 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts: -------------------------------------------------------------------------------- 1 | import { Provider, BlockTag } from '@ethersproject/providers' 2 | import { ChildToParentMessageReader } from '@arbitrum/sdk' 3 | 4 | /** 5 | * Fetches initiated ETH withdrawals from event logs in range of [fromBlock, toBlock]. 6 | * 7 | * @param query Query params 8 | * @param query.receiver Address that received the funds 9 | * @param query.fromBlock Start at this block number (including) 10 | * @param query.toBlock Stop at this block number (including) 11 | * @param query.l2Provider Provider for the L2 network 12 | */ 13 | export function fetchETHWithdrawalsFromEventLogs({ 14 | receiver, 15 | fromBlock, 16 | toBlock, 17 | l2Provider 18 | }: { 19 | receiver?: string 20 | fromBlock: BlockTag 21 | toBlock: BlockTag 22 | l2Provider: Provider 23 | }) { 24 | if (typeof receiver === 'undefined') { 25 | return Promise.resolve([]) 26 | } 27 | 28 | // funds received by this address 29 | return ChildToParentMessageReader.getChildToParentEvents( 30 | l2Provider, 31 | { fromBlock, toBlock }, 32 | undefined, 33 | receiver 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /packages/scripts/src/addOrbitChain/provider.ts: -------------------------------------------------------------------------------- 1 | import { StaticJsonRpcProvider } from "@ethersproject/providers"; 2 | import { ConnectionInfo } from "ethers/lib/utils"; 3 | 4 | export const getProvider = (chainInfo: { 5 | rpcUrl: string; 6 | name: string; 7 | chainId: number; 8 | }) => { 9 | const THROTTLE_LIMIT = 10; 10 | 11 | const connection: ConnectionInfo = { 12 | url: chainInfo.rpcUrl, 13 | timeout: 300000, 14 | allowGzip: true, 15 | skipFetchSetup: true, 16 | throttleLimit: THROTTLE_LIMIT, 17 | throttleSlotInterval: 3000, 18 | throttleCallback: async (attempt: number) => { 19 | // Always retry until we hit the THROTTLE_LIMIT 20 | // Otherwise, it only throttles for specific response codes 21 | // Return true to continue retrying, false to stop 22 | return attempt <= THROTTLE_LIMIT; 23 | }, 24 | headers: { 25 | Accept: "*/*", 26 | "Accept-Encoding": "gzip, deflate, br", 27 | }, 28 | }; 29 | 30 | const provider = new StaticJsonRpcProvider(connection, { 31 | name: chainInfo.name, 32 | chainId: chainInfo.chainId, 33 | }); 34 | 35 | return provider; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/scripts/src/addOrbitChain/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import * as core from "@actions/core"; 3 | import { 4 | initializeAndFetchData, 5 | processChainData, 6 | handleImages, 7 | createAndValidateOrbitChain, 8 | updateAndValidateOrbitChainsList, 9 | } from "./transforms"; 10 | 11 | /** 12 | * Main function to add an Orbit chain 13 | * @param targetJsonPath Path to the target JSON file 14 | */ 15 | export async function addOrbitChain(targetJsonPath: string): Promise { 16 | try { 17 | await initializeAndFetchData(); 18 | 19 | const { branchName, validatedIncomingData } = await processChainData(); 20 | 21 | const { chainLogoPath, nativeTokenLogoPath } = await handleImages( 22 | branchName, 23 | validatedIncomingData 24 | ); 25 | 26 | const orbitChain = await createAndValidateOrbitChain( 27 | validatedIncomingData, 28 | chainLogoPath, 29 | nativeTokenLogoPath 30 | ); 31 | 32 | await updateAndValidateOrbitChainsList(orbitChain, targetJsonPath); 33 | } catch (error) { 34 | core.setFailed(`Error in addOrbitChain: ${error}`); 35 | throw error; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/sidebar/bridge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/support/index.ts: -------------------------------------------------------------------------------- 1 | import '@synthetixio/synpress/support' 2 | import logCollector from 'cypress-terminal-report/src/installLogsCollector' 3 | 4 | import { 5 | getL1NetworkConfig, 6 | getL2NetworkConfig, 7 | getL2TestnetNetworkConfig 8 | } from './common' 9 | import './commands' 10 | 11 | logCollector({ 12 | collectTypes: [ 13 | 'cy:command', 14 | 'cy:log', 15 | 'cons:debug', 16 | 'cons:error', 17 | 'cons:info', 18 | 'cons:warn' 19 | ] 20 | }) 21 | 22 | before(() => { 23 | // connect to sepolia to avoid connecting to localhost twice and failing 24 | cy.setupMetamask(Cypress.env('PRIVATE_KEY'), 'sepolia') 25 | .task('getNetworkSetupComplete') 26 | .then(complete => { 27 | if (!complete) { 28 | // L1 29 | // only CI setup is required, Metamask already has localhost 30 | if (Cypress.env('ETH_RPC_URL') !== 'http://localhost:8545') { 31 | cy.addMetamaskNetwork(getL1NetworkConfig()) 32 | } 33 | 34 | // L2 35 | cy.addMetamaskNetwork(getL2NetworkConfig()) 36 | cy.addMetamaskNetwork(getL2TestnetNetworkConfig()) 37 | 38 | cy.task('setNetworkSetupComplete') 39 | } 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/jest.config.js: -------------------------------------------------------------------------------- 1 | const nextJest = require('next/jest') 2 | 3 | const createJestConfig = nextJest({ 4 | dir: './' 5 | }) 6 | 7 | const customJestConfig = { 8 | testEnvironment: 'jsdom', 9 | setupFiles: ['/setupTests.ts'], 10 | 11 | // https://jestjs.io/docs/configuration#testmatch-arraystring 12 | // removed ["**/__tests__/**/*.[jt]s?(x)"] from jest's default testMatch to ignore helpers.ts present in __test__ folders 13 | testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'] 14 | } 15 | 16 | // Lists out node modules that need to be transformed before running tests 17 | const transformNodeModules = [ 18 | 'query-string', 19 | // The following are dependencies for query-string (https://github.com/sindresorhus/query-string/blob/main/package.json) 20 | 'decode-uri-component', 21 | 'split-on-first', 22 | 'filter-obj', 23 | // wagmi 24 | '@wagmi', 25 | 'wagmi' 26 | ] 27 | 28 | module.exports = async function () { 29 | const config = await createJestConfig(customJestConfig)() 30 | 31 | return { 32 | ...config, 33 | transformIgnorePatterns: [ 34 | `node_modules/(?!(${transformNodeModules.join('|')})/)`, 35 | '^.+\\.module\\.(css|sass|scss)$' 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchEthDepositsToCustomDestinationFromSubgraph.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getQueryCoveringNitroWithResults, 3 | getQueryCoveringNitroWithoutResults 4 | } from './fetchEthDepositsToCustomDestinationTestHelpers' 5 | import { fetchEthDepositsToCustomDestinationFromSubgraph } from '../fetchEthDepositsToCustomDestinationFromSubgraph' 6 | 7 | describe('fetchEthDepositsToCustomDestinationFromSubgraph', () => { 8 | it('fetches deposits from subgraph with zero results', async () => { 9 | const result = await fetchEthDepositsToCustomDestinationFromSubgraph( 10 | getQueryCoveringNitroWithoutResults() 11 | ) 12 | 13 | expect(result).toHaveLength(0) 14 | }) 15 | 16 | it('fetches deposits from subgraph', async () => { 17 | const result = await fetchEthDepositsToCustomDestinationFromSubgraph( 18 | getQueryCoveringNitroWithResults() 19 | ) 20 | 21 | expect(result).toHaveLength(1) 22 | expect(result).toEqual( 23 | expect.arrayContaining([ 24 | expect.objectContaining({ 25 | transactionHash: 26 | '0x309c2f9778797445dc3b9da63be5569bb33edf88222775060532bc85edce1897' 27 | }) 28 | ]) 29 | ) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { Switch } from '@headlessui/react' 2 | import { CheckIcon } from '@heroicons/react/24/outline' 3 | import { twMerge } from 'tailwind-merge' 4 | 5 | export type CheckboxProps = { 6 | label: string | React.ReactNode 7 | checked: boolean 8 | onChange: (checked: boolean) => void 9 | } 10 | 11 | export function Checkbox(props: CheckboxProps) { 12 | return ( 13 | 17 | 26 | 27 | 28 | 34 | {props.label} 35 | 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/withdrawals/__tests__/fetchWithdrawalsTestHelpers.ts: -------------------------------------------------------------------------------- 1 | import { StaticJsonRpcProvider } from '@ethersproject/providers' 2 | 3 | const sender = '0x2Ce910fBba65B454bBAf6A18c952A70f3bcd8299' 4 | const l2Provider = new StaticJsonRpcProvider('https://arb1.arbitrum.io/rpc') 5 | const l2ChainId = 42161 6 | 7 | const baseQuery = { 8 | sender, 9 | l2ChainId, 10 | l2Provider, 11 | l2GatewayAddresses: [ 12 | '0x09e9222E96E7B4AE2a407B98d48e330053351EEe', // L2 Standard Gateway 13 | '0x096760F208390250649E3e8763348E783AEF5562', // L2 Custom Gateway 14 | '0x6c411aD3E74De3E7Bd422b94A27770f5B86C623B' // L2 WETH Gateway 15 | ] 16 | } 17 | 18 | export function getQueryCoveringClassicOnlyWithoutResults() { 19 | // keeping the block range low (not fetching from 0) to make sure we don't run into event-log deadline-exceeded error #904 20 | return { ...baseQuery, fromBlock: 19411899, toBlock: 19416899 } 21 | } 22 | 23 | export function getQueryCoveringClassicOnlyWithResults() { 24 | return { ...baseQuery, fromBlock: 19416900, toBlock: 20961064 } 25 | } 26 | 27 | export function getQueryCoveringClassicAndNitroWithResults() { 28 | return { ...baseQuery, fromBlock: 21114936, toBlock: 35108652 } 29 | } 30 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["@typescript-eslint", "jest"], 3 | extends: [ 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:prettier/recommended", 6 | "plugin:jest/recommended", 7 | "next", 8 | ], 9 | parser: "@typescript-eslint/parser", 10 | parserOptions: { 11 | project: ["./tsconfig.eslint.json", "./packages/*/tsconfig.json"], 12 | tsconfigRootDir: __dirname, 13 | sourceType: "module", 14 | }, 15 | settings: { 16 | react: { 17 | version: "detect", 18 | }, 19 | next: { 20 | rootDir: "packages/arb-token-bridge-ui/", 21 | }, 22 | }, 23 | rules: { 24 | "react/jsx-uses-react": "off", // we're using React 17+ so it's irrelevant 25 | "react/react-in-jsx-scope": "off", // we're using React 17+ so it's irrelevant 26 | "@typescript-eslint/explicit-module-boundary-types": "off", // allow type inference for function return type 27 | "@typescript-eslint/ban-ts-comment": [ 28 | "error", 29 | { 30 | "ts-expect-error": "allow-with-description", 31 | "ts-ignore": "allow-with-description", 32 | "ts-nocheck": "allow-with-description", 33 | "ts-check": "allow-with-description", 34 | }, 35 | ], 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/RARIMainnetLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelUtils.ts: -------------------------------------------------------------------------------- 1 | import { useArbQueryParams } from '../../hooks/useArbQueryParams' 2 | 3 | export enum ImportTokenModalStatus { 4 | // "IDLE" is here to distinguish between the modal never being opened, and being closed after a user interaction 5 | IDLE, 6 | OPEN, 7 | CLOSED 8 | } 9 | 10 | export function getWarningTokenDescription(warningTokenType: number) { 11 | switch (warningTokenType) { 12 | case 0: 13 | return 'a supply rebasing token' 14 | case 1: 15 | return 'an interest accruing token' 16 | default: 17 | return 'a non-standard ERC20 token' 18 | } 19 | } 20 | 21 | export function useTokenFromSearchParams(): { 22 | tokenFromSearchParams: string | undefined 23 | setTokenQueryParam: (token: string | undefined) => void 24 | } { 25 | const [{ token: tokenFromSearchParams }, setQueryParams] = useArbQueryParams() 26 | 27 | const setTokenQueryParam = (token: string | undefined) => 28 | setQueryParams({ token }) 29 | 30 | if (!tokenFromSearchParams) { 31 | return { 32 | tokenFromSearchParams: undefined, 33 | setTokenQueryParam 34 | } 35 | } 36 | 37 | return { 38 | tokenFromSearchParams, 39 | setTokenQueryParam 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/run-cctp-tests.yml: -------------------------------------------------------------------------------- 1 | name: CCTP Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }}-cctp 9 | cancel-in-progress: true 10 | 11 | env: 12 | NEXT_PUBLIC_INFURA_KEY: ${{ secrets.NEXT_PUBLIC_INFURA_KEY }} 13 | NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID }} 14 | THE_GRAPH_NETWORK_API_KEY: ${{ secrets.THE_GRAPH_NETWORK_API_KEY }} 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | jobs: 18 | cctp-e2e-tests: 19 | name: "CCTP E2E Tests" 20 | uses: ./.github/workflows/e2e-tests.yml 21 | with: 22 | test_type: 'cctp' 23 | secrets: inherit 24 | 25 | test-e2e-success: 26 | name: "CCTP Test E2E Success" 27 | runs-on: ubuntu-latest 28 | needs: [cctp-e2e-tests] 29 | if: always() 30 | steps: 31 | - name: CCTP E2E Succeeded 32 | if: needs.cctp-e2e-tests.result == 'success' || needs.cctp-e2e-tests.result == 'skipped' 33 | run: echo "CCTP E2E tests passed" 34 | 35 | - name: CCTP E2E Failed 36 | if: needs.cctp-e2e-tests.result != 'success' && needs.cctp-e2e-tests.result != 'skipped' 37 | run: exit 1 38 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/icons/wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/actions/check-files/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Check Files' 2 | description: 'Checks modified files to determine if tests should be run' 3 | outputs: 4 | run_tests: 5 | description: 'Whether tests should be run based on modified files' 6 | value: ${{ steps.check-files.outputs.run_tests }} 7 | runs: 8 | using: "composite" 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 2 14 | 15 | - name: Check modified files 16 | id: check-files 17 | shell: bash 18 | run: | 19 | echo "=============== list modified files ===============" 20 | files=`git diff --name-only HEAD^ HEAD` 21 | echo "$files" 22 | for file in $files; do 23 | if [[ $file != packages/* ]] && ! [[ $file =~ .*\.(lock|yml)$ ]]; then 24 | # if not in packages/ and does not end with .lock or .yml 25 | echo "run_tests=false" >> $GITHUB_OUTPUT 26 | elif [[ $file == .github/ISSUE_TEMPLATE/* ]]; then 27 | echo "run_tests=false" >> $GITHUB_OUTPUT 28 | elif [[ $file =~ .*\.(md|svg|png|webp|gif|txt)$ ]]; then 29 | echo "run_tests=false" >> $GITHUB_OUTPUT 30 | else 31 | echo "run_tests=true" >> $GITHUB_OUTPUT 32 | break 33 | fi 34 | done -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/App/BlockedDialog.tsx: -------------------------------------------------------------------------------- 1 | import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' 2 | 3 | import { Dialog, DialogProps } from '../common/Dialog' 4 | import { ExternalLink } from '../common/ExternalLink' 5 | import { GET_HELP_LINK } from '../../constants' 6 | 7 | export function BlockedDialog(props: DialogProps & { address: string }) { 8 | return ( 9 | 13 | 14 | This wallet address is blocked 15 | 16 | } 17 | isFooterHidden={true} 18 | > 19 |
20 | {props.address.toLowerCase()} 21 | This address is affiliated with a blocked activity. 22 | 23 | If you think this was an error, you can request a review by filing a{' '} 24 | 28 | support ticket 29 | 30 | . 31 | 32 |
33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: File a feature request 3 | title: "[feat]: " 4 | labels: ["new feature", "triage"] 5 | assignees: 6 | - octocat 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this feature request form! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Contact Details 16 | description: How can we get in touch with you if we need more info? 17 | placeholder: ex. email@example.com 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: details 22 | attributes: 23 | label: Describe the feature or enhancement 24 | description: Please include how the feature should behave in all possible scenarios. 25 | placeholder: Tell us what you want! 26 | validations: 27 | required: true 28 | - type: checkboxes 29 | id: terms 30 | attributes: 31 | label: Code of Conduct 32 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/OffchainLabs/arbitrum-token-bridge/blob/82e4cfc0f569467f20ad0ba7a82c366e99140cd3/CODE_OF_CONDUCT.md) 33 | options: 34 | - label: I agree to follow this project's Code of Conduct 35 | required: true 36 | -------------------------------------------------------------------------------- /.github/workflows/monitor-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": { 3 | "generateCommand": "generateCoreChainsToMonitor", 4 | "configFile": "__auto-generated-core-chains.json", 5 | "slackTokens": { 6 | "assertion": "CORE_CHAIN_ASSERTION_MONITORING_SLACK_TOKEN", 7 | "batch-poster": "CORE_CHAIN_BATCH_POSTER_MONITORING_SLACK_TOKEN", 8 | "retryable": "CORE_CHAIN_RETRYABLE_MONITORING_SLACK_TOKEN" 9 | }, 10 | "slackChannels": { 11 | "assertion": "CORE_CHAIN_ASSERTION_MONITORING_SLACK_CHANNEL", 12 | "batch-poster": "CORE_CHAIN_BATCH_POSTER_MONITORING_SLACK_CHANNEL", 13 | "retryable": "CORE_CHAIN_RETRYABLE_MONITORING_SLACK_CHANNEL" 14 | } 15 | }, 16 | "orbit": { 17 | "generateCommand": "generateOrbitChainsToMonitor", 18 | "configFile": "__auto-generated-orbit-chains.json", 19 | "slackTokens": { 20 | "assertion": "ORBIT_CHAIN_ASSERTION_MONITORING_SLACK_TOKEN", 21 | "batch-poster": "ORBIT_CHAIN_BATCH_POSTER_MONITORING_SLACK_TOKEN", 22 | "retryable": "ORBIT_RETRYABLE_MONITORING_SLACK_TOKEN" 23 | }, 24 | "slackChannels": { 25 | "assertion": "ORBIT_CHAIN_ASSERTION_MONITORING_SLACK_CHANNEL", 26 | "batch-poster": "ORBIT_CHAIN_BATCH_POSTER_MONITORING_SLACK_CHANNEL", 27 | "retryable": "ORBIT_RETRYABLE_MONITORING_SLACK_CHANNEL" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/NoteBox.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ExclamationTriangleIcon, 3 | InformationCircleIcon, 4 | XCircleIcon 5 | } from '@heroicons/react/24/outline' 6 | import { PropsWithChildren } from 'react' 7 | import { twMerge } from 'tailwind-merge' 8 | 9 | type NoteBoxProps = PropsWithChildren<{ 10 | variant?: 'info' | 'warning' | 'error' 11 | className?: string 12 | }> 13 | 14 | const iconClassName = 'h-3 w-3 shrink-0 mt-[2px]' 15 | 16 | const wrapperClassNames = { 17 | info: 'bg-cyan text-cyan-dark', 18 | warning: 'bg-orange text-orange-dark', 19 | error: 'bg-brick text-brick-dark' 20 | } 21 | 22 | export const NoteBox = ({ 23 | children, 24 | variant = 'info', 25 | className 26 | }: NoteBoxProps) => { 27 | return ( 28 |
35 | {variant === 'info' && ( 36 | 37 | )} 38 | {variant === 'warning' && ( 39 | 40 | )} 41 | {variant === 'error' && } 42 | {children} 43 |
44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | import { MergedTransaction } from '../../state/app/state' 4 | import { TransactionHistorySearchBar } from './TransactionHistorySearchBar' 5 | import { TransactionHistorySearchResults } from './TransactionHistorySearchResults' 6 | 7 | type TxDetailsStore = { 8 | tx: MergedTransaction | null 9 | isOpen: boolean 10 | open: (tx: MergedTransaction) => void 11 | close: () => void 12 | reset: () => void 13 | } 14 | 15 | export const useTxDetailsStore = create(set => ({ 16 | tx: null, 17 | isOpen: false, 18 | open: (tx: MergedTransaction) => { 19 | set(() => ({ 20 | tx 21 | })) 22 | // this is so that we can trigger transition when opening the panel 23 | setTimeout(() => { 24 | set(() => ({ 25 | isOpen: true 26 | })) 27 | }, 0) 28 | }, 29 | close: () => set({ isOpen: false }), 30 | reset: () => set({ tx: null }) 31 | })) 32 | 33 | export const TransactionHistory = () => { 34 | return ( 35 |
36 | 37 | 38 | 39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/next.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check type next.config.js 2 | 3 | /** 4 | * @type {import('next').NextConfig} 5 | **/ 6 | 7 | module.exports = { 8 | distDir: 'build', 9 | productionBrowserSourceMaps: true, 10 | reactStrictMode: true, 11 | images: { 12 | remotePatterns: [ 13 | { 14 | protocol: 'https', 15 | hostname: 'portal.arbitrum.io' 16 | } 17 | ] 18 | }, 19 | async headers() { 20 | return [ 21 | { 22 | source: '/api/status', 23 | headers: [ 24 | { 25 | key: 'Access-Control-Allow-Origin', 26 | value: 'https://portal.arbitrum.io' 27 | }, 28 | { 29 | key: 'Access-Control-Allow-Methods', 30 | value: 'GET' 31 | } 32 | ] 33 | } 34 | ] 35 | }, 36 | async redirects() { 37 | return [ 38 | { 39 | source: '/:slug((?!^$|api/|_next/|public/)(?!.*\\.[^/]+$).+)', 40 | missing: [ 41 | { 42 | type: 'query', 43 | key: 'destinationChain' 44 | }, 45 | { 46 | type: 'header', 47 | key: 'accept', 48 | value: 'image/.*' 49 | } 50 | ], 51 | destination: '/?destinationChain=:slug', 52 | permanent: true 53 | } 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/ApeChainLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/hooks/useAccountType.ts: -------------------------------------------------------------------------------- 1 | import { useAccount } from 'wagmi' 2 | import useSWRImmutable from 'swr/immutable' 3 | 4 | import { addressIsSmartContract } from '../util/AddressUtils' 5 | import { useNetworks } from './useNetworks' 6 | 7 | type Result = { 8 | isEOA: boolean 9 | isSmartContractWallet: boolean 10 | isLoading: boolean 11 | } 12 | 13 | export function useAccountType(): Result { 14 | const { address, isConnected } = useAccount() 15 | // TODO: change to use connected chain when Safe wallet returns it correctly 16 | // atm Safe UI would try to switch connected chain even when user is at the correct chain 17 | // so the connected chain WAGMI returns is the wrong chain where the SCW is not deployed on 18 | const [{ sourceChain }] = useNetworks() 19 | 20 | const { data: isSmartContractWallet = false, isLoading } = useSWRImmutable( 21 | address && isConnected && sourceChain 22 | ? [address, sourceChain.id, 'useAccountType'] 23 | : null, 24 | ([_address, chainId]) => addressIsSmartContract(_address, chainId), 25 | { 26 | shouldRetryOnError: true, 27 | errorRetryCount: 2, 28 | errorRetryInterval: 5_000 29 | } 30 | ) 31 | 32 | // By default, assume it's an EOA 33 | return { 34 | isEOA: !isSmartContractWallet, 35 | isSmartContractWallet, 36 | isLoading 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/Notifications.tsx: -------------------------------------------------------------------------------- 1 | // import { InformationCircleIcon } from '@heroicons/react/24/outline' 2 | // import { twMerge } from 'tailwind-merge' 3 | 4 | import { PropsWithChildren } from 'react' 5 | 6 | function NotificationContainer({ children }: PropsWithChildren) { 7 | return ( 8 |
9 |
10 |
{children}
11 |
12 |
13 | ) 14 | } 15 | 16 | // function Notification({ 17 | // infoIcon, 18 | // children, 19 | // mode = 'dark' 20 | // }: { 21 | // infoIcon?: boolean 22 | // mode?: 'light' | 'dark' 23 | // children: React.ReactNode 24 | // }) { 25 | // return ( 26 | //
32 | // {infoIcon && ( 33 | //
40 | // ) 41 | // } 42 | 43 | export function Notifications() { 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /packages/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "1.0.0", 4 | "description": "Collection of utility scripts", 5 | "main": "dist/index.js", 6 | "type": "commonjs", 7 | "scripts": { 8 | "build": "vite build", 9 | "test": "vitest", 10 | "coverage": "vitest run --coverage", 11 | "start": "node dist/index.js", 12 | "add-orbit-chain": "node dist/scripts.cjs.js add-orbit-chain", 13 | "validate-orbit-chains-data": "node dist/scripts.cjs.js validate-orbit-chains-data" 14 | 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "@actions/core": "^1.10.1", 20 | "@actions/github": "^6.0.0", 21 | "@arbitrum/sdk": "^4.0.2", 22 | "@octokit/rest": "^21.0.2", 23 | "axios": "^1.7.7", 24 | "commander": "^12.1.0", 25 | "ethers": "^5.7.2", 26 | "file-type": "^19.6.0", 27 | "mime-types": "^2.1.35", 28 | "sharp": "0.32.6", 29 | "zod": "^3.23.8" 30 | 31 | }, 32 | "devDependencies": { 33 | "@types/mime-types": "^2.1.4", 34 | "@types/node": "^22.7.1", 35 | "@typescript-eslint/eslint-plugin": "^5.0.0", 36 | "@typescript-eslint/parser": "^5.0.0", 37 | "@vitest/coverage-v8": "^0.34.0", 38 | "dotenv": "^16.4.5", 39 | "eslint": "^8.0.0", 40 | "typescript": "^5.0.0", 41 | "vite": "^5.4.7", 42 | "vitest": "^0.34.0" 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/GoogleCalendar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/arrows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/SafeImage.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, ImgHTMLAttributes } from 'react' 2 | 3 | import { sanitizeImageSrc } from '../../util' 4 | 5 | export type SafeImageProps = ImgHTMLAttributes & { 6 | fallback?: JSX.Element 7 | } 8 | 9 | export function SafeImage(props: SafeImageProps) { 10 | const { fallback = null, src, ...imgProps } = props 11 | const [validImageSrc, setValidImageSrc] = useState(false) 12 | 13 | useEffect(() => { 14 | const image = new Image() 15 | 16 | if (typeof src === 'undefined') { 17 | setValidImageSrc(false) 18 | } else { 19 | const sanitizedImageSrc = sanitizeImageSrc(src) 20 | 21 | image.onerror = () => setValidImageSrc(false) 22 | image.onload = () => setValidImageSrc(sanitizedImageSrc) 23 | image.src = sanitizedImageSrc 24 | } 25 | 26 | return function cleanup() { 27 | // Abort previous loading 28 | image.src = '' 29 | } 30 | }, [src]) 31 | 32 | if (!validImageSrc) { 33 | return fallback 34 | } 35 | 36 | // SafeImage is used for token logo, we don't know at buildtime where those images will be loaded from 37 | // It would throw error if it's loaded from external domains 38 | // eslint-disable-next-line @next/next/no-img-element 39 | return {props.alt 40 | } 41 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/xErc20Utils.ts: -------------------------------------------------------------------------------- 1 | import { getProviderForChainId } from '@/token-bridge-sdk/utils' 2 | import { fetchErc20L2GatewayAddress } from './TokenUtils' 3 | import { ChainId } from './networks' 4 | import { TokenWithdrawalApprovalParams } from './L2ApprovalUtils' 5 | 6 | export const xErc20Gateways: { 7 | [chainId: number]: { 8 | parentChainId: ChainId 9 | parentGateway: string 10 | childGateway: string 11 | } 12 | } = { 13 | [ChainId.ArbitrumSepolia]: { 14 | parentChainId: ChainId.Sepolia, 15 | parentGateway: '0x30BEc9c7C2d102aF63F23712bEAc69cdF013f062', 16 | childGateway: '0x30BEc9c7C2d102aF63F23712bEAc69cdF013f062' 17 | } 18 | } 19 | 20 | export async function xErc20RequiresApprovalOnChildChain({ 21 | tokenAddressOnParentChain, 22 | parentChainId, 23 | childChainId 24 | }: TokenWithdrawalApprovalParams): Promise { 25 | const gatewayData = xErc20Gateways[childChainId] 26 | 27 | if (gatewayData?.parentChainId !== parentChainId) { 28 | return false 29 | } 30 | 31 | const childChainProvider = getProviderForChainId(childChainId) 32 | 33 | const childChainGatewayAddress = await fetchErc20L2GatewayAddress({ 34 | erc20L1Address: tokenAddressOnParentChain, 35 | l2Provider: childChainProvider 36 | }) 37 | 38 | return ( 39 | childChainGatewayAddress.toLowerCase() === 40 | gatewayData.childGateway.toLowerCase() 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, PropsWithChildren, useState } from 'react' 2 | 3 | import { ExternalLink } from '../common/ExternalLink' 4 | import { Transition } from '@headlessui/react' 5 | import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline' 6 | 7 | export const TransactionsTableExternalLink = ({ 8 | children, 9 | href, 10 | disabled = false 11 | }: PropsWithChildren<{ href: string; disabled?: boolean }>) => { 12 | const [show, setShow] = useState(false) 13 | 14 | if (disabled) { 15 | return children 16 | } 17 | 18 | return ( 19 | setShow(true)} 22 | onMouseLeave={() => setShow(false)} 23 | className="arb-hover flex items-center space-x-2" 24 | > 25 | {children} 26 | 27 | 36 | 37 | 38 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/e2e/specs/txHistory.cy.ts: -------------------------------------------------------------------------------- 1 | const DEPOSIT_ROW_IDENTIFIER = /deposit-row-*/i 2 | const CLAIMABLE_ROW_IDENTIFIER = /claimable-row-*/i 3 | 4 | describe('Transaction History', () => { 5 | it('should successfully open and use pending transactions panel', () => { 6 | cy.login({ 7 | networkType: 'parentChain', 8 | networkName: 'sepolia', 9 | query: { 10 | sourceChain: 'sepolia', 11 | destinationChain: 'arbitrum-sepolia' 12 | } 13 | }) 14 | // open tx history panel 15 | context('open transactions history panel', () => { 16 | cy.switchToTransactionHistoryTab('pending') 17 | cy.findAllByTestId(CLAIMABLE_ROW_IDENTIFIER) 18 | .its('length') 19 | .should('be.gt', 0) 20 | }) 21 | }) 22 | 23 | it('should successfully open and use settled transactions panel', () => { 24 | cy.login({ 25 | networkType: 'parentChain', 26 | networkName: 'sepolia', 27 | query: { 28 | sourceChain: 'sepolia', 29 | destinationChain: 'arbitrum-sepolia' 30 | } 31 | }) 32 | context('open transactions history panel', () => { 33 | cy.switchToTransactionHistoryTab('settled') 34 | cy.findAllByTestId(CLAIMABLE_ROW_IDENTIFIER) 35 | .its('length') 36 | .should('be.gt', 0) 37 | cy.findAllByTestId(DEPOSIT_ROW_IDENTIFIER) 38 | .its('length') 39 | .should('be.gt', 0) 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/AddressUtils.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@ethersproject/providers' 2 | 3 | import { getAPIBaseUrl } from '.' 4 | import { getProviderForChainId } from '../token-bridge-sdk/utils' 5 | 6 | export type Address = `0x${string}` 7 | 8 | export async function addressIsSmartContract(address: string, chainId: number) { 9 | const provider = getProviderForChainId(chainId) 10 | try { 11 | return (await provider.getCode(address)).length > 2 12 | } catch (_) { 13 | return false 14 | } 15 | } 16 | 17 | export async function addressIsDenylisted(address: string) { 18 | // The denylist consists of an array of addresses from Ethereum, Arbitrum One and Sepolia. 19 | // We do not separate them as it's unlikely for anyone to have a wallet address matching our contract addresses. 20 | try { 21 | const denylistResponse = await fetch( 22 | `${getAPIBaseUrl()}/api/denylist?address=${address}`, 23 | { 24 | method: 'GET', 25 | headers: { 'Content-Type': 'application/json' } 26 | } 27 | ) 28 | return (await denylistResponse.json()).data as boolean 29 | } catch (error) { 30 | console.error(error) 31 | return false 32 | } 33 | } 34 | 35 | export function getNonce( 36 | address: string | undefined, 37 | { provider }: { provider: Provider } 38 | ): Promise { 39 | if (typeof address === 'undefined') { 40 | return 0 as unknown as Promise 41 | } 42 | 43 | return provider.getTransactionCount(address) 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/formatSpecfiles.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const specFiles = require("../../packages/arb-token-bridge-ui/tests/e2e/specfiles.json"); 3 | const cctpFiles = require("../../packages/arb-token-bridge-ui/tests/e2e/cctp.json"); 4 | 5 | const tests = []; 6 | 7 | const testType = process.argv[2]; 8 | switch (testType) { 9 | case "regular": { 10 | specFiles.forEach((spec) => { 11 | tests.push({ 12 | ...spec, 13 | type: "regular", 14 | typeName: "", 15 | }); 16 | tests.push({ 17 | ...spec, 18 | type: "orbit-eth", 19 | typeName: "with L3 (ETH)", 20 | }); 21 | tests.push({ 22 | ...spec, 23 | type: "orbit-custom-6dec", 24 | typeName: "with L3 (6 decimals custom)", 25 | }); 26 | tests.push({ 27 | ...spec, 28 | type: "orbit-custom-18dec", 29 | typeName: "with L3 (18 decimals custom)", 30 | }); 31 | tests.push({ 32 | ...spec, 33 | type: "orbit-custom-20dec", 34 | typeName: "with L3 (20 decimals custom)", 35 | }); 36 | }); 37 | break; 38 | } 39 | case "cctp": { 40 | // Running CCTP tests in parallel cause nonce issues, we're running the two tests sequentially 41 | tests.push({ 42 | name: "cctp", 43 | typeName: "", 44 | file: "tests/e2e/specs/**/*Cctp.cy.{js,jsx,ts,tsx}", 45 | recordVideo: false, 46 | type: "cctp", 47 | }); 48 | break; 49 | } 50 | } 51 | 52 | console.log(JSON.stringify(tests)); 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arb-token-bridge-ui-mono", 3 | "private": true, 4 | "license": "Apache-2.0", 5 | "version": "1.0.0", 6 | "description": "", 7 | "main": "index.js", 8 | "scripts": { 9 | "dev": "yarn workspace arb-token-bridge-ui dev", 10 | "build": "yarn workspace arb-token-bridge-ui build", 11 | "start": "yarn workspace arb-token-bridge-ui start", 12 | "audit:ci": "audit-ci --config ./audit-ci.jsonc", 13 | "test:ci": "yarn workspace arb-token-bridge-ui test:ci", 14 | "prettier:check": "./node_modules/.bin/prettier --check .", 15 | "prettier:format": "./node_modules/.bin/prettier --write .", 16 | "lint": "yarn workspace arb-token-bridge-ui lint", 17 | "lint:fix": "yarn workspace arb-token-bridge-ui lint:fix", 18 | "test:e2e": "yarn workspace arb-token-bridge-ui env-cmd --silent --file .e2e.env yarn synpress run --configFile synpress.config.ts", 19 | "test:e2e:cctp": "yarn test:e2e --configFile synpress.cctp.config.ts", 20 | "test:e2e:orbit": "E2E_ORBIT=true yarn test:e2e", 21 | "test:e2e:orbit:custom-gas-token": "E2E_ORBIT_CUSTOM_GAS_TOKEN=true yarn test:e2e" 22 | }, 23 | "resolutions": { 24 | "**/@walletconnect/ethereum-provider": "2.13.1", 25 | "**/@ethersproject/providers/ws": "7.5.10", 26 | "**/@synthetixio/synpress/ws": "8.17.1", 27 | "**/elliptic": "6.6.0" 28 | }, 29 | "keywords": [], 30 | "author": "", 31 | "workspaces": [ 32 | "packages/*" 33 | ], 34 | "devDependencies": { 35 | "audit-ci": "^6.3.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/NativeCurrencyPrice.tsx: -------------------------------------------------------------------------------- 1 | import { NativeCurrency } from '../../hooks/useNativeCurrency' 2 | import { useNetworks } from '../../hooks/useNetworks' 3 | import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' 4 | import { useAppState } from '../../state' 5 | import { isNetwork } from '../../util/networks' 6 | import { useETHPrice } from '../../hooks/useETHPrice' 7 | import { formatUSD } from '../../util/NumberUtils' 8 | 9 | export function useIsBridgingEth(childChainNativeCurrency: NativeCurrency) { 10 | const { 11 | app: { selectedToken } 12 | } = useAppState() 13 | const isBridgingEth = 14 | selectedToken === null && !childChainNativeCurrency.isCustom 15 | return isBridgingEth 16 | } 17 | 18 | export function NativeCurrencyPrice({ 19 | amount, 20 | showBrackets = false 21 | }: { 22 | amount: number | undefined 23 | showBrackets?: boolean 24 | }) { 25 | const [networks] = useNetworks() 26 | const { childChain } = useNetworksRelationship(networks) 27 | const { isTestnet } = isNetwork(childChain.id) 28 | 29 | // currently only ETH price is supported 30 | const { ethToUSD } = useETHPrice() 31 | 32 | if (isTestnet) { 33 | return null 34 | } 35 | 36 | if (typeof amount === 'undefined') { 37 | return null 38 | } 39 | 40 | return ( 41 | 42 | {showBrackets && '('} 43 | {formatUSD(ethToUSD(amount))} 44 | {showBrackets && ')'} 45 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useSelectedTokenIsWithdrawOnly.ts: -------------------------------------------------------------------------------- 1 | import useSWRImmutable from 'swr/immutable' 2 | import { useMemo } from 'react' 3 | 4 | import { useAppState } from '../../../state' 5 | import { useNetworks } from '../../../hooks/useNetworks' 6 | import { useNetworksRelationship } from '../../../hooks/useNetworksRelationship' 7 | import { isWithdrawOnlyToken } from '../../../util/WithdrawOnlyUtils' 8 | 9 | export function useSelectedTokenIsWithdrawOnly() { 10 | const { 11 | app: { selectedToken } 12 | } = useAppState() 13 | const [networks] = useNetworks() 14 | const { isDepositMode, parentChain, childChain } = 15 | useNetworksRelationship(networks) 16 | 17 | const queryKey = useMemo(() => { 18 | if (!selectedToken) { 19 | return null 20 | } 21 | if (!isDepositMode) { 22 | return null 23 | } 24 | return [ 25 | selectedToken.address.toLowerCase(), 26 | parentChain.id, 27 | childChain.id 28 | ] as const 29 | }, [selectedToken, isDepositMode, parentChain.id, childChain.id]) 30 | 31 | const { data: isSelectedTokenWithdrawOnly, isLoading } = useSWRImmutable( 32 | queryKey, 33 | ([parentChainErc20Address, parentChainId, childChainId]) => 34 | isWithdrawOnlyToken({ 35 | parentChainErc20Address, 36 | parentChainId, 37 | childChainId 38 | }) 39 | ) 40 | 41 | return { 42 | isSelectedTokenWithdrawOnly, 43 | isSelectedTokenWithdrawOnlyLoading: isLoading 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useIsCctpTransfer.ts: -------------------------------------------------------------------------------- 1 | import { useNetworks } from '../../../hooks/useNetworks' 2 | import { useNetworksRelationship } from '../../../hooks/useNetworksRelationship' 3 | import { useAppState } from '../../../state' 4 | import { isNetwork } from '../../../util/networks' 5 | import { 6 | isTokenArbitrumOneNativeUSDC, 7 | isTokenArbitrumSepoliaNativeUSDC, 8 | isTokenMainnetUSDC, 9 | isTokenSepoliaUSDC 10 | } from '../../../util/TokenUtils' 11 | 12 | export const useIsCctpTransfer = function () { 13 | const { 14 | app: { selectedToken } 15 | } = useAppState() 16 | const [networks] = useNetworks() 17 | const { childChain, isDepositMode, isTeleportMode } = 18 | useNetworksRelationship(networks) 19 | const { isArbitrumOne, isArbitrumSepolia } = isNetwork(childChain.id) 20 | 21 | if (!selectedToken) { 22 | return false 23 | } 24 | 25 | if (isTeleportMode) { 26 | return false 27 | } 28 | 29 | if (isDepositMode) { 30 | if (isTokenMainnetUSDC(selectedToken.address) && isArbitrumOne) { 31 | return true 32 | } 33 | 34 | if (isTokenSepoliaUSDC(selectedToken.address) && isArbitrumSepolia) { 35 | return true 36 | } 37 | } else { 38 | if (isTokenArbitrumOneNativeUSDC(selectedToken.address) && isArbitrumOne) { 39 | return true 40 | } 41 | 42 | if ( 43 | isTokenArbitrumSepoliaNativeUSDC(selectedToken.address) && 44 | isArbitrumSepolia 45 | ) { 46 | return true 47 | } 48 | } 49 | 50 | return false 51 | } 52 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/pages/api/status.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | export type ArbitrumStatusResponse = { 5 | content: { 6 | components: { 7 | id: string 8 | name: string 9 | description: string 10 | status: 11 | | 'UNDERMAINTENANCE' 12 | | 'OPERATIONAL' 13 | | 'DEGRADEDPERFORMANCE' 14 | | 'PARTIALOUTAGE' 15 | | 'MAJOROUTAGE' 16 | }[] 17 | } 18 | } 19 | 20 | const STATUS_URL = 'https://status.arbitrum.io/v2/components.json' 21 | 22 | export default async function handler( 23 | req: NextApiRequest, 24 | res: NextApiResponse<{ 25 | data: ArbitrumStatusResponse | undefined 26 | message?: string 27 | }> 28 | ) { 29 | try { 30 | // validate method 31 | if (req.method !== 'GET') { 32 | res 33 | .status(400) 34 | .send({ message: `invalid_method: ${req.method}`, data: undefined }) 35 | return 36 | } 37 | 38 | const statusSummary = (await axios.get(STATUS_URL)).data 39 | const resultJson = { 40 | meta: { 41 | timestamp: new Date().toISOString() 42 | }, 43 | content: statusSummary 44 | } 45 | 46 | res.setHeader('Cache-Control', `max-age=0, s-maxage=${10 * 60}`) // cache response for 10 minutes 47 | res.status(200).json({ data: resultJson as ArbitrumStatusResponse }) 48 | } catch (error: any) { 49 | res.status(500).json({ 50 | message: error?.message ?? 'Something went wrong', 51 | data: undefined 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useIsTransferAllowed.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { useAccount, useNetwork } from 'wagmi' 3 | 4 | import { useAppState } from '../../../state' 5 | import { useNetworks } from '../../../hooks/useNetworks' 6 | import { useDestinationAddressError } from './useDestinationAddressError' 7 | 8 | export function useIsTransferAllowed() { 9 | const { 10 | app: { 11 | arbTokenBridgeLoaded, 12 | arbTokenBridge: { eth } 13 | } 14 | } = useAppState() 15 | const { address: walletAddress, isConnected } = useAccount() 16 | // do not use `useChainId` because it won't detect chains outside of our wagmi config 17 | const { chain } = useNetwork() 18 | const [networks] = useNetworks() 19 | const { destinationAddressError } = useDestinationAddressError() 20 | 21 | return useMemo(() => { 22 | const isConnectedToTheWrongChain = chain?.id !== networks.sourceChain.id 23 | 24 | if (!arbTokenBridgeLoaded) { 25 | return false 26 | } 27 | if (!eth) { 28 | return false 29 | } 30 | if (!isConnected) { 31 | return false 32 | } 33 | if (!walletAddress) { 34 | return false 35 | } 36 | if (isConnectedToTheWrongChain) { 37 | return false 38 | } 39 | if (!!destinationAddressError) { 40 | return false 41 | } 42 | return true 43 | }, [ 44 | arbTokenBridgeLoaded, 45 | chain?.id, 46 | destinationAddressError, 47 | isConnected, 48 | eth, 49 | networks.sourceChain.id, 50 | walletAddress 51 | ]) 52 | } 53 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TopNavBar.tsx: -------------------------------------------------------------------------------- 1 | import { Tab } from '@headlessui/react' 2 | import { PaperAirplaneIcon } from '@heroicons/react/24/outline' 3 | import { PropsWithChildren } from 'react' 4 | import { twMerge } from 'tailwind-merge' 5 | import Image from 'next/image' 6 | import { useTransactionReminderInfo } from './TransactionHistory/useTransactionReminderInfo' 7 | 8 | function StyledTab({ children, ...props }: PropsWithChildren) { 9 | return ( 10 | 14 | {children} 15 | 16 | ) 17 | } 18 | 19 | StyledTab.displayName = 'StyledTab' 20 | 21 | export function TopNavBar() { 22 | const { colorClassName } = useTransactionReminderInfo() 23 | 24 | return ( 25 | 30 | 31 | 32 | Bridge 33 | 34 | 35 | history icon 41 | Txn History{' '} 42 | 45 | 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadinessUtils.ts: -------------------------------------------------------------------------------- 1 | export enum TransferReadinessRichErrorMessage { 2 | GAS_ESTIMATION_FAILURE, 3 | TOKEN_WITHDRAW_ONLY, 4 | TOKEN_TRANSFER_DISABLED 5 | } 6 | 7 | type GetInsufficientFundsErrorMessageParams = { 8 | asset: string 9 | chain: string 10 | } 11 | 12 | type GetInsufficientFundsForGasFeesErrorMessageParams = 13 | GetInsufficientFundsErrorMessageParams & { 14 | balance: string 15 | requiredBalance: string 16 | } 17 | 18 | export function getInsufficientFundsErrorMessage({ 19 | asset, 20 | chain 21 | }: GetInsufficientFundsErrorMessageParams) { 22 | return `Insufficient ${asset}. Please add more funds to ${chain}.` 23 | } 24 | 25 | export function getInsufficientFundsForGasFeesErrorMessage({ 26 | asset, 27 | chain, 28 | balance, 29 | requiredBalance 30 | }: GetInsufficientFundsForGasFeesErrorMessageParams) { 31 | const errorMessage = `Please add more ${asset} on ${chain} to pay for gas fees.` 32 | 33 | if (balance === requiredBalance) { 34 | // An edge case where formatAmount returns the same value. In this case we don't want to show balances because in the UI it's the same as requiredBalance. 35 | return errorMessage 36 | } 37 | 38 | return ( 39 | errorMessage + 40 | ` You currently have ${balance} ${asset}, but the transaction requires ${requiredBalance} ${asset}.` 41 | ) 42 | } 43 | 44 | export function getSmartContractWalletTeleportTransfersNotSupportedErrorMessage() { 45 | return `LayerLeap transfers using smart contract wallets aren't supported yet.` 46 | } 47 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/pages/api/denylist.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next' 2 | import denylist from '../../../public/__auto-generated-denylist.json' 3 | 4 | const ONE_WEEK_IN_SECONDS = 60 * 60 * 24 * 7 5 | 6 | type Request = NextApiRequest & { query: { address: string } } 7 | 8 | export default async function handler( 9 | req: Request, 10 | res: NextApiResponse<{ data: boolean | undefined; message?: string }> 11 | ) { 12 | try { 13 | // validate method 14 | if (req.method !== 'GET') { 15 | res 16 | .status(400) 17 | .send({ message: `invalid_method: ${req.method}`, data: undefined }) 18 | return 19 | } 20 | 21 | const { address } = req.query 22 | 23 | if (typeof address !== 'string') { 24 | res.status(400).send({ 25 | message: `invalid_parameter: expected 'address' to be a string but got ${typeof address}`, 26 | data: undefined 27 | }) 28 | return 29 | } 30 | 31 | const isDenylisted = new Set(denylist.content).has(address.toLowerCase()) 32 | 33 | // https://vercel.com/docs/concepts/functions/serverless-functions/edge-caching#cache-control 34 | // https://vercel.com/docs/concepts/functions/serverless-functions/edge-caching#recommended-cache-control 35 | res.setHeader('Cache-Control', `max-age=0, s-maxage=${ONE_WEEK_IN_SECONDS}`) 36 | 37 | res.status(200).json({ data: isDenylisted }) 38 | } catch (error: any) { 39 | res.status(500).json({ 40 | message: error?.message ?? 'Something went wrong', 41 | data: undefined 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/sidebar/tools.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/TokenTeleportEnabledUtils.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from './networks' 2 | 3 | export type TeleportEnabledToken = { 4 | symbol: string 5 | l1Address: string 6 | allowedL3ChainIds: number[] 7 | } 8 | 9 | const teleportEnabledTokens: { 10 | [parentChainId: number]: TeleportEnabledToken[] 11 | } = { 12 | [ChainId.Ethereum]: [ 13 | { 14 | symbol: 'WETH', 15 | l1Address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', 16 | allowedL3ChainIds: [1380012617, 70700, 70701] 17 | }, 18 | { 19 | symbol: 'RARI', 20 | l1Address: '0xFca59Cd816aB1eaD66534D82bc21E7515cE441CF', 21 | allowedL3ChainIds: [1380012617] // only allowed for RARI 22 | } 23 | ], 24 | [ChainId.Sepolia]: [ 25 | { 26 | symbol: 'WETH', 27 | l1Address: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14', 28 | allowedL3ChainIds: [ 29 | 1918988905 // RARI Testnet 30 | ] 31 | }, 32 | { 33 | symbol: 'LINK', 34 | l1Address: '0x779877A7B0D9E8603169DdbD7836e478b4624789', 35 | allowedL3ChainIds: [ 36 | 1918988905 // RARI Testnet 37 | ] 38 | } 39 | ] 40 | } 41 | 42 | export function isTeleportEnabledToken( 43 | erc20L1Address: string, 44 | parentChainId: number, 45 | childChainId: number 46 | ) { 47 | // check teleport enabled tokens and return true if the token is not enabled 48 | return (teleportEnabledTokens[parentChainId] ?? []) 49 | .filter(token => token.allowedL3ChainIds.includes(childChainId)) 50 | .map(token => token.l1Address.toLowerCase()) 51 | .includes(erc20L1Address.toLowerCase()) 52 | } 53 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/sidebar/projects.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const TOS_VERSION = 2 2 | 3 | export const TOS_LOCALSTORAGE_KEY = 'arbitrum:bridge:tos-v' + TOS_VERSION 4 | 5 | const SUPPORT_LINK_BASE = 'https://support.arbitrum.io' 6 | 7 | export const GET_HELP_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/requests/new?ticket_form_id=18155929976987` 8 | 9 | export const PORTAL_DOMAIN = 'https://portal.arbitrum.io' 10 | 11 | export const DOCS_DOMAIN = 'https://docs.arbitrum.io' 12 | 13 | export const USDC_LEARN_MORE_LINK = `${DOCS_DOMAIN}/bridge-tokens/concepts/usdc-concept` 14 | 15 | export const TOKEN_APPROVAL_ARTICLE_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/articles/18213893952923` 16 | 17 | export const ETH_BALANCE_ARTICLE_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/articles/18213854684699` 18 | 19 | export const CONFIRMATION_PERIOD_ARTICLE_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/articles/18213843096091` 20 | 21 | export const FAST_WITHDRAWAL_DOCS_ARTICLE_LINK = `${DOCS_DOMAIN}/run-arbitrum-node/arbos-releases/arbos31#additional-requirement-for-arbitrum-orbit-chains-who-wish-to-enable-fast-withdrawals` 22 | 23 | export const ORBIT_QUICKSTART_LINK = 24 | 'https://docs.arbitrum.io/launch-orbit-chain/orbit-quickstart' 25 | 26 | export const CCTP_DOCUMENTATION = 27 | 'https://www.circle.com/en/cross-chain-transfer-protocol' 28 | 29 | export const MULTICALL_TESTNET_ADDRESS = 30 | '0xcA11bde05977b3631167028862bE2a173976CA11' 31 | 32 | export const ETHER_TOKEN_LOGO = '/images/EthereumLogoRound.svg' 33 | 34 | export const ether = { name: 'Ether', symbol: 'ETH', decimals: 18 } as const 35 | 36 | export const PORTAL_API_ENDPOINT = 'https://portal.arbitrum.io' 37 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/syncers/TokenListSyncer.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useAccount } from 'wagmi' 3 | import { useNetworks } from '../../hooks/useNetworks' 4 | import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' 5 | 6 | import { useAppState } from '../../state' 7 | import { 8 | addBridgeTokenListToBridge, 9 | BRIDGE_TOKEN_LISTS 10 | } from '../../util/TokenListUtils' 11 | 12 | // Adds whitelisted tokens to the bridge data on app load 13 | // In the token list we should show later only tokens with positive balances 14 | const TokenListSyncer = (): JSX.Element => { 15 | const { 16 | app: { arbTokenBridge, arbTokenBridgeLoaded } 17 | } = useAppState() 18 | const { address: walletAddress } = useAccount() 19 | const [networks] = useNetworks() 20 | const { childChain } = useNetworksRelationship(networks) 21 | 22 | useEffect(() => { 23 | if (!arbTokenBridgeLoaded) { 24 | return 25 | } 26 | 27 | if (!walletAddress) { 28 | return 29 | } 30 | 31 | const tokenListsToSet = BRIDGE_TOKEN_LISTS.filter(bridgeTokenList => { 32 | // Always load the Arbitrum Token token list 33 | if (bridgeTokenList.isArbitrumTokenTokenList) { 34 | return true 35 | } 36 | 37 | return ( 38 | bridgeTokenList.originChainID === childChain.id && 39 | bridgeTokenList.isDefault 40 | ) 41 | }) 42 | 43 | tokenListsToSet.forEach(bridgeTokenList => { 44 | addBridgeTokenListToBridge(bridgeTokenList, arbTokenBridge) 45 | }) 46 | }, [walletAddress, childChain.id, arbTokenBridgeLoaded]) 47 | 48 | return <> 49 | } 50 | 51 | export { TokenListSyncer } 52 | -------------------------------------------------------------------------------- /packages/scripts/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Command } from "commander"; 3 | import * as fs from "fs"; 4 | import { addOrbitChain } from "./addOrbitChain"; 5 | import { validateOrbitChainsList } from "./addOrbitChain/schemas"; 6 | 7 | const program = new Command(); 8 | 9 | program 10 | .name("orbit-scripts") 11 | .description("CLI for Orbit chain management scripts") 12 | .version("1.0.0"); 13 | 14 | program 15 | .command("add-orbit-chain ") 16 | .description("Add a new Orbit chain") 17 | .action((targetJsonPath) => { 18 | addOrbitChain(targetJsonPath).catch((error) => { 19 | console.error(`Error in addOrbitChain: ${error}`); 20 | process.exit(1); 21 | }); 22 | }); 23 | 24 | program 25 | .command("validate-orbit-chains-data") 26 | .description("Validate the orbitChainsData.json file") 27 | .argument("", "Path to the orbitChainsData.json file") 28 | .action((file: string) => { 29 | try { 30 | const data = fs.readFileSync(file, "utf8"); 31 | const orbitChainsList = JSON.parse(data); 32 | validateOrbitChainsList(orbitChainsList).catch((error) => { 33 | console.error(`Error in validateOrbitChainsList: ${error}`); 34 | process.exit(1); 35 | }); 36 | } catch (error) { 37 | console.error("Error reading or parsing file:", error); 38 | process.exit(1); 39 | } 40 | }); 41 | 42 | // Add more commands here as needed, for example: 43 | // program 44 | // .command('some-other-script') 45 | // .description('Description of the other script') 46 | // .action(() => { 47 | // // Call the function for the other script 48 | // }); 49 | 50 | program.parse(process.argv); 51 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Cache build artifacts 2 | 3 | on: 4 | workflow_call: 5 | 6 | env: 7 | NEXT_PUBLIC_INFURA_KEY: ${{ secrets.NEXT_PUBLIC_INFURA_KEY }} 8 | NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID }} 9 | THE_GRAPH_NETWORK_API_KEY: ${{ secrets.THE_GRAPH_NETWORK_API_KEY }} 10 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 11 | 12 | jobs: 13 | check-build-cache: 14 | name: Check cache for build artifacts 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Check cache for build artifacts 21 | id: cache 22 | uses: actions/cache/restore@v4 23 | with: 24 | path: | 25 | ./packages/arb-token-bridge-ui/build 26 | key: build-artifacts-${{ github.run_id }} 27 | 28 | - name: Install node_modules 29 | if: steps.cache.outputs.cache-hit != 'true' 30 | uses: OffchainLabs/actions/node-modules/install@main 31 | 32 | - name: Build 33 | if: steps.cache.outputs.cache-hit != 'true' 34 | shell: bash 35 | run: yarn build 36 | env: 37 | NEXT_PUBLIC_IS_E2E_TEST: true 38 | NEXT_PUBLIC_INFURA_KEY: ${{ secrets.NEXT_PUBLIC_INFURA_KEY }} 39 | NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID }} 40 | THE_GRAPH_NETWORK_API_KEY: ${{ secrets.THE_GRAPH_NETWORK_API_KEY }} 41 | 42 | - name: Cache build artifacts 43 | if: steps.cache.outputs.cache-hit != 'true' 44 | uses: actions/cache/save@v4 45 | with: 46 | path: | 47 | ./packages/arb-token-bridge-ui/build 48 | key: build-artifacts-${{ github.run_id }} 49 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/UsdcLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/fetchL2Gateways.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@ethersproject/providers' 2 | import { getArbitrumNetwork } from '@arbitrum/sdk' 3 | 4 | import { 5 | l2ArbReverseGatewayAddresses, 6 | l2DaiGatewayAddresses, 7 | l2LptGatewayAddresses, 8 | l2MoonGatewayAddresses, 9 | l2UsdcGatewayAddresses, 10 | l2wstETHGatewayAddresses 11 | } from '../util/networks' 12 | 13 | /** 14 | * Fetch L2 gateways for a given L2 network using its provider. Useful for specifying which gateways to use when fetching withdrawals. 15 | * 16 | * @param l2Provider Provider for the L2 network 17 | */ 18 | export async function fetchL2Gateways(l2Provider: Provider) { 19 | const l2Network = await getArbitrumNetwork(l2Provider) 20 | 21 | const gatewaysToUse = [] 22 | const l2ArbReverseGateway = l2ArbReverseGatewayAddresses[l2Network.chainId] 23 | const l2DaiGateway = l2DaiGatewayAddresses[l2Network.chainId] 24 | const l2wstETHGateway = l2wstETHGatewayAddresses[l2Network.chainId] 25 | const l2LptGateway = l2LptGatewayAddresses[l2Network.chainId] 26 | const l2MoonGateway = l2MoonGatewayAddresses[l2Network.chainId] 27 | const l2UsdcGateway = l2UsdcGatewayAddresses[l2Network.chainId] 28 | 29 | if (l2ArbReverseGateway) { 30 | gatewaysToUse.push(l2ArbReverseGateway) 31 | } 32 | if (l2DaiGateway) { 33 | gatewaysToUse.push(l2DaiGateway) 34 | } 35 | if (l2wstETHGateway) { 36 | gatewaysToUse.push(l2wstETHGateway) 37 | } 38 | if (l2LptGateway) { 39 | gatewaysToUse.push(l2LptGateway) 40 | } 41 | if (l2MoonGateway) { 42 | gatewaysToUse.push(l2MoonGateway) 43 | } 44 | if (l2UsdcGateway) { 45 | gatewaysToUse.push(l2UsdcGateway) 46 | } 47 | 48 | return gatewaysToUse 49 | } 50 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransactionHistory/EmptyTransactionHistory.tsx: -------------------------------------------------------------------------------- 1 | import { GET_HELP_LINK } from '../../constants' 2 | import { ExternalLink } from '../common/ExternalLink' 3 | import { 4 | ContentWrapper, 5 | HistoryLoader, 6 | LoadMoreButton 7 | } from './TransactionHistoryTable' 8 | 9 | export const EmptyTransactionHistory = ({ 10 | loading, 11 | isError, 12 | paused, 13 | resume, 14 | tabType 15 | }: { 16 | loading: boolean 17 | isError: boolean 18 | paused: boolean 19 | resume: () => void 20 | tabType: 'pending' | 'settled' 21 | }) => { 22 | if (loading) { 23 | return ( 24 | 25 | 26 | 27 | ) 28 | } 29 | if (isError) { 30 | return ( 31 | 32 |

33 | We seem to be having a difficult time loading your data, we're 34 | working hard to resolve it. 35 |

36 |

Please give it a moment and then try refreshing the page.

37 |

38 | If the problem persists, please file a ticket{' '} 39 | 40 | here 41 | 42 | . 43 |

44 |
45 | ) 46 | } 47 | if (paused) { 48 | return ( 49 | 50 |

There are no recent {tabType} transactions.

51 | 52 |
53 | ) 54 | } 55 | return ( 56 | 57 | No {tabType} transactions. 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/Sidebar/AppMobileSidebar.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { PlusCircleIcon } from '@heroicons/react/24/outline' 3 | import { MenuItem } from '@offchainlabs/cobalt' 4 | import { ConnectButton } from '@rainbow-me/rainbowkit' 5 | import dynamic from 'next/dynamic' 6 | import { usePostHog } from 'posthog-js/react' 7 | import { useAccount } from 'wagmi' 8 | import { AccountMenuItem } from './AccountMenuItem' 9 | 10 | // Dynamically import the MobileSidebar component with SSR disabled 11 | const DynamicMobileSidebar = dynamic( 12 | () => 13 | import('@offchainlabs/cobalt').then(mod => ({ 14 | default: mod.MobileSidebar 15 | })), 16 | { ssr: false } 17 | ) 18 | 19 | export const AppMobileSidebar: React.FC = () => { 20 | const posthog = usePostHog() 21 | const { isConnected } = useAccount() 22 | 23 | return ( 24 |
25 | 30 | {isConnected ? ( 31 | 32 | ) : ( 33 | 34 | {({ openConnectModal }) => ( 35 | } 39 | className="border-lime-dark bg-lime-dark py-3" 40 | isMobile 41 | /> 42 | )} 43 | 44 | )} 45 | 46 |
47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/sidebar/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/header/headerLogo_governance.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import { ArbitrumNetwork } from '@arbitrum/sdk' 2 | import { OrbitChainConfig } from '../src/util/orbitChainsList' 3 | import { getExplorerUrl, rpcURLs } from '../src/util/networks' 4 | 5 | export interface ChainToMonitor extends ArbitrumNetwork { 6 | parentRpcUrl: string 7 | orbitRpcUrl: string 8 | explorerUrl: string 9 | parentExplorerUrl: string 10 | } 11 | 12 | export const sanitizeExplorerUrl = (url: string) => { 13 | // we want explorer urls to have a trailing slash 14 | if (url.endsWith('/')) { 15 | return url 16 | } else { 17 | return url + '/' 18 | } 19 | } 20 | 21 | export const sanitizeRpcUrl = (url: string) => { 22 | // we want rpc urls to NOT have a trailing slash 23 | if (url.endsWith('/')) { 24 | return url.slice(0, -1) 25 | } else { 26 | return url 27 | } 28 | } 29 | 30 | const hasExplorerUrl = ( 31 | chain: ArbitrumNetwork | OrbitChainConfig 32 | ): chain is OrbitChainConfig => { 33 | return typeof (chain as OrbitChainConfig).explorerUrl !== 'undefined' 34 | } 35 | 36 | // make the chain data compatible with that required by the retryable-monitoring script 37 | // TODO: in a later refactor, we will update the term `orbitRpcUrl` to chain-agnostic, `rpcUrl` 38 | export const getChainToMonitor = ({ 39 | chain, 40 | rpcUrl 41 | }: { 42 | chain: ArbitrumNetwork | OrbitChainConfig 43 | rpcUrl: string 44 | }): ChainToMonitor => ({ 45 | ...chain, 46 | explorerUrl: sanitizeExplorerUrl( 47 | hasExplorerUrl(chain) ? chain.explorerUrl : getExplorerUrl(chain.chainId) 48 | ), 49 | orbitRpcUrl: sanitizeRpcUrl(rpcUrl), 50 | parentRpcUrl: sanitizeRpcUrl(rpcURLs[chain.parentChainId]), 51 | parentExplorerUrl: sanitizeExplorerUrl(getExplorerUrl(chain.parentChainId)) 52 | }) 53 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/common/Transition.tsx: -------------------------------------------------------------------------------- 1 | import { Transition as HeadlessUiTransition } from '@headlessui/react' 2 | import { PropsWithChildren } from 'react' 3 | import { twMerge } from 'tailwind-merge' 4 | 5 | type TransitionSpeed = 'slow' | 'normal' | 'fast' 6 | 7 | type TransitionOptions = { 8 | enterSpeed?: TransitionSpeed 9 | leaveSpeed?: TransitionSpeed 10 | unmountOnLeave?: boolean 11 | } 12 | 13 | type TransitionProps = PropsWithChildren<{ 14 | isOpen?: boolean 15 | options?: TransitionOptions 16 | className?: string 17 | afterLeave?: () => void 18 | }> 19 | 20 | function getDurationClassName(speed: TransitionSpeed) { 21 | switch (speed) { 22 | case 'slow': 23 | return 'duration-1000' 24 | case 'normal': 25 | return 'duration-500' 26 | case 'fast': 27 | return 'duration-200' 28 | } 29 | } 30 | 31 | export const Transition = (props: TransitionProps) => { 32 | const { options, children, className, isOpen, afterLeave } = props 33 | 34 | const enterSpeed = options?.enterSpeed ?? 'fast' 35 | const leaveSpeed = options?.leaveSpeed ?? 'fast' 36 | const unmountOnLeave = options?.unmountOnLeave ?? true 37 | 38 | return ( 39 | 51 | {children} 52 | 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/ArbitrumOneLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from 'wagmi' 2 | import { mainnet, arbitrum } from 'wagmi/chains' 3 | 4 | import { 5 | chainToWagmiChain, 6 | sepolia, 7 | holesky, 8 | arbitrumNova, 9 | arbitrumSepolia, 10 | localL1Network, 11 | localL2Network, 12 | localL3Network, 13 | baseSepolia, 14 | base 15 | } from './wagmiAdditionalNetworks' 16 | import { ChainId, getCustomChainFromLocalStorageById } from '../networks' 17 | import { orbitChains } from '../orbitChainsList' 18 | 19 | export function getWagmiChain(chainId: number): Chain { 20 | const customChain = getCustomChainFromLocalStorageById(chainId) 21 | const orbitChain = orbitChains[chainId] 22 | 23 | if (customChain) { 24 | return chainToWagmiChain(customChain) 25 | } 26 | 27 | if (orbitChain) { 28 | return chainToWagmiChain(orbitChain) 29 | } 30 | 31 | switch (chainId) { 32 | case ChainId.Ethereum: 33 | return mainnet 34 | 35 | case ChainId.ArbitrumOne: 36 | return arbitrum 37 | 38 | case ChainId.ArbitrumNova: 39 | return arbitrumNova 40 | 41 | case ChainId.Base: 42 | return base 43 | 44 | // Testnets 45 | case ChainId.Sepolia: 46 | return sepolia 47 | 48 | case ChainId.Holesky: 49 | return holesky 50 | 51 | case ChainId.ArbitrumSepolia: 52 | return arbitrumSepolia 53 | 54 | case ChainId.BaseSepolia: 55 | return baseSepolia 56 | 57 | // Local networks 58 | case ChainId.Local: 59 | return localL1Network 60 | 61 | case ChainId.ArbitrumLocal: 62 | return localL2Network 63 | 64 | case ChainId.L3Local: 65 | return localL3Network 66 | 67 | default: 68 | throw new Error(`[getWagmiChain] Unexpected chain id: ${chainId}`) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useNativeCurrencyBalances.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { BigNumber } from 'ethers' 3 | 4 | import { useNativeCurrency } from '../../../hooks/useNativeCurrency' 5 | import { useNetworks } from '../../../hooks/useNetworks' 6 | import { useNetworksRelationship } from '../../../hooks/useNetworksRelationship' 7 | import { useBalances } from '../../../hooks/useBalances' 8 | 9 | export function useNativeCurrencyBalances(): { 10 | sourceBalance: BigNumber | null 11 | destinationBalance: BigNumber | null 12 | } { 13 | const [networks] = useNetworks() 14 | const { childChainProvider, isDepositMode } = 15 | useNetworksRelationship(networks) 16 | const nativeCurrency = useNativeCurrency({ provider: childChainProvider }) 17 | 18 | const { ethParentBalance, erc20ParentBalances, ethChildBalance } = 19 | useBalances() 20 | 21 | return useMemo(() => { 22 | if (!nativeCurrency.isCustom) { 23 | return { 24 | sourceBalance: isDepositMode ? ethParentBalance : ethChildBalance, 25 | destinationBalance: isDepositMode ? ethChildBalance : ethParentBalance 26 | } 27 | } 28 | 29 | const customFeeTokenParentBalance = 30 | erc20ParentBalances?.[nativeCurrency.address] ?? null 31 | const customFeeTokenChildBalance = ethChildBalance 32 | 33 | return { 34 | sourceBalance: isDepositMode 35 | ? customFeeTokenParentBalance 36 | : customFeeTokenChildBalance, 37 | destinationBalance: isDepositMode 38 | ? customFeeTokenChildBalance 39 | : customFeeTokenParentBalance 40 | } 41 | }, [ 42 | nativeCurrency, 43 | erc20ParentBalances, 44 | ethChildBalance, 45 | isDepositMode, 46 | ethParentBalance 47 | ]) 48 | } 49 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/scripts/generateCoreChainsToMonitor.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import 'dotenv/config' 3 | import { getArbitrumNetwork } from '@arbitrum/sdk' 4 | import { ChainId, rpcURLs } from '../src/util/networks' 5 | import { getChainToMonitor } from './utils' 6 | 7 | // github secrets return '' for empty values, so we need to sanitize the value 8 | const sanitizeEnvValue = (envValue: any) => { 9 | return typeof envValue === 'string' && envValue !== '' ? envValue : undefined 10 | } 11 | 12 | async function generateCoreChainsToMonitor() { 13 | const novaChain = { 14 | ...getArbitrumNetwork(ChainId.ArbitrumNova), 15 | rpcURL: 16 | sanitizeEnvValue(process.env.NOVA_MONITOR_RPC_URL) ?? 17 | rpcURLs[ChainId.ArbitrumNova] 18 | } 19 | const arbOneChain = { 20 | ...getArbitrumNetwork(ChainId.ArbitrumOne), 21 | rpcURL: 22 | sanitizeEnvValue(process.env.ARB_ONE_MONITOR_RPC_URL) ?? 23 | rpcURLs[ChainId.ArbitrumOne] 24 | } 25 | 26 | // don't need to monitor arbOne chain in case of retryable-monitoring 27 | const chains = 28 | process.env.INCLUDE_ARB_ONE_AS_CORE_CHAIN === 'true' 29 | ? [arbOneChain, novaChain] 30 | : [novaChain] 31 | 32 | // make the chain data compatible with that required by the monitoring script 33 | const coreChainsToMonitor = chains.map(coreChain => 34 | getChainToMonitor({ 35 | chain: coreChain, 36 | rpcUrl: coreChain.rpcURL 37 | }) 38 | ) 39 | 40 | // write to orbit-chains.json, we will use this json as an input to the monitoring script 41 | const resultsJson = JSON.stringify( 42 | { 43 | childChains: coreChainsToMonitor 44 | }, 45 | null, 46 | 2 47 | ) 48 | fs.writeFileSync('./public/__auto-generated-core-chains.json', resultsJson) 49 | } 50 | 51 | generateCoreChainsToMonitor() 52 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/public/images/header/headerLogo_help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/util/L2NativeUtils.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '../util/networks' 2 | import { CommonAddress } from './CommonAddressUtils' 3 | 4 | export type L2NativeToken = { 5 | name: string 6 | symbol: string 7 | address: string 8 | decimals: number 9 | logoURI: string 10 | } 11 | 12 | export const ArbOneNativeUSDC = { 13 | name: 'USD Coin', 14 | symbol: 'USDC', 15 | address: CommonAddress.ArbitrumOne.USDC, 16 | decimals: 6, 17 | logoURI: 'https://s2.coinmarketcap.com/static/img/coins/64x64/3408.png' 18 | } 19 | 20 | const L2NativeTokens: { [chainId: number]: L2NativeToken[] } = { 21 | [ChainId.ArbitrumOne]: [ 22 | { 23 | name: 'GMX', 24 | symbol: 'GMX', 25 | address: '0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a', 26 | decimals: 18, 27 | logoURI: 'https://s2.coinmarketcap.com/static/img/coins/64x64/11857.png' 28 | }, 29 | { 30 | name: 'STASIS EURO', 31 | symbol: 'EURS', 32 | address: '0xD22a58f79e9481D1a88e00c343885A588b34b68B', 33 | decimals: 2, 34 | logoURI: 'https://s2.coinmarketcap.com/static/img/coins/64x64/2989.png' 35 | } 36 | ], 37 | [ChainId.ArbitrumNova]: [] 38 | } 39 | 40 | function find(erc20L2Address: string, l2ChainId: number) { 41 | return ( 42 | (L2NativeTokens[l2ChainId] ?? []) 43 | // 44 | .find( 45 | token => token.address.toLowerCase() === erc20L2Address.toLowerCase() 46 | ) 47 | ) 48 | } 49 | 50 | export function getL2NativeToken( 51 | erc20L2Address: string, 52 | l2ChainId: number 53 | ): L2NativeToken { 54 | const token = find(erc20L2Address, l2ChainId) 55 | 56 | if (typeof token === 'undefined') { 57 | throw new Error( 58 | `Can't find L2-native token with address ${erc20L2Address} on chain ${l2ChainId}` 59 | ) 60 | } 61 | 62 | return token 63 | } 64 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/TransferPanel/CctpTabContent.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react' 2 | import { CCTP_DOCUMENTATION } from '../../constants' 3 | import { useCCTPIsBlocked } from '../../hooks/CCTP/useCCTPIsBlocked' 4 | import { ExternalLink } from '../common/ExternalLink' 5 | import { getExplorerUrl, getNetworkName } from '../../util/networks' 6 | import { CCTPSupportedChainId, getUSDCAddresses } from '../../state/cctpState' 7 | 8 | export const CctpTabContent = ({ 9 | destinationChainId, 10 | children 11 | }: PropsWithChildren<{ 12 | destinationChainId: CCTPSupportedChainId 13 | }>) => { 14 | const { data: isCctpBlocked, isLoading: isLoadingIsCctpBlocked } = 15 | useCCTPIsBlocked() 16 | 17 | if (isLoadingIsCctpBlocked || isCctpBlocked) { 18 | return ( 19 |

20 | Access to Circle's bridge is restricted in certain jurisdictions. 21 | For more details, please consult Circle's{' '} 22 | 23 | documentation. 24 | {' '} 25 |

26 | ) 27 | } 28 | 29 | return ( 30 | <> 31 |

32 | Receive{' '} 33 | 39 | Native USDC 40 | {' '} 41 | on {getNetworkName(destinationChainId)} with Circle's{' '} 42 | 43 | Cross-Chain Transfer Protocol 44 | {' '} 45 | within the Arbitrum Bridge. 46 |

47 | {children} 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/src/components/Sidebar/AccountMenuItem.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ArrowLeftOnRectangleIcon, 3 | ArrowTopRightOnSquareIcon, 4 | Cog6ToothIcon, 5 | DocumentTextIcon 6 | } from '@heroicons/react/24/outline' 7 | import { MenuItem, MenuItemExpandable } from '@offchainlabs/cobalt' 8 | 9 | import { getExplorerUrl } from '../../util/networks' 10 | import { SafeImage } from '../common/SafeImage' 11 | import { useAccountMenu } from '../../hooks/useAccountMenu' 12 | import { CustomBoringAvatar } from '../common/CustomBoringAvatar' 13 | 14 | export const AccountMenuItem = () => { 15 | const { 16 | address, 17 | accountShort, 18 | ensName, 19 | ensAvatar, 20 | disconnect, 21 | udInfo, 22 | chain, 23 | setQueryParams 24 | } = useAccountMenu() 25 | 26 | return ( 27 | } 35 | /> 36 | } 37 | > 38 | {chain && ( 39 | } 42 | href={`${getExplorerUrl(chain.id)}/address/${address}`} 43 | isMobile 44 | /> 45 | )} 46 | } 49 | onClick={() => setQueryParams({ settingsOpen: true })} 50 | isMobile 51 | /> 52 | } 55 | onClick={() => disconnect()} 56 | isMobile 57 | /> 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /packages/arb-token-bridge-ui/tests/e2e/specfiles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Login and balance check", 4 | "file": "tests/e2e/specs/**/login.cy.{js,jsx,ts,tsx}", 5 | "recordVideo": "false" 6 | }, 7 | { 8 | "name": "Deposit native token", 9 | "file": "tests/e2e/specs/**/depositNativeToken.cy.{js,jsx,ts,tsx}", 10 | "recordVideo": "false" 11 | }, 12 | { 13 | "name": "Withdraw native token", 14 | "file": "tests/e2e/specs/**/withdrawNativeToken.cy.{js,jsx,ts,tsx}", 15 | "recordVideo": "false" 16 | }, 17 | { 18 | "name": "Deposit ERC20", 19 | "file": "tests/e2e/specs/**/depositERC20.cy.{js,jsx,ts,tsx}", 20 | "recordVideo": "false" 21 | }, 22 | { 23 | "name": "Withdraw ERC20", 24 | "file": "tests/e2e/specs/**/withdrawERC20.cy.{js,jsx,ts,tsx}", 25 | "recordVideo": "false" 26 | }, 27 | { 28 | "name": "Batch deposit", 29 | "file": "tests/e2e/specs/**/batchDeposit.cy.{js,jsx,ts,tsx}", 30 | "recordVideo": "false" 31 | }, 32 | { 33 | "name": "TX history", 34 | "file": "tests/e2e/specs/**/txHistory.cy.{js,jsx,ts,tsx}", 35 | "recordVideo": "false" 36 | }, 37 | { 38 | "name": "Approve ERC20", 39 | "file": "tests/e2e/specs/**/approveToken.cy.{js,jsx,ts,tsx}", 40 | "recordVideo": "false" 41 | }, 42 | { 43 | "name": "Import test ERC20", 44 | "file": "tests/e2e/specs/**/importToken.cy.{js,jsx,ts,tsx}", 45 | "recordVideo": "false" 46 | }, 47 | { 48 | "name": "Read classic deposits", 49 | "file": "tests/e2e/specs/**/readClassicDeposits.cy.{js,jsx,ts,tsx}", 50 | "recordVideo": "false" 51 | }, 52 | { 53 | "name": "Redeem Retryable", 54 | "file": "tests/e2e/specs/**/redeemRetryable.cy.{js,jsx,ts,tsx}", 55 | "recordVideo": "false" 56 | }, 57 | { 58 | "name": "Switch network", 59 | "file": "tests/e2e/specs/**/switchNetworks.cy.{js,jsx,ts,tsx}", 60 | "recordVideo": "false" 61 | } 62 | ] 63 | --------------------------------------------------------------------------------