From 9bbc901bf85f4f83c5328531bbfe1b7960fdde9e Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Tue, 18 Feb 2025 12:09:04 +0100 Subject: [PATCH] fix: integrate spl tokens --- .../controller-init/controller-list.ts | 2 + .../controller-init/messengers/index.ts | 3 + .../messengers/multichain/index.ts | 2 + app/scripts/metamask-controller.js | 1 + app/scripts/migrations/144.test.ts | 87 --------- app/scripts/migrations/144.ts | 71 ------- app/scripts/migrations/index.js | 1 - shared/constants/network.ts | 5 + shared/constants/price-api-currencies.ts | 46 ----- shared/lib/accounts/solana-wallet-snap.ts | 3 +- .../app/assets/hooks/useMultichainAssets.tsx | 87 +++++++++ .../app/assets/hooks/useTokenDisplayInfo.tsx | 4 +- .../cells/token-cell-percent-change.tsx | 26 +-- .../app/assets/token-list/token-list.tsx | 18 +- ui/components/app/assets/types.ts | 1 + .../app/assets/util/networkTitleOverrides.ts | 2 +- .../constants/available-conversions.json | 184 ++++++++++++------ .../components/confirm/footer/footer.test.tsx | 24 --- .../components/confirm/footer/footer.tsx | 6 - ui/selectors/multichain-assets-rates.test.ts | 32 +++ ui/selectors/multichain-assets-rates.ts | 15 ++ 21 files changed, 288 insertions(+), 332 deletions(-) delete mode 100644 app/scripts/migrations/144.test.ts delete mode 100644 app/scripts/migrations/144.ts delete mode 100644 shared/constants/price-api-currencies.ts create mode 100644 ui/components/app/assets/hooks/useMultichainAssets.tsx create mode 100644 ui/selectors/multichain-assets-rates.test.ts create mode 100644 ui/selectors/multichain-assets-rates.ts diff --git a/app/scripts/controller-init/controller-list.ts b/app/scripts/controller-init/controller-list.ts index 029f22f7da5d..308592022f49 100644 --- a/app/scripts/controller-init/controller-list.ts +++ b/app/scripts/controller-init/controller-list.ts @@ -46,6 +46,7 @@ export type Controller = | MultiChainAssetsRatesController | MultichainBalancesController | MultichainTransactionsController + | MultiChainAssetsRatesController | NetworkController | OnboardingController | PermissionController< @@ -78,6 +79,7 @@ export type ControllerFlatState = AccountsController['state'] & MultiChainAssetsRatesController['state'] & MultichainBalancesController['state'] & MultichainTransactionsController['state'] & + MultiChainAssetsRatesController['state'] & NetworkController['state'] & OnboardingController['state'] & PermissionController< diff --git a/app/scripts/controller-init/messengers/index.ts b/app/scripts/controller-init/messengers/index.ts index 62b4f63b5cd8..bd3c1f86f252 100644 --- a/app/scripts/controller-init/messengers/index.ts +++ b/app/scripts/controller-init/messengers/index.ts @@ -50,6 +50,9 @@ export const CONTROLLER_MESSENGERS = { getMessenger: getMultichainTransactionsControllerMessenger, getInitMessenger: noop, }, + MultiChainAssetsRatesController: { + getMessenger: getMultiChainAssetsRatesControllerMessenger, + }, RateLimitController: { getMessenger: getRateLimitControllerMessenger, getInitMessenger: getRateLimitControllerInitMessenger, diff --git a/app/scripts/controller-init/messengers/multichain/index.ts b/app/scripts/controller-init/messengers/multichain/index.ts index dae4039662fe..38561c9eade8 100644 --- a/app/scripts/controller-init/messengers/multichain/index.ts +++ b/app/scripts/controller-init/messengers/multichain/index.ts @@ -2,8 +2,10 @@ export { getMultichainAssetsControllerMessenger } from './multichain-assets-cont export { getMultiChainAssetsRatesControllerMessenger } from './multichain-assets-rates-controller-messenger'; export { getMultichainBalancesControllerMessenger } from './multichain-balances-controller-messenger'; export { getMultichainTransactionsControllerMessenger } from './multichain-transactions-controller-messenger'; +export { getMultiChainAssetsRatesControllerMessenger } from './multichain-assets-rates-controller-messenger'; export type { MultichainAssetsControllerMessenger } from './multichain-assets-controller-messenger'; export type { MultiChainAssetsRatesControllerMessenger } from './multichain-assets-rates-controller-messenger'; export type { MultichainBalancesControllerMessenger } from './multichain-balances-controller-messenger'; export type { MultichainTransactionsControllerMessenger } from './multichain-transactions-controller-messenger'; +export type { MultiChainAssetsRatesControllerMessenger } from './multichain-assets-rates-controller-messenger'; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8a335ac1209c..0fabd57302a1 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2058,6 +2058,7 @@ export default class MetamaskController extends EventEmitter { MultiChainAssetsRatesController: MultiChainAssetsRatesControllerInit, MultichainBalancesController: MultichainBalancesControllerInit, MultichainTransactionsController: MultichainTransactionsControllerInit, + MultiChainAssetsRatesController: MultiChainAssetsRatesControllerInit, ///: END:ONLY_INCLUDE_IF }; diff --git a/app/scripts/migrations/144.test.ts b/app/scripts/migrations/144.test.ts deleted file mode 100644 index 55bd0e412803..000000000000 --- a/app/scripts/migrations/144.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { migrate, version } from './144'; - -const oldVersion = 143; - -const DEFAULT_CURRENCY = 'usd'; -const VALID_CURRENCY = 'eur'; -const INVALID_CURRENCY = 'INVALID_CURRENCY'; - -describe(`migration #${version}`, () => { - it('updates the version metadata', async () => { - const oldStorage = { - meta: { version: oldVersion }, - data: {}, - }; - const newStorage = await migrate(oldStorage); - expect(newStorage.meta).toStrictEqual({ version }); - }); - - describe(`migration #${version}`, () => { - it('does nothing if CurrencyController is missing', async () => { - const oldStorage = { - meta: { version: oldVersion }, - data: {}, - }; - const newStorage = await migrate(oldStorage); - expect(newStorage.data).toStrictEqual({}); - }); - - it('does nothing if CurrencyController is not an object', async () => { - const oldStorage = { - meta: { version: oldVersion }, - data: { - CurrencyController: 'invalidData', - }, - }; - const newStorage = await migrate(oldStorage); - expect(newStorage.data).toStrictEqual(oldStorage.data); - }); - - it('sets currentCurrency to "USD" if it is missing', async () => { - const oldStorage = { - meta: { version: oldVersion }, - data: { - CurrencyController: {}, - }, - }; - const expectedData = { - CurrencyController: { - currentCurrency: DEFAULT_CURRENCY, - }, - }; - const newStorage = await migrate(oldStorage); - expect(newStorage.data).toStrictEqual(expectedData); - }); - - it('sets currentCurrency to "USD" if it is invalid', async () => { - const oldStorage = { - meta: { version: oldVersion }, - data: { - CurrencyController: { - currentCurrency: INVALID_CURRENCY, - }, - }, - }; - const expectedData = { - CurrencyController: { - currentCurrency: DEFAULT_CURRENCY, - }, - }; - const newStorage = await migrate(oldStorage); - expect(newStorage.data).toStrictEqual(expectedData); - }); - - it('does nothing if currentCurrency is valid', async () => { - const oldStorage = { - meta: { version: oldVersion }, - data: { - CurrencyController: { - currentCurrency: VALID_CURRENCY, - }, - }, - }; - const newStorage = await migrate(oldStorage); - expect(newStorage.data).toStrictEqual(oldStorage.data); - }); - }); -}); diff --git a/app/scripts/migrations/144.ts b/app/scripts/migrations/144.ts deleted file mode 100644 index 77a526605439..000000000000 --- a/app/scripts/migrations/144.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { hasProperty } from '@metamask/utils'; -import { cloneDeep, isObject } from 'lodash'; -import { PRICE_API_CURRENCIES } from '../../../shared/constants/price-api-currencies'; - -type VersionedData = { - meta: { version: number }; - data: Record; -}; - -type CurrencyController = { - currentCurrency?: string; -}; - -export const version = 144; -const DEFAULT_CURRENCY = 'usd'; - -/** - * This migration ensures that the `currentCurrency` in `CurrencyController` - * is set to a valid available currency. If it's missing or invalid, it defaults to "USD". - * - * @param originalVersionedData - The original MetaMask extension state. - * @returns Updated versioned MetaMask extension state. - */ -export async function migrate( - originalVersionedData: VersionedData, -): Promise { - const versionedData = cloneDeep(originalVersionedData); - versionedData.meta.version = version; - transformState(versionedData.data); - return versionedData; -} - -function transformState(state: Record) { - if (!hasProperty(state, 'CurrencyController')) { - global.sentry?.captureException?.( - new Error(`Migration ${version}: Missing CurrencyController in state`), - ); - return; - } - - const currencyController = state.CurrencyController as CurrencyController; - - if (!isObject(currencyController)) { - global.sentry?.captureException?.( - new Error( - `Migration ${version}: Invalid CurrencyController state type '${typeof currencyController}'`, - ), - ); - return; - } - - const { currentCurrency } = currencyController; - - if (!currentCurrency) { - global.sentry?.captureException?.( - new Error( - `Migration ${version}: Missing currentCurrency in CurrencyController, defaulting to ${DEFAULT_CURRENCY}`, - ), - ); - currencyController.currentCurrency = DEFAULT_CURRENCY; - return; - } - - const isValidCurrency = PRICE_API_CURRENCIES.some( - (currency) => currency === currentCurrency, - ); - - if (!isValidCurrency) { - currencyController.currentCurrency = DEFAULT_CURRENCY; - } -} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 1306d1bca3aa..6df962d4709f 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -168,7 +168,6 @@ const migrations = [ require('./141'), require('./142'), require('./143'), - require('./144'), ]; export default migrations; diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 1fab49ad25be..0595ad2c3541 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -3,6 +3,7 @@ import { RpcEndpointType, } from '@metamask/network-controller'; import { capitalize, pick } from 'lodash'; +import { MultichainNetworks } from './multichain/networks'; /** * A type representing built-in network types, used as an identifier. */ @@ -176,6 +177,7 @@ export const CHAIN_IDS = { INK: '0xdef1', MODE_SEPOLIA: '0x397', MODE: '0x868b', + SOLANA: MultichainNetworks.SOLANA, } as const; export const CHAINLIST_CHAIN_IDS_MAP = { @@ -520,6 +522,7 @@ export const SONIC_MAINNET_IMAGE_URL = './images/sonic.svg'; export const SONEIUM_IMAGE_URL = './images/soneium.svg'; export const MODE_SEPOLIA_IMAGE_URL = './images/mode-sepolia.svg'; export const MODE_IMAGE_URL = './images/mode.svg'; +export const SOLANA_IMAGE_URL = './images/solana-logo.svg'; export const UNICHAIN_IMAGE_URL = './images/unichain.svg'; export const INFURA_PROVIDER_TYPES = [ @@ -885,6 +888,7 @@ export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { [CHAINLIST_CHAIN_IDS_MAP.SONEIUM_TESTNET]: SONEIUM_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.MODE_SEPOLIA]: MODE_SEPOLIA_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.MODE]: MODE_IMAGE_URL, + [CHAIN_IDS.SOLANA]: SOLANA_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.UNICHAIN]: UNICHAIN_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.UNICHAIN_SEPOLIA]: UNICHAIN_IMAGE_URL, } as const; @@ -932,6 +936,7 @@ export const CHAIN_ID_TOKEN_IMAGE_MAP = { [CHAINLIST_CHAIN_IDS_MAP.SONIC_MAINNET]: SONIC_MAINNET_IMAGE_URL, [CHAIN_IDS.MODE]: ETH_TOKEN_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.FUNKICHAIN]: ETH_TOKEN_IMAGE_URL, + [CHAIN_IDS.SOLANA]: SOLANA_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.UNICHAIN]: ETH_TOKEN_IMAGE_URL, [CHAINLIST_CHAIN_IDS_MAP.UNICHAIN_SEPOLIA]: ETH_TOKEN_IMAGE_URL, } as const; diff --git a/shared/constants/price-api-currencies.ts b/shared/constants/price-api-currencies.ts deleted file mode 100644 index c600c227d86f..000000000000 --- a/shared/constants/price-api-currencies.ts +++ /dev/null @@ -1,46 +0,0 @@ -export const PRICE_API_CURRENCIES = [ - 'aud', - 'hkd', - 'sgd', - 'idr', - 'inr', - 'nzd', - 'php', - 'btc', - 'cad', - 'eur', - 'gbp', - 'jpy', - 'ltc', - 'rub', - 'uah', - 'usd', - 'xlm', - 'xrp', - 'sek', - 'aed', - 'ars', - 'bch', - 'bnb', - 'brl', - 'clp', - 'cny', - 'czk', - 'dkk', - 'chf', - 'dot', - 'eos', - 'eth', - 'gel', - 'huf', - 'ils', - 'krw', - 'mxn', - 'myr', - 'ngn', - 'nok', - 'pln', - 'thb', - 'try', - 'zar', -]; diff --git a/shared/lib/accounts/solana-wallet-snap.ts b/shared/lib/accounts/solana-wallet-snap.ts index 1f6622c8bbdc..b88fa88b98c7 100644 --- a/shared/lib/accounts/solana-wallet-snap.ts +++ b/shared/lib/accounts/solana-wallet-snap.ts @@ -3,7 +3,8 @@ import { SnapId } from '@metamask/snaps-sdk'; // the Snap is being pre-installed only for Flask build (for the moment). import SolanaWalletSnap from '@metamask/solana-wallet-snap/dist/preinstalled-snap.json'; -export const SOLANA_WALLET_SNAP_ID: SnapId = SolanaWalletSnap.snapId as SnapId; +export const SOLANA_WALLET_SNAP_ID: SnapId = + 'local:http://localhost:8080' as SnapId; export const SOLANA_WALLET_NAME: string = SolanaWalletSnap.manifest.proposedName; diff --git a/ui/components/app/assets/hooks/useMultichainAssets.tsx b/ui/components/app/assets/hooks/useMultichainAssets.tsx new file mode 100644 index 000000000000..4d7bb50d282f --- /dev/null +++ b/ui/components/app/assets/hooks/useMultichainAssets.tsx @@ -0,0 +1,87 @@ +import { useSelector } from 'react-redux'; +import { Hex } from '@metamask/utils'; +import { getMultichainBalances } from '../../../../selectors/multichain'; +import { + getAccountAssets, + getAssetsMetadata, +} from '../../../../selectors/assets'; +import { CHAIN_ID_TOKEN_IMAGE_MAP } from '../../../../../shared/constants/network'; +import { getSelectedInternalAccount } from '../../../../selectors'; +import { + TranslateFunction, + networkTitleOverrides, +} from '../util/networkTitleOverrides'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { getAssetsRates } from '../../../../selectors/multichain-assets-rates'; +import { formatWithThreshold } from '../util/formatWithThreshold'; +import { getIntlLocale } from '../../../../ducks/locale/locale'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; + +const useMultiChainAssets = () => { + const t = useI18nContext(); + const locale = useSelector(getIntlLocale); + const currentCurrency = useSelector(getCurrentCurrency); + const account = useSelector(getSelectedInternalAccount); + const multichainBalances = useSelector(getMultichainBalances); + const accountAssets = useSelector(getAccountAssets); + const assetsMetadata = useSelector(getAssetsMetadata); + const assetRates = useSelector(getAssetsRates); + + const assetIds = accountAssets[account.id] || []; + const balances = multichainBalances[account.id]; + + return assetIds.map((assetId) => { + const [chainId, assetDetails] = assetId.split('/'); + const isToken = assetDetails.split(':')[0] === 'token'; + + const balance = balances[assetId] || { amount: '0', unit: '' }; + const rate = assetRates[assetId]?.rate || '0'; + const fiatBalance = parseFloat(rate) * parseFloat(balance.amount); + + const fiatAmount = formatWithThreshold(fiatBalance, 0.01, locale, { + style: 'currency', + currency: currentCurrency.toUpperCase(), + }); + + const metadata = assetsMetadata[assetId] || { + name: balance.unit, + symbol: balance.unit || '', + fungible: true, + units: [{ name: assetId, symbol: balance.unit || '', decimals: 0 }], + }; + + let tokenImage = ''; + + if (isToken) { + tokenImage = metadata.iconUrl || ''; + } else { + tokenImage = + CHAIN_ID_TOKEN_IMAGE_MAP[ + chainId as keyof typeof CHAIN_ID_TOKEN_IMAGE_MAP + ] || ''; + } + + const decimals = metadata.units[0]?.decimals || 0; + + return { + title: isToken + ? metadata.name + : networkTitleOverrides(t as TranslateFunction, { + title: balance.unit, + }), + address: assetId as Hex, + symbol: metadata.symbol, + image: tokenImage, + decimals, + chainId, + isNative: false, + primary: balance.amount, + secondary: fiatAmount, // secondary balance (usually in fiat) + string: '', + tokenFiatAmount: fiatBalance, // for now we are keeping this is to satisfy sort, this should be fiat amount + isStakeable: false, + }; + }); +}; + +export default useMultiChainAssets; diff --git a/ui/components/app/assets/hooks/useTokenDisplayInfo.tsx b/ui/components/app/assets/hooks/useTokenDisplayInfo.tsx index 753dfdb0da96..8c620884965f 100644 --- a/ui/components/app/assets/hooks/useTokenDisplayInfo.tsx +++ b/ui/components/app/assets/hooks/useTokenDisplayInfo.tsx @@ -109,9 +109,9 @@ export const useTokenDisplayInfo = ({ } // TODO non-evm assets. this is only the native token return { - title: token.symbol, + title: token.title, tokenImage: token.image, - primary: '', + primary: token.primary, secondary: token.secondary, isStakeable: false, tokenChainImage: token.image as string, diff --git a/ui/components/app/assets/token-cell/cells/token-cell-percent-change.tsx b/ui/components/app/assets/token-cell/cells/token-cell-percent-change.tsx index 5dc220e9ec93..539f708375ef 100644 --- a/ui/components/app/assets/token-cell/cells/token-cell-percent-change.tsx +++ b/ui/components/app/assets/token-cell/cells/token-cell-percent-change.tsx @@ -2,20 +2,11 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { getNativeTokenAddress } from '@metamask/assets-controllers'; import { Hex } from '@metamask/utils'; -import { - TextColor, - TextVariant, -} from '../../../../../helpers/constants/design-system'; -import { Text } from '../../../../component-library'; +import { Box } from '../../../../component-library'; import { getMarketData } from '../../../../../selectors'; import { getMultichainIsEvm } from '../../../../../selectors/multichain'; -import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { TokenFiatDisplayInfo } from '../../types'; import { PercentageChange } from '../../../../multichain/token-list-item/price/percentage-change'; -import { - TranslateFunction, - networkTitleOverrides, -} from '../../util/networkTitleOverrides'; type TokenCellPercentChangeProps = { token: TokenFiatDisplayInfo; @@ -24,7 +15,6 @@ type TokenCellPercentChangeProps = { export const TokenCellPercentChange = React.memo( ({ token }: TokenCellPercentChangeProps) => { const isEvm = useSelector(getMultichainIsEvm); - const t = useI18nContext(); const multiChainMarketData = useSelector(getMarketData); // We do not want to display any percentage with non-EVM since we don't have the data for this yet. @@ -52,17 +42,9 @@ export const TokenCellPercentChange = React.memo( ); } - // fallback value (is this valid?) - return ( - - {networkTitleOverrides(t as TranslateFunction, token)} - - ); + // we don't support non-evm price changes yet. + // annoyingly, we need an empty component here for flexbox to align everything nicely + return ; }, (prevProps, nextProps) => prevProps.token.address === nextProps.token.address, ); diff --git a/ui/components/app/assets/token-list/token-list.tsx b/ui/components/app/assets/token-list/token-list.tsx index 84a771fe0878..975c82f34866 100644 --- a/ui/components/app/assets/token-list/token-list.tsx +++ b/ui/components/app/assets/token-list/token-list.tsx @@ -4,7 +4,6 @@ import { Hex } from '@metamask/utils'; import TokenCell from '../token-cell'; import { getChainIdsToPoll, - getCurrentNetwork, getNewTokensImported, getPreferences, getSelectedAccount, @@ -12,11 +11,15 @@ import { } from '../../../../selectors'; import { endTrace, TraceName } from '../../../../../shared/lib/trace'; import { useTokenBalances as pollAndUpdateEvmBalances } from '../../../../hooks/useTokenBalances'; -import { useNativeTokenBalance, useNetworkFilter } from '../hooks'; +import { useNetworkFilter } from '../hooks'; import { TokenWithFiatAmount } from '../types'; -import { getMultichainIsEvm } from '../../../../selectors/multichain'; import { filterAssets } from '../util/filter'; import { sortAssets } from '../util/sort'; +import useMultiChainAssets from '../hooks/useMultichainAssets'; +import { + getMultichainIsEvm, + getMultichainNetwork, +} from '../../../../selectors/multichain'; type TokenListProps = { onTokenClick: (chainId: string, address: string) => void; @@ -27,7 +30,7 @@ function TokenList({ onTokenClick }: TokenListProps) { const chainIdsToPoll = useSelector(getChainIdsToPoll); const newTokensImported = useSelector(getNewTokensImported); const evmBalances = useSelector(getTokenBalancesEvm); // TODO: This is where we need to select non evm-assets from state, when isEvm is false - const currentNetwork = useSelector(getCurrentNetwork); + const currentNetwork = useSelector(getMultichainNetwork); const { tokenSortConfig, privacyMode } = useSelector(getPreferences); const selectedAccount = useSelector(getSelectedAccount); @@ -36,18 +39,18 @@ function TokenList({ onTokenClick }: TokenListProps) { chainIds: chainIdsToPoll as Hex[], }); - const nonEvmNativeToken = useNativeTokenBalance(); + const multichainAssets = useMultiChainAssets(); // network filter to determine which tokens to show in list // on EVM we want to filter based on network filter controls, on non-evm we only want tokens from that chain identifier const { networkFilter } = useNetworkFilter(); const sortedFilteredTokens = useMemo(() => { - const balances = isEvm ? evmBalances : [nonEvmNativeToken]; + const balances = isEvm ? evmBalances : multichainAssets; const filteredAssets: TokenWithFiatAmount[] = filterAssets(balances, [ { key: 'chainId', - opts: isEvm ? networkFilter : { [nonEvmNativeToken.chainId]: true }, + opts: isEvm ? networkFilter : { [currentNetwork.chainId]: true }, filterCallback: 'inclusive', }, ]); @@ -61,6 +64,7 @@ function TokenList({ onTokenClick }: TokenListProps) { selectedAccount, newTokensImported, evmBalances, + multichainAssets, ]); useEffect(() => { diff --git a/ui/components/app/assets/types.ts b/ui/components/app/assets/types.ts index af832b4095c6..558b65c48ad5 100644 --- a/ui/components/app/assets/types.ts +++ b/ui/components/app/assets/types.ts @@ -45,6 +45,7 @@ export type TokenWithFiatAmount = Token & TokenDisplayValues & TokenBalanceValues & { isStakeable?: boolean; + title: string; }; export type TokenFiatDisplayInfo = TokenWithFiatAmount & TokenDisplayInfo; diff --git a/ui/components/app/assets/util/networkTitleOverrides.ts b/ui/components/app/assets/util/networkTitleOverrides.ts index 297fe3a525c1..b7a22b442140 100644 --- a/ui/components/app/assets/util/networkTitleOverrides.ts +++ b/ui/components/app/assets/util/networkTitleOverrides.ts @@ -10,7 +10,7 @@ export type TranslateFunction = (arg: string) => string; // usually in the case of native tokens for L1 networks export const networkTitleOverrides = ( t: TranslateFunction, // translate function from useI18nContext() hook - token: TokenFiatDisplayInfo, + token: Partial, ) => { switch (token.title) { case CURRENCY_SYMBOLS.ETH: diff --git a/ui/helpers/constants/available-conversions.json b/ui/helpers/constants/available-conversions.json index dcd9b6eb6448..0d57979201d1 100644 --- a/ui/helpers/constants/available-conversions.json +++ b/ui/helpers/constants/available-conversions.json @@ -27,6 +27,26 @@ "code": "php", "name": "Philippine Peso" }, + { + "code": "adt", + "name": "adToken" + }, + { + "code": "adx", + "name": "AdEx" + }, + { + "code": "ant", + "name": "Aragon" + }, + { + "code": "bat", + "name": "Basic Attention Token" + }, + { + "code": "bnt", + "name": "Bancor" + }, { "code": "btc", "name": "Bitcoin" @@ -35,144 +55,180 @@ "code": "cad", "name": "Canadian Dollar" }, + { + "code": "crb", + "name": "CreditBit" + }, + { + "code": "cvc", + "name": "Civic" + }, + { + "code": "dash", + "name": "Dash" + }, + { + "code": "dgd", + "name": "DigixDAO" + }, + { + "code": "etc", + "name": "Ethereum Classic" + }, { "code": "eur", "name": "Euro" }, + { + "code": "fun", + "name": "FunFair" + }, { "code": "gbp", "name": "Pound Sterling" }, + { + "code": "gno", + "name": "Gnosis" + }, + { + "code": "gnt", + "name": "Golem" + }, + { + "code": "hmq", + "name": "Humaniq" + }, { "code": "jpy", "name": "Japanese Yen" }, { - "code": "ltc", - "name": "Litecoin" + "code": "lsk", + "name": "Lisk" }, { - "code": "rub", - "name": "Russian Ruble" + "code": "ltc", + "name": "Litecoin" }, { - "code": "uah", - "name": "Ukrainian Hryvnia" + "code": "lun", + "name": "Lunyr" }, { - "code": "usd", - "name": "United States Dollar" + "code": "mco", + "name": "Monaco" }, { - "code": "xlm", - "name": "Stellar Lumen" + "code": "mtl", + "name": "Metal" }, { - "code": "xrp", - "name": "Ripple" + "code": "myst", + "name": "Mysterium" }, { - "code": "sek", - "name": "Swedish Krona" + "code": "nmr", + "name": "Numeraire" }, { - "code": "aed", - "name": "United Arab Emirates Dirham" + "code": "omg", + "name": "OmiseGO" }, { - "code": "ars", - "name": "Argentine Peso" + "code": "pay", + "name": "TenX" }, { - "code": "bch", - "name": "Bitcoin Cash" + "code": "ptoy", + "name": "Patientory" }, { - "code": "bnb", - "name": "Binance Coin" + "code": "qrl", + "name": "Quantum-Resistant Ledger" }, { - "code": "brl", - "name": "Brazilian Real" + "code": "qtum", + "name": "Qtum" }, { - "code": "clp", - "name": "Chilean Peso" + "code": "rep", + "name": "Augur" }, { - "code": "cny", - "name": "Chinese Yuan" + "code": "rlc", + "name": "iEx.ec" }, { - "code": "czk", - "name": "Czech Koruna" + "code": "rub", + "name": "Russian Ruble" }, { - "code": "dkk", - "name": "Danish Krone" + "code": "sc", + "name": "Siacoin" }, { - "code": "chf", - "name": "Swiss Franc" + "code": "sngls", + "name": "SingularDTV" }, { - "code": "dot", - "name": "Polkadot" + "code": "snt", + "name": "Status" }, { - "code": "eos", - "name": "EOS" + "code": "steem", + "name": "Steem" }, { - "code": "eth", - "name": "Ethereum" + "code": "storj", + "name": "Storj" }, { - "code": "gel", - "name": "Georgian Lari" + "code": "time", + "name": "ChronoBank" }, { - "code": "huf", - "name": "Hungarian Forint" + "code": "tkn", + "name": "TokenCard" }, { - "code": "ils", - "name": "Israeli Shekel" + "code": "uah", + "name": "Ukrainian Hryvnia" }, { - "code": "krw", - "name": "South Korean Won" + "code": "usd", + "name": "United States Dollar" }, { - "code": "mxn", - "name": "Mexican Peso" + "code": "wings", + "name": "Wings" }, { - "code": "myr", - "name": "Malaysian Ringgit" + "code": "xem", + "name": "NEM" }, { - "code": "ngn", - "name": "Nigerian Naira" + "code": "xlm", + "name": "Stellar Lumen" }, { - "code": "nok", - "name": "Norwegian Krone" + "code": "xmr", + "name": "Monero" }, { - "code": "pln", - "name": "Polish Zloty" + "code": "xrp", + "name": "Ripple" }, { - "code": "thb", - "name": "Thai Baht" + "code": "zec", + "name": "Zcash" }, { - "code": "try", - "name": "Turkish Lira" + "code": "dai", + "name": "DAI" }, { - "code": "zar", - "name": "South African Rand" + "code": "sek", + "name": "Swedish Krona" } ] diff --git a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx index b931ec782f65..506d3b9af9f4 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx @@ -136,20 +136,8 @@ describe('ConfirmFooter', () => { // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any .mockImplementation(() => ({} as any)); - const updateCustomNonceSpy = jest - .spyOn(Actions, 'updateCustomNonce') - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => ({} as any)); - const setNextNonceSpy = jest - .spyOn(Actions, 'setNextNonce') - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => ({} as any)); fireEvent.click(cancelButton); expect(rejectSpy).toHaveBeenCalled(); - expect(updateCustomNonceSpy).toHaveBeenCalledWith(''); - expect(setNextNonceSpy).toHaveBeenCalledWith(''); }); it('invoke required actions when submit button is clicked', () => { @@ -160,20 +148,8 @@ describe('ConfirmFooter', () => { // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any .mockImplementation(() => ({} as any)); - const updateCustomNonceSpy = jest - .spyOn(Actions, 'updateCustomNonce') - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => ({} as any)); - const setNextNonceSpy = jest - .spyOn(Actions, 'setNextNonce') - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => ({} as any)); fireEvent.click(submitButton); expect(resolveSpy).toHaveBeenCalled(); - expect(updateCustomNonceSpy).toHaveBeenCalledWith(''); - expect(setNextNonceSpy).toHaveBeenCalledWith(''); }); it('displays a danger "Confirm" button there are danger alerts', async () => { diff --git a/ui/pages/confirmations/components/confirm/footer/footer.tsx b/ui/pages/confirmations/components/confirm/footer/footer.tsx index 095caaee88c3..9e20f22a15d3 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.tsx @@ -20,11 +20,9 @@ import useAlerts from '../../../../../hooks/useAlerts'; import { rejectPendingApproval, resolvePendingApproval, - setNextNonce, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) updateAndApproveTx, ///: END:ONLY_INCLUDE_IF - updateCustomNonce, } from '../../../../../store/actions'; import { isSignatureTransactionType } from '../../../utils'; import { useConfirmContext } from '../../../context/confirm'; @@ -190,8 +188,6 @@ const Footer = () => { dispatch( rejectPendingApproval(currentConfirmation.id, serializeError(error)), ); - dispatch(updateCustomNonce('')); - dispatch(setNextNonce('')); }, [currentConfirmation], ); @@ -223,8 +219,6 @@ const Footer = () => { } else { dispatch(resolvePendingApproval(currentConfirmation.id, undefined)); } - dispatch(updateCustomNonce('')); - dispatch(setNextNonce('')); }, [currentConfirmation, customNonceValue]); const handleFooterCancel = useCallback(() => { diff --git a/ui/selectors/multichain-assets-rates.test.ts b/ui/selectors/multichain-assets-rates.test.ts new file mode 100644 index 000000000000..89ffa1892cc0 --- /dev/null +++ b/ui/selectors/multichain-assets-rates.test.ts @@ -0,0 +1,32 @@ +import { getAssetsRates, AssetsState } from './multichain-assets-rates'; + +// Mock state for testing +const mockState = { + metamask: { + conversionRates: { + 'token-1': { rate: 1.5, currency: 'USD' }, + 'token-2': { rate: 0.8, currency: 'EUR' }, + }, + }, +}; +describe('getAssetsRates', () => { + it('should return the assetsRates from the state', () => { + const result = getAssetsRates(mockState); + expect(result).toEqual(mockState.metamask.conversionRates); + }); + + it('should return an empty object if assetsRates is empty', () => { + const emptyState: AssetsState = { + metamask: { + conversionRates: {}, + }, + }; + const result = getAssetsRates(emptyState); + expect(result).toEqual({}); + }); + + it('should return undefined if state does not have metamask property', () => { + const invalidState = {} as AssetsState; + expect(() => getAssetsRates(invalidState)).toThrow(); + }); +}); diff --git a/ui/selectors/multichain-assets-rates.ts b/ui/selectors/multichain-assets-rates.ts new file mode 100644 index 000000000000..1333a9c655e8 --- /dev/null +++ b/ui/selectors/multichain-assets-rates.ts @@ -0,0 +1,15 @@ +import { MultichainAssetsRatesControllerState } from '@metamask/assets-controllers'; + +export type AssetsState = { + metamask: MultichainAssetsRatesControllerState; +}; + +/** + * Gets non-EVM accounts assets rates. + * + * @param state - Redux state object. + * @returns An object containing non-EVM assets per accounts. + */ +export function getAssetsRates(state: AssetsState) { + return state.metamask.conversionRates; +}