From 6d3283911b677fd3b73201f5831ef4d3f1b5ee17 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Tue, 14 Jan 2025 12:01:45 -0500 Subject: [PATCH] stash --- .../metamask.ts => background/background.ts} | 192 +++--- .../{metamask => background}/constants.ts | 4 +- ui/ducks/index.ts | 2 + ui/ducks/metamask/metamask.js | 551 ++++++++++++++++++ ui/store/actions.ts | 2 +- ui/store/store.ts | 31 +- 6 files changed, 681 insertions(+), 101 deletions(-) rename ui/ducks/{metamask/metamask.ts => background/background.ts} (72%) rename ui/ducks/{metamask => background}/constants.ts (97%) create mode 100644 ui/ducks/metamask/metamask.js diff --git a/ui/ducks/metamask/metamask.ts b/ui/ducks/background/background.ts similarity index 72% rename from ui/ducks/metamask/metamask.ts rename to ui/ducks/background/background.ts index 0786ae34ed92..c92c29eba076 100644 --- a/ui/ducks/metamask/metamask.ts +++ b/ui/ducks/background/background.ts @@ -31,11 +31,11 @@ import * as actionConstants from '../../store/actionConstants'; import { updateTransactionGasFees } from '../../store/actions'; import { setCustomGasLimit, setCustomGasPrice } from '../gas/gas.duck'; import { MetaMaskReduxState } from '../../store/store'; -import type { BackgroundStateProxy } from '../../../shared/types/metamask'; -import { initialMetamaskState } from './constants'; +import { BackgroundStateProxy } from '../../../shared/types/background'; +import { initialBackgroundState } from './constants'; -export type MetaMaskSliceState = { - metamask: BackgroundStateProxy; +export type BackgroundSliceState = { + background: BackgroundStateProxy }; /** @@ -46,8 +46,8 @@ export type MetaMaskSliceState = { * @param action * @returns */ -export default function reduceMetamask( - state: MetaMaskSliceState, +export default function reduceBackground( + state: BackgroundSliceState, action: AnyAction, ): BackgroundStateProxy { // I don't think we should be spreading initialMetamaskState into this. Once the @@ -58,16 +58,16 @@ export default function reduceMetamask( // for this slice*. I attempted to remove this and it caused nearly 40 test // failures. We are going to refactor this slice anyways, possibly removing // it so we will fix this issue when that time comes. - const metamaskState = { ...initialMetamaskState, ...state.metamask }; + const backgroundState = { ...initialBackgroundState, ...state.background }; switch (action.type) { case actionConstants.UPDATE_METAMASK_STATE: - return { ...metamaskState, ...action.value }; + return { ...backgroundState, ...action.value }; case actionConstants.LOCK_METAMASK: return { - ...metamaskState, + ...backgroundState, KeyringController: { - ...metamaskState.KeyringController, + ...backgroundState.KeyringController, isUnlocked: false, }, }; @@ -76,18 +76,18 @@ export default function reduceMetamask( const { account } = action.value; const name = action.value.label; const accountToUpdate = Object.values( - metamaskState.AccountsController.internalAccounts.accounts, + backgroundState.AccountsController.internalAccounts.accounts, ).find((internalAccount) => { return internalAccount.address.toLowerCase() === account.toLowerCase(); }); if (!accountToUpdate) { - return metamaskState; + return backgroundState; } const internalAccounts = { - ...metamaskState.AccountsController.internalAccounts, + ...backgroundState.AccountsController.internalAccounts, accounts: { - ...metamaskState.AccountsController.internalAccounts.accounts, + ...backgroundState.AccountsController.internalAccounts.accounts, [accountToUpdate.id]: { ...accountToUpdate, metadata: { @@ -97,12 +97,12 @@ export default function reduceMetamask( }, }, }; - return Object.assign(metamaskState, { internalAccounts }); + return Object.assign(backgroundState, { internalAccounts }); } case actionConstants.UPDATE_TRANSACTION_PARAMS: { const { id: txId, value } = action; - let { transactions } = metamaskState.TxController ?? {}; + let { transactions } = backgroundState.TxController ?? {}; transactions = transactions?.map((tx) => { if (tx.id === txId) { const newTx = { ...tx }; @@ -113,34 +113,34 @@ export default function reduceMetamask( }); return { - ...metamaskState, - TxController: { ...metamaskState.TxController, transactions }, + ...backgroundState, + TxController: { ...backgroundState.TxController, transactions }, }; } case actionConstants.SET_PARTICIPATE_IN_METAMETRICS: return { - ...metamaskState, + ...backgroundState, MetaMetricsController: { - ...metamaskState.MetaMetricsController, + ...backgroundState.MetaMetricsController, participateInMetaMetrics: action.value, }, }; case actionConstants.SET_DATA_COLLECTION_FOR_MARKETING: return { - ...metamaskState, + ...backgroundState, MetaMetricsController: { - ...metamaskState.MetaMetricsController, + ...backgroundState.MetaMetricsController, dataCollectionForMarketing: action.value, }, }; case actionConstants.COMPLETE_ONBOARDING: { return { - ...metamaskState, + ...backgroundState, OnboardingController: { - ...metamaskState.OnboardingController, + ...backgroundState.OnboardingController, completedOnboarding: true, }, }; @@ -148,17 +148,17 @@ export default function reduceMetamask( case actionConstants.RESET_ONBOARDING: { return { - ...metamaskState, + ...backgroundState, isInitialized: false, OnboardingController: { - ...metamaskState.OnboardingController, + ...backgroundState.OnboardingController, completedOnboarding: false, firstTimeFlowType: null, onboardingTabs: {}, seedPhraseBackedUp: null, }, KeyringController: { - ...metamaskState.KeyringController, + ...backgroundState.KeyringController, isUnlocked: false, }, }; @@ -166,16 +166,16 @@ export default function reduceMetamask( case actionConstants.SET_FIRST_TIME_FLOW_TYPE: { return { - ...metamaskState, + ...backgroundState, OnboardingController: { - ...metamaskState.OnboardingController, + ...backgroundState.OnboardingController, firstTimeFlowType: action.value, }, }; } default: - return metamaskState; + return backgroundState; } } @@ -233,29 +233,29 @@ export function updateGasFees({ // Selectors -export const getAlertEnabledness = (state: MetaMaskSliceState) => - state.metamask.AlertController.alertEnabledness; +export const getAlertEnabledness = (state: BackgroundSliceState) => + state.background.AlertController.alertEnabledness; export const getUnconnectedAccountAlertEnabledness = ( - state: MetaMaskSliceState, + state: BackgroundSliceState, ) => getAlertEnabledness(state)[AlertTypes.unconnectedAccount]; -export const getWeb3ShimUsageAlertEnabledness = (state: MetaMaskSliceState) => +export const getWeb3ShimUsageAlertEnabledness = (state: BackgroundSliceState) => getAlertEnabledness(state)[AlertTypes.web3ShimUsage]; -export const getUnconnectedAccountAlertShown = (state: MetaMaskSliceState) => - state.metamask.AlertController.unconnectedAccountAlertShownOrigins; +export const getUnconnectedAccountAlertShown = (state: BackgroundSliceState) => + state.background.AlertController.unconnectedAccountAlertShownOrigins; -export const getTokens = (state: MetaMaskSliceState) => - state.metamask.TokensController.tokens; +export const getTokens = (state: BackgroundSliceState) => + state.background.TokensController.tokens; -export function getNftsDropdownState(state: MetaMaskSliceState) { - return state.metamask.AppStateController.nftsDropdownState; +export function getNftsDropdownState(state: BackgroundSliceState) { + return state.background.AppStateController.nftsDropdownState; } -export const getNfts = (state: MetaMaskSliceState) => { +export const getNfts = (state: BackgroundSliceState) => { const { - metamask: { + background: { NftController: { allNfts }, }, } = state; @@ -266,9 +266,9 @@ export const getNfts = (state: MetaMaskSliceState) => { return allNfts?.[selectedAddress]?.[chainId] ?? []; }; -export const getNFTsByChainId = (state: MetaMaskSliceState, chainId: Hex) => { +export const getNFTsByChainId = (state: BackgroundSliceState, chainId: Hex) => { const { - metamask: { + background: { NftController: { allNfts }, }, } = state; @@ -277,9 +277,9 @@ export const getNFTsByChainId = (state: MetaMaskSliceState, chainId: Hex) => { return allNfts?.[selectedAddress]?.[chainId] ?? []; }; -export const getNftContracts = (state: MetaMaskSliceState) => { +export const getNftContracts = (state: BackgroundSliceState) => { const { - metamask: { + background: { NftController: { allNftContracts }, }, } = state; @@ -288,29 +288,29 @@ export const getNftContracts = (state: MetaMaskSliceState) => { return allNftContracts?.[selectedAddress]?.[chainId]; }; -export function getBlockGasLimit(state: MetaMaskSliceState) { - return state.metamask.AccountTracker.currentBlockGasLimit; +export function getBlockGasLimit(state: BackgroundSliceState) { + return state.background.AccountTracker.currentBlockGasLimit; } -export function getNativeCurrency(state: MetaMaskSliceState) { +export function getNativeCurrency(state: BackgroundSliceState) { return getProviderConfig(state).ticker; } -export function getConversionRate(state: MetaMaskSliceState) { - return state.metamask.CurrencyController.currencyRates[ +export function getConversionRate(state: BackgroundSliceState) { + return state.background.CurrencyController.currencyRates[ getProviderConfig(state).ticker ]?.conversionRate; } -export function getCurrencyRates(state: MetaMaskSliceState) { - return state.metamask.CurrencyController.currencyRates; +export function getCurrencyRates(state: BackgroundSliceState) { + return state.background.CurrencyController.currencyRates; } -export function getSendHexDataFeatureFlagState(state: MetaMaskSliceState) { - return state.metamask.PreferencesController.featureFlags.sendHexData; +export function getSendHexDataFeatureFlagState(state: BackgroundSliceState) { + return state.background.PreferencesController.featureFlags.sendHexData; } -export function getSendToAccounts(state: MetaMaskSliceState) { +export function getSendToAccounts(state: BackgroundSliceState) { const fromAccounts = accountsWithSendEtherInfoSelector(state); const addressBookAccounts = getAddressBook(state); return [...fromAccounts, ...addressBookAccounts]; @@ -321,10 +321,10 @@ export function getSendToAccounts(state: MetaMaskSliceState) { * * @param state */ -export function isNotEIP1559Network(state: MetaMaskSliceState) { +export function isNotEIP1559Network(state: BackgroundSliceState) { const selectedNetworkClientId = getSelectedNetworkClientId(state); return ( - state.metamask.NetworkController.networksMetadata[selectedNetworkClientId] + state.background.NetworkController.networksMetadata[selectedNetworkClientId] .EIPS[1559] === false ); } @@ -336,53 +336,53 @@ 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: BackgroundSliceState, networkClientId: string, ) { const selectedNetworkClientId = getSelectedNetworkClientId(state); return ( - state.metamask.NetworkController.networksMetadata?.[ + state.background.NetworkController.networksMetadata?.[ networkClientId ?? selectedNetworkClientId ]?.EIPS[1559] === true ); } -function getGasFeeControllerEstimateType(state: MetaMaskSliceState) { - return state.metamask.GasFeeController.gasEstimateType; +function getGasFeeControllerEstimateType(state: BackgroundSliceState) { + return state.background.GasFeeController.gasEstimateType; } function getGasFeeControllerEstimateTypeByChainId( - state: MetaMaskSliceState, + state: BackgroundSliceState, chainId: Hex, ) { - return state.metamask.GasFeeController.gasFeeEstimatesByChainId?.[chainId] + return state.background.GasFeeController.gasFeeEstimatesByChainId?.[chainId] ?.gasEstimateType; } -function getGasFeeControllerEstimates(state: MetaMaskSliceState) { - return state.metamask.GasFeeController.gasFeeEstimates; +function getGasFeeControllerEstimates(state: BackgroundSliceState) { + return state.background.GasFeeController.gasFeeEstimates; } function getGasFeeControllerEstimatesByChainId( - state: MetaMaskSliceState, + state: BackgroundSliceState, chainId: Hex, ) { return ( - state.metamask.GasFeeController.gasFeeEstimatesByChainId?.[chainId] + state.background.GasFeeController.gasFeeEstimatesByChainId?.[chainId] ?.gasFeeEstimates ?? {} ); } function getTransactionGasFeeEstimates( - state: MetaMaskSliceState & Pick, + state: BackgroundSliceState & Pick, ) { const transactionMetadata = state.confirmTransaction?.txData; return transactionMetadata?.gasFeeEstimates; } function getTransactionGasFeeEstimatesByChainId( - state: MetaMaskSliceState & Pick, + state: BackgroundSliceState & Pick, chainId: Hex, ) { const transactionMetadata = state.confirmTransaction?.txData; @@ -425,8 +425,8 @@ export const getGasEstimateTypeByChainId = createSelector( * @param state * @returns The balances of imported and detected tokens across all accounts and chains. */ -export function getTokenBalances(state: MetaMaskSliceState) { - return state.metamask.TokenBalancesController.tokenBalances; +export function getTokenBalances(state: BackgroundSliceState) { + return state.background.TokenBalancesController.tokenBalances; } export const getGasFeeEstimatesByChainId = createSelector( @@ -467,20 +467,20 @@ export const getGasFeeEstimates = createSelector( }, ); -export function getEstimatedGasFeeTimeBounds(state: MetaMaskSliceState) { - return state.metamask.GasFeeController.estimatedGasFeeTimeBounds; +export function getEstimatedGasFeeTimeBounds(state: BackgroundSliceState) { + return state.background.GasFeeController.estimatedGasFeeTimeBounds; } export function getEstimatedGasFeeTimeBoundsByChainId( - state: MetaMaskSliceState, + state: BackgroundSliceState, chainId: Hex, ) { - return state.metamask.GasFeeController.gasFeeEstimatesByChainId?.[chainId] + return state.background.GasFeeController.gasFeeEstimatesByChainId?.[chainId] ?.estimatedGasFeeTimeBounds; } export function getIsGasEstimatesLoading( - state: MetaMaskSliceState & Pick, + state: BackgroundSliceState & Pick, ) { const networkAndAccountSupports1559 = checkNetworkAndAccountSupports1559(state); @@ -502,7 +502,7 @@ export function getIsGasEstimatesLoading( } export function getIsGasEstimatesLoadingByChainId( - state: MetaMaskSliceState & Pick, + state: BackgroundSliceState & Pick, { chainId, networkClientId }: { chainId: Hex; networkClientId: string }, ) { const networkAndAccountSupports1559 = checkNetworkAndAccountSupports1559( @@ -527,7 +527,7 @@ export function getIsGasEstimatesLoadingByChainId( } export function getIsNetworkBusyByChainId( - state: MetaMaskSliceState & Pick, + state: BackgroundSliceState & Pick, chainId: Hex, ) { const gasFeeEstimates = getGasFeeEstimatesByChainId(state, chainId); @@ -537,19 +537,19 @@ export function getIsNetworkBusyByChainId( : false; } -export function getCompletedOnboarding(state: MetaMaskSliceState) { - return state.metamask.OnboardingController.completedOnboarding; +export function getCompletedOnboarding(state: BackgroundSliceState) { + return state.background.OnboardingController.completedOnboarding; } -export function getIsInitialized(state: MetaMaskSliceState) { - return state.metamask.isInitialized; +export function getIsInitialized(state: BackgroundSliceState) { + return state.background.isInitialized; } -export function getIsUnlocked(state: MetaMaskSliceState) { - return state.metamask.KeyringController.isUnlocked; +export function getIsUnlocked(state: BackgroundSliceState) { + return state.background.KeyringController.isUnlocked; } -export function getSeedPhraseBackedUp(state: MetaMaskSliceState) { - return state.metamask.OnboardingController.seedPhraseBackedUp; +export function getSeedPhraseBackedUp(state: BackgroundSliceState) { + return state.background.OnboardingController.seedPhraseBackedUp; } /** @@ -560,10 +560,10 @@ export function getSeedPhraseBackedUp(state: MetaMaskSliceState) { * @returns The keyring which contains the passed address, or undefined */ export function findKeyringForAddress( - state: MetaMaskSliceState, + state: BackgroundSliceState, address: string, ) { - const keyring = state.metamask.KeyringController.keyrings.find((kr) => { + const keyring = state.background.KeyringController.keyrings.find((kr) => { return kr.accounts.some((account) => { return ( isEqualCaseInsensitive(account, addHexPrefix(address)) || @@ -581,8 +581,8 @@ 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) { - return state.metamask.PreferencesController.ledgerTransportType; +export function getLedgerTransportType(state: BackgroundSliceState) { + return state.background.PreferencesController.ledgerTransportType; } /** @@ -592,7 +592,7 @@ 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: BackgroundSliceState, address: string) { const keyring = findKeyringForAddress(state, address); return keyring?.type === KeyringType.ledger; @@ -603,15 +603,15 @@ export function isAddressLedger(state: MetaMaskSliceState, address: string) { * in state) * * @param state - the redux state object - * @param state.metamask + * @param state.background * @returns true if the user has a Ledger account and false otherwise */ -export function doesUserHaveALedgerAccount(state: MetaMaskSliceState) { - return state.metamask.KeyringController.keyrings.some((kr) => { +export function doesUserHaveALedgerAccount(state: BackgroundSliceState) { + return state.background.KeyringController.keyrings.some((kr) => { return kr.type === KeyringType.ledger; }); } export function getCurrentCurrency(state) { - return state.metamask.currentCurrency; + return state.background.currentCurrency; } diff --git a/ui/ducks/metamask/constants.ts b/ui/ducks/background/constants.ts similarity index 97% rename from ui/ducks/metamask/constants.ts rename to ui/ducks/background/constants.ts index 10590f9f7b09..7fafa3845f72 100644 --- a/ui/ducks/metamask/constants.ts +++ b/ui/ducks/background/constants.ts @@ -5,14 +5,14 @@ import { getDefaultPreferencesControllerState } from '../../../app/scripts/contr import type { BackgroundStateProxy, MemStoreControllersComposedState, -} from '../../../shared/types/metamask'; +} from '../../../shared/types/background'; import { DEFAULT_AUTO_LOCK_TIME_LIMIT, ThemeType, } from '../../../shared/constants/preferences'; import { LedgerTransportTypes } from '../../../shared/constants/hardware-wallets'; -export const initialMetamaskState: Omit< +export const initialBackgroundState: Omit< BackgroundStateProxy, keyof MemStoreControllersComposedState > & diff --git a/ui/ducks/index.ts b/ui/ducks/index.ts index deba3123916f..2dbfa8a55d61 100644 --- a/ui/ducks/index.ts +++ b/ui/ducks/index.ts @@ -5,6 +5,7 @@ import localeMessagesReducer from './locale/locale'; import sendReducer from './send/send'; import domainReducer from './domains'; import appStateReducer from './app/app'; +import backgroundReducer from './background/background'; import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck'; import gasReducer from './gas/gas.duck'; import { invalidCustomNetwork, unconnectedAccount } from './alerts'; @@ -19,6 +20,7 @@ export default combineReducers({ [AlertTypes.unconnectedAccount]: unconnectedAccount, activeTab: (s) => (s === undefined ? null : s), metamask: metamaskReducer, + background: backgroundReducer, appState: appStateReducer, DNS: domainReducer, history: historyReducer, diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js new file mode 100644 index 000000000000..20d25fb58129 --- /dev/null +++ b/ui/ducks/metamask/metamask.js @@ -0,0 +1,551 @@ +import { addHexPrefix, isHexString } from 'ethereumjs-util'; +import { createSelector } from 'reselect'; +import { mergeGasFeeEstimates } from '@metamask/transaction-controller'; +import { AlertTypes } from '../../../shared/constants/alerts'; +import { + GasEstimateTypes, + NetworkCongestionThresholds, +} from '../../../shared/constants/gas'; +import { KeyringType } from '../../../shared/constants/keyring'; +import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../shared/constants/preferences'; +import { decGWEIToHexWEI } from '../../../shared/modules/conversion.utils'; +import { stripHexPrefix } from '../../../shared/modules/hexstring-utils'; +import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; +import { + accountsWithSendEtherInfoSelector, + checkNetworkAndAccountSupports1559, + getAddressBook, +} from '../../selectors/selectors'; +import { + getProviderConfig, + getSelectedNetworkClientId, +} from '../../../shared/modules/selectors/networks'; +import { getSelectedInternalAccount } from '../../selectors/accounts'; +import * as actionConstants from '../../store/actionConstants'; +import { updateTransactionGasFees } from '../../store/actions'; +import { setCustomGasLimit, setCustomGasPrice } from '../gas/gas.duck'; + +const initialState = { + isInitialized: false, + isUnlocked: false, + internalAccounts: { accounts: {}, selectedAccount: '' }, + transactions: [], + networkConfigurations: {}, + addressBook: [], + useBlockie: false, + featureFlags: {}, + currentLocale: '', + currentBlockGasLimit: '', + currentBlockGasLimitByChainId: {}, + preferences: { + autoLockTimeLimit: DEFAULT_AUTO_LOCK_TIME_LIMIT, + showExtensionInFullSizeView: false, + showFiatInTestnets: false, + showTestNetworks: false, + smartTransactionsOptInStatus: true, + petnamesEnabled: true, + featureNotificationsEnabled: false, + privacyMode: false, + showMultiRpcModal: false, + }, + firstTimeFlowType: null, + completedOnboarding: false, + knownMethodData: {}, + use4ByteResolution: true, + participateInMetaMetrics: null, + dataCollectionForMarketing: null, + currencyRates: { + ETH: { + conversionRate: null, + }, + }, +}; + +/** + * Temporary types for this slice so that inferrence of MetaMask state tree can + * occur + * + * @param {typeof initialState} state - State + * @param {any} action + * @returns {typeof initialState} + */ +export default function reduceMetamask(state = initialState, action) { + // I don't think we should be spreading initialState into this. Once the + // state tree has begun by way of the first reduce call the initialState is + // set. The only time it should be used again is if we reset the state with a + // deliberate action. However, our tests are *relying upon the initialState + // tree above to be spread into the reducer as a way of hydrating the state + // for this slice*. I attempted to remove this and it caused nearly 40 test + // failures. We are going to refactor this slice anyways, possibly removing + // it so we will fix this issue when that time comes. + const metamaskState = { ...initialState, ...state }; + switch (action.type) { + case actionConstants.UPDATE_METAMASK_STATE: + return { ...metamaskState, ...action.value }; + + case actionConstants.LOCK_METAMASK: + return { + ...metamaskState, + isUnlocked: false, + }; + + case actionConstants.SET_ACCOUNT_LABEL: { + const { account } = action.value; + const name = action.value.label; + const accountToUpdate = Object.values( + metamaskState.internalAccounts.accounts, + ).find((internalAccount) => { + return internalAccount.address.toLowerCase() === account.toLowerCase(); + }); + + const internalAccounts = { + ...metamaskState.internalAccounts, + accounts: { + ...metamaskState.internalAccounts.accounts, + [accountToUpdate.id]: { + ...accountToUpdate, + metadata: { + ...accountToUpdate.metadata, + name, + }, + }, + }, + }; + return Object.assign(metamaskState, { internalAccounts }); + } + + case actionConstants.UPDATE_TRANSACTION_PARAMS: { + const { id: txId, value } = action; + let { transactions } = metamaskState; + transactions = transactions.map((tx) => { + if (tx.id === txId) { + const newTx = { ...tx }; + newTx.txParams = value; + return newTx; + } + return tx; + }); + + return { + ...metamaskState, + transactions, + }; + } + + case actionConstants.SET_PARTICIPATE_IN_METAMETRICS: + return { + ...metamaskState, + participateInMetaMetrics: action.value, + }; + + case actionConstants.SET_DATA_COLLECTION_FOR_MARKETING: + return { + ...metamaskState, + dataCollectionForMarketing: action.value, + }; + + case actionConstants.COMPLETE_ONBOARDING: { + return { + ...metamaskState, + completedOnboarding: true, + }; + } + + case actionConstants.RESET_ONBOARDING: { + return { + ...metamaskState, + isInitialized: false, + completedOnboarding: false, + firstTimeFlowType: null, + isUnlocked: false, + onboardingTabs: {}, + seedPhraseBackedUp: null, + }; + } + + case actionConstants.SET_FIRST_TIME_FLOW_TYPE: { + return { + ...metamaskState, + firstTimeFlowType: action.value, + }; + } + + default: + return metamaskState; + } +} + +const toHexWei = (value, expectHexWei) => { + return addHexPrefix(expectHexWei ? value : decGWEIToHexWEI(value)); +}; + +// Action Creators +export function updateGasFees({ + gasPrice, + gasLimit, + maxPriorityFeePerGas, + maxFeePerGas, + transaction, + expectHexWei = false, +}) { + return async (dispatch) => { + const txParamsCopy = { ...transaction.txParams, gas: gasLimit }; + if (gasPrice) { + dispatch( + setCustomGasPrice(toHexWei(txParamsCopy.gasPrice, expectHexWei)), + ); + txParamsCopy.gasPrice = toHexWei(gasPrice, expectHexWei); + } else if (maxFeePerGas && maxPriorityFeePerGas) { + txParamsCopy.maxFeePerGas = toHexWei(maxFeePerGas, expectHexWei); + txParamsCopy.maxPriorityFeePerGas = addHexPrefix( + decGWEIToHexWEI(maxPriorityFeePerGas), + ); + } + const updatedTx = { + ...transaction, + txParams: txParamsCopy, + }; + + const customGasLimit = isHexString(addHexPrefix(gasLimit)) + ? addHexPrefix(gasLimit) + : addHexPrefix(gasLimit.toString(16)); + dispatch(setCustomGasLimit(customGasLimit)); + await dispatch(updateTransactionGasFees(updatedTx.id, updatedTx)); + }; +} + +// Selectors + +export const getAlertEnabledness = (state) => state.metamask.alertEnabledness; + +export const getUnconnectedAccountAlertEnabledness = (state) => + getAlertEnabledness(state)[AlertTypes.unconnectedAccount]; + +export const getWeb3ShimUsageAlertEnabledness = (state) => + getAlertEnabledness(state)[AlertTypes.web3ShimUsage]; + +export const getUnconnectedAccountAlertShown = (state) => + state.metamask.unconnectedAccountAlertShownOrigins; + +export const getTokens = (state) => state.metamask.tokens; + +export function getNftsDropdownState(state) { + return state.metamask.nftsDropdownState; +} + +export const getNfts = (state) => { + const { + metamask: { allNfts }, + } = state; + const { address: selectedAddress } = getSelectedInternalAccount(state); + + const { chainId } = getProviderConfig(state); + + return allNfts?.[selectedAddress]?.[chainId] ?? []; +}; + +export const getNFTsByChainId = (state, chainId) => { + const { + metamask: { allNfts }, + } = state; + const { address: selectedAddress } = getSelectedInternalAccount(state); + + return allNfts?.[selectedAddress]?.[chainId] ?? []; +}; + +export const getNftContracts = (state) => { + const { + metamask: { allNftContracts }, + } = state; + const { address: selectedAddress } = getSelectedInternalAccount(state); + const { chainId } = getProviderConfig(state); + return allNftContracts?.[selectedAddress]?.[chainId] ?? []; +}; + +export function getBlockGasLimit(state) { + return state.metamask.currentBlockGasLimit; +} + +export function getNativeCurrency(state) { + return getProviderConfig(state).ticker; +} + +export function getConversionRate(state) { + return state.metamask.currencyRates[getProviderConfig(state).ticker] + ?.conversionRate; +} + +export function getCurrencyRates(state) { + return state.metamask.currencyRates; +} + +export function getSendHexDataFeatureFlagState(state) { + return state.metamask.featureFlags.sendHexData; +} + +export function getSendToAccounts(state) { + const fromAccounts = accountsWithSendEtherInfoSelector(state); + const addressBookAccounts = getAddressBook(state); + return [...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) { + const selectedNetworkClientId = getSelectedNetworkClientId(state); + return ( + state.metamask.networksMetadata[selectedNetworkClientId].EIPS[1559] === + false + ); +} + +/** + * Function returns true if network details are fetched and it is found to support EIP-1559 + * + * @param state + * @param networkClientId - The optional network client ID to check for EIP-1559 support. Defaults to the currently selected network. + */ +export function isEIP1559Network(state, networkClientId) { + const selectedNetworkClientId = getSelectedNetworkClientId(state); + + return ( + state.metamask.networksMetadata?.[ + networkClientId ?? selectedNetworkClientId + ]?.EIPS[1559] === true + ); +} + +function getGasFeeControllerEstimateType(state) { + return state.metamask.gasEstimateType; +} + +function getGasFeeControllerEstimateTypeByChainId(state, chainId) { + return state.metamask.gasFeeEstimatesByChainId?.[chainId]?.gasEstimateType; +} + +function getGasFeeControllerEstimates(state) { + return state.metamask.gasFeeEstimates; +} + +function getGasFeeControllerEstimatesByChainId(state, chainId) { + return state.metamask.gasFeeEstimatesByChainId?.[chainId]?.gasFeeEstimates; +} + +function getTransactionGasFeeEstimates(state) { + const transactionMetadata = state.confirmTransaction?.txData; + return transactionMetadata?.gasFeeEstimates; +} + +function getTransactionGasFeeEstimatesByChainId(state, chainId) { + const transactionMetadata = state.confirmTransaction?.txData; + const transactionChainId = transactionMetadata?.chainId; + + if (transactionChainId !== chainId) { + return undefined; + } + + return transactionMetadata?.gasFeeEstimates; +} + +const getTransactionGasFeeEstimateType = createSelector( + getTransactionGasFeeEstimates, + (transactionGasFeeEstimates) => transactionGasFeeEstimates?.type, +); + +const getTransactionGasFeeEstimateTypeByChainId = createSelector( + getTransactionGasFeeEstimatesByChainId, + (transactionGasFeeEstimates) => transactionGasFeeEstimates?.type, +); + +export const getGasEstimateType = createSelector( + getGasFeeControllerEstimateType, + getTransactionGasFeeEstimateType, + (gasFeeControllerEstimateType, transactionGasFeeEstimateType) => { + return transactionGasFeeEstimateType ?? gasFeeControllerEstimateType; + }, +); + +export const getGasEstimateTypeByChainId = createSelector( + getGasFeeControllerEstimateTypeByChainId, + getTransactionGasFeeEstimateTypeByChainId, + (gasFeeControllerEstimateType, transactionGasFeeEstimateType) => { + return transactionGasFeeEstimateType ?? gasFeeControllerEstimateType; + }, +); + +/** + * Returns the balances of imported and detected tokens across all accounts and chains. + * + * @param {*} state + * @returns { import('@metamask/assets-controllers').TokenBalancesControllerState['tokenBalances']} + */ +export function getTokenBalances(state) { + return state.metamask.tokenBalances; +} + +export const getGasFeeEstimatesByChainId = createSelector( + getGasFeeControllerEstimatesByChainId, + getTransactionGasFeeEstimatesByChainId, + (gasFeeControllerEstimates, transactionGasFeeEstimates) => { + if (transactionGasFeeEstimates) { + return mergeGasFeeEstimates({ + gasFeeControllerEstimates, + transactionGasFeeEstimates, + }); + } + + return gasFeeControllerEstimates; + }, +); + +export const getGasFeeEstimates = createSelector( + getGasFeeControllerEstimates, + getTransactionGasFeeEstimates, + (gasFeeControllerEstimates, transactionGasFeeEstimates) => { + if (transactionGasFeeEstimates) { + return mergeGasFeeEstimates({ + gasFeeControllerEstimates, + transactionGasFeeEstimates, + }); + } + + return gasFeeControllerEstimates; + }, +); + +export function getEstimatedGasFeeTimeBounds(state) { + return state.metamask.estimatedGasFeeTimeBounds; +} + +export function getEstimatedGasFeeTimeBoundsByChainId(state, chainId) { + return state.metamask.gasFeeEstimatesByChainId?.[chainId] + ?.estimatedGasFeeTimeBounds; +} + +export function getIsGasEstimatesLoading(state) { + 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 function getIsGasEstimatesLoadingByChainId( + 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, chainId) { + const gasFeeEstimates = getGasFeeEstimatesByChainId(state, chainId); + return gasFeeEstimates?.networkCongestion >= NetworkCongestionThresholds.busy; +} + +export function getCompletedOnboarding(state) { + return state.metamask.completedOnboarding; +} +export function getIsInitialized(state) { + return state.metamask.isInitialized; +} + +export function getIsUnlocked(state) { + return state.metamask.isUnlocked; +} + +export function getSeedPhraseBackedUp(state) { + return state.metamask.seedPhraseBackedUp; +} + +/** + * Given the redux state object and an address, finds a keyring that contains that address, if one exists + * + * @param {object} state - the redux state object + * @param {string} address - the address to search for among the keyring addresses + * @returns {object | undefined} The keyring which contains the passed address, or undefined + */ +export function findKeyringForAddress(state, address) { + const keyring = state.metamask.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 + * + * @param {object} state - the redux state object + * @returns {string} The users preferred ledger transport type. One of 'webhid' on chrome or 'u2f' on firefox + */ +export function getLedgerTransportType(state) { + return state.metamask.ledgerTransportType; +} + +/** + * Given the redux state object and an address, returns a boolean indicating whether the passed address is part of a Ledger keyring + * + * @param {object} state - the redux state object + * @param {string} address - the address to search for among all keyring addresses + * @returns {boolean} true if the passed address is part of a ledger keyring, and false otherwise + */ +export function isAddressLedger(state, address) { + const keyring = findKeyringForAddress(state, address); + + return keyring?.type === KeyringType.ledger; +} + +/** + * Given the redux state object, returns a boolean indicating whether the user has any Ledger accounts added to MetaMask (i.e. Ledger keyrings + * in state) + * + * @param {object} state - the redux state object + * @returns {boolean} true if the user has a Ledger account and false otherwise + */ +export function doesUserHaveALedgerAccount(state) { + return state.metamask.keyrings.some((kr) => { + return kr.type === KeyringType.ledger; + }); +} + +export function getCurrentCurrency(state) { + return state.metamask.currentCurrency; +} diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 1988a32ad78b..ae8648ee6285 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -119,7 +119,7 @@ import { import { ThemeType } from '../../shared/constants/preferences'; import { FirstTimeFlowType } from '../../shared/constants/onboarding'; import { getMethodDataAsync } from '../../shared/lib/four-byte'; -import { BackgroundStateProxy } from '../../shared/types/metamask'; +import { BackgroundStateProxy } from '../../shared/types/background'; import { DecodedTransactionDataResponse } from '../../shared/types/transaction-decode'; import { LastInteractedConfirmationInfo } from '../pages/confirmations/types/confirm'; import { EndTraceRequest } from '../../shared/lib/trace'; diff --git a/ui/store/store.ts b/ui/store/store.ts index 4934a61ff24a..c2d5bb3afde0 100644 --- a/ui/store/store.ts +++ b/ui/store/store.ts @@ -3,7 +3,8 @@ import { configureStore as baseConfigureStore } from '@reduxjs/toolkit'; import devtoolsEnhancer from 'remote-redux-devtools'; import rootReducer from '../ducks'; import { AppSliceState } from '../ducks/app/app'; -import { MetaMaskSliceState } from '../ducks/metamask/metamask'; +import { BackgroundSliceState } from '../ducks/background/background'; +import { BackgroundStateProxy } from '../../shared/types/background'; /** * This interface is temporary and is copied from the message-manager.js file @@ -34,6 +35,31 @@ export type MessagesIndexedById = { type RootReducerReturnType = ReturnType; +type _$toIntersection2 = ( + T extends unknown ? (x: T) => unknown : never +) extends (x: infer X) => void + ? X + : never; + +type _$toIntersection = boolean extends T + ? boolean & _$toIntersection2> + : _$toIntersection2; + +type _$toList = _$toIntersection< + T extends unknown ? (t: T) => T : never +> extends (x: never) => infer X + ? _$toList, [X, ...O]> + : O; + +type FlattenObjectOneLevel< + O extends Record, + TopLevelKeysTuple extends PropertyKey[] = _$toList, + SecondLevelKeysTuple = [], +> = {}; + +type FlattenedBackgroundStateProxy = + FlattenObjectOneLevel; + /** * `ReduxState` overrides incorrectly typed properties of `RootReducerReturnType`, and is only intended to be used as an input for `configureStore`. * The `MetaMaskReduxState` type (derived from the returned output of `configureStore`) is to be used consistently as the single source-of-truth and representation of Redux state shape. @@ -45,7 +71,8 @@ type ReduxState = { activeTab: { origin: string; }; - metamask: MetaMaskSliceState['metamask']; + metamask: FlattenedBackgroundStateProxy; + background: BackgroundSliceState['background']; appState: AppSliceState['appState']; } & Omit;