diff --git a/app/scripts/controllers/swaps/swaps.types.ts b/app/scripts/controllers/swaps/swaps.types.ts index fca608629a8a..11ba9725f6b8 100644 --- a/app/scripts/controllers/swaps/swaps.types.ts +++ b/app/scripts/controllers/swaps/swaps.types.ts @@ -52,12 +52,10 @@ export type SwapsControllerState = { swapsStxGetTransactionsRefreshTime: number; swapsStxMaxFeeMultiplier: number; swapsFeatureFlags: - | ({ - smartTransactions?: Partial; - } & { - [networkName: string]: { smartTransactions?: Partial }; - }) - | Record; + | Partial + | { + [networkName: string]: Partial; + }; }; }; diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index f99cf675f534..d3d9fc7f7611 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -74,7 +74,7 @@ export function createPPOMMiddleware< const { chainId } = getProviderConfig({ - metamask: networkController.state, + metamask: { NetworkController: networkController.state }, }) ?? {}; if (!chainId) { return; diff --git a/shared/constants/multichain/assets.ts b/shared/constants/multichain/assets.ts index 58e0869ff045..c467687b6f1f 100644 --- a/shared/constants/multichain/assets.ts +++ b/shared/constants/multichain/assets.ts @@ -1,5 +1,5 @@ import { CaipAssetType } from '@metamask/keyring-api'; -import { MultichainNetworks } from './networks'; +import { MultichainNetworkIds, MultichainNetworks } from './networks'; export const MULTICHAIN_NATIVE_CURRENCY_TO_CAIP19 = { BTC: `${MultichainNetworks.BITCOIN}/slip44:0`, @@ -20,7 +20,7 @@ export enum MultichainNativeAssets { * Each network is mapped to an array containing its native asset for consistency. */ export const MULTICHAIN_NETWORK_TO_ASSET_TYPES: Record< - MultichainNetworks, + MultichainNetworkIds, CaipAssetType[] > = { [MultichainNetworks.SOLANA]: [MultichainNativeAssets.SOLANA], diff --git a/shared/modules/selectors/smart-transactions.ts b/shared/modules/selectors/smart-transactions.ts index 5487d2e86178..83437b1214de 100644 --- a/shared/modules/selectors/smart-transactions.ts +++ b/shared/modules/selectors/smart-transactions.ts @@ -47,7 +47,6 @@ export const getSmartTransactionsOptInStatusInternal = createSelector( * @returns true if the user has explicitly opted in, false if they have opted out, * or null if they have not explicitly opted in or out. */ -// @ts-expect-error TODO: Fix types for `getSmartTransactionsOptInStatusInternal` once `getPreferences is converted to TypeScript export const getSmartTransactionsOptInStatusForMetrics = createSelector( getSmartTransactionsOptInStatusInternal, (optInStatus: boolean): boolean => optInStatus, @@ -60,7 +59,6 @@ export const getSmartTransactionsOptInStatusForMetrics = createSelector( * @param state * @returns */ -// @ts-expect-error TODO: Fix types for `getSmartTransactionsOptInStatusInternal` once `getPreferences is converted to TypeScript export const getSmartTransactionsPreferenceEnabled = createSelector( getSmartTransactionsOptInStatusInternal, (optInStatus: boolean): boolean => { @@ -100,7 +98,7 @@ export const getSmartTransactionsEnabled = ( // TODO: Create a new proxy service only for MM feature flags. const smartTransactionsFeatureFlagEnabled = state.metamask.SwapsController.swapsState?.swapsFeatureFlags - ?.smartTransactions?.extensionActive; + ?.extensionActive; const smartTransactionsLiveness = state.metamask.SmartTransactionsController.smartTransactionsState?.liveness; return Boolean( diff --git a/ui/ducks/confirm-transaction/confirm-transaction.duck.js b/ui/ducks/confirm-transaction/confirm-transaction.duck.js index 00b9521ad40e..c7af5315ff41 100644 --- a/ui/ducks/confirm-transaction/confirm-transaction.duck.js +++ b/ui/ducks/confirm-transaction/confirm-transaction.duck.js @@ -22,7 +22,8 @@ import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils'; // Actions -const createActionType = (action) => `metamask/confirm-transaction/${action}`; +const createActionType = (action: string) => + `metamask/confirm-transaction/${action}`; const UPDATE_TX_DATA = createActionType('UPDATE_TX_DATA'); const UPDATE_TOKEN_DATA = createActionType('UPDATE_TOKEN_DATA'); diff --git a/ui/ducks/metamask/metamask.ts b/ui/ducks/metamask/metamask.ts index d2762c1502e5..e48f210b290a 100644 --- a/ui/ducks/metamask/metamask.ts +++ b/ui/ducks/metamask/metamask.ts @@ -8,6 +8,7 @@ import { TransactionParams, } from '@metamask/transaction-controller'; import { Hex } from '@metamask/utils'; +import { createDeepEqualSelector } from '../../../shared/modules/selectors/util'; import { AlertTypes } from '../../../shared/constants/alerts'; import { GasEstimateTypes, @@ -25,6 +26,7 @@ import { import { getProviderConfig, getSelectedNetworkClientId, + ProviderConfigState, } from '../../../shared/modules/selectors/networks'; import { getSelectedInternalAccount } from '../../selectors/accounts'; import * as actionConstants from '../../store/actionConstants'; @@ -239,98 +241,121 @@ export function updateGasFees({ // Selectors -export const getAlertEnabledness = (state: MetaMaskSliceState) => - state.metamask.AlertController.alertEnabledness; +export const getAlertEnabledness = ( + state: MetaMaskSliceControllerState<'AlertController'>, +) => state.metamask.AlertController.alertEnabledness; export const getUnconnectedAccountAlertEnabledness = ( - state: MetaMaskSliceState, + state: MetaMaskSliceControllerState<'AlertController'>, ) => getAlertEnabledness(state)[AlertTypes.unconnectedAccount]; -export const getWeb3ShimUsageAlertEnabledness = (state: MetaMaskSliceState) => - getAlertEnabledness(state)[AlertTypes.web3ShimUsage]; +export const getWeb3ShimUsageAlertEnabledness = ( + state: MetaMaskSliceControllerState<'AlertController'>, +) => getAlertEnabledness(state)[AlertTypes.web3ShimUsage]; -export const getUnconnectedAccountAlertShown = (state: MetaMaskSliceState) => - state.metamask.AlertController.unconnectedAccountAlertShownOrigins; +export const getUnconnectedAccountAlertShown = ( + state: MetaMaskSliceControllerState<'AlertController'>, +) => state.metamask.AlertController.unconnectedAccountAlertShownOrigins; -export const getTokens = (state: MetaMaskSliceState) => - state.metamask.TokensController.tokens; +export const getTokens = ( + state: MetaMaskSliceControllerState<'TokensController'>, +) => state.metamask.TokensController.tokens; -export function getNftsDropdownState(state: MetaMaskSliceState) { +export function getNftsDropdownState( + state: MetaMaskSliceControllerState<'AppStateController'>, +) { return state.metamask.AppStateController.nftsDropdownState; } -export const getNfts = (state: MetaMaskSliceState) => { - const { - metamask: { - NftController: { allNfts }, - }, - } = state; - const { address: selectedAddress } = getSelectedInternalAccount(state); - - const { chainId } = getProviderConfig(state); +export function getAllNfts( + state: MetaMaskSliceControllerState<'NftController'>, +) { + return state.metamask.NftController.allNfts; +} - return allNfts?.[selectedAddress]?.[chainId] ?? []; -}; +export const getNfts = createDeepEqualSelector( + getAllNfts, + getSelectedInternalAccount, + getProviderConfig, + (allNfts, { address: selectedAddress }, { chainId }) => + allNfts?.[selectedAddress]?.[chainId] ?? [], +); -export const getNFTsByChainId = (state: MetaMaskSliceState, chainId?: Hex) => { +export const getNFTsByChainId = ( + state: MetaMaskSliceControllerState<'NftController' | 'AccountsController'>, + chainId?: Hex, +) => { if (!chainId) { return []; } - const { - metamask: { - NftController: { allNfts }, - }, - } = state; + const allNfts = getAllNfts(state); const { address: selectedAddress } = getSelectedInternalAccount(state); return allNfts?.[selectedAddress]?.[chainId] ?? []; }; -export const getNftContracts = (state: MetaMaskSliceState) => { - const { - metamask: { - NftController: { allNftContracts }, - }, - } = state; - const { address: selectedAddress } = getSelectedInternalAccount(state); - const { chainId } = getProviderConfig(state); - return allNftContracts?.[selectedAddress]?.[chainId]; -}; +export function getAllNftContracts( + state: MetaMaskSliceControllerState<'NftController'>, +) { + return state.metamask.NftController.allNftContracts; +} -export function getBlockGasLimit(state: MetaMaskSliceState) { +export const getNftContracts = createDeepEqualSelector( + getAllNftContracts, + getSelectedInternalAccount, + getProviderConfig, + (allNftContracts, { address: selectedAddress }, { chainId }) => + allNftContracts?.[selectedAddress]?.[chainId], +); + +export function getBlockGasLimit( + state: MetaMaskSliceControllerState<'AccountTracker'>, +) { return state.metamask.AccountTracker.currentBlockGasLimit; } -export function getNativeCurrency(state: MetaMaskSliceState) { +export function getNativeCurrency(state: ProviderConfigState) { return getProviderConfig(state).ticker; } -export function getConversionRate(state: MetaMaskSliceState) { +export function getConversionRate( + state: MetaMaskSliceControllerState<'CurrencyController'> & + ProviderConfigState, +) { return state.metamask.CurrencyController.currencyRates[ getProviderConfig(state).ticker ]?.conversionRate; } -export function getCurrencyRates(state: MetaMaskSliceState) { +export function getCurrencyRates( + state: MetaMaskSliceControllerState<'CurrencyController'>, +) { return state.metamask.CurrencyController.currencyRates; } -export function getSendHexDataFeatureFlagState(state: MetaMaskSliceState) { +export function getSendHexDataFeatureFlagState( + state: MetaMaskSliceControllerState<'PreferencesController'>, +) { return state.metamask.PreferencesController.featureFlags.sendHexData; } -export function getSendToAccounts(state: MetaMaskSliceState) { - const fromAccounts = accountsWithSendEtherInfoSelector(state); - const addressBookAccounts = getAddressBook(state); - return [...fromAccounts, ...addressBookAccounts]; -} +export const getSendToAccounts = createDeepEqualSelector( + accountsWithSendEtherInfoSelector, + getAddressBook, + (fromAccounts, addressBookAccounts) => [ + ...fromAccounts, + ...addressBookAccounts, + ], +); /** * Function returns true if network details are fetched and it is found to not support EIP-1559 * * @param state */ -export function isNotEIP1559Network(state: MetaMaskSliceState) { +export function isNotEIP1559Network( + state: MetaMaskSliceControllerState<'NetworkController'>, +) { const selectedNetworkClientId = getSelectedNetworkClientId(state); return ( state.metamask.NetworkController.networksMetadata[selectedNetworkClientId] @@ -345,7 +370,7 @@ export function isNotEIP1559Network(state: MetaMaskSliceState) { * @param networkClientId - The optional network client ID to check for EIP-1559 support. Defaults to the currently selected network. */ export function isEIP1559Network( - state: MetaMaskSliceState, + state: MetaMaskSliceControllerState<'NetworkController'>, networkClientId?: string, ) { const selectedNetworkClientId = getSelectedNetworkClientId(state); @@ -357,24 +382,28 @@ export function isEIP1559Network( ); } -function getGasFeeControllerEstimateType(state: MetaMaskSliceState) { +function getGasFeeControllerEstimateType( + state: MetaMaskSliceControllerState<'GasFeeController'>, +) { return state.metamask.GasFeeController.gasEstimateType; } function getGasFeeControllerEstimateTypeByChainId( - state: MetaMaskSliceState, + state: MetaMaskSliceControllerState<'GasFeeController'>, chainId: Hex, ) { return state.metamask.GasFeeController.gasFeeEstimatesByChainId?.[chainId] ?.gasEstimateType; } -function getGasFeeControllerEstimates(state: MetaMaskSliceState) { +function getGasFeeControllerEstimates( + state: MetaMaskSliceControllerState<'GasFeeController'>, +) { return state.metamask.GasFeeController.gasFeeEstimates; } function getGasFeeControllerEstimatesByChainId( - state: MetaMaskSliceState, + state: MetaMaskSliceControllerState<'GasFeeController'>, chainId: Hex, ) { return ( @@ -384,14 +413,14 @@ function getGasFeeControllerEstimatesByChainId( } function getTransactionGasFeeEstimates( - state: MetaMaskSliceState & Pick, + state: Pick, ) { const transactionMetadata = state.confirmTransaction?.txData; return transactionMetadata?.gasFeeEstimates; } function getTransactionGasFeeEstimatesByChainId( - state: MetaMaskSliceState & Pick, + state: Pick, chainId: Hex, ) { const transactionMetadata = state.confirmTransaction?.txData; @@ -434,7 +463,9 @@ export const getGasEstimateTypeByChainId = createSelector( * @param state * @returns The balances of imported and detected tokens across all accounts and chains. */ -export function getTokenBalances(state: MetaMaskSliceState) { +export function getTokenBalances( + state: MetaMaskSliceControllerState<'TokenBalancesController'>, +) { return state.metamask.TokenBalancesController.tokenBalances; } @@ -476,88 +507,105 @@ export const getGasFeeEstimates = createSelector( }, ); -export function getEstimatedGasFeeTimeBounds(state: MetaMaskSliceState) { +export function getEstimatedGasFeeTimeBounds( + state: MetaMaskSliceControllerState<'GasFeeController'>, +) { return state.metamask.GasFeeController.estimatedGasFeeTimeBounds; } export function getEstimatedGasFeeTimeBoundsByChainId( - state: MetaMaskSliceState, + state: MetaMaskSliceControllerState<'GasFeeController'>, chainId: Hex, ) { return state.metamask.GasFeeController.gasFeeEstimatesByChainId?.[chainId] ?.estimatedGasFeeTimeBounds; } -export function getIsGasEstimatesLoading( - state: MetaMaskSliceState & Pick, -) { - const networkAndAccountSupports1559 = - checkNetworkAndAccountSupports1559(state); - const gasEstimateType = getGasEstimateType(state); - - // We consider the gas estimate to be loading if the gasEstimateType is - // 'NONE' or if the current gasEstimateType cannot be supported by the current - // network - const isEIP1559TolerableEstimateType = - gasEstimateType === GasEstimateTypes.feeMarket || - gasEstimateType === GasEstimateTypes.ethGasPrice; - const isGasEstimatesLoading = - gasEstimateType === GasEstimateTypes.none || - (networkAndAccountSupports1559 && !isEIP1559TolerableEstimateType) || - (!networkAndAccountSupports1559 && - gasEstimateType === GasEstimateTypes.feeMarket); - - return isGasEstimatesLoading; -} +export const getIsGasEstimatesLoading = createDeepEqualSelector( + checkNetworkAndAccountSupports1559, + getGasEstimateType, + (networkAndAccountSupports1559, gasEstimateType) => { + // We consider the gas estimate to be loading if the gasEstimateType is + // 'NONE' or if the current gasEstimateType cannot be supported by the current + // network + const isEIP1559TolerableEstimateType = + gasEstimateType === GasEstimateTypes.feeMarket || + gasEstimateType === GasEstimateTypes.ethGasPrice; + const isGasEstimatesLoading = + gasEstimateType === GasEstimateTypes.none || + (networkAndAccountSupports1559 && !isEIP1559TolerableEstimateType) || + (!networkAndAccountSupports1559 && + gasEstimateType === GasEstimateTypes.feeMarket); + + return isGasEstimatesLoading; + }, +); -export function getIsGasEstimatesLoadingByChainId( - state: MetaMaskSliceState & Pick, - { chainId, networkClientId }: { chainId: Hex; networkClientId: string }, -) { - const networkAndAccountSupports1559 = checkNetworkAndAccountSupports1559( +export const getIsGasEstimatesLoadingByChainId = createDeepEqualSelector( + ( + state: Parameters[0] & + Parameters[0], + { chainId, networkClientId }: { chainId: Hex; networkClientId: string }, + ) => ({ state, + chainId, networkClientId, - ); - const gasEstimateType = getGasEstimateTypeByChainId(state, chainId); - - // We consider the gas estimate to be loading if the gasEstimateType is - // 'NONE' or if the current gasEstimateType cannot be supported by the current - // network - const isEIP1559TolerableEstimateType = - gasEstimateType === GasEstimateTypes.feeMarket || - gasEstimateType === GasEstimateTypes.ethGasPrice; - const isGasEstimatesLoading = - gasEstimateType === GasEstimateTypes.none || - (networkAndAccountSupports1559 && !isEIP1559TolerableEstimateType) || - (!networkAndAccountSupports1559 && - gasEstimateType === GasEstimateTypes.feeMarket); - - return isGasEstimatesLoading; -} + }), + ({ state, chainId, networkClientId }) => { + const networkAndAccountSupports1559 = checkNetworkAndAccountSupports1559( + state, + networkClientId, + ); + const gasEstimateType = getGasEstimateTypeByChainId(state, chainId); + + // We consider the gas estimate to be loading if the gasEstimateType is + // 'NONE' or if the current gasEstimateType cannot be supported by the current + // network + const isEIP1559TolerableEstimateType = + gasEstimateType === GasEstimateTypes.feeMarket || + gasEstimateType === GasEstimateTypes.ethGasPrice; + const isGasEstimatesLoading = + gasEstimateType === GasEstimateTypes.none || + (networkAndAccountSupports1559 && !isEIP1559TolerableEstimateType) || + (!networkAndAccountSupports1559 && + gasEstimateType === GasEstimateTypes.feeMarket); + + return isGasEstimatesLoading; + }, +); -export function getIsNetworkBusyByChainId( - state: MetaMaskSliceState & Pick, - chainId: Hex, -) { - const gasFeeEstimates = getGasFeeEstimatesByChainId(state, chainId); - return 'networkCongestion' in gasFeeEstimates - ? (gasFeeEstimates?.networkCongestion ?? 0) >= - NetworkCongestionThresholds.busy - : false; -} +export const getIsNetworkBusyByChainId = createDeepEqualSelector( + (state: Parameters[0], chainId: Hex) => ({ + state, + chainId, + }), + ({ state, chainId }) => { + const gasFeeEstimates = getGasFeeEstimatesByChainId(state, chainId); + return 'networkCongestion' in gasFeeEstimates + ? (gasFeeEstimates?.networkCongestion ?? 0) >= + NetworkCongestionThresholds.busy + : false; + }, +); -export function getCompletedOnboarding(state: MetaMaskSliceState) { +export function getCompletedOnboarding( + state: MetaMaskSliceControllerState<'OnboardingController'>, +) { return state.metamask.OnboardingController.completedOnboarding; } export function getIsInitialized(state: MetaMaskSliceState) { return state.metamask.isInitialized; } -export function getIsUnlocked(state: MetaMaskSliceState) { +export function getIsUnlocked( + state: MetaMaskSliceControllerState<'KeyringController'>, +) { return state.metamask.KeyringController.isUnlocked; } -export function getSeedPhraseBackedUp(state: MetaMaskSliceState) { +export function getSeedPhraseBackedUp( + state: MetaMaskSliceControllerState<'OnboardingController'>, +) { return state.metamask.OnboardingController.seedPhraseBackedUp; } @@ -568,21 +616,21 @@ export function getSeedPhraseBackedUp(state: MetaMaskSliceState) { * @param address - the address to search for among the keyring addresses * @returns The keyring which contains the passed address, or undefined */ -export function findKeyringForAddress( - state: MetaMaskSliceState, - address: string, -) { - const keyring = state.metamask.KeyringController.keyrings.find((kr) => { - return kr.accounts.some((account) => { - return ( - isEqualCaseInsensitive(account, addHexPrefix(address)) || - isEqualCaseInsensitive(account, stripHexPrefix(address)) - ); +export const findKeyringForAddress = createDeepEqualSelector( + (state: MetaMaskSliceControllerState<'KeyringController'>) => + state.metamask.KeyringController.keyrings, + (_state: Record, address: string) => address, + (keyrings, address) => { + return keyrings.find((kr) => { + return kr.accounts.some((account) => { + return ( + isEqualCaseInsensitive(account, addHexPrefix(address)) || + isEqualCaseInsensitive(account, stripHexPrefix(address)) + ); + }); }); - }); - - return keyring; -} + }, +); /** * Given the redux state object, returns the users preferred ledger transport type @@ -590,7 +638,9 @@ export function findKeyringForAddress( * @param state - the redux state object * @returns The user's preferred ledger transport type as a string. One of 'webhid' on chrome or 'u2f' on firefox */ -export function getLedgerTransportType(state: MetaMaskSliceState) { +export function getLedgerTransportType( + state: MetaMaskSliceControllerState<'PreferencesController'>, +) { return state.metamask.PreferencesController.ledgerTransportType; } @@ -601,7 +651,10 @@ export function getLedgerTransportType(state: MetaMaskSliceState) { * @param address - the address to search for among all keyring addresses * @returns 'true' if the passed address is part of a ledger keyring, and 'false' otherwise */ -export function isAddressLedger(state: MetaMaskSliceState, address: string) { +export function isAddressLedger( + state: Parameters[0], + address: string, +) { const keyring = findKeyringForAddress(state, address); return keyring?.type === KeyringType.ledger; @@ -615,12 +668,16 @@ export function isAddressLedger(state: MetaMaskSliceState, address: string) { * @param state.metamask * @returns true if the user has a Ledger account and false otherwise */ -export function doesUserHaveALedgerAccount(state: MetaMaskSliceState) { +export function doesUserHaveALedgerAccount( + state: MetaMaskSliceControllerState<'KeyringController'>, +) { return state.metamask.KeyringController.keyrings.some((kr) => { return kr.type === KeyringType.ledger; }); } -export function getCurrentCurrency(state: MetaMaskSliceState) { +export function getCurrentCurrency( + state: MetaMaskSliceControllerState<'CurrencyController'>, +) { return state.metamask.CurrencyController.currentCurrency; } diff --git a/ui/helpers/utils/confirm-tx.util.ts b/ui/helpers/utils/confirm-tx.util.ts index cf760163f2d4..0f0dac819787 100644 --- a/ui/helpers/utils/confirm-tx.util.ts +++ b/ui/helpers/utils/confirm-tx.util.ts @@ -36,7 +36,7 @@ export function getTransactionFee({ numberOfDecimals, }: { value: string; - fromCurrency: EtherDenomination; + fromCurrency: EtherDenomination | string; toCurrency: string; conversionRate: number; numberOfDecimals: number; diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index d687d9b82338..3bb5eb2434aa 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -299,6 +299,12 @@ export function shortenAddress(address = '') { }); } +/** + * + * @param {(import('@metamask/keyring-api').InternalAccount | import('../../selectors/selectors.types').InternalAccountWithBalance)[]} accounts - The internal accounts list. + * @param {string} targetAddress + * @returns {import('@metamask/keyring-api').InternalAccount | import('../../selectors/selectors.types').InternalAccountWithBalance | undefined} + */ export function getAccountByAddress(accounts = [], targetAddress) { return accounts.find(({ address }) => address === targetAddress); } diff --git a/ui/pages/bridge/utils/quote.ts b/ui/pages/bridge/utils/quote.ts index ee78e055698d..0ba821b121c9 100644 --- a/ui/pages/bridge/utils/quote.ts +++ b/ui/pages/bridge/utils/quote.ts @@ -95,7 +95,7 @@ const calcTotalGasFee = ({ bridgeQuote: QuoteResponse & L1GasFees; feePerGasInDecGwei: string; priorityFeePerGasInDecGwei: string; - nativeExchangeRate?: number; + nativeExchangeRate?: number | null; }) => { const { approval, trade, l1GasFeesInHexWei } = bridgeQuote; @@ -139,7 +139,7 @@ export const calcEstimatedAndMaxTotalGasFee = ({ estimatedBaseFeeInDecGwei: string; maxFeePerGasInDecGwei: string; maxPriorityFeePerGasInDecGwei: string; - nativeExchangeRate?: number; + nativeExchangeRate?: number | null; }) => { const { amount, valueInCurrency } = calcTotalGasFee({ bridgeQuote, diff --git a/ui/selectors/confirm-transaction.ts b/ui/selectors/confirm-transaction.ts index 2017c3c68932..cba12c4c4a6c 100644 --- a/ui/selectors/confirm-transaction.ts +++ b/ui/selectors/confirm-transaction.ts @@ -1,5 +1,8 @@ import { createSelector } from 'reselect'; -import { TransactionEnvelopeType } from '@metamask/transaction-controller'; +import { + TransactionEnvelopeType, + TransactionMeta, +} from '@metamask/transaction-controller'; import txHelper from '../helpers/utils/tx-helper'; import { roundExponential, @@ -11,6 +14,7 @@ import { getGasEstimateType, getGasFeeEstimates, getNativeCurrency, + MetaMaskSliceControllerState, } from '../ducks/metamask/metamask'; import { GasEstimateTypes, @@ -42,16 +46,17 @@ import { getUnapprovedTransactions, selectTransactionMetadata, selectTransactionSender, - unapprovedPersonalMsgsSelector, unapprovedDecryptMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector, + unapprovedPersonalMsgsSelector, unapprovedTypedMessagesSelector, } from './transactions'; - -const unapprovedTxsSelector = (state) => getUnapprovedTransactions(state); +import { MetaMaskReduxState } from '../store/store'; +import { getKnownPropertyNames } from '@metamask/utils'; +import { createDeepEqualSelector } from '../../shared/modules/selectors/util'; export const unconfirmedTransactionsListSelector = createSelector( - unapprovedTxsSelector, + getUnapprovedTransactions, unapprovedPersonalMsgsSelector, unapprovedDecryptMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector, @@ -76,7 +81,7 @@ export const unconfirmedTransactionsListSelector = createSelector( ); export const unconfirmedTransactionsHashSelector = createSelector( - unapprovedTxsSelector, + getUnapprovedTransactions, unapprovedPersonalMsgsSelector, unapprovedDecryptMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector, @@ -90,18 +95,17 @@ export const unconfirmedTransactionsHashSelector = createSelector( unapprovedTypedMessages = {}, chainId, ) => { - const filteredUnapprovedTxs = Object.keys(unapprovedTxs).reduce( - (acc, address) => { - const transactions = { ...acc }; + const filteredUnapprovedTxs = getKnownPropertyNames(unapprovedTxs).reduce<{ + [address: string]: TransactionMeta; + }>((acc, address) => { + const transactions = { ...acc }; - if (unapprovedTxs[address].chainId === chainId) { - transactions[address] = unapprovedTxs[address]; - } + if (unapprovedTxs[address].chainId === chainId) { + transactions[address] = unapprovedTxs[address]; + } - return transactions; - }, - {}, - ); + return transactions; + }, {}); return { ...filteredUnapprovedTxs, @@ -132,15 +136,21 @@ export const unconfirmedMessagesHashSelector = createSelector( }; }, ); -export const use4ByteResolutionSelector = (state) => - state.metamask.use4ByteResolution; -export const currentCurrencySelector = (state) => - state.metamask.currentCurrency; -export const conversionRateSelector = (state) => - state.metamask.currencyRates[getProviderConfig(state).ticker]?.conversionRate; -export const txDataSelector = (state) => state.confirmTransaction.txData; -const tokenDataSelector = (state) => state.confirmTransaction.tokenData; -const tokenPropsSelector = (state) => state.confirmTransaction.tokenProps; +export const use4ByteResolutionSelector = (state: MetaMaskSliceControllerState<'PreferencesController'>) => + state.metamask.PreferencesController.use4ByteResolution; +export const currentCurrencySelector = (state: MetaMaskSliceControllerState<'CurrencyController'>) => + state.metamask.CurrencyController.currentCurrency; +export const conversionRateSelector = createDeepEqualSelector( + (state: MetaMaskSliceControllerState<'CurrencyController'>) => state.metamask.CurrencyController.currencyRates, + getProviderConfig, + (currencyRates, providerConfig) => currencyRates[providerConfig.ticker]?.conversionRate +); +export const txDataSelector = (state: Pick) => + state.confirmTransaction.txData; +const tokenDataSelector = (state: Pick) => + state.confirmTransaction.tokenData; +const tokenPropsSelector = (state: Pick) => + state.confirmTransaction.tokenProps; const tokenDecimalsSelector = createSelector( tokenPropsSelector, @@ -194,32 +204,33 @@ export const sendTokenTokenAmountAndToAddressSelector = createSelector( ); export const contractExchangeRateSelector = createSelector( - (state) => getTokenExchangeRates(state), + getTokenExchangeRates, tokenAddressSelector, (contractExchangeRates, tokenAddress) => { - return contractExchangeRates[ - Object.keys(contractExchangeRates).find((address) => { + const address = getKnownPropertyNames(contractExchangeRates).find( + (address) => { return isEqualCaseInsensitive(address, tokenAddress); - }) - ]; + }, + ); + return address ? contractExchangeRates[address] : undefined; }, ); -export const transactionFeeSelector = function (state, txData) { - const currentCurrency = currentCurrencySelector(state); - const conversionRate = conversionRateSelector(state); - const nativeCurrency = getNativeCurrency(state); - const gasFeeEstimates = getGasFeeEstimates(state) || {}; - const gasEstimateType = getGasEstimateType(state); - const networkAndAccountSupportsEIP1559 = - checkNetworkAndAccountSupports1559(state); - +export const transactionFeeSelector = createDeepEqualSelector( + (_state: Record, txData: Partial) => txData, + currentCurrencySelector, + conversionRateSelector, + getNativeCurrency, + getGasFeeEstimates, + getGasEstimateType, + checkNetworkAndAccountSupports1559, + (txData, currentCurrency, conversionRate, nativeCurrency, gasFeeEstimates, gasEstimateType, networkAndAccountSupportsEIP1559) => { const gasEstimationObject = { gasLimit: txData.txParams?.gas ?? '0x0', }; if (networkAndAccountSupportsEIP1559) { - const { gasPrice = '0' } = gasFeeEstimates; + const { gasPrice } = gasFeeEstimates; const selectedGasEstimates = gasFeeEstimates[txData.userFeeLevel] || {}; if (txData.txParams?.type === TransactionEnvelopeType.legacy) { gasEstimationObject.gasPrice = @@ -331,22 +342,32 @@ export const transactionFeeSelector = function (state, txData) { hexTransactionTotal, gasEstimationObject, }; -}; +}); -export function selectTransactionFeeById(state, transactionId) { +export function selectTransactionFeeById( + state: , + transactionId: string, +) { const transactionMetadata = selectTransactionMetadata(state, transactionId); return transactionFeeSelector(state, transactionMetadata ?? {}); } // Cannot use createSelector due to circular dependency caused by getMetaMaskAccounts. -export function selectTransactionAvailableBalance(state, transactionId) { +export function selectTransactionAvailableBalance = createDeepEqualSelector( + (state, transactionId) => ({ state, transactionId }) + getMetaMaskAccounts, + () +) { const accounts = getMetaMaskAccounts(state); const sender = selectTransactionSender(state, transactionId); - return accounts[sender]?.balance; + return sender ? accounts[sender]?.balance : undefined; } -export function selectIsMaxValueEnabled(state, transactionId) { +export function selectIsMaxValueEnabled( + state: Pick, + transactionId: string, +) { return state.confirmTransaction.maxValueMode?.[transactionId] ?? false; } @@ -359,7 +380,6 @@ export const selectMaxValue = createSelector( : undefined, ); -/** @type {state: any, transactionId: string => string} */ export const selectTransactionValue = createSelector( selectIsMaxValueEnabled, selectMaxValue,