diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenImportDialog.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenImportDialog.tsx index 6709ca10d1..da78b114ef 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenImportDialog.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenImportDialog.tsx @@ -176,7 +176,7 @@ export function TokenImportDialog({ const selectToken = useCallback( async (_token: ERC20BridgeToken) => { - await token.updateTokenData(_token.address) + await token.updateTokenData(_token.address.toLowerCase()) actions.app.setSelectedToken(_token) }, [token, actions] diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenRow.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenRow.tsx index 65fcb9dafe..1c3e5eb986 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenRow.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenRow.tsx @@ -8,10 +8,7 @@ import { Chain } from 'wagmi' import { Loader } from '../common/atoms/Loader' import { useAppState } from '../../state' -import { - listIdsToNames, - SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID -} from '../../util/TokenListUtils' +import { listIdsToNames } from '../../util/TokenListUtils' import { formatAmount } from '../../util/NumberUtils' import { shortenAddress } from '../../util/CommonUtils' import { @@ -20,6 +17,7 @@ import { sanitizeTokenName, sanitizeTokenSymbol } from '../../util/TokenUtils' +import { isArbitrumToken as isArbitrumTokenCheck } from '../../util/ArbTokenUtils' import { SafeImage } from '../common/SafeImage' import { getExplorerUrl, getNetworkName } from '../../util/networks' import { Tooltip } from '../common/Tooltip' @@ -179,13 +177,7 @@ function useTokenInfo(token: ERC20BridgeToken | null) { const balance = useBalanceOnSourceChain(token) - const isArbitrumToken = useMemo(() => { - if (!token) { - return false - } - - return token.listIds.has(SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID) - }, [token]) + const isArbitrumToken = isArbitrumTokenCheck(token) const isPotentialFakeArbitrumToken = useMemo(() => { if (!token || isArbitrumToken) { @@ -193,8 +185,8 @@ function useTokenInfo(token: ERC20BridgeToken | null) { } return ( - token.name.toLowerCase().startsWith('arb') || - token.symbol.toLowerCase().startsWith('arb') + token.name.toLowerCase().startsWith('arbitrum') || + token.symbol.toLowerCase() === 'arb' ) }, [token, isArbitrumToken]) @@ -252,6 +244,8 @@ function TokenBalance({ token }: { token: ERC20BridgeToken | null }) { isTokenArbitrumOneNativeUSDC(token?.address) || isTokenArbitrumSepoliaNativeUSDC(token?.address) + const isArbitrumToken = isArbitrumTokenCheck(token) + const tokenIsAddedToTheBridge = useMemo(() => { // Can happen when switching networks. if (typeof bridgeTokens === 'undefined') { @@ -266,8 +260,12 @@ function TokenBalance({ token }: { token: ERC20BridgeToken | null }) { return true } - return typeof bridgeTokens[token.address] !== 'undefined' - }, [bridgeTokens, isArbitrumNativeUSDC, token]) + if (isArbitrumToken) { + return true + } + + return typeof bridgeTokens[token.address.toLowerCase()] !== 'undefined' + }, [bridgeTokens, isArbitrumNativeUSDC, isArbitrumToken, token]) const decimals = useMemo(() => { if (token) { diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx index 93af1c6977..b9ec18292f 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx @@ -68,16 +68,23 @@ function TokenListRow({ tokenList }: { tokenList: BridgeTokenList }) { app: { arbTokenBridge } } = useAppState() const { bridgeTokens, token } = arbTokenBridge + const [networks] = useNetworks() + const { childChain, parentChain } = useNetworksRelationship(networks) const toggleTokenList = useCallback( (bridgeTokenList: BridgeTokenList, isActive: boolean) => { if (isActive) { token.removeTokensFromList(bridgeTokenList.id) } else { - addBridgeTokenListToBridge(bridgeTokenList, arbTokenBridge) + addBridgeTokenListToBridge({ + bridgeTokenList, + arbTokenBridge, + parentChainId: parentChain.id, + childChainId: childChain.id + }) } }, - [arbTokenBridge, token] + [arbTokenBridge, childChain.id, parentChain.id, token] ) const isActive = Object.keys(bridgeTokens ?? []).some(address => { @@ -311,9 +318,9 @@ function TokensPanel({ return true } - // Always show official ARB token except from or to Orbit chain + // Always show official ARB token if (token?.listIds.has(SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID)) { - return !isOrbitChain + return true } const balance = getBalance(address) @@ -554,7 +561,9 @@ export function TokenSearch({ return } - if (!_token.address) { + const lowercasedTokenAddress = _token.address.toLowerCase() + + if (!lowercasedTokenAddress) { return } @@ -566,8 +575,8 @@ export function TokenSearch({ try { // Native USDC on L2 won't have a corresponding L1 address const isL2NativeUSDC = - isTokenArbitrumOneNativeUSDC(_token.address) || - isTokenArbitrumSepoliaNativeUSDC(_token.address) + isTokenArbitrumOneNativeUSDC(lowercasedTokenAddress) || + isTokenArbitrumSepoliaNativeUSDC(lowercasedTokenAddress) if (isL2NativeUSDC) { if (isLoadingAccountType) { @@ -596,7 +605,7 @@ export function TokenSearch({ name: 'USD Coin', type: TokenType.ERC20, symbol: 'USDC', - address: _token.address, + address: lowercasedTokenAddress, l2Address: childChainUsdcAddress, decimals: 6, listIds: new Set() @@ -609,8 +618,8 @@ export function TokenSearch({ } // Token not added to the bridge, so we'll handle importing it - if (typeof bridgeTokens[_token.address] === 'undefined') { - setTokenQueryParam(_token.address) + if (typeof bridgeTokens[lowercasedTokenAddress] === 'undefined') { + setTokenQueryParam(lowercasedTokenAddress) return } @@ -619,19 +628,19 @@ export function TokenSearch({ } const data = await fetchErc20Data({ - address: _token.address, + address: lowercasedTokenAddress, provider: parentChainProvider }) if (data) { - token.updateTokenData(_token.address) + token.updateTokenData(lowercasedTokenAddress) setSelectedToken({ ...erc20DataToErc20BridgeToken(data), l2Address: _token.l2Address }) } - if (isTransferDisabledToken(_token.address, childChain.id)) { + if (isTransferDisabledToken(lowercasedTokenAddress, childChain.id)) { openTransferDisabledDialog() return } diff --git a/packages/arb-token-bridge-ui/src/components/syncers/TokenListSyncer.tsx b/packages/arb-token-bridge-ui/src/components/syncers/TokenListSyncer.tsx index d2bdb11462..9101a0c060 100644 --- a/packages/arb-token-bridge-ui/src/components/syncers/TokenListSyncer.tsx +++ b/packages/arb-token-bridge-ui/src/components/syncers/TokenListSyncer.tsx @@ -1,5 +1,4 @@ import { useEffect } from 'react' -import { useAccount } from 'wagmi' import { useNetworks } from '../../hooks/useNetworks' import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' @@ -15,19 +14,14 @@ const TokenListSyncer = (): JSX.Element => { const { app: { arbTokenBridge, arbTokenBridgeLoaded } } = useAppState() - const { address: walletAddress } = useAccount() const [networks] = useNetworks() - const { childChain } = useNetworksRelationship(networks) + const { childChain, parentChain } = useNetworksRelationship(networks) useEffect(() => { if (!arbTokenBridgeLoaded) { return } - if (!walletAddress) { - return - } - const tokenListsToSet = BRIDGE_TOKEN_LISTS.filter(bridgeTokenList => { // Always load the Arbitrum Token token list if (bridgeTokenList.isArbitrumTokenTokenList) { @@ -41,9 +35,14 @@ const TokenListSyncer = (): JSX.Element => { }) tokenListsToSet.forEach(bridgeTokenList => { - addBridgeTokenListToBridge(bridgeTokenList, arbTokenBridge) + addBridgeTokenListToBridge({ + bridgeTokenList, + arbTokenBridge, + parentChainId: parentChain.id, + childChainId: childChain.id + }) }) - }, [walletAddress, childChain.id, arbTokenBridgeLoaded]) + }, [childChain.id, arbTokenBridgeLoaded, parentChain.id]) return <> } diff --git a/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts b/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts index c4b4451e12..f384449470 100644 --- a/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts +++ b/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts @@ -21,9 +21,17 @@ import { Transaction, ParentToChildMessageData } from './useTransactions' +import { ChainId } from '../util/networks' export { OutgoingMessageState } +export type AddTokensFromListArgs = { + arbTokenList: TokenList + listId: number + parentChainId: ChainId + childChainId: ChainId +} + export enum TokenType { ERC20 = 'ERC20' } @@ -147,7 +155,12 @@ export interface ArbTokenBridgeEth { export interface ArbTokenBridgeToken { add: (erc20L1orL2Address: string) => Promise addL2NativeToken: (erc20L2Address: string) => void - addTokensFromList: (tokenList: TokenList, listID: number) => void + addTokensFromList: ({ + arbTokenList, + listId, + parentChainId, + childChainId + }: AddTokensFromListArgs) => void removeTokensFromList: (listID: number) => void updateTokenData: (l1Address: string) => Promise triggerOutbox: (params: { diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts b/packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts index 955e15a097..f083d3f0e3 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts @@ -1,10 +1,9 @@ -import { useCallback, useState, useMemo } from 'react' +import { useCallback, useState } from 'react' import { Chain, useAccount } from 'wagmi' import { BigNumber } from 'ethers' import { Signer } from '@ethersproject/abstract-signer' import { JsonRpcProvider } from '@ethersproject/providers' import { useLocalStorage } from '@rehooks/local-storage' -import { TokenList } from '@uniswap/token-lists' import { EventArgs, ChildToParentMessage, @@ -19,7 +18,8 @@ import { ERC20BridgeToken, L2ToL1EventResultPlus, TokenType, - L2ToL1EventResult + L2ToL1EventResult, + AddTokensFromListArgs } from './arbTokenBridge.types' import { useBalance } from './useBalance' import { @@ -33,6 +33,7 @@ import { import { getL2NativeToken } from '../util/L2NativeUtils' import { CommonAddress } from '../util/CommonAddressUtils' import { isNetwork } from '../util/networks' +import { isArbitrumTokenList } from '../util/TokenListUtils' import { useDestinationAddressStore } from '../components/TransferPanel/AdvancedSettings' import { isValidTeleportChainPair } from '@/token-bridge-sdk/teleport' import { getProviderForChainId } from '@/token-bridge-sdk/utils' @@ -135,8 +136,6 @@ export const useArbTokenBridge = ( React.Dispatch ] - const l1NetworkID = useMemo(() => String(l1.network.id), [l1.network.id]) - const [transactions, { addTransaction, updateTransaction }] = useTransactions() @@ -157,9 +156,13 @@ export const useArbTokenBridge = ( }) } - const addTokensFromList = async (arbTokenList: TokenList, listId: number) => { - const l1ChainID = l1.network.id - const l2ChainID = l2.network.id + const addTokensFromList = async ({ + arbTokenList, + listId, + parentChainId, + childChainId + }: AddTokensFromListArgs) => { + const isChildChainOrbit = isNetwork(childChainId).isOrbitChain const bridgeTokensToAdd: ContractStorage = {} @@ -169,7 +172,7 @@ export const useArbTokenBridge = ( const { address, name, symbol, extensions, decimals, logoURI, chainId } = tokenData - if (![l1ChainID, l2ChainID].includes(chainId)) { + if (![parentChainId, childChainId].includes(chainId)) { continue } @@ -205,18 +208,25 @@ export const useArbTokenBridge = ( })() if (bridgeInfo) { - const l1Address = bridgeInfo[l1NetworkID]?.tokenAddress.toLowerCase() - - if (!l1Address) { + const isArbitrumTokenAndIsChildChainOrbit = + isArbitrumTokenList(listId) && isChildChainOrbit + const parentChainAddress = isArbitrumTokenAndIsChildChainOrbit + ? address.toLowerCase() + : bridgeInfo[parentChainId]?.tokenAddress.toLowerCase() + const childChainAddress = isArbitrumTokenAndIsChildChainOrbit + ? undefined + : address.toLowerCase() + + if (!parentChainAddress) { return } - bridgeTokensToAdd[l1Address] = { + bridgeTokensToAdd[parentChainAddress] = { name, type: TokenType.ERC20, symbol, - address: l1Address, - l2Address: address.toLowerCase(), + address: parentChainAddress, + l2Address: childChainAddress, decimals, logoURI, listIds: new Set([listId]) @@ -257,10 +267,10 @@ export const useArbTokenBridge = ( // USDC is not on any token list as it's unbridgeable // but we still want to detect its balance on user's wallet - if (isNetwork(l2ChainID).isArbitrumOne) { + if (isNetwork(childChainId).isArbitrumOne) { l2Addresses.push(CommonAddress.ArbitrumOne.USDC) } - if (isNetwork(l2ChainID).isArbitrumSepolia) { + if (isNetwork(childChainId).isArbitrumSepolia) { l2Addresses.push(CommonAddress.ArbitrumSepolia.USDC) } @@ -270,6 +280,7 @@ export const useArbTokenBridge = ( return } const { address, l2Address } = tokenToAdd + if (address) { l1Addresses.push(address) } @@ -409,6 +420,7 @@ export const useArbTokenBridge = ( } }, [ + destinationAddress, bridgeTokens, setBridgeTokens, updateErc20L1Balance, diff --git a/packages/arb-token-bridge-ui/src/hooks/useTokenLists.ts b/packages/arb-token-bridge-ui/src/hooks/useTokenLists.ts index fc69158be0..b9263d05c8 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTokenLists.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTokenLists.ts @@ -5,18 +5,16 @@ import { fetchTokenListFromURL, TokenListWithId } from '../util/TokenListUtils' -import { isNetwork } from '../util/networks' export function fetchTokenLists( forL2ChainId: number ): Promise { return new Promise(resolve => { - const { isOrbitChain } = isNetwork(forL2ChainId) const requestListArray = BRIDGE_TOKEN_LISTS.filter( bridgeTokenList => bridgeTokenList.originChainID === forL2ChainId || - // Always load the Arbitrum Token token list except from or to Orbit chain - (bridgeTokenList.isArbitrumTokenTokenList && !isOrbitChain) + // Always load the Arbitrum Token token list + bridgeTokenList.isArbitrumTokenTokenList ) Promise.all( diff --git a/packages/arb-token-bridge-ui/src/util/ArbTokenUtils.ts b/packages/arb-token-bridge-ui/src/util/ArbTokenUtils.ts new file mode 100644 index 0000000000..84250e1bfb --- /dev/null +++ b/packages/arb-token-bridge-ui/src/util/ArbTokenUtils.ts @@ -0,0 +1,10 @@ +import { ERC20BridgeToken } from '../hooks/arbTokenBridge.types' +import { SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID } from './TokenListUtils' + +export function isArbitrumToken(token: ERC20BridgeToken | null) { + if (!token) { + return false + } + + return token.listIds.has(SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID) +} diff --git a/packages/arb-token-bridge-ui/src/util/TokenListUtils.ts b/packages/arb-token-bridge-ui/src/util/TokenListUtils.ts index 2fd1588848..58cc87b4a7 100644 --- a/packages/arb-token-bridge-ui/src/util/TokenListUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/TokenListUtils.ts @@ -3,6 +3,7 @@ import { schema, TokenList } from '@uniswap/token-lists' import Ajv from 'ajv' import addFormats from 'ajv-formats' import { ImageProps } from 'next/image' + import UniswapLogo from '@/images/lists/uniswap.png' import CMCLogo from '@/images/lists/cmc.png' import CoinGeckoLogo from '@/images/lists/coinGecko.svg' @@ -12,6 +13,9 @@ import { ChainId } from './networks' export const SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID = 0 +export const isArbitrumTokenList = (listId: number) => + listId === SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID + export interface BridgeTokenList { id: number originChainID: number @@ -199,15 +203,28 @@ export const validateTokenList = (tokenList: TokenList) => { return validate(tokenList) } -export const addBridgeTokenListToBridge = ( - bridgeTokenList: BridgeTokenList, +export const addBridgeTokenListToBridge = ({ + bridgeTokenList, + arbTokenBridge, + parentChainId, + childChainId +}: { + bridgeTokenList: BridgeTokenList arbTokenBridge: ArbTokenBridge -) => { + parentChainId: ChainId + childChainId: ChainId +}) => { fetchTokenListFromURL(bridgeTokenList.url).then( ({ isValid, data: tokenList }) => { if (!isValid) return + if (!tokenList) return - arbTokenBridge.token.addTokensFromList(tokenList!, bridgeTokenList.id) + arbTokenBridge.token.addTokensFromList({ + arbTokenList: tokenList, + listId: bridgeTokenList.id, + parentChainId, + childChainId + }) } ) }