diff --git a/shared/modules/selectors/feature-flags.ts b/shared/modules/selectors/feature-flags.ts index 34358e2e0e9f..62fcdc6f91c1 100644 --- a/shared/modules/selectors/feature-flags.ts +++ b/shared/modules/selectors/feature-flags.ts @@ -3,15 +3,17 @@ import { ProviderConfigState, getCurrentChainId } from './networks'; type FeatureFlagsMetaMaskState = { metamask: { - swapsState: { - swapsFeatureFlags: { - [key: string]: { - extensionActive: boolean; - mobileActive: boolean; - smartTransactions: { - expectedDeadline?: number; - maxDeadline?: number; - extensionReturnTxHashAsap?: boolean; + SwapsController: { + swapsState: { + swapsFeatureFlags: { + [key: string]: { + extensionActive: boolean; + mobileActive: boolean; + smartTransactions: { + expectedDeadline?: number; + maxDeadline?: number; + extensionReturnTxHashAsap?: boolean; + }; }; }; }; @@ -24,7 +26,8 @@ export function getFeatureFlagsByChainId( ) { const chainId = getCurrentChainId(state); const networkName = getNetworkNameByChainId(chainId); - const featureFlags = state.metamask.swapsState?.swapsFeatureFlags; + const featureFlags = + state.metamask.SwapsController.swapsState?.swapsFeatureFlags; if (!featureFlags?.[networkName]) { return null; } diff --git a/shared/modules/selectors/util.js b/shared/modules/selectors/util.js deleted file mode 100644 index d44b7d905087..000000000000 --- a/shared/modules/selectors/util.js +++ /dev/null @@ -1,17 +0,0 @@ -import { TransactionStatus } from '@metamask/transaction-controller'; -import { isEqual } from 'lodash'; -import { createSelectorCreator, defaultMemoize } from 'reselect'; - -export const createDeepEqualSelector = createSelectorCreator( - defaultMemoize, - isEqual, -); - -export const filterAndShapeUnapprovedTransactions = (transactions) => { - return transactions - .filter(({ status }) => status === TransactionStatus.unapproved) - .reduce((result, transaction) => { - result[transaction.id] = transaction; - return result; - }, {}); -}; diff --git a/shared/modules/selectors/util.ts b/shared/modules/selectors/util.ts new file mode 100644 index 000000000000..e6dc33b13a97 --- /dev/null +++ b/shared/modules/selectors/util.ts @@ -0,0 +1,25 @@ +import { + TransactionMeta, + TransactionStatus, +} from '@metamask/transaction-controller'; +import { isEqual } from 'lodash'; +import { createSelectorCreator, defaultMemoize } from 'reselect'; + +export const createDeepEqualSelector = createSelectorCreator( + defaultMemoize, + isEqual, +); + +export const filterAndShapeUnapprovedTransactions = ( + transactions: TransactionMeta[], +) => { + return transactions + .filter(({ status }) => status === TransactionStatus.unapproved) + .reduce<{ [transactionId: string]: TransactionMeta }>( + (result, transaction) => { + result[transaction.id] = transaction; + return result; + }, + {}, + ); +}; diff --git a/ui/ducks/bridge-status/selectors.ts b/ui/ducks/bridge-status/selectors.ts index a1031e5e415f..a87215503ad1 100644 --- a/ui/ducks/bridge-status/selectors.ts +++ b/ui/ducks/bridge-status/selectors.ts @@ -1,24 +1,20 @@ import { createSelector } from 'reselect'; import { Hex } from '@metamask/utils'; -import { - BridgeStatusState, - BridgeHistoryItem, -} from '../../../shared/types/bridge-status'; +import { BridgeHistoryItem } from '../../../shared/types/bridge-status'; import { getSelectedAddress } from '../../selectors'; import { Numeric } from '../../../shared/modules/Numeric'; -import { - getCurrentChainId, - ProviderConfigState, -} from '../../../shared/modules/selectors/networks'; +import { BackgroundStateProxy } from '../../../shared/types/metamask'; +import { getCurrentChainId } from '../../../shared/modules/selectors/networks'; -export type BridgeStatusAppState = ProviderConfigState & { - metamask: { - bridgeStatusState: BridgeStatusState; - }; +export type BridgeStatusAppState = { + metamask: Pick< + BackgroundStateProxy, + 'BridgeStatusController' | 'AccountsController' | 'NetworkController' + >; }; export const selectBridgeStatusState = (state: BridgeStatusAppState) => - state.metamask.bridgeStatusState; + state.metamask.BridgeStatusController.bridgeStatusState; /** * Returns a mapping of srcTxMetaId to txHistoryItem for the selected address diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index a55d646a9b68..bd4713d7c80a 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -1,7 +1,4 @@ -import { - NetworkConfiguration, - NetworkState, -} from '@metamask/network-controller'; +import { NetworkConfiguration } from '@metamask/network-controller'; import { orderBy, uniqBy } from 'lodash'; import { createSelector } from 'reselect'; import { GasFeeEstimates } from '@metamask/gas-fee-controller'; @@ -18,7 +15,6 @@ import { BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE, } from '../../../shared/constants/bridge'; import { - BridgeState, BridgeFeatureFlagsKey, // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths @@ -50,15 +46,14 @@ import { isNativeAddress, } from '../../pages/bridge/utils/quote'; import { decGWEIToHexWEI } from '../../../shared/modules/conversion.utils'; +import { MultichainState } from '../../selectors/multichain'; +import { BackgroundStateProxy } from '../../../shared/types/metamask'; import { BridgeSlice } from './bridge'; -type BridgeAppState = { - metamask: { bridgeState: BridgeState } & NetworkState & { - useExternalServices: boolean; - currencyRates: { [currency: string]: { conversionRate: number } }; - }; +export type BridgeAppState = { + metamask: Pick; bridge: BridgeSlice; -}; +} & MultichainState; // only includes networks user has added export const getAllBridgeableNetworks = createDeepEqualSelector( @@ -77,7 +72,8 @@ export const getAllBridgeableNetworks = createDeepEqualSelector( export const getFromChains = createDeepEqualSelector( getAllBridgeableNetworks, - (state: BridgeAppState) => state.metamask.bridgeState?.bridgeFeatureFlags, + (state: BridgeAppState) => + state.metamask.BridgeController.bridgeState?.bridgeFeatureFlags, (allBridgeableNetworks, bridgeFeatureFlags) => allBridgeableNetworks.filter(({ chainId }) => bridgeFeatureFlags[BridgeFeatureFlagsKey.NETWORK_SRC_ALLOWLIST].includes( @@ -101,7 +97,8 @@ export const getFromChain = createDeepEqualSelector( export const getToChains = createDeepEqualSelector( getFromChain, getAllBridgeableNetworks, - (state: BridgeAppState) => state.metamask.bridgeState?.bridgeFeatureFlags, + (state: BridgeAppState) => + state.metamask.BridgeController.bridgeState?.bridgeFeatureFlags, ( fromChain, allBridgeableNetworks, @@ -125,19 +122,23 @@ export const getToChain = createDeepEqualSelector( ); export const getFromTokens = (state: BridgeAppState) => { - return state.metamask.bridgeState.srcTokens ?? {}; + return state.metamask.BridgeController.bridgeState.srcTokens ?? {}; }; export const getFromTopAssets = (state: BridgeAppState) => { - return state.metamask.bridgeState.srcTopAssets ?? []; + return state.metamask.BridgeController.bridgeState.srcTopAssets ?? []; }; export const getToTopAssets = (state: BridgeAppState) => { - return state.bridge.toChainId ? state.metamask.bridgeState.destTopAssets : []; + return state.bridge.toChainId + ? state.metamask.BridgeController.bridgeState.destTopAssets + : []; }; export const getToTokens = (state: BridgeAppState) => { - return state.bridge.toChainId ? state.metamask.bridgeState.destTokens : {}; + return state.bridge.toChainId + ? state.metamask.BridgeController.bridgeState.destTokens + : {}; }; export const getFromToken = ( @@ -158,12 +159,12 @@ export const getFromAmount = (state: BridgeAppState): string | null => state.bridge.fromTokenInputValue; export const getQuoteRequest = (state: BridgeAppState) => { - const { quoteRequest } = state.metamask.bridgeState; + const { quoteRequest } = state.metamask.BridgeController.bridgeState; return quoteRequest; }; export const getBridgeQuotesConfig = (state: BridgeAppState) => - state.metamask.bridgeState?.bridgeFeatureFlags[ + state.metamask.BridgeController.bridgeState?.bridgeFeatureFlags[ BridgeFeatureFlagsKey.EXTENSION_CONFIG ] ?? {}; @@ -190,7 +191,7 @@ export const getBridgeSortOrder = (state: BridgeAppState) => // A dest network can be selected before it's imported // The cached exchange rate won't be available so the rate from the bridge state is used const _getToTokenExchangeRate = createSelector( - (state) => state.metamask.currencyRates, + (state) => state.metamask.CurrencyController.currencyRates, (state: BridgeAppState) => state.bridge.toTokenExchangeRate, getToChain, getToToken, @@ -205,7 +206,7 @@ const _getToTokenExchangeRate = createSelector( ); const _getQuotesWithMetadata = createDeepEqualSelector( - (state) => state.metamask.bridgeState.quotes, + (state) => state.metamask.BridgeController.bridgeState.quotes, _getToTokenExchangeRate, (state: BridgeAppState) => state.bridge.fromTokenExchangeRate, getConversionRate, @@ -320,7 +321,8 @@ const _getQuoteIdentifier = ({ quote }: QuoteResponse & L1GasFees) => `${quote.bridgeId}-${quote.bridges[0]}-${quote.steps.length}`; const _getSelectedQuote = createSelector( - (state: BridgeAppState) => state.metamask.bridgeState.quotesRefreshCount, + (state: BridgeAppState) => + state.metamask.BridgeController.bridgeState.quotesRefreshCount, (state: BridgeAppState) => state.bridge.selectedQuote, _getSortedQuotesWithMetadata, (quotesRefreshCount, selectedQuote, sortedQuotesWithMetadata) => @@ -338,10 +340,12 @@ export const getBridgeQuotes = createSelector( _getSortedQuotesWithMetadata, _getRecommendedQuote, _getSelectedQuote, - (state) => state.metamask.bridgeState.quotesLastFetched, + (state) => state.metamask.BridgeController.bridgeState.quotesLastFetched, (state) => - state.metamask.bridgeState.quotesLoadingStatus === RequestStatus.LOADING, - (state: BridgeAppState) => state.metamask.bridgeState.quotesRefreshCount, + state.metamask.BridgeController.bridgeState.quotesLoadingStatus === + RequestStatus.LOADING, + (state: BridgeAppState) => + state.metamask.BridgeController.bridgeState.quotesRefreshCount, getBridgeQuotesConfig, getQuoteRequest, ( diff --git a/ui/helpers/utils/token-util.js b/ui/helpers/utils/token-util.js index 10beae68b39e..6098c617fe0f 100644 --- a/ui/helpers/utils/token-util.js +++ b/ui/helpers/utils/token-util.js @@ -193,7 +193,7 @@ export function getTokenApprovedParam(tokenData = {}) { /** * Get the token balance converted to fiat and optionally formatted for display * - * @param {number} [contractExchangeRate] - The exchange rate between the current token and the native currency + * @param {number | string} [contractExchangeRate] - The exchange rate between the current token and the native currency * @param {number} conversionRate - The exchange rate between the current fiat currency and the native currency * @param {string} currentCurrency - The currency code for the user's chosen fiat currency * @param {string} [tokenAmount] - The current token balance diff --git a/ui/hooks/snaps/useDisplayName.ts b/ui/hooks/snaps/useDisplayName.ts index 6a6d3d7e6b51..90a15e14cda9 100644 --- a/ui/hooks/snaps/useDisplayName.ts +++ b/ui/hooks/snaps/useDisplayName.ts @@ -5,10 +5,10 @@ import { getMemoizedAccountName, getAddressBookEntryByNetwork, AddressBookMetaMaskState, - AccountsMetaMaskState, } from '../../selectors/snaps'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import { decimalToHex } from '../../../shared/modules/conversion.utils'; +import { MultichainState } from '../../selectors/multichain'; export type UseDisplayNameParams = { chain: { @@ -38,7 +38,7 @@ export const useDisplayName = ( const parsedAddress = isEip155 ? toChecksumHexAddress(address) : address; - const accountName = useSelector((state: AccountsMetaMaskState) => + const accountName = useSelector((state: MultichainState) => getMemoizedAccountName(state, parsedAddress), ); diff --git a/ui/hooks/useAccountTotalCrossChainFiatBalance.ts b/ui/hooks/useAccountTotalCrossChainFiatBalance.ts index ac5658278946..e8b18a47d00d 100644 --- a/ui/hooks/useAccountTotalCrossChainFiatBalance.ts +++ b/ui/hooks/useAccountTotalCrossChainFiatBalance.ts @@ -15,7 +15,7 @@ import { TokenWithBalance } from '../components/app/assets/asset-list/asset-list import { getNetworkConfigurationsByChainId } from '../../shared/modules/selectors/networks'; type AddressBalances = { - [address: string]: number; + [address: string]: string | number; }; export type Balances = { @@ -61,7 +61,7 @@ export const useAccountTotalCrossChainFiatBalance = ( ]; const totalFiatValue = getTokenFiatAmount( tokenExchangeRate, - conversionRate, + conversionRate ?? 0, currentCurrency, token.string, token.symbol, @@ -79,7 +79,7 @@ export const useAccountTotalCrossChainFiatBalance = ( const nativeFiatValue = getValueFromWeiHex({ value: balanceCached, toCurrency: currentCurrency, - conversionRate, + conversionRate: conversionRate ?? 0, numberOfDecimals: 2, }); return { diff --git a/ui/hooks/useDisplayName.ts b/ui/hooks/useDisplayName.ts index 66463b884338..129dce25a047 100644 --- a/ui/hooks/useDisplayName.ts +++ b/ui/hooks/useDisplayName.ts @@ -16,7 +16,7 @@ export type UseDisplayNameRequest = { preferContractSymbol?: boolean; type: NameType; value: string; - variation: string; + variation: Hex; }; export type UseDisplayNameResponse = { diff --git a/ui/hooks/useGetFormattedTokensPerChain.test.ts b/ui/hooks/useGetFormattedTokensPerChain.test.ts index 231faae66bf9..0f76e6ca6e59 100644 --- a/ui/hooks/useGetFormattedTokensPerChain.test.ts +++ b/ui/hooks/useGetFormattedTokensPerChain.test.ts @@ -96,7 +96,7 @@ describe('useGetFormattedTokensPerChain', () => { it('should tokensWithBalances for an array of chainIds', async () => { (stringifyBalance as jest.Mock).mockReturnValueOnce(10.5); (stringifyBalance as jest.Mock).mockReturnValueOnce(13); - const allChainIDs = ['0x1']; + const allChainIDs = ['0x1' as const]; const isTokenNetworkFilterEqualCurrentNetwork = true; const shouldHideZeroBalanceTokens = true; const testAccount = { diff --git a/ui/hooks/useGetFormattedTokensPerChain.ts b/ui/hooks/useGetFormattedTokensPerChain.ts index 21a5c1a7a78a..576ccfcf1da2 100644 --- a/ui/hooks/useGetFormattedTokensPerChain.ts +++ b/ui/hooks/useGetFormattedTokensPerChain.ts @@ -1,3 +1,4 @@ +import { Hex } from '@metamask/utils'; import { useSelector } from 'react-redux'; import { BN } from 'bn.js'; import { Token } from '@metamask/assets-controllers'; @@ -22,14 +23,14 @@ export const useGetFormattedTokensPerChain = ( account: { address: string }, shouldHideZeroBalanceTokens: boolean, shouldGetTokensPerCurrentChain: boolean, - allChainIDs: string[], + allChainIDs: Hex[], ) => { const currentChainId = useSelector(getCurrentChainId); const importedTokens = useSelector(getAllTokens); // returns the tokens only when they are imported const currentTokenBalances: { tokenBalances: TokenBalancesMapping } = useTokenBalances({ - chainIds: allChainIDs as `0x${string}`[], + chainIds: allChainIDs, }); // We will calculate aggregated balance only after the user imports the tokens to the wallet diff --git a/ui/hooks/useMultichainAccountTotalFiatBalance.ts b/ui/hooks/useMultichainAccountTotalFiatBalance.ts index 335b8399c6d5..a8ccbddf058e 100644 --- a/ui/hooks/useMultichainAccountTotalFiatBalance.ts +++ b/ui/hooks/useMultichainAccountTotalFiatBalance.ts @@ -9,6 +9,7 @@ import { } from '../selectors/multichain'; import { formatCurrency } from '../helpers/utils/confirm-tx.util'; import { MULTICHAIN_NATIVE_CURRENCY_TO_CAIP19 } from '../../shared/constants/multichain/assets'; +import { Numeric } from '../../shared/modules/Numeric'; import { getTokenFiatAmount } from '../helpers/utils/token-util'; import { useMultichainSelector } from './useMultichainSelector'; import { useAccountTotalFiatBalance } from './useAccountTotalFiatBalance'; @@ -26,7 +27,7 @@ export const useMultichainAccountTotalFiatBalance = ( account: InternalAccount, shouldHideZeroBalanceTokens: boolean = false, ): { - formattedFiat: string; + formattedFiat: string | Numeric; totalFiatBalance: string; tokensWithBalances: { address: string; @@ -54,7 +55,7 @@ export const useMultichainAccountTotalFiatBalance = ( getMultichainConversionRate, account, ); - const nativeCurrencyImage: string = useMultichainSelector( + const nativeCurrencyImage: string | undefined = useMultichainSelector( getMultichainCurrencyImage, account, ); @@ -86,7 +87,7 @@ export const useMultichainAccountTotalFiatBalance = ( // Create an object with native token info. NOTE: Native token info is fetched from a separate controller const nativeTokenValues = { - iconUrl: nativeCurrencyImage, + iconUrl: nativeCurrencyImage ?? '', symbol: ticker, fiatBalance: totalFiatBalance, }; diff --git a/ui/hooks/useTheme.ts b/ui/hooks/useTheme.ts index f5d04bec9739..7679ab92ba8d 100644 --- a/ui/hooks/useTheme.ts +++ b/ui/hooks/useTheme.ts @@ -21,13 +21,14 @@ export function useTheme() { const [theme, setTheme] = useState(settingTheme); useEffect(() => { - const result = + const result: ThemeType | null = !settingTheme || settingTheme === ThemeType.os - ? document.documentElement.getAttribute('data-theme') + ? (document.documentElement.getAttribute( + 'data-theme', + ) as ThemeType | null) : settingTheme; - const isValidTheme = validThemes.includes( - result as ThemeType.light | ThemeType.dark, - ); + const isValidTheme = + validThemes.find((validTheme) => validTheme === result) !== undefined; if (!isValidTheme) { console.warn( @@ -36,7 +37,9 @@ export function useTheme() { setTheme(ThemeType.light); } - setTheme(result); + if (result) { + setTheme(result); + } }, [settingTheme]); return theme; diff --git a/ui/hooks/useTokensWithFiltering.ts b/ui/hooks/useTokensWithFiltering.ts index d729ce3c1fdc..dce8dc9487ae 100644 --- a/ui/hooks/useTokensWithFiltering.ts +++ b/ui/hooks/useTokensWithFiltering.ts @@ -104,7 +104,7 @@ export const useTokensWithFiltering = ( return (function* (): Generator< AssetWithDisplayData | AssetWithDisplayData > { - const balance = hexToBN(balanceOnActiveChain); + const balance = hexToBN(balanceOnActiveChain ?? '0'); const srcBalanceFields = sortOrder === TokenBucketPriority.owned ? { diff --git a/ui/selectors/selectors.ts b/ui/selectors/selectors.ts index 84d1bed0309b..ebc03ef39fff 100644 --- a/ui/selectors/selectors.ts +++ b/ui/selectors/selectors.ts @@ -136,6 +136,7 @@ import type { MetaMaskReduxState } from '../store/store'; import { MultichainNetworks } from '../../shared/constants/multichain/networks'; import { EtherDenomination } from '../../shared/constants/common'; import { BackgroundStateProxy } from '../../shared/types/metamask'; +import { BridgeAppState } from '../ducks/bridge/selectors'; import { getAllUnapprovedTransactions, getCurrentNetworkTransactions, @@ -170,6 +171,33 @@ import { isTokenImageMapChainId, } from './selectors.utils'; +export type SwapsEthToken = { + /** + * The symbol for ETH, namely "ETH". + */ + symbol: 'ETH'; + /** + * The name of the ETH currency, "Ether". + */ + name: 'Ether'; + /** + * A substitute address for the metaswap-api to recognize the ETH token. + */ + address: string; + /** + * The number of ETH decimals, i.e. 18 + */ + decimals: 18; + /** + * The user's ETH balance in decimal wei, with a precision of 4 decimal places. + */ + balance: string; + /** + * The user's ETH balance in decimal ETH/ + */ + string: string; +}; + /** `appState` slice selectors */ export const getConfirmationExchangeRates = (state: AppSliceState) => { @@ -654,11 +682,11 @@ export function getMetaMaskCachedBalances(state: MultichainState) { export function getCrossChainMetaMaskCachedBalances(state: MetamaskSliceState) { const allAccountsByChainId = state.metamask.AccountTracker.accountsByChainId; return getKnownPropertyNames(allAccountsByChainId).reduce< - Record> + Record> >((acc, topLevelKey) => { acc[topLevelKey] = getKnownPropertyNames( allAccountsByChainId[topLevelKey], - ).reduce>((innerAcc, innerKey) => { + ).reduce>((innerAcc, innerKey) => { innerAcc[innerKey] = allAccountsByChainId[topLevelKey][innerKey].balance ?? '0'; return innerAcc; @@ -1728,7 +1756,7 @@ export function getIpfsGateway(state: MetamaskSliceState) { return state.metamask.PreferencesController.ipfsGateway; } -export function getUseExternalServices(state: MetamaskSliceState) { +export function getUseExternalServices(state: BridgeAppState) { return state.metamask.PreferencesController.useExternalServices; } @@ -1749,18 +1777,6 @@ export function getWeb3ShimUsageStateForOrigin( return state.metamask.AlertController.web3ShimUsageOrigins?.[origin]; } -/** - * @typedef {object} SwapsEthToken - * @property {string} symbol - The symbol for ETH, namely "ETH" - * @property {string} name - The name of the ETH currency, "Ether" - * @property {string} address - A substitute address for the metaswap-api to - * recognize the ETH token - * @property {string} decimals - The number of ETH decimals, i.e. 18 - * @property {string} balance - The user's ETH balance in decimal wei, with a - * precision of 4 decimal places - * @property {string} string - The user's ETH balance in decimal ETH - */ - /** * Swaps related code uses token objects for various purposes. These objects * always have the following properties: `symbol`, `name`, `address`, and @@ -1785,7 +1801,7 @@ export function getWeb3ShimUsageStateForOrigin( */ export function getSwapsDefaultToken( - state: MetamaskSliceState, + state: MultichainState, overrideChainId = null, ) { const selectedAccount = getSelectedAccount(state); @@ -1795,7 +1811,13 @@ export function getSwapsDefaultToken( const chainId = overrideChainId ?? currentChainId; const defaultTokenObject = isSwapsChainId(chainId) ? SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId] - : {}; + : { + symbol: 'ETH', + name: 'Ether', + address: '', + decimals: 18, + iconUrl: '', + }; return { ...defaultTokenObject, @@ -1808,10 +1830,7 @@ export function getSwapsDefaultToken( }; } -export function getIsSwapsChain( - state: MetamaskSliceState, - overrideChainId: Hex, -) { +export function getIsSwapsChain(state: MultichainState, overrideChainId: Hex) { const currentChainId = getCurrentChainId(state); const chainId = overrideChainId ?? currentChainId; const isNotDevelopment =