diff --git a/bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx b/bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx index 3d49bcb00..dfb940d20 100644 --- a/bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx +++ b/bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx @@ -4,6 +4,8 @@ import { Autocomplete } from "@material-ui/lab"; import { createFilterOptions } from "@material-ui/lab/Autocomplete"; import { TokenInfo } from "@solana/spl-token-registry"; import React, { useCallback, useMemo } from "react"; +import useMetaplexData from "../../hooks/useMetaplexData"; +import useSolanaTokenMap from "../../hooks/useSolanaTokenMap"; import { DataWrapper } from "../../store/helpers"; import { ParsedTokenAccount } from "../../store/transferSlice"; import { WORMHOLE_V1_MINT_AUTHORITY } from "../../utils/consts"; @@ -30,10 +32,8 @@ type SolanaSourceTokenSelectorProps = { value: ParsedTokenAccount | null; onChange: (newValue: ParsedTokenAccount | null) => void; accounts: ParsedTokenAccount[]; - solanaTokenMap: DataWrapper | undefined; - metaplexData: any; //DataWrapper<(Metadata | undefined)[]> | undefined | null; disabled: boolean; - mintAccounts: DataWrapper> | undefined; + mintAccounts: DataWrapper> | undefined; resetAccounts: (() => void) | undefined; nft?: boolean; }; @@ -41,16 +41,27 @@ type SolanaSourceTokenSelectorProps = { export default function SolanaSourceTokenSelector( props: SolanaSourceTokenSelectorProps ) { - const { value, onChange, disabled, resetAccounts, nft } = props; + const { value, onChange, disabled, resetAccounts, nft, mintAccounts } = props; const classes = useStyles(); const resetAccountWrapper = resetAccounts || (() => {}); //This should never happen. + const solanaTokenMap = useSolanaTokenMap(); + + const mintAddresses = useMemo(() => { + const output: string[] = []; + mintAccounts?.data?.forEach( + (mintAuth, mintAddress) => mintAddress && output.push(mintAddress) + ); + return output; + }, [mintAccounts?.data]); + + const metaplex = useMetaplexData(mintAddresses); const memoizedTokenMap: Map = useMemo(() => { const output = new Map(); - if (props.solanaTokenMap?.data) { - for (const data of props.solanaTokenMap.data) { + if (solanaTokenMap.data) { + for (const data of solanaTokenMap.data) { if (data && data.address) { output.set(data.address, data); } @@ -58,26 +69,12 @@ export default function SolanaSourceTokenSelector( } return output; - }, [props.solanaTokenMap]); - - const memoizedMetaplex: Map = useMemo(() => { - const output = new Map(); - - if (props.metaplexData.data) { - for (const data of props.metaplexData.data) { - if (data && data.mint) { - output.set(data.mint, data); - } - } - } - - return output; - }, [props.metaplexData]); + }, [solanaTokenMap]); const getSymbol = (account: ParsedTokenAccount) => { return ( memoizedTokenMap.get(account.mintKey)?.symbol || - memoizedMetaplex.get(account.mintKey)?.data?.symbol || + metaplex.data?.get(account.mintKey)?.data?.symbol || undefined ); }; @@ -85,7 +82,7 @@ export default function SolanaSourceTokenSelector( const getName = (account: ParsedTokenAccount) => { return ( memoizedTokenMap.get(account.mintKey)?.name || - memoizedMetaplex.get(account.mintKey)?.data?.name || + metaplex.data?.get(account.mintKey)?.data?.name || undefined ); }; @@ -130,11 +127,11 @@ export default function SolanaSourceTokenSelector( const renderAccount = ( account: ParsedTokenAccount, solanaTokenMap: Map, - metaplexData: Map, + metaplexData: Map | null | undefined, classes: any ) => { const tokenMapData = solanaTokenMap.get(account.mintKey); - const metaplexValue = metaplexData.get(account.mintKey); + const metaplexValue = metaplexData?.get(account.mintKey); const mintPrettyString = shortenAddress(account.mintKey); const accountAddressPrettyString = shortenAddress(account.publicKey); @@ -185,13 +182,12 @@ export default function SolanaSourceTokenSelector( //Thus we should wait for the metadata to arrive before rendering it. //TODO This can flicker dependent on how fast the useEffects in the getSourceAccounts hook complete. const isLoading = - props.metaplexData.isFetching || - props.solanaTokenMap?.isFetching || + metaplex.isFetching || + solanaTokenMap.isFetching || props.mintAccounts?.isFetching; const accountLoadError = - !(props.mintAccounts?.isFetching || props.mintAccounts?.data) && - "Unable to retrieve your token accounts"; + props.mintAccounts?.error && "Unable to retrieve your token accounts"; const error = accountLoadError; //This exists to remove NFTs from the list of potential options. It requires reading the metaplex data, so it would be @@ -200,10 +196,10 @@ export default function SolanaSourceTokenSelector( return props.accounts.filter((x) => { //TODO, do a better check which likely involves supply or checking masterEdition. const isNFT = - x.decimals === 0 && memoizedMetaplex.get(x.mintKey)?.data?.uri; + x.decimals === 0 && metaplex.data?.get(x.mintKey)?.data?.uri; return nft ? isNFT : !isNFT; }); - }, [memoizedMetaplex, nft, props.accounts]); + }, [metaplex.data, nft, props.accounts]); const isOptionDisabled = useMemo(() => { return (value: ParsedTokenAccount) => isWormholev1(value.mintKey); @@ -232,12 +228,7 @@ export default function SolanaSourceTokenSelector( /> )} renderOption={(option) => { - return renderAccount( - option, - memoizedTokenMap, - memoizedMetaplex, - classes - ); + return renderAccount(option, memoizedTokenMap, metaplex.data, classes); }} getOptionDisabled={isOptionDisabled} getOptionLabel={(option) => { diff --git a/bridge_ui/src/components/TokenSelectors/SourceTokenSelector.tsx b/bridge_ui/src/components/TokenSelectors/SourceTokenSelector.tsx index 41ff07937..742176dc1 100644 --- a/bridge_ui/src/components/TokenSelectors/SourceTokenSelector.tsx +++ b/bridge_ui/src/components/TokenSelectors/SourceTokenSelector.tsx @@ -88,8 +88,6 @@ export const TokenSelector = (props: TokenSelectorProps) => { onChange={handleOnChange} disabled={disabled} accounts={maps?.tokenAccounts?.data || []} - solanaTokenMap={maps?.tokenMap} - metaplexData={maps?.metaplex} mintAccounts={maps?.mintAccounts} resetAccounts={maps?.resetAccounts} nft={nft} @@ -109,7 +107,6 @@ export const TokenSelector = (props: TokenSelectorProps) => { value={sourceParsedTokenAccount || null} disabled={disabled} onChange={handleOnChange} - tokenMap={maps?.terraTokenMap} resetAccounts={maps?.resetAccounts} /> ) : ( diff --git a/bridge_ui/src/components/TokenSelectors/TerraSourceTokenSelector.tsx b/bridge_ui/src/components/TokenSelectors/TerraSourceTokenSelector.tsx index cc849760c..33786ac4e 100644 --- a/bridge_ui/src/components/TokenSelectors/TerraSourceTokenSelector.tsx +++ b/bridge_ui/src/components/TokenSelectors/TerraSourceTokenSelector.tsx @@ -13,12 +13,10 @@ import { } from "@terra-money/wallet-provider"; import { formatUnits } from "ethers/lib/utils"; import React, { useCallback, useMemo, useState } from "react"; -import { - createParsedTokenAccount, - TerraTokenMap, +import { createParsedTokenAccount } from "../../hooks/useGetSourceParsedTokenAccounts"; +import useTerraTokenMap, { TerraTokenMetadata, -} from "../../hooks/useGetSourceParsedTokenAccounts"; -import { DataWrapper } from "../../store/helpers"; +} from "../../hooks/useTerraTokenMap"; import { ParsedTokenAccount } from "../../store/transferSlice"; import { TERRA_HOST } from "../../utils/consts"; import { shortenAddress } from "../../utils/solana"; @@ -44,7 +42,6 @@ type TerraSourceTokenSelectorProps = { value: ParsedTokenAccount | null; onChange: (newValue: ParsedTokenAccount | null) => void; disabled: boolean; - tokenMap: DataWrapper | undefined; //TODO better type resetAccounts: (() => void) | undefined; }; @@ -90,7 +87,8 @@ export default function TerraSourceTokenSelector( props: TerraSourceTokenSelectorProps ) { const classes = useStyles(); - const { onChange, value, disabled, tokenMap, resetAccounts } = props; + const { onChange, value, disabled, resetAccounts } = props; + const tokenMap = useTerraTokenMap(); const [advancedMode, setAdvancedMode] = useState(false); const [advancedModeHolderString, setAdvancedModeHolderString] = useState(""); const [advancedModeError, setAdvancedModeError] = useState(""); @@ -115,10 +113,10 @@ export default function TerraSourceTokenSelector( const isLoading = tokenMap?.isFetching || false; const terraTokenArray = useMemo(() => { - const values = props.tokenMap?.data?.mainnet; + const values = tokenMap.data?.mainnet; const items = Object.values(values || {}); return items || []; - }, [props.tokenMap]); + }, [tokenMap]); const valueToOption = (fromProps: ParsedTokenAccount | undefined | null) => { if (!fromProps) return null; diff --git a/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.ts b/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.ts index 41db7c263..4655a633d 100644 --- a/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.ts +++ b/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.ts @@ -5,7 +5,6 @@ import { } from "@certusone/wormhole-sdk"; import { Dispatch } from "@reduxjs/toolkit"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { ENV, TokenListProvider } from "@solana/spl-token-registry"; import { AccountInfo, Connection, @@ -18,7 +17,6 @@ import { useCallback, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useSolanaWallet } from "../contexts/SolanaWalletContext"; -import { DataWrapper } from "../store/helpers"; import { errorSourceParsedTokenAccounts as errorSourceParsedTokenAccountsNFT, fetchSourceParsedTokenAccounts as fetchSourceParsedTokenAccountsNFT, @@ -32,20 +30,10 @@ import { selectNFTSourceChain, selectNFTSourceParsedTokenAccounts, selectNFTSourceWalletAddress, - selectSolanaTokenMap, selectSourceWalletAddress, - selectTerraTokenMap, selectTransferSourceChain, selectTransferSourceParsedTokenAccounts, } from "../store/selectors"; -import { - errorSolanaTokenMap, - errorTerraTokenMap, - fetchSolanaTokenMap, - fetchTerraTokenMap, - receiveSolanaTokenMap, - receiveTerraTokenMap, -} from "../store/tokenSlice"; import { errorSourceParsedTokenAccounts, fetchSourceParsedTokenAccounts, @@ -56,17 +44,7 @@ import { setSourceParsedTokenAccounts, setSourceWalletAddress, } from "../store/transferSlice"; -import { - CLUSTER, - COVALENT_GET_TOKENS_URL, - SOLANA_HOST, - TERRA_TOKEN_METADATA_URL, -} from "../utils/consts"; -import { - decodeMetadata, - getMetadataAddress, - Metadata, -} from "../utils/metaplex"; +import { COVALENT_GET_TOKENS_URL, SOLANA_HOST } from "../utils/consts"; import { extractMintAuthorityInfo, getMultipleAccountsRPC, @@ -120,19 +98,6 @@ export function createNFTParsedTokenAccount( }; } -export type TerraTokenMetadata = { - protocol: string; - symbol: string; - token: string; - icon: string; -}; - -export type TerraTokenMap = { - mainnet: { - [address: string]: TerraTokenMetadata; - }; -}; - const createParsedTokenAccountFromInfo = ( pubkey: PublicKey, item: AccountInfo @@ -158,6 +123,9 @@ const createParsedTokenAccountFromCovalent = ( decimals: covalent.contract_decimals, uiAmount: Number(formatUnits(covalent.balance, covalent.contract_decimals)), uiAmountString: formatUnits(covalent.balance, covalent.contract_decimals), + symbol: covalent.contract_ticker_symbol, + name: covalent.contract_name, + logo: covalent.logo_url, }; }; @@ -243,46 +211,10 @@ const getEthereumAccountsCovalent = async ( return output; } catch (error) { - console.error(error); return Promise.reject("Unable to retrieve your Ethereum Tokens."); } }; -const environment = CLUSTER === "testnet" ? ENV.Testnet : ENV.MainnetBeta; - -const getMetaplexData = async (mintAddresses: string[]) => { - const promises = []; - for (const address of mintAddresses) { - promises.push(getMetadataAddress(address)); - } - const metaAddresses = await Promise.all(promises); - const connection = new Connection(SOLANA_HOST, "finalized"); - const results = await getMultipleAccountsRPC( - connection, - metaAddresses.map((pair) => pair && pair[0]) - ); - - const output = results.map((account) => { - if (account === null) { - return undefined; - } else { - if (account.data) { - try { - const MetadataParsed = decodeMetadata(account.data); - return MetadataParsed; - } catch (e) { - console.error(e); - return undefined; - } - } else { - return undefined; - } - } - }); - - return output; -}; - const getSolanaParsedTokenAccounts = ( walletAddress: string, dispatch: Dispatch, @@ -317,20 +249,6 @@ const getSolanaParsedTokenAccounts = ( ); }; -const getSolanaTokenMap = (dispatch: Dispatch) => { - dispatch(fetchSolanaTokenMap()); - - new TokenListProvider().resolve().then( - (tokens) => { - const tokenList = tokens.filterByChainId(environment).getList(); - dispatch(receiveSolanaTokenMap(tokenList)); - }, - (error) => { - console.error(error); - dispatch(errorSolanaTokenMap("Failed to retrieve the Solana token map.")); - } - ); -}; /** * Fetches the balance of an asset for the connected wallet * This should handle every type of chain in the future, but only reads the Transfer state. @@ -343,21 +261,14 @@ function useGetAvailableTokens(nft: boolean = false) { ? selectNFTSourceParsedTokenAccounts : selectTransferSourceParsedTokenAccounts ); - const solanaTokenMap = useSelector(selectSolanaTokenMap); - const terraTokenMap = useSelector(selectTerraTokenMap); const lookupChain = useSelector( nft ? selectNFTSourceChain : selectTransferSourceChain ); const solanaWallet = useSolanaWallet(); const solPK = solanaWallet?.publicKey; - //const terraWallet = useConnectedWallet(); //TODO const { provider, signerAddress } = useEthereumProvider(); - const [metaplex, setMetaplex] = useState(undefined); - const [metaplexLoading, setMetaplexLoading] = useState(false); - const [metaplexError, setMetaplexError] = useState(null); - const [covalent, setCovalent] = useState(undefined); const [covalentLoading, setCovalentLoading] = useState(false); const [covalentError, setCovalentError] = useState( @@ -422,39 +333,7 @@ function useGetAvailableTokens(nft: boolean = false) { resetSourceAccounts, ]); - // Solana metaplex load - useEffect(() => { - let cancelled = false; - if (tokenAccounts.data && lookupChain === CHAIN_ID_SOLANA) { - setMetaplexLoading(true); - const accounts = tokenAccounts.data.map((account) => account.mintKey); - accounts.filter((x) => !!x); - getMetaplexData(accounts as string[]).then( - (results) => { - if (!cancelled) { - setMetaplex(results); - setMetaplexLoading(false); - } else { - } - }, - (error) => { - if (!cancelled) { - console.error(error); - setMetaplexLoading(false); - setMetaplexError(error); - } else { - } - } - ); - } else { - } - - return () => { - cancelled = true; - }; - }, [tokenAccounts, lookupChain]); - - //Solana token map & accountinfos load + //Solana accountinfos load useEffect(() => { if (lookupChain === CHAIN_ID_SOLANA && solPK) { if ( @@ -462,27 +341,10 @@ function useGetAvailableTokens(nft: boolean = false) { ) { getSolanaParsedTokenAccounts(solPK.toString(), dispatch, nft); } - if ( - !( - solanaTokenMap.data || - solanaTokenMap.isFetching || - solanaTokenMap.error - ) - ) { - getSolanaTokenMap(dispatch); - } } return () => {}; - }, [ - dispatch, - solanaWallet, - lookupChain, - solPK, - tokenAccounts, - solanaTokenMap, - nft, - ]); + }, [dispatch, solanaWallet, lookupChain, solPK, tokenAccounts, nft]); //Solana Mint Accounts lookup useEffect(() => { @@ -605,46 +467,9 @@ function useGetAvailableTokens(nft: boolean = false) { //At present, we don't have any mechanism for doing this. useEffect(() => {}, []); - //Terra metadata load - useEffect(() => { - let cancelled = false; - - if (terraTokenMap.data || lookupChain !== CHAIN_ID_TERRA) { - return; //So we don't fetch the whole list on every mount. - } - - dispatch(fetchTerraTokenMap()); - axios.get(TERRA_TOKEN_METADATA_URL).then( - (response) => { - if (!cancelled) { - //TODO parse this in a safer manner - dispatch(receiveTerraTokenMap(response.data as TerraTokenMap)); - } - }, - (error) => { - if (!cancelled) { - dispatch( - errorTerraTokenMap("Failed to retrieve the Terra Token List.") - ); - } - } - ); - - return () => { - cancelled = true; - }; - }, [lookupChain, terraTokenMap.data, dispatch]); - return lookupChain === CHAIN_ID_SOLANA ? { - tokenMap: solanaTokenMap, tokenAccounts: tokenAccounts, - metaplex: { - data: metaplex, - isFetching: metaplexLoading, - error: metaplexError, - receivedAt: null, //TODO - } as DataWrapper, mintAccounts: { data: solanaMintAccounts, isFetching: solanaMintAccountsLoading, @@ -666,7 +491,6 @@ function useGetAvailableTokens(nft: boolean = false) { } : lookupChain === CHAIN_ID_TERRA ? { - terraTokenMap: terraTokenMap, resetAccounts: resetSourceAccounts, } : undefined; diff --git a/bridge_ui/src/hooks/useMetaplexData.ts b/bridge_ui/src/hooks/useMetaplexData.ts new file mode 100644 index 000000000..9d41f255d --- /dev/null +++ b/bridge_ui/src/hooks/useMetaplexData.ts @@ -0,0 +1,108 @@ +import { Connection } from "@solana/web3.js"; +import { useLayoutEffect, useState } from "react"; +import { DataWrapper } from "../store/helpers"; +import { SOLANA_HOST } from "../utils/consts"; +import { + decodeMetadata, + getMetadataAddress, + Metadata, +} from "../utils/metaplex"; +import { getMultipleAccountsRPC } from "../utils/solana"; + +const getMetaplexData = async (mintAddresses: string[]) => { + const promises = []; + for (const address of mintAddresses) { + promises.push(getMetadataAddress(address)); + } + const metaAddresses = await Promise.all(promises); + const connection = new Connection(SOLANA_HOST, "finalized"); + const results = await getMultipleAccountsRPC( + connection, + metaAddresses.map((pair) => pair && pair[0]) + ); + + const output = results.map((account) => { + if (account === null) { + return undefined; + } else { + if (account.data) { + try { + const MetadataParsed = decodeMetadata(account.data); + return MetadataParsed; + } catch (e) { + console.error(e); + return undefined; + } + } else { + return undefined; + } + } + }); + + return output; +}; + +const createResultMap = ( + addresses: string[], + metadatas: (Metadata | undefined)[] +) => { + const output = new Map(); + + addresses.forEach((address) => { + const metadata = metadatas.find((x) => x?.mint === address); + if (metadata) { + output.set(address, metadata); + } else { + output.set(address, undefined); + } + }); + + return output; +}; + +const useMetaplexData = ( + addresses: string[] +): DataWrapper | undefined> => { + const [results, setResults] = useState< + Map | undefined + >(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + const [receivedAt, setReceivedAt] = useState(null); + + useLayoutEffect(() => { + let cancelled = false; + setIsLoading(true); + getMetaplexData(addresses).then( + (results) => { + if (!cancelled) { + setResults(createResultMap(addresses, results)); + setIsLoading(false); + setError(""); + setReceivedAt(new Date().toISOString()); + } + }, + (error) => { + if (!cancelled) { + setResults(undefined); + setIsLoading(false); + setError("Failed to fetch Metaplex data."); + setReceivedAt(new Date().toISOString()); + } + } + ); + + return () => { + cancelled = true; + }; + }, [addresses, setResults, setIsLoading, setError]); + + return { + data: results, + isFetching: isLoading, + error, + receivedAt, + }; +}; + +export default useMetaplexData; diff --git a/bridge_ui/src/hooks/useSolanaTokenMap.ts b/bridge_ui/src/hooks/useSolanaTokenMap.ts new file mode 100644 index 000000000..9adaca8ae --- /dev/null +++ b/bridge_ui/src/hooks/useSolanaTokenMap.ts @@ -0,0 +1,47 @@ +import { Dispatch } from "@reduxjs/toolkit"; +import { ENV, TokenInfo, TokenListProvider } from "@solana/spl-token-registry"; +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { DataWrapper } from "../store/helpers"; +import { selectSolanaTokenMap } from "../store/selectors"; +import { + errorSolanaTokenMap, + fetchSolanaTokenMap, + receiveSolanaTokenMap, +} from "../store/tokenSlice"; +import { CLUSTER } from "../utils/consts"; + +const environment = CLUSTER === "testnet" ? ENV.Testnet : ENV.MainnetBeta; + +const useSolanaTokenMap = (): DataWrapper => { + const tokenMap = useSelector(selectSolanaTokenMap); + const dispatch = useDispatch(); + const shouldFire = + tokenMap.data === undefined || + (tokenMap.data === null && !tokenMap.isFetching); + + useEffect(() => { + if (shouldFire) { + getSolanaTokenMap(dispatch); + } + }, [dispatch, shouldFire]); + + return tokenMap; +}; + +const getSolanaTokenMap = (dispatch: Dispatch) => { + dispatch(fetchSolanaTokenMap()); + + new TokenListProvider().resolve().then( + (tokens) => { + const tokenList = tokens.filterByChainId(environment).getList(); + dispatch(receiveSolanaTokenMap(tokenList)); + }, + (error) => { + console.error(error); + dispatch(errorSolanaTokenMap("Failed to retrieve the Solana token map.")); + } + ); +}; + +export default useSolanaTokenMap; diff --git a/bridge_ui/src/hooks/useTerraTokenMap.ts b/bridge_ui/src/hooks/useTerraTokenMap.ts new file mode 100644 index 000000000..fdf872d5c --- /dev/null +++ b/bridge_ui/src/hooks/useTerraTokenMap.ts @@ -0,0 +1,55 @@ +import { Dispatch } from "@reduxjs/toolkit"; +import axios from "axios"; +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { DataWrapper } from "../store/helpers"; +import { selectTerraTokenMap } from "../store/selectors"; +import { + errorTerraTokenMap, + fetchTerraTokenMap, + receiveTerraTokenMap, +} from "../store/tokenSlice"; +import { TERRA_TOKEN_METADATA_URL } from "../utils/consts"; + +export type TerraTokenMetadata = { + protocol: string; + symbol: string; + token: string; + icon: string; +}; + +export type TerraTokenMap = { + mainnet: { + [address: string]: TerraTokenMetadata; + }; +}; + +const useTerraTokenMap = (): DataWrapper => { + const terraTokenMap = useSelector(selectTerraTokenMap); + const dispatch = useDispatch(); + const shouldFire = + terraTokenMap.data === undefined || + (terraTokenMap.data === null && !terraTokenMap.isFetching); + + useEffect(() => { + if (shouldFire) { + getTerraTokenMap(dispatch); + } + }, [shouldFire, dispatch]); + + return terraTokenMap; +}; + +const getTerraTokenMap = (dispatch: Dispatch) => { + dispatch(fetchTerraTokenMap()); + axios.get(TERRA_TOKEN_METADATA_URL).then( + (response) => { + dispatch(receiveTerraTokenMap(response.data as TerraTokenMap)); + }, + (error) => { + dispatch(errorTerraTokenMap("Failed to retrieve the Terra Token List.")); + } + ); +}; + +export default useTerraTokenMap; diff --git a/bridge_ui/src/store/tokenSlice.ts b/bridge_ui/src/store/tokenSlice.ts index feb72bb3c..ee14ea9f6 100644 --- a/bridge_ui/src/store/tokenSlice.ts +++ b/bridge_ui/src/store/tokenSlice.ts @@ -1,6 +1,6 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { TokenInfo } from "@solana/spl-token-registry"; -import { TerraTokenMap } from "../hooks/useGetSourceParsedTokenAccounts"; +import { TerraTokenMap } from "../hooks/useTerraTokenMap"; import { DataWrapper, errorDataWrapper, diff --git a/bridge_ui/src/store/transferSlice.ts b/bridge_ui/src/store/transferSlice.ts index 6308775e6..86c05258b 100644 --- a/bridge_ui/src/store/transferSlice.ts +++ b/bridge_ui/src/store/transferSlice.ts @@ -24,6 +24,9 @@ export interface ParsedTokenAccount { decimals: number; uiAmount: number; uiAmountString: string; + symbol?: string; + name?: string; + logo?: string; } export interface Transaction {