diff --git a/bridge_ui/src/components/TokenSelectors/EvmTokenPicker.tsx b/bridge_ui/src/components/TokenSelectors/EvmTokenPicker.tsx index 2b5e2bba..60ef654b 100644 --- a/bridge_ui/src/components/TokenSelectors/EvmTokenPicker.tsx +++ b/bridge_ui/src/components/TokenSelectors/EvmTokenPicker.tsx @@ -89,6 +89,7 @@ export default function EvmTokenPicker( ); } else { return ethTokenToParsedTokenAccount( + chainId, tokenAccount as ethers_contracts.TokenImplementation, signerAddress ); @@ -100,7 +101,7 @@ export default function EvmTokenPicker( return Promise.reject({ error: t("Wallet is not connected.") }); } }, - [isReady, nft, provider, signerAddress, t] + [isReady, nft, provider, signerAddress, t, chainId] ); const onChangeWrapper = useCallback( diff --git a/bridge_ui/src/components/Transfer/AddToMetamask.tsx b/bridge_ui/src/components/Transfer/AddToMetamask.tsx index 0b1d545f..ca1732a1 100644 --- a/bridge_ui/src/components/Transfer/AddToMetamask.tsx +++ b/bridge_ui/src/components/Transfer/AddToMetamask.tsx @@ -43,6 +43,7 @@ export default function AddToMetamask() { try { const token = await getEthereumToken(targetAsset, provider); const { symbol, decimals } = await ethTokenToParsedTokenAccount( + targetChain, token, signerAddress ); @@ -74,6 +75,7 @@ export default function AddToMetamask() { signerAddress, hasCorrectEvmNetwork, sourceParsedTokenAccount, + targetChain ]); return provider && signerAddress && diff --git a/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.tsx b/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.tsx index e68a9b8f..5ca87881 100644 --- a/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.tsx +++ b/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.tsx @@ -22,7 +22,8 @@ import { WSOL_ADDRESS, WSOL_DECIMALS, hexToUint8Array, - getTokenPoolId + getTokenPoolId, + tryNativeToHexString } from "@alephium/wormhole-sdk"; import { Dispatch } from "@reduxjs/toolkit"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; @@ -125,10 +126,10 @@ import { } from "../utils/solana"; import { fetchSingleMetadata } from "./useAlgoMetadata"; import { ALPH_TOKEN_ID, NodeProvider } from "@alephium/web3"; -import { getAvailableBalances, getAlephiumTokenLogoURI } from "../utils/alephium"; +import { getAvailableBalances, getAlephiumTokenLogoAndSymbol } from "../utils/alephium"; import { getRegisteredTokens } from "../utils/tokens"; import { useWallet } from "@alephium/web3-react"; -import { getETHTokenLogoURI } from "../utils/evm"; +import { getBSCTokenLogoAndSymbol, getETHTokenLogoAndSymbol } from "../utils/evm"; import { Alert } from "@material-ui/lab"; import parseError from "../utils/parseError"; import i18n from "../i18n"; @@ -622,13 +623,13 @@ export const getEVMAccounts = async (chainId: ChainId, signer: ethers.Signer, wa if (tokenAccounts.find((t) => t.contract_address.toLowerCase() === result.tokenId.toLowerCase()) !== undefined) { continue } - const tokenLogoURI = await getTokenLogoURI(token.tokenChain, token.nativeAddress) + const info = await getTokenLogoAndSymbol(token.tokenChain, token.nativeAddress) tokenAccounts.push({ contract_decimals: token.decimals, - contract_ticker_symbol: token.symbol, + contract_ticker_symbol: info?.symbol ?? token.symbol, contract_name: token.name, contract_address: result.tokenId, - logo_url: token.logo ?? tokenLogoURI, + logo_url: info?.logoURI ?? token.logo, balance: result.balance.toString(), quote: undefined, quote_rate: undefined @@ -774,7 +775,7 @@ const getAlephiumParsedTokenAccounts = async (address: string, provider: NodePro const amount = balances.get(localTokenId.toLowerCase()) if (amount === undefined) continue - const tokenLogoURI = await getTokenLogoURI(token.tokenChain, token.nativeAddress) + const info = await getTokenLogoAndSymbol(token.tokenChain, token.nativeAddress) const uiAmount = formatUnits(amount, token.decimals) tokenAccounts.push(createParsedTokenAccount( address, @@ -783,9 +784,9 @@ const getAlephiumParsedTokenAccounts = async (address: string, provider: NodePro token.decimals, parseFloat(uiAmount), uiAmount, - token.symbol, + info?.symbol ?? token.symbol, token.name, - tokenLogoURI, + info?.logoURI ?? token.logo, localTokenId === ALPH_TOKEN_ID )) } @@ -797,11 +798,24 @@ const getAlephiumParsedTokenAccounts = async (address: string, provider: NodePro } } -async function getTokenLogoURI(tokenChainId: ChainId, tokenId: string): Promise { +async function getTokenLogoAndSymbol(tokenChainId: ChainId, tokenId: string): Promise<{ logoURI?: string, symbol?: string } | undefined> { + if (tokenChainId !== CHAIN_ID_ALEPHIUM) { + const wrappedIdOnALPH = getTokenPoolId( + ALEPHIUM_TOKEN_BRIDGE_CONTRACT_ID, + tokenChainId, + tryNativeToHexString(tokenId, tokenChainId), + ALEPHIUM_BRIDGE_GROUP_INDEX + ) + const info = await getAlephiumTokenLogoAndSymbol(wrappedIdOnALPH) + if (info !== undefined) return info + } + if (tokenChainId === CHAIN_ID_ETH) { - return getETHTokenLogoURI(tokenId) + return getETHTokenLogoAndSymbol(tokenId) } else if (tokenChainId === CHAIN_ID_ALEPHIUM) { - return getAlephiumTokenLogoURI(tokenId) + return getAlephiumTokenLogoAndSymbol(tokenId) + } else if (tokenChainId === CHAIN_ID_BSC) { + return getBSCTokenLogoAndSymbol(tokenId) } else { return undefined } diff --git a/bridge_ui/src/utils/alephium.ts b/bridge_ui/src/utils/alephium.ts index b8423d33..f15a3725 100644 --- a/bridge_ui/src/utils/alephium.ts +++ b/bridge_ui/src/utils/alephium.ts @@ -193,6 +193,15 @@ export async function getAlephiumTokenInfo(provider: NodeProvider, tokenId: stri } } +export async function getAlephiumTokenLogoAndSymbol(tokenId: string) { + if (tokenId === ALPH_TOKEN_ID) { + return { logoURI: alephiumIcon, symbol: 'ALPH' } + } + const tokenInfo = await getTokenFromTokenList(tokenId) + if (tokenInfo === undefined) return undefined + return { logoURI: tokenInfo.logoURI, symbol: tokenInfo.symbol } +} + export async function getAlephiumTokenLogoURI(tokenId: string): Promise { return tokenId === ALPH_TOKEN_ID ? alephiumIcon diff --git a/bridge_ui/src/utils/evm.ts b/bridge_ui/src/utils/evm.ts index 456aa261..61c8f285 100644 --- a/bridge_ui/src/utils/evm.ts +++ b/bridge_ui/src/utils/evm.ts @@ -22,27 +22,59 @@ interface TokenInfo { logoURI: string } -let _whitelist: TokenInfo[] | undefined = undefined +const TokenListURLs = { + [CHAIN_ID_ETH]: 'https://tokens-1inch-eth.ipns.dweb.link/', + [CHAIN_ID_BSC]: 'https://tokens.coingecko.com/binance-smart-chain/all.json' +} + +let _tokenWhiteList: Map = new Map() + +function getTokenListURL(chainId: ChainId): string { + if (chainId === CHAIN_ID_BSC || chainId === CHAIN_ID_ETH) { + return TokenListURLs[chainId] + } + throw new Error(`Invalid evm chain id: ${chainId}`) +} -async function loadETHTokenWhitelist(): Promise { - if (_whitelist !== undefined) return _whitelist - const { data: { tokens } } = await axios.get('https://tokens-1inch-eth.ipns.dweb.link/') - _whitelist = tokens +async function loadEVMTokenWhitelist(chainId: ChainId): Promise { + const whitelist = _tokenWhiteList.get(chainId) + if (whitelist !== undefined) return whitelist + const url = getTokenListURL(chainId) + const { data: { tokens } } = await axios.get(url) + _tokenWhiteList.set(chainId, tokens) return tokens } -export async function checkETHToken(tokenAddress: string) { +async function checkEVMToken(chainId: ChainId, tokenAddress: string) { if (CLUSTER !== 'mainnet') return - const tokenWhitelist = await loadETHTokenWhitelist() + const tokenWhitelist = await loadEVMTokenWhitelist(chainId) if (tokenWhitelist.find((token) => token.address.toLowerCase() === tokenAddress.toLowerCase()) === undefined) { - throw new Error(`${i18n.t('Token {{ tokenAddress }} does not exist in the token list', { tokenAddress })}: https://tokenlists.org/token-list?url=tokens.1inch.eth`) + throw new Error(`${i18n.t('Token {{ tokenAddress }} does not exist in the token list', { tokenAddress })}: ${getTokenListURL(chainId)}`) } } -export async function getETHTokenLogoURI(tokenAddress: string): Promise { - const tokenWhitelist = await loadETHTokenWhitelist() - return tokenWhitelist.find((token) => token.address.toLowerCase() === tokenAddress.toLowerCase())?.logoURI +async function getEVMTokenLogoAndSymbol(chainId: ChainId, tokenAddress: string) { + const tokenWhitelist = await loadEVMTokenWhitelist(chainId) + const tokenInfo = tokenWhitelist.find((token) => token.address.toLowerCase() === tokenAddress.toLowerCase()) + if (tokenInfo === undefined) return undefined + return { logoURI: tokenInfo.logoURI, symbol: tokenInfo.symbol } +} + +export async function checkETHToken(tokenAddress: string) { + await checkEVMToken(CHAIN_ID_ETH, tokenAddress) +} + +export async function getETHTokenLogoAndSymbol(tokenAddress: string) { + return await getEVMTokenLogoAndSymbol(CHAIN_ID_ETH, tokenAddress) +} + +export async function checkBSCToken(tokenAddress: string) { + await checkEVMToken(CHAIN_ID_BSC, tokenAddress) +} + +export async function getBSCTokenLogoAndSymbol(tokenAddress: string) { + return await getEVMTokenLogoAndSymbol(CHAIN_ID_BSC, tokenAddress) } //This is a valuable intermediate step to the parsed token account, as the token has metadata information on it. @@ -55,6 +87,7 @@ export async function getEthereumToken( } export async function ethTokenToParsedTokenAccount( + chainId: ChainId, token: ethers_contracts.TokenImplementation, signerAddress: string ) { @@ -62,7 +95,7 @@ export async function ethTokenToParsedTokenAccount( const balance = await token.balanceOf(signerAddress); const symbol = await token.symbol(); const name = await token.name(); - const logoURI = await getETHTokenLogoURI(token.address) + const logoURI = (await getEVMTokenLogoAndSymbol(chainId, token.address))?.logoURI return createParsedTokenAccount( signerAddress, token.address,