diff --git a/app/actions/transaction/index.js b/app/actions/transaction/index.js index 030e78570cd..f2ecbae38d1 100644 --- a/app/actions/transaction/index.js +++ b/app/actions/transaction/index.js @@ -108,6 +108,18 @@ export function setTransactionObject(transaction) { }; } +/** + * Sets the current transaction ID only. + * + * @param {object} transactionId - Id of the current transaction. + */ +export function setTransactionId(transactionId) { + return { + type: 'SET_TRANSACTION_ID', + transactionId, + }; +} + /** * Enable selectable tokens (ERC20 and Ether) to send in a transaction * diff --git a/app/components/UI/BrowserBottomBar/__snapshots__/index.test.tsx.snap b/app/components/UI/BrowserBottomBar/__snapshots__/index.test.tsx.snap index 9f9d573a63e..8e98692769f 100644 --- a/app/components/UI/BrowserBottomBar/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/BrowserBottomBar/__snapshots__/index.test.tsx.snap @@ -1,204 +1,28 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`BrowserBottomBar should render correctly 1`] = ` - - - - - - - - - - - - - - - - - - - - + showTabs={[Function]} + showUrlModal={[Function]} + toggleOptions={[Function]} +/> `; diff --git a/app/components/UI/BrowserBottomBar/index.js b/app/components/UI/BrowserBottomBar/index.js index 0f0a5ab0bbe..b005dfe0c15 100644 --- a/app/components/UI/BrowserBottomBar/index.js +++ b/app/components/UI/BrowserBottomBar/index.js @@ -8,7 +8,6 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import SimpleLineIcons from 'react-native-vector-icons/SimpleLineIcons'; import FeatherIcons from 'react-native-vector-icons/Feather'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import Device from '../../../util/device'; import { ThemeContext, mockTheme } from '../../../util/theme'; @@ -21,6 +20,7 @@ import { OPTIONS_BUTTON, SEARCH_BUTTON, } from '../../../../wdio/screen-objects/testIDs/BrowserScreen/BrowserScreen.testIds'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; // NOTE: not needed anymore. The use of BottomTabBar already accomodates the home indicator height // TODO: test on an android device @@ -67,7 +67,7 @@ const createStyles = (colors) => * Browser bottom bar that contains icons for navigation * tab management, url change and other options */ -export default class BrowserBottomBar extends PureComponent { +class BrowserBottomBar extends PureComponent { static propTypes = { /** * Boolean that determines if you can navigate back @@ -101,17 +101,21 @@ export default class BrowserBottomBar extends PureComponent { * Function that toggles the options menu */ toggleOptions: PropTypes.func, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; trackSearchEvent = () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_SEARCH_USED, { + this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_SEARCH_USED, { option_chosen: 'Browser Bottom Bar Menu', number_of_tabs: undefined, }); }; trackNavigationEvent = (navigationOption) => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_NAVIGATION, { + this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_NAVIGATION, { option_chosen: navigationOption, os: Platform.OS, }); @@ -213,3 +217,4 @@ export default class BrowserBottomBar extends PureComponent { } BrowserBottomBar.contextType = ThemeContext; +export default withMetricsAwareness(BrowserBottomBar); diff --git a/app/components/UI/CollectibleContracts/index.js b/app/components/UI/CollectibleContracts/index.js index 5939091cc02..4dbd431d91c 100644 --- a/app/components/UI/CollectibleContracts/index.js +++ b/app/components/UI/CollectibleContracts/index.js @@ -4,7 +4,6 @@ import { TouchableOpacity, StyleSheet, View, - InteractionManager, Image, Platform, FlatList, @@ -15,7 +14,6 @@ import { fontStyles } from '../../../styles/common'; import { strings } from '../../../../locales/i18n'; import Engine from '../../../core/Engine'; import CollectibleContractElement from '../CollectibleContractElement'; -import Analytics from '../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { collectibleContractsSelector, @@ -46,6 +44,7 @@ import { NFT_TAB_CONTAINER_ID, } from '../../../../wdio/screen-objects/testIDs/Screens/WalletView.testIds'; import Logger from '../../../util/Logger'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -110,6 +109,7 @@ const CollectibleContracts = ({ (singleCollectible) => singleCollectible.isCurrentlyOwned === true, ); const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const [isAddNFTEnabled, setIsAddNFTEnabled] = useState(true); const [refreshing, setRefreshing] = useState(false); @@ -274,11 +274,9 @@ const CollectibleContracts = ({ const goToAddCollectible = useCallback(() => { setIsAddNFTEnabled(false); navigation.push('AddAsset', { assetType: 'collectible' }); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.WALLET_ADD_COLLECTIBLES); - setIsAddNFTEnabled(true); - }); - }, [navigation]); + trackEvent(MetaMetricsEvents.WALLET_ADD_COLLECTIBLES); + setIsAddNFTEnabled(true); + }, [navigation, trackEvent]); const renderFooter = useCallback( () => ( diff --git a/app/components/UI/ComponentErrorBoundary/index.js b/app/components/UI/ComponentErrorBoundary/index.js index caac84bec33..d532728cdc3 100644 --- a/app/components/UI/ComponentErrorBoundary/index.js +++ b/app/components/UI/ComponentErrorBoundary/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Logger from '../../../util/Logger'; -import { trackErrorAsAnalytics } from '../../../util/analyticsV2'; +import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics'; class ComponentErrorBoundary extends React.Component { state = { error: null }; diff --git a/app/components/UI/DeleteWalletModal/index.tsx b/app/components/UI/DeleteWalletModal/index.tsx index 8f0672525aa..c67ca098349 100644 --- a/app/components/UI/DeleteWalletModal/index.tsx +++ b/app/components/UI/DeleteWalletModal/index.tsx @@ -23,8 +23,8 @@ import Device from '../../../util/device'; import Routes from '../../../constants/navigation/Routes'; import { DeleteWalletModalSelectorsIDs } from '../../../../e2e/selectors/Modals/DeleteWalletModal.selectors'; import generateTestId from '../../../../wdio/utils/generateTestId'; -import { trackEventV2 as trackEvent } from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const DELETE_KEYWORD = 'delete'; @@ -35,6 +35,7 @@ if (Device.isAndroid() && UIManager.setLayoutAnimationEnabledExperimental) { const DeleteWalletModal = () => { const navigation = useNavigation(); const { colors, themeAppearance } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const modalRef = useRef(null); diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js index a63f0ae3cbe..fb48b8e3c45 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -41,7 +41,6 @@ import Engine from '../../../core/Engine'; import Logger from '../../../util/Logger'; import Device from '../../../util/device'; import ReceiveRequest from '../ReceiveRequest'; -import Analytics from '../../../core/Analytics/Analytics'; import AppConstants from '../../../core/AppConstants'; import { MetaMetricsEvents } from '../../../core/Analytics'; import URL from 'url-parse'; @@ -53,7 +52,6 @@ import DeeplinkManager from '../../../core/DeeplinkManager/SharedDeeplinkManager import SettingsNotification from '../SettingsNotification'; import { RPC } from '../../../constants/network'; import { findRouteNameFromNavigatorState } from '../../../util/general'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { isDefaultAccountName, doENSReverseLookup, @@ -90,6 +88,7 @@ import { import { createAccountSelectorNavDetails } from '../../Views/AccountSelector'; import NetworkInfo from '../NetworkInfo'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -452,6 +451,10 @@ class DrawerView extends PureComponent { * Redux action to close info network modal */ toggleInfoNetworkModal: PropTypes.func, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -546,15 +549,14 @@ class DrawerView extends PureComponent { ) { // eslint-disable-next-line react/no-did-update-set-state this.setState({ showProtectWalletModal: true }); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_VIEWED, - { - wallet_protection_required: false, - source: 'Backup Alert', - }, - ); - }); + + this.props.metrics.trackEvent( + MetaMetricsEvents.WALLET_SECURITY_PROTECT_VIEWED, + { + wallet_protection_required: false, + source: 'Backup Alert', + }, + ); } else { // eslint-disable-next-line react/no-did-update-set-state this.setState({ showProtectWalletModal: false }); @@ -624,15 +626,13 @@ class DrawerView extends PureComponent { }; trackEvent = (event) => { - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(event); - }); + this.props.metrics.trackEvent(event); }; // NOTE: do we need this event? trackOpenBrowserEvent = () => { const { providerConfig } = this.props; - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_OPENED, { + this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_OPENED, { source: 'In-app Navigation', chain_id: getDecimalChainId(providerConfig.chainId), }); @@ -928,7 +928,7 @@ class DrawerView extends PureComponent { this.props.passwordSet ? { screen: 'AccountBackupStep1' } : undefined, ); InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED, { wallet_protection_required: true, @@ -1273,4 +1273,7 @@ const mapDispatchToProps = (dispatch) => ({ DrawerView.contextType = ThemeContext; -export default connect(mapStateToProps, mapDispatchToProps)(DrawerView); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(DrawerView)); diff --git a/app/components/UI/EditGasFee1559/index.js b/app/components/UI/EditGasFee1559/index.js index 430f213e352..048ac447ef8 100644 --- a/app/components/UI/EditGasFee1559/index.js +++ b/app/components/UI/EditGasFee1559/index.js @@ -23,7 +23,6 @@ import PropTypes from 'prop-types'; import BigNumber from 'bignumber.js'; import FadeAnimationView from '../FadeAnimationView'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import TimeEstimateInfoModal from '../TimeEstimateInfoModal'; import useModalHandler from '../../Base/hooks/useModalHandler'; @@ -35,6 +34,7 @@ import { GAS_LIMIT_MIN, GAS_PRICE_MIN as GAS_MIN, } from '../../../util/gasUtils'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -199,6 +199,8 @@ const EditGasFee1559 = ({ hideTimeEstimateInfoModal, ] = useModalHandler(false); const { colors } = useTheme(); + const { trackEvent } = useMetrics(); + const styles = createStyles(colors); const getAnalyticsParams = useCallback(() => { @@ -217,26 +219,23 @@ const EditGasFee1559 = ({ const toggleAdvancedOptions = useCallback(() => { if (!showAdvancedOptions) { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.GAS_ADVANCED_OPTIONS_CLICKED, getAnalyticsParams(), ); } setShowAdvancedOptions((showAdvancedOptions) => !showAdvancedOptions); - }, [getAnalyticsParams, showAdvancedOptions]); + }, [getAnalyticsParams, showAdvancedOptions, trackEvent]); const toggleLearnMoreModal = useCallback(() => { setShowLearnMoreModal((showLearnMoreModal) => !showLearnMoreModal); }, []); const save = useCallback(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.GAS_FEE_CHANGED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.GAS_FEE_CHANGED, getAnalyticsParams()); onSave(selectedOption); - }, [getAnalyticsParams, onSave, selectedOption]); + }, [getAnalyticsParams, onSave, selectedOption, trackEvent]); const changeGas = useCallback( (gas, selectedOption) => { diff --git a/app/components/UI/EditGasFeeLegacy/index.js b/app/components/UI/EditGasFeeLegacy/index.js index d1529909f31..4bfcdb2923c 100644 --- a/app/components/UI/EditGasFeeLegacy/index.js +++ b/app/components/UI/EditGasFeeLegacy/index.js @@ -23,7 +23,6 @@ import Device from '../../../util/device'; import { getDecimalChainId, isMainnetByChainId } from '../../../util/networks'; import FadeAnimationView from '../FadeAnimationView'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import AppConstants from '../../../core/AppConstants'; import { useTheme } from '../../../util/theme'; @@ -33,6 +32,7 @@ import { GAS_LIMIT_MIN, GAS_PRICE_MIN, } from '../../../util/gasUtils'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -148,6 +148,7 @@ const EditGasFeeLegacy = ({ const [selectedOption, setSelectedOption] = useState(selected); const [gasPriceError, setGasPriceError] = useState(); const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const getAnalyticsParams = useCallback(() => { @@ -166,22 +167,19 @@ const EditGasFeeLegacy = ({ const toggleAdvancedOptions = useCallback(() => { if (!showAdvancedOptions) { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.GAS_ADVANCED_OPTIONS_CLICKED, getAnalyticsParams(), ); } setShowAdvancedOptions((showAdvancedOptions) => !showAdvancedOptions); - }, [getAnalyticsParams, showAdvancedOptions]); + }, [getAnalyticsParams, showAdvancedOptions, trackEvent]); const save = useCallback(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.GAS_FEE_CHANGED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.GAS_FEE_CHANGED, getAnalyticsParams()); onSave(selectedOption); - }, [getAnalyticsParams, onSave, selectedOption]); + }, [getAnalyticsParams, onSave, selectedOption, trackEvent]); const changeGas = useCallback( (gas, selectedOption) => { diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index 12ef5230bb4..ebc6d36f840 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -99,6 +99,7 @@ import { import { useMetrics } from '../../../components/hooks/useMetrics'; import { addTransaction } from '../../../util/transaction-controller'; import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics'; +import { selectGasFeeEstimates } from '../../../selectors/confirmTransaction'; const POLLING_INTERVAL = 30000; const SLIPPAGE_BUCKETS = { @@ -2325,8 +2326,7 @@ const mapStateToProps = (state) => ({ state.engine.backgroundState.SwapsController.quoteRefreshSeconds, gasEstimateType: state.engine.backgroundState.GasFeeController.gasEstimateType, - gasFeeEstimates: - state.engine.backgroundState.GasFeeController.gasFeeEstimates, + gasFeeEstimates: selectGasFeeEstimates(state), usedGasEstimate: state.engine.backgroundState.SwapsController.usedGasEstimate, usedCustomGas: state.engine.backgroundState.SwapsController.usedCustomGas, primaryCurrency: state.settings.primaryCurrency, diff --git a/app/components/UI/Transactions/index.js b/app/components/UI/Transactions/index.js index d4834c565a4..19d25f14e24 100644 --- a/app/components/UI/Transactions/index.js +++ b/app/components/UI/Transactions/index.js @@ -73,6 +73,7 @@ import { speedUpTransaction, updateIncomingTransactions, } from '../../../util/transaction-controller'; +import { selectGasFeeEstimates } from '../../../selectors/confirmTransaction'; const createStyles = (colors, typography) => StyleSheet.create({ @@ -867,8 +868,7 @@ const mapStateToProps = (state) => ({ selectedAddress: selectSelectedAddress(state), networkConfigurations: selectNetworkConfigurations(state), providerConfig: selectProviderConfig(state), - gasFeeEstimates: - state.engine.backgroundState.GasFeeController.gasFeeEstimates, + gasFeeEstimates: selectGasFeeEstimates(state), primaryCurrency: state.settings.primaryCurrency, tokens: selectTokensByAddress(state), gasEstimateType: diff --git a/app/components/Views/AccountActions/AccountActions.tsx b/app/components/Views/AccountActions/AccountActions.tsx index 8469f1653ef..6a083f36cac 100644 --- a/app/components/Views/AccountActions/AccountActions.tsx +++ b/app/components/Views/AccountActions/AccountActions.tsx @@ -20,7 +20,7 @@ import { getEtherscanAddressUrl, getEtherscanBaseUrl, } from '../../../util/etherscan'; -import { Analytics, MetaMetricsEvents } from '../../../core/Analytics'; +import { MetaMetricsEvents } from '../../../core/Analytics'; import { RPC } from '../../../constants/network'; import { selectNetworkConfigurations, @@ -33,7 +33,6 @@ import { strings } from '../../../../locales/i18n'; import styleSheet from './AccountActions.styles'; import Logger from '../../../util/Logger'; import { protectWalletModalVisible } from '../../../actions/user'; -import AnalyticsV2 from '../../../util/analyticsV2'; import Routes from '../../../constants/navigation/Routes'; import generateTestId from '../../../../wdio/utils/generateTestId'; import { @@ -42,12 +41,14 @@ import { SHOW_PRIVATE_KEY, VIEW_ETHERSCAN, } from './AccountActions.constants'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const AccountActions = () => { const { styles } = useStyles(styleSheet, {}); const sheetRef = useRef(null); const { navigate } = useNavigation(); const dispatch = useDispatch(); + const { trackEvent } = useMetrics(); const providerConfig = useSelector(selectProviderConfig); @@ -94,7 +95,7 @@ const AccountActions = () => { goToBrowserUrl(url, etherscan_url); } - Analytics.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_VIEW_ETHERSCAN); + trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_VIEW_ETHERSCAN); }); }; @@ -110,18 +111,13 @@ const AccountActions = () => { Logger.log('Error while trying to share address', err); }); - Analytics.trackEvent( - MetaMetricsEvents.NAVIGATION_TAPS_SHARE_PUBLIC_ADDRESS, - ); + trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SHARE_PUBLIC_ADDRESS); }); }; const goToExportPrivateKey = () => { sheetRef.current?.onCloseBottomSheet(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.REVEAL_PRIVATE_KEY_INITIATED, - {}, - ); + trackEvent(MetaMetricsEvents.REVEAL_PRIVATE_KEY_INITIATED); navigate(Routes.SETTINGS.REVEAL_PRIVATE_CREDENTIAL, { credentialName: 'private_key', diff --git a/app/components/Views/AccountBackupStep1/index.js b/app/components/Views/AccountBackupStep1/index.js index 99804c6ab18..0c492426f48 100644 --- a/app/components/Views/AccountBackupStep1/index.js +++ b/app/components/Views/AccountBackupStep1/index.js @@ -29,9 +29,8 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; import DefaultPreference from 'react-native-default-preference'; import { useTheme } from '../../../util/theme'; -import trackAfterInteractions from '../../../util/metrics/TrackAfterInteraction/trackAfterInteractions'; -import Logger from '../../../util/Logger'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const createStyles = (colors) => StyleSheet.create({ mainWrapper: { @@ -128,9 +127,7 @@ const AccountBackupStep1 = (props) => { const styles = createStyles(colors); const track = (event, properties) => { - trackAfterInteractions(event, properties).catch(() => { - Logger.log('AccountBackupStep1', `Failed to track ${event}`); - }); + trackOnboarding(event, properties); }; useEffect(() => { diff --git a/app/components/Views/AccountBackupStep1B/index.js b/app/components/Views/AccountBackupStep1B/index.js index 61dbbab92dd..7693a2881d4 100644 --- a/app/components/Views/AccountBackupStep1B/index.js +++ b/app/components/Views/AccountBackupStep1B/index.js @@ -24,9 +24,8 @@ import { CHOOSE_PASSWORD_STEPS } from '../../../constants/onboarding'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { useTheme } from '../../../util/theme'; -import trackAfterInteractions from '../../../util/metrics/TrackAfterInteraction/trackAfterInteractions'; -import Logger from '../../../util/Logger'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const explain_backup_seedphrase = require('../../../images/explain-backup-seedphrase.png'); // eslint-disable-line @@ -207,9 +206,7 @@ const AccountBackupStep1B = (props) => { const styles = createStyles(colors); const track = (event, properties) => { - trackAfterInteractions(event, properties).catch(() => { - Logger.log('AccountBackupStep1B', `Failed to track ${event}`); - }); + trackOnboarding(event, properties); }; useEffect(() => { diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index c99a0f926a5..4990423e22a 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -18,7 +18,6 @@ import BottomSheet, { import UntypedEngine from '../../../core/Engine'; import { isDefaultAccountName } from '../../../util/ENSUtils'; import Logger from '../../../util/Logger'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { SelectedAccount } from '../../../components/UI/AccountSelectorList/AccountSelectorList.types'; import { @@ -54,13 +53,15 @@ import AccountConnectSingleSelector from './AccountConnectSingleSelector'; import AccountConnectMultiSelector from './AccountConnectMultiSelector'; import useFavicon from '../../hooks/useFavicon/useFavicon'; import URLParse from 'url-parse'; -import { trackDappVisitedEvent } from '../../../analytics'; +import { trackDappVisitedEvent } from '../../../util/metrics'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const AccountConnect = (props: AccountConnectProps) => { const Engine = UntypedEngine as any; const { hostInfo, permissionRequestId } = props.route.params; const [isLoading, setIsLoading] = useState(false); const navigation = useNavigation(); + const { trackEvent } = useMetrics(); const selectedWalletAddress = useSelector(selectSelectedAddress); const [selectedAddresses, setSelectedAddresses] = useState([ selectedWalletAddress, @@ -117,12 +118,12 @@ const AccountConnect = (props: AccountConnectProps) => { (requestId) => { Engine.context.PermissionController.rejectPermissionsRequest(requestId); - AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_REQUEST_CANCELLED, { + trackEvent(MetaMetricsEvents.CONNECT_REQUEST_CANCELLED, { number_of_accounts: accountsLength, source: 'permission system', }); }, - [Engine.context.PermissionController, accountsLength], + [Engine.context.PermissionController, accountsLength, trackEvent], ); const triggerDappVisitedEvent = useCallback( @@ -160,7 +161,7 @@ const AccountConnect = (props: AccountConnectProps) => { triggerDappVisitedEvent(connectedAccountLength); - AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_REQUEST_COMPLETED, { + trackEvent(MetaMetricsEvents.CONNECT_REQUEST_COMPLETED, { number_of_accounts: accountsLength, number_of_accounts_connected: connectedAccountLength, account_type: getAddressAccountType(activeAddress), @@ -204,6 +205,7 @@ const AccountConnect = (props: AccountConnectProps) => { toastRef, accountsLength, triggerDappVisitedEvent, + trackEvent, ]); const handleCreateAccount = useCallback( @@ -216,17 +218,14 @@ const AccountConnect = (props: AccountConnectProps) => { addedAccountAddress, ) as string; !isMultiSelect && setSelectedAddresses([checksummedAddress]); - AnalyticsV2.trackEvent( - MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, - {}, - ); + trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT); } catch (e: any) { Logger.error(e, 'error while trying to add a new account'); } finally { setIsLoading(false); } }, - [Engine.context], + [Engine.context, trackEvent], ); const hideSheet = (callback?: () => void) => @@ -266,16 +265,13 @@ const AccountConnect = (props: AccountConnectProps) => { case USER_INTENT.Import: { navigation.navigate('ImportPrivateKeyView'); // TODO: Confirm if this is where we want to track importing an account or within ImportPrivateKeyView screen. - AnalyticsV2.trackEvent( - MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, - {}, - ); + trackEvent(MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT); break; } case USER_INTENT.ConnectHW: { navigation.navigate('ConnectQRHardwareFlow'); // TODO: Confirm if this is where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen. - AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET, {}); + trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET); break; } @@ -293,6 +289,7 @@ const AccountConnect = (props: AccountConnectProps) => { permissionRequestId, handleCreateAccount, handleConnect, + trackEvent, ]); const handleSheetDismiss = () => { diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index f98ff3e13fa..75e131a49c5 100755 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -27,7 +27,6 @@ import { ToastVariants, } from '../../../component-library/components/Toast'; import { ToastOptions } from '../../../component-library/components/Toast/Toast.types'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { useAccounts, Account } from '../../hooks/useAccounts'; import getAccountNameWithENS from '../../../util/accounts'; @@ -50,9 +49,11 @@ import AccountPermissionsRevoke from './AccountPermissionsRevoke'; import { USER_INTENT } from '../../../constants/permissions'; import useFavicon from '../../hooks/useFavicon/useFavicon'; import URLParse from 'url-parse'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const AccountPermissions = (props: AccountPermissionsProps) => { const navigation = useNavigation(); + const { trackEvent } = useMetrics(); const Engine = UntypedEngine as any; const { hostInfo: { @@ -170,11 +171,8 @@ const AccountPermissions = (props: AccountPermissionsProps) => { try { setIsLoading(true); await KeyringController.addNewAccount(); - AnalyticsV2.trackEvent( - MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, - {}, - ); - AnalyticsV2.trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { + trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT); + trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { source: metricsSource, number_of_accounts: accounts?.length, }); @@ -226,7 +224,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { const totalAccounts = accountsLength; // TODO: confirm this value is the newly added accounts or total connected accounts const connectedAccounts = connectedAccountLength; - AnalyticsV2.trackEvent(MetaMetricsEvents.ADD_ACCOUNT_DAPP_PERMISSIONS, { + trackEvent(MetaMetricsEvents.ADD_ACCOUNT_DAPP_PERMISSIONS, { number_of_accounts: totalAccounts, number_of_accounts_connected: connectedAccounts, number_of_networks: nonTestnetNetworks, @@ -246,6 +244,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { accountAvatarType, accountsLength, nonTestnetNetworks, + trackEvent, ]); useEffect(() => { @@ -256,7 +255,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { case USER_INTENT.Confirm: { handleConnect(); hideSheet(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { + trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { source: metricsSource, number_of_accounts: accounts?.length, }); @@ -275,17 +274,14 @@ const AccountPermissions = (props: AccountPermissionsProps) => { case USER_INTENT.Import: { navigation.navigate('ImportPrivateKeyView'); // Is this where we want to track importing an account or within ImportPrivateKeyView screen? - AnalyticsV2.trackEvent( - MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, - {}, - ); + trackEvent(MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT); break; } case USER_INTENT.ConnectHW: { navigation.navigate('ConnectQRHardwareFlow'); // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? - AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET, {}); + trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET); break; } @@ -303,6 +299,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { handleCreateAccount, handleConnect, accounts?.length, + trackEvent, ]); const renderConnectedScreen = useCallback( diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx index a727f51378e..ca2112a893c 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -24,7 +24,6 @@ import { ToastVariants, } from '../../../../component-library/components/Toast'; import getAccountNameWithENS from '../../../../util/accounts'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import Routes from '../../../../constants/navigation/Routes'; import { selectProviderConfig } from '../../../../selectors/networkController'; @@ -33,6 +32,7 @@ import { ConnectedAccountsSelectorsIDs } from '../../../../../e2e/selectors/Moda // Internal dependencies. import { AccountPermissionsConnectedProps } from './AccountPermissionsConnected.types'; import styles from './AccountPermissionsConnected.styles'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const AccountPermissionsConnected = ({ ensByAccountAddress, @@ -49,6 +49,7 @@ const AccountPermissionsConnected = ({ urlWithProtocol, }: AccountPermissionsConnectedProps) => { const { navigate } = useNavigation(); + const { trackEvent } = useMetrics(); const providerConfig: ProviderConfig = useSelector(selectProviderConfig); @@ -112,10 +113,10 @@ const AccountPermissionsConnected = ({ screen: Routes.SHEET.NETWORK_SELECTOR, }); - AnalyticsV2.trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { + trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { chain_id: getDecimalChainId(providerConfig.chainId), }); - }, [providerConfig.chainId, navigate]); + }, [providerConfig.chainId, navigate, trackEvent]); const renderSheetAction = useCallback( () => ( diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx index 21261f0f44b..1a7bad87f57 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx @@ -25,7 +25,6 @@ import { ToastOptions } from '../../../../component-library/components/Toast/Toa import { AccountPermissionsScreens } from '../AccountPermissions.types'; import getAccountNameWithENS from '../../../../util/accounts'; import { MetaMetricsEvents } from '../../../../core/Analytics'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { selectAccountsLength } from '../../../../selectors/accountTrackerController'; // Internal dependencies. @@ -37,6 +36,7 @@ import Avatar from '../../../../component-library/components/Avatars/Avatar/Avat import { AvatarVariant } from '../../../../component-library/components/Avatars/Avatar'; import { selectNetworkConfigurations } from '../../../../selectors/networkController'; import { ConnectedAccountsSelectorsIDs } from '../../../../../e2e/selectors/Modals/ConnectedAccountModal.selectors'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const AccountPermissionsRevoke = ({ ensByAccountAddress, @@ -52,6 +52,7 @@ const AccountPermissionsRevoke = ({ }: AccountPermissionsRevokeProps) => { const Engine = UntypedEngine as any; const { styles } = useStyles(styleSheet, {}); + const { trackEvent } = useMetrics(); const activeAddress = permittedAddresses[0]; const { toastRef } = useContext(ToastContext); @@ -67,20 +68,17 @@ const AccountPermissionsRevoke = ({ await Engine.context.PermissionController.revokeAllPermissions( hostname, ); - AnalyticsV2.trackEvent( - MetaMetricsEvents.REVOKE_ACCOUNT_DAPP_PERMISSIONS, - { - number_of_accounts: accountsLength, - number_of_accounts_connected: permittedAddresses.length, - number_of_networks: nonTestnetNetworks, - }, - ); + trackEvent(MetaMetricsEvents.REVOKE_ACCOUNT_DAPP_PERMISSIONS, { + number_of_accounts: accountsLength, + number_of_accounts_connected: permittedAddresses.length, + number_of_networks: nonTestnetNetworks, + }); } catch (e) { Logger.log(`Failed to revoke all accounts for ${hostname}`, e); } }, /* eslint-disable-next-line */ - [hostname], + [hostname, trackEvent], ); const renderSheetAction = useCallback( @@ -178,14 +176,11 @@ const AccountPermissionsRevoke = ({ labelOptions, }); } - AnalyticsV2.trackEvent( - MetaMetricsEvents.REVOKE_ACCOUNT_DAPP_PERMISSIONS, - { - number_of_accounts: accountsLength, - number_of_accounts_connected: permittedAddresses.length, - number_of_networks: nonTestnetNetworks, - }, - ); + trackEvent(MetaMetricsEvents.REVOKE_ACCOUNT_DAPP_PERMISSIONS, { + number_of_accounts: accountsLength, + number_of_accounts_connected: permittedAddresses.length, + number_of_networks: nonTestnetNetworks, + }); } }} label={strings('accounts.disconnect')} diff --git a/app/components/Views/AccountSelector/AccountSelector.tsx b/app/components/Views/AccountSelector/AccountSelector.tsx index 26ee3bdccbd..c911bdcc759 100644 --- a/app/components/Views/AccountSelector/AccountSelector.tsx +++ b/app/components/Views/AccountSelector/AccountSelector.tsx @@ -6,7 +6,7 @@ import React, { useRef, useState, } from 'react'; -import { InteractionManager, Platform, View } from 'react-native'; +import { Platform, View } from 'react-native'; // External dependencies. import AccountSelectorList from '../../UI/AccountSelectorList'; @@ -15,7 +15,6 @@ import BottomSheet, { } from '../../../component-library/components/BottomSheets/BottomSheet'; import SheetHeader from '../../../component-library/components/Sheet/SheetHeader'; import UntypedEngine from '../../../core/Engine'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { strings } from '../../../../locales/i18n'; import { useAccounts } from '../../hooks/useAccounts'; @@ -40,9 +39,11 @@ import styles from './AccountSelector.styles'; import { useDispatch, useSelector } from 'react-redux'; import { setReloadAccounts } from '../../../actions/accounts'; import { RootState } from '../../../reducers'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const AccountSelector = ({ route }: AccountSelectorProps) => { const dispatch = useDispatch(); + const { trackEvent } = useMetrics(); const { onSelectAccount, checkBalanceError } = route.params || {}; const { reloadAccounts } = useSelector((state: RootState) => state.accounts); @@ -68,15 +69,14 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { PreferencesController.setSelectedAddress(address); sheetRef.current?.onCloseBottomSheet(); onSelectAccount?.(address); - InteractionManager.runAfterInteractions(() => { - // Track Event: "Switched Account" - AnalyticsV2.trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { - source: 'Wallet Tab', - number_of_accounts: accounts?.length, - }); + + // Track Event: "Switched Account" + trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { + source: 'Wallet Tab', + number_of_accounts: accounts?.length, }); }, - [Engine.context, accounts?.length, onSelectAccount], + [Engine.context, accounts?.length, onSelectAccount, trackEvent], ); const onRemoveImportedAccount = useCallback( diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js index 31ce6f0a665..777a69d6abe 100644 --- a/app/components/Views/ActivityView/index.js +++ b/app/components/Views/ActivityView/index.js @@ -12,10 +12,10 @@ import RampOrdersList from '../../UI/Ramp/Views/OrdersList'; import ErrorBoundary from '../ErrorBoundary'; import { useTheme } from '../../../util/theme'; import Routes from '../../../constants/navigation/Routes'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { selectAccountsByChainId } from '../../../selectors/accountTrackerController'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const styles = StyleSheet.create({ wrapper: { @@ -25,6 +25,7 @@ const styles = StyleSheet.create({ const ActivityView = () => { const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const navigation = useNavigation(); const selectedAddress = useSelector(selectSelectedAddress); const hasOrders = useSelector((state) => getHasOrders(state) || false); @@ -35,11 +36,11 @@ const ActivityView = () => { screen: Routes.SHEET.ACCOUNT_SELECTOR, }); // Track Event: "Opened Acount Switcher" - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, { + trackEvent(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, { number_of_accounts: Object.keys(accountsByChainId[selectedAddress] ?? {}) .length, }); - }, [navigation, accountsByChainId, selectedAddress]); + }, [navigation, accountsByChainId, selectedAddress, trackEvent]); useEffect( () => { diff --git a/app/components/Views/AddAccountActions/AddAccountActions.tsx b/app/components/Views/AddAccountActions/AddAccountActions.tsx index 0dca0e554ad..d64547b9861 100644 --- a/app/components/Views/AddAccountActions/AddAccountActions.tsx +++ b/app/components/Views/AddAccountActions/AddAccountActions.tsx @@ -8,7 +8,6 @@ import SheetHeader from '../../../component-library/components/Sheet/SheetHeader import AccountAction from '../AccountAction/AccountAction'; import { IconName } from '../../../component-library/components/Icons/Icon'; import { strings } from '../../../../locales/i18n'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import Logger from '../../../util/Logger'; import Engine from '../../../core/Engine'; @@ -17,22 +16,24 @@ import Engine from '../../../core/Engine'; import { AddAccountActionsProps } from './AddAccountActions.types'; import { AddAccountModalSelectorsIDs } from '../../../../e2e/selectors/Modals/AddAccountModal.selectors'; import Routes from '../../../constants/navigation/Routes'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const AddAccountActions = ({ onBack }: AddAccountActionsProps) => { const { navigate } = useNavigation(); + const { trackEvent } = useMetrics(); const [isLoading, setIsLoading] = useState(false); const openImportAccount = useCallback(() => { navigate('ImportPrivateKeyView'); onBack(); - AnalyticsV2.trackEvent(MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, {}); - }, [navigate, onBack]); + trackEvent(MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, {}); + }, [navigate, onBack, trackEvent]); const openConnectHardwareWallet = useCallback(() => { navigate(Routes.HW.CONNECT); onBack(); - AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET, {}); - }, [onBack, navigate]); + trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET, {}); + }, [onBack, navigate, trackEvent]); const createNewAccount = useCallback(async () => { const { KeyringController, PreferencesController } = Engine.context; @@ -41,7 +42,7 @@ const AddAccountActions = ({ onBack }: AddAccountActionsProps) => { const { addedAccountAddress } = await KeyringController.addNewAccount(); PreferencesController.setSelectedAddress(addedAccountAddress); - AnalyticsV2.trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, {}); + trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, {}); } catch (e: any) { Logger.error(e, 'error while trying to add a new account'); } finally { @@ -49,7 +50,7 @@ const AddAccountActions = ({ onBack }: AddAccountActionsProps) => { setIsLoading(false); } - }, [onBack, setIsLoading]); + }, [onBack, setIsLoading, trackEvent]); return ( diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js index b68c7a1f7bf..fa5ba2ecc57 100644 --- a/app/components/Views/Asset/index.js +++ b/app/components/Views/Asset/index.js @@ -22,7 +22,6 @@ import { TX_UNAPPROVED, } from '../../../constants/transaction'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import Analytics from '../../../core/Analytics/Analytics'; import AppConstants from '../../../core/AppConstants'; import { swapsLivenessSelector, @@ -65,6 +64,7 @@ import { TOKEN_OVERVIEW_SWAP_BUTTON, } from '../../../../wdio/screen-objects/testIDs/Screens/TokenOverviewScreen.testIds'; import { updateIncomingTransactions } from '../../../util/transaction-controller'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -176,6 +176,10 @@ class Asset extends PureComponent { * Boolean that indicates if native token is supported to buy */ isNetworkBuyNativeTokenSupported: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -474,15 +478,11 @@ class Asset extends PureComponent { const onBuy = () => { navigation.navigate(Routes.RAMP.BUY); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters( - MetaMetricsEvents.BUY_BUTTON_CLICKED, - { - text: 'Buy', - location: 'Token Screen', - chain_id_destination: chainId, - }, - ); + + this.props.metrics.trackEvent(MetaMetricsEvents.BUY_BUTTON_CLICKED, { + text: 'Buy', + location: 'Token Screen', + chain_id_destination: chainId, }); }; @@ -595,4 +595,4 @@ const mapStateToProps = (state) => ({ ), }); -export default connect(mapStateToProps)(Asset); +export default connect(mapStateToProps)(withMetricsAwareness(Asset)); diff --git a/app/components/Views/AssetDetails/index.tsx b/app/components/Views/AssetDetails/index.tsx index 58a233599da..5af5ba85310 100644 --- a/app/components/Views/AssetDetails/index.tsx +++ b/app/components/Views/AssetDetails/index.tsx @@ -30,7 +30,6 @@ import { import WarningMessage from '../confirmations/SendFlow/WarningMessage'; import { useTheme } from '../../../util/theme'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import Routes from '../../../constants/navigation/Routes'; import { selectChainId, @@ -43,6 +42,7 @@ import { import { selectTokens } from '../../../selectors/tokensController'; import { selectContractExchangeRates } from '../../../selectors/tokenRatesController'; import { selectContractBalances } from '../../../selectors/tokenBalancesController'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors: any) => StyleSheet.create({ @@ -104,6 +104,7 @@ interface Props { const AssetDetails = (props: Props) => { const { address } = props.route.params; const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const navigation = useNavigation(); const dispatch = useDispatch(); @@ -178,7 +179,7 @@ const AssetDetails = (props: Props) => { tokenSymbol: symbol, }), }); - AnalyticsV2.trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { + trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { location: 'token_details', token_standard: 'ERC20', asset_type: 'token', diff --git a/app/components/Views/Browser/index.js b/app/components/Views/Browser/index.js index 6420d4d4b1e..499b65840c3 100644 --- a/app/components/Views/Browser/index.js +++ b/app/components/Views/Browser/index.js @@ -18,7 +18,6 @@ import BrowserTab from '../BrowserTab'; import AppConstants from '../../../core/AppConstants'; import { baseStyles } from '../../../styles/common'; import { useTheme } from '../../../util/theme'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { getPermittedAccounts, @@ -39,6 +38,7 @@ import { selectAccountsLength } from '../../../selectors/accountTrackerControlle import URL from 'url-parse'; import { isEqual } from 'lodash'; import { selectNetworkConfigurations } from '../../../selectors/networkController'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const margin = 16; const THUMB_WIDTH = Dimensions.get('window').width / 2 - margin * 2; @@ -63,6 +63,7 @@ const Browser = (props) => { } = props; const previousTabs = useRef(null); const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const { toastRef } = useContext(ToastContext); const browserUrl = props.route?.params?.url; const prevSiteHostname = useRef(browserUrl); @@ -92,7 +93,7 @@ const Browser = (props) => { }, isEqual); const handleRightTopButtonAnalyticsEvent = () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.OPEN_DAPP_PERMISSIONS, { + trackEvent(MetaMetricsEvents.OPEN_DAPP_PERMISSIONS, { number_of_accounts: accountsLength, number_of_accounts_connected: permittedAccountsList.length, number_of_networks: nonTestnetNetworks, @@ -131,7 +132,7 @@ const Browser = (props) => { }; const switchToTab = (tab) => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_SWITCH_TAB, {}); + trackEvent(MetaMetricsEvents.BROWSER_SWITCH_TAB, {}); setActiveTab(tab.id); hideTabsAndUpdateUrl(tab.url); updateTabInfo(tab.url, tab.id); diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index cea53ff6d75..539a36ccdae 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -7,7 +7,6 @@ import { Alert, Linking, BackHandler, - InteractionManager, Platform, } from 'react-native'; import { isEqual } from 'lodash'; @@ -49,9 +48,7 @@ import { addToHistory, addToWhitelist } from '../../../actions/browser'; import Device from '../../../util/device'; import AppConstants from '../../../core/AppConstants'; import SearchApi from 'react-native-search-api'; -import Analytics from '../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2, { trackErrorAsAnalytics } from '../../../util/analyticsV2'; import setOnboardingWizardStep from '../../../actions/wizard'; import OnboardingWizard from '../../UI/OnboardingWizard'; import DrawerStatusTracker from '../../../core/DrawerStatusTracker'; @@ -101,7 +98,9 @@ import { TextVariant } from '../../../component-library/components/Texts/Text'; import { regex } from '../../../../app/util/regex'; import { selectChainId } from '../../../selectors/networkController'; import { BrowserViewSelectorsIDs } from '../../../../e2e/selectors/BrowserView.selectors'; -import { trackDappVisitedEvent } from '../../../analytics'; +import { useMetrics } from '../../../components/hooks/useMetrics'; +import trackDappVisitedEvent from '../../../util/metrics/trackDappVisited'; +import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics'; const { HOMEPAGE_URL, NOTIFICATION_NAMES } = AppConstants; const HOMEPAGE_HOST = new URL(HOMEPAGE_URL)?.hostname; @@ -290,7 +289,7 @@ export const BrowserTab = (props) => { const { colors, shadows } = useTheme(); const styles = createStyles(colors, shadows); const favicon = useFavicon(url.current); - + const { trackEvent, isEnabled, getMetaMetricsId } = useMetrics(); /** * Is the current tab the active tab */ @@ -386,10 +385,9 @@ export const BrowserTab = (props) => { const toggleOptions = useCallback(() => { dismissTextSelectionIfNeeded(); setShowOptions(!showOptions); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.DAPP_BROWSER_OPTIONS); - }); - }, [dismissTextSelectionIfNeeded, showOptions]); + + trackEvent(MetaMetricsEvents.DAPP_BROWSER_OPTIONS); + }, [dismissTextSelectionIfNeeded, showOptions, trackEvent]); /** * Show the options menu @@ -713,8 +711,8 @@ export const BrowserTab = (props) => { */ const injectHomePageScripts = async (bookmarks) => { const { current } = webviewRef; - const analyticsEnabled = Analytics.checkEnabled(); - const disctinctId = await Analytics.getDistinctId(); + const analyticsEnabled = isEnabled(); + const disctinctId = await getMetaMetricsId(); const homepageScripts = ` window.__mmFavorites = ${JSON.stringify(bookmarks || props.bookmarks)}; window.__mmSearchEngine = "${props.searchEngine}"; @@ -842,11 +840,11 @@ export const BrowserTab = (props) => { ); const trackEventSearchUsed = useCallback(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_SEARCH_USED, { + trackEvent(MetaMetricsEvents.BROWSER_SEARCH_USED, { option_chosen: 'Search on URL', number_of_tabs: undefined, }); - }, []); + }, [trackEvent]); /** * Function that allows custom handling of any web view requests. @@ -963,7 +961,7 @@ export const BrowserTab = (props) => { toggleOptionsIfNeeded(); if (url.current === HOMEPAGE_URL) return reload(); await go(HOMEPAGE_URL); - Analytics.trackEvent(MetaMetricsEvents.DAPP_HOME); + trackEvent(MetaMetricsEvents.DAPP_HOME); }; /** @@ -1108,12 +1106,9 @@ export const BrowserTab = (props) => { error, setAccountsPermissionsVisible: () => { // Track Event: "Opened Acount Switcher" - AnalyticsV2.trackEvent( - MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, - { - number_of_accounts: accounts?.length, - }, - ); + trackEvent(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, { + number_of_accounts: accounts?.length, + }); props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.ACCOUNT_PERMISSIONS, params: { @@ -1185,7 +1180,7 @@ export const BrowserTab = (props) => { * Track new tab event */ const trackNewTabEvent = () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_NEW_TAB, { + trackEvent(MetaMetricsEvents.BROWSER_NEW_TAB, { option_chosen: 'Browser Options', number_of_tabs: undefined, }); @@ -1195,7 +1190,7 @@ export const BrowserTab = (props) => { * Track add site to favorites event */ const trackAddToFavoritesEvent = () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_ADD_FAVORITES, { + trackEvent(MetaMetricsEvents.BROWSER_ADD_FAVORITES, { dapp_name: title.current || '', }); }; @@ -1204,14 +1199,14 @@ export const BrowserTab = (props) => { * Track share site event */ const trackShareEvent = () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_SHARE_SITE); + trackEvent(MetaMetricsEvents.BROWSER_SHARE_SITE); }; /** * Track reload site event */ const trackReloadEvent = () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_RELOAD); + trackEvent(MetaMetricsEvents.BROWSER_RELOAD); }; /** @@ -1246,7 +1241,7 @@ export const BrowserTab = (props) => { }, }); trackAddToFavoritesEvent(); - Analytics.trackEvent(MetaMetricsEvents.DAPP_ADD_TO_FAVORITE); + trackEvent(MetaMetricsEvents.DAPP_ADD_TO_FAVORITE); }; /** @@ -1273,7 +1268,7 @@ export const BrowserTab = (props) => { error, ), ); - Analytics.trackEvent(MetaMetricsEvents.DAPP_OPEN_IN_BROWSER); + trackEvent(MetaMetricsEvents.DAPP_OPEN_IN_BROWSER); }; /** diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index 547b0d1f43c..fc9a6cf9197 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -62,7 +62,7 @@ import AnimatedFox from 'react-native-animated-fox'; import { LoginOptionsSwitch } from '../../UI/LoginOptionsSwitch'; import navigateTermsOfUse from '../../../util/termsOfUse/termsOfUse'; import { ChoosePasswordSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ChoosePassword.selectors'; -import trackAfterInteractions from '../../../util/metrics/TrackAfterInteraction/trackAfterInteractions'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const createStyles = (colors) => StyleSheet.create({ @@ -260,9 +260,7 @@ class ChoosePassword extends PureComponent { keyringControllerPasswordSet = false; track = (event, properties) => { - trackAfterInteractions(event, properties).catch(() => { - Logger.log('ChoosePassword', `Failed to track ${event}`); - }); + trackOnboarding(event, properties); }; updateNavBar = () => { diff --git a/app/components/Views/confirmations/Approval/components/TransactionEditor/index.js b/app/components/Views/confirmations/Approval/components/TransactionEditor/index.js index 4c40adcfb53..206df0d6104 100644 --- a/app/components/Views/confirmations/Approval/components/TransactionEditor/index.js +++ b/app/components/Views/confirmations/Approval/components/TransactionEditor/index.js @@ -50,6 +50,8 @@ import { import { selectAccounts } from '../../../../../../selectors/accountTrackerController'; import { selectContractBalances } from '../../../../../../selectors/tokenBalancesController'; import { selectSelectedAddress } from '../../../../../../selectors/preferencesController'; +import { selectGasFeeEstimates } from '../../../../../../selectors/confirmTransaction'; +import { selectGasFeeControllerEstimateType } from '../../../../../../selectors/gasFeeController'; const EDIT = 'edit'; const REVIEW = 'review'; @@ -970,10 +972,8 @@ const mapStateToProps = (state) => ({ ticker: selectTicker(state), transaction: getNormalizedTxState(state), activeTabUrl: getActiveTabUrl(state), - gasFeeEstimates: - state.engine.backgroundState.GasFeeController.gasFeeEstimates, - gasEstimateType: - state.engine.backgroundState.GasFeeController.gasEstimateType, + gasFeeEstimates: selectGasFeeEstimates(state), + gasEstimateType: selectGasFeeControllerEstimateType(state), conversionRate: selectConversionRate(state), currentCurrency: selectCurrentCurrency(state), nativeCurrency: selectNativeCurrency(state), diff --git a/app/components/Views/confirmations/Approval/index.js b/app/components/Views/confirmations/Approval/index.js index d74f4e6f865..03a7ef623a4 100644 --- a/app/components/Views/confirmations/Approval/index.js +++ b/app/components/Views/confirmations/Approval/index.js @@ -9,7 +9,6 @@ import { getTransactionOptionsTitle } from '../../../UI/Navbar'; import { resetTransaction } from '../../../../actions/transaction'; import { connect } from 'react-redux'; import NotificationManager from '../../../../core/NotificationManager'; -import Analytics from '../../../../core/Analytics/Analytics'; import AppConstants from '../../../../core/AppConstants'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import { @@ -26,7 +25,6 @@ import { } from '../../../../util/address'; import { WALLET_CONNECT_ORIGIN } from '../../../../util/walletconnect'; import Logger from '../../../../util/Logger'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller'; import { KEYSTONE_TX_CANCELED } from '../../../../constants/error'; import { ThemeContext, mockTheme } from '../../../../util/theme'; @@ -50,6 +48,7 @@ import { getBlockaidMetricsParams } from '../../../../util/blockaid'; import { getDecimalChainId } from '../../../../util/networks'; import { updateTransaction } from '../../../../util/transaction-controller'; +import { withMetricsAwareness } from '../../../../components/hooks/useMetrics'; const REVIEW = 'review'; const EDIT = 'edit'; @@ -111,6 +110,10 @@ class Approval extends PureComponent { * A string representing the network chainId */ chainId: PropTypes.string, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -220,7 +223,7 @@ class Approval extends PureComponent { navigation && navigation.setParams({ mode: REVIEW, dispatch: this.onModeChange }); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.DAPP_TRANSACTION_STARTED, this.getAnalyticsParams(), ); @@ -230,7 +233,7 @@ class Approval extends PureComponent { * Call Analytics to track confirm started event for approval screen */ trackConfirmScreen = () => { - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_CONFIRM_STARTED, this.getTrackingParams(), ); @@ -242,7 +245,7 @@ class Approval extends PureComponent { trackEditScreen = async () => { const { transaction } = this.props; const actionKey = await getTransactionReviewActionKey(transaction); - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_EDIT_TRANSACTION, { ...this.getTrackingParams(), @@ -255,7 +258,7 @@ class Approval extends PureComponent { * Call Analytics to track cancel pressed */ trackOnCancel = () => { - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_CANCEL_TRANSACTION, this.getTrackingParams(), ); @@ -347,10 +350,13 @@ class Approval extends PureComponent { this.props.hideModal(); this.state.mode === REVIEW && this.trackOnCancel(); this.showWalletConnectNotification(); - AnalyticsV2.trackEvent(MetaMetricsEvents.DAPP_TRANSACTION_CANCELLED, { - ...this.getAnalyticsParams(), - ...this.getBlockaidMetricsParams(), - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.DAPP_TRANSACTION_CANCELLED, + { + ...this.getAnalyticsParams(), + ...this.getBlockaidMetricsParams(), + }, + ); }; onLedgerConfirmation = (approve, transactionId, gaParams) => { @@ -368,7 +374,7 @@ class Approval extends PureComponent { this.showWalletConnectNotification(); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.DAPP_TRANSACTION_CANCELLED, gaParams, ); @@ -376,7 +382,7 @@ class Approval extends PureComponent { this.showWalletConnectNotification(true); } } finally { - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.DAPP_TRANSACTION_COMPLETED, gaParams, ); @@ -491,19 +497,22 @@ class Approval extends PureComponent { this.setState({ transactionHandled: true }); this.props.hideModal(); } else { - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, ); } this.setState({ transactionHandled: false }); } - AnalyticsV2.trackEvent(MetaMetricsEvents.DAPP_TRANSACTION_COMPLETED, { - ...this.getAnalyticsParams({ - gasEstimateType, - gasSelected, - }), - ...this.getBlockaidMetricsParams(), - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.DAPP_TRANSACTION_COMPLETED, + { + ...this.getAnalyticsParams({ + gasEstimateType, + gasSelected, + }), + ...this.getBlockaidMetricsParams(), + }, + ); this.setState({ transactionConfirmed: false }); }; @@ -640,4 +649,7 @@ const mapDispatchToProps = (dispatch) => ({ Approval.contextType = ThemeContext; -export default connect(mapStateToProps, mapDispatchToProps)(Approval); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(Approval)); diff --git a/app/components/Views/confirmations/ApproveView/Approve/index.js b/app/components/Views/confirmations/ApproveView/Approve/index.js index 71617d418a0..879b5bc5266 100644 --- a/app/components/Views/confirmations/ApproveView/Approve/index.js +++ b/app/components/Views/confirmations/ApproveView/Approve/index.js @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { Alert, InteractionManager, AppState, View } from 'react-native'; +import { Alert, AppState, View } from 'react-native'; import PropTypes from 'prop-types'; import { getApproveNavbar } from '../../../../UI/Navbar'; import { connect } from 'react-redux'; @@ -14,7 +14,7 @@ import AddNickname from '../../components/ApproveTransactionReview/AddNickname'; import Modal from 'react-native-modal'; import { strings } from '../../../../../../locales/i18n'; import { getNetworkNonce } from '../../../../../util/networks'; -import Analytics from '../../../../../core/Analytics/Analytics'; + import { setTransactionObject, setNonce, @@ -39,7 +39,6 @@ import { MetaMetricsEvents } from '../../../../../core/Analytics'; import Logger from '../../../../../util/Logger'; import EditGasFee1559 from '../../components/EditGasFee1559Update'; import EditGasFeeLegacy from '../../components/EditGasFeeLegacyUpdate'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import AppConstants from '../../../../../core/AppConstants'; import { shallowEqual } from '../../../../../util/general'; import { KEYSTONE_TX_CANCELED } from '../../../../../constants/error'; @@ -74,6 +73,9 @@ import { ethErrors } from 'eth-rpc-errors'; import { getLedgerKeyring } from '../../../../../core/Ledger/Ledger'; import ExtendedKeyringTypes from '../../../../../constants/keyringTypes'; import { updateTransaction } from '../../../../../util/transaction-controller'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; +import { selectGasFeeEstimates } from '../../../../../selectors/confirmTransaction'; +import { selectGasFeeControllerEstimateType } from '../../../../../selectors/gasFeeController'; const EDIT = 'edit'; const REVIEW = 'review'; @@ -166,6 +168,10 @@ class Approve extends PureComponent { * Object that represents the navigator */ navigation: PropTypes.object, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -357,15 +363,14 @@ class Approve extends PureComponent { }; trackApproveEvent = (event) => { - const { transaction, tokensLength, accountsLength, providerType } = + const { transaction, tokensLength, accountsLength, providerType, metrics } = this.props; - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(event, { - view: transaction.origin, - numberOfTokens: tokensLength, - numberOfAccounts: accountsLength, - network: providerType, - }); + + metrics.trackEvent(event, { + view: transaction.origin, + numberOfTokens: tokensLength, + numberOfAccounts: accountsLength, + network: providerType, }); }; @@ -466,6 +471,7 @@ class Approve extends PureComponent { }; onLedgerConfirmation = (approve, transactionId, gaParams) => { + const { metrics } = this.props; const { TransactionController } = Engine.context; try { //manual cancel from UI when transaction is awaiting from ledger confirmation @@ -478,7 +484,7 @@ class Approve extends PureComponent { TransactionController.cancelTransaction(transactionId); - AnalyticsV2.trackEvent(MetaMetricsEvents.APPROVAL_CANCELLED, gaParams); + metrics.trackEvent(MetaMetricsEvents.APPROVAL_CANCELLED, gaParams); NotificationManager.showSimpleNotification({ status: `simple_notification_rejected`, @@ -488,14 +494,14 @@ class Approve extends PureComponent { }); } } finally { - AnalyticsV2.trackEvent(MetaMetricsEvents.APPROVAL_COMPLETED, gaParams); + metrics.trackEvent(MetaMetricsEvents.APPROVAL_COMPLETED, gaParams); } }; onConfirm = async () => { const { TransactionController, KeyringController, ApprovalController } = Engine.context; - const { transactions, gasEstimateType } = this.props; + const { transactions, gasEstimateType, metrics } = this.props; const { legacyGasTransaction, transactionConfirmed, @@ -564,7 +570,7 @@ class Approve extends PureComponent { waitForResult: true, }); - AnalyticsV2.trackEvent( + metrics.trackEvent( MetaMetricsEvents.APPROVAL_COMPLETED, this.getAnalyticsParams(), ); @@ -577,9 +583,7 @@ class Approve extends PureComponent { ); Logger.error(error, 'error while trying to send transaction (Approve)'); } else { - AnalyticsV2.trackEvent( - MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, - ); + metrics.trackEvent(MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED); } this.setState({ transactionHandled: false }); } @@ -587,6 +591,7 @@ class Approve extends PureComponent { }; onCancel = () => { + const { metrics, hideModal } = this.props; Engine.rejectPendingApproval( this.props.transaction.id, ethErrors.provider.userRejectedRequest(), @@ -595,11 +600,11 @@ class Approve extends PureComponent { logErrors: false, }, ); - AnalyticsV2.trackEvent( + metrics.trackEvent( MetaMetricsEvents.APPROVAL_CANCELLED, this.getAnalyticsParams(), ); - this.props.hideModal(); + hideModal(); NotificationManager.showSimpleNotification({ status: `simple_notification_rejected`, @@ -614,13 +619,10 @@ class Approve extends PureComponent { }; onModeChange = (mode) => { + const { metrics } = this.props; this.setState({ mode }); if (mode === EDIT) { - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent( - MetaMetricsEvents.SEND_FLOW_ADJUSTS_TRANSACTION_FEE, - ); - }); + metrics.trackEvent(MetaMetricsEvents.SEND_FLOW_ADJUSTS_TRANSACTION_FEE); } }; @@ -893,10 +895,8 @@ const mapStateToProps = (state) => ({ accountsLength: selectAccountsLength(state), primaryCurrency: state.settings.primaryCurrency, chainId: selectChainId(state), - gasFeeEstimates: - state.engine.backgroundState.GasFeeController.gasFeeEstimates, - gasEstimateType: - state.engine.backgroundState.GasFeeController.gasEstimateType, + gasFeeEstimates: selectGasFeeEstimates(state), + gasEstimateType: selectGasFeeControllerEstimateType(state), conversionRate: selectConversionRate(state), currentCurrency: selectCurrentCurrency(state), nativeCurrency: selectNativeCurrency(state), @@ -916,4 +916,7 @@ const mapDispatchToProps = (dispatch) => ({ Approve.contextType = ThemeContext; -export default connect(mapStateToProps, mapDispatchToProps)(Approve); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(Approve)); diff --git a/app/components/Views/confirmations/Send/index.js b/app/components/Views/confirmations/Send/index.js index 0aeb712218b..01f584c1496 100644 --- a/app/components/Views/confirmations/Send/index.js +++ b/app/components/Views/confirmations/Send/index.js @@ -28,7 +28,6 @@ import { import { toggleDappTransactionModal } from '../../../../actions/modals'; import NotificationManager from '../../../../core/NotificationManager'; import { showAlert } from '../../../../actions/alert'; -import Analytics from '../../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import { getTransactionReviewActionKey, @@ -42,7 +41,6 @@ import TransactionTypes from '../../../../core/TransactionTypes'; import { MAINNET } from '../../../../constants/network'; import BigNumber from 'bignumber.js'; import { WalletDevice } from '@metamask/transaction-controller'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { addTransaction, estimateGas, @@ -63,6 +61,7 @@ import { selectSelectedAddress, } from '../../../../selectors/preferencesController'; import { ethErrors } from 'eth-rpc-errors'; +import { withMetricsAwareness } from '../../../../components/hooks/useMetrics'; const REVIEW = 'review'; const EDIT = 'edit'; @@ -151,6 +150,10 @@ class Send extends PureComponent { * Object that represents the current route info like params passed to it */ route: PropTypes.object, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -618,7 +621,7 @@ class Send extends PureComponent { ); Logger.error(error, 'error while trying to send transaction (Send)'); } else { - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, ); } @@ -634,7 +637,7 @@ class Send extends PureComponent { * Call Analytics to track confirm started event for send screen */ trackConfirmScreen = () => { - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_CONFIRM_STARTED, this.getTrackingParams(), ); @@ -646,7 +649,7 @@ class Send extends PureComponent { trackEditScreen = async () => { const { transaction } = this.props; const actionKey = await getTransactionReviewActionKey(transaction); - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_EDIT_TRANSACTION, { ...this.getTrackingParams(), @@ -659,7 +662,7 @@ class Send extends PureComponent { * Call Analytics to track cancel pressed */ trackOnCancel = () => { - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_CANCEL_TRANSACTION, this.getTrackingParams(), ); @@ -669,7 +672,7 @@ class Send extends PureComponent { * Call Analytics to track confirm pressed */ trackOnConfirm = () => { - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_COMPLETED_TRANSACTION, this.getTrackingParams(), ); @@ -782,4 +785,7 @@ const mapDispatchToProps = (dispatch) => ({ Send.contextType = ThemeContext; -export default connect(mapStateToProps, mapDispatchToProps)(Send); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(Send)); diff --git a/app/components/Views/confirmations/SendFlow/Amount/index.js b/app/components/Views/confirmations/SendFlow/Amount/index.js index e5dfcd27d90..79251004053 100644 --- a/app/components/Views/confirmations/SendFlow/Amount/index.js +++ b/app/components/Views/confirmations/SendFlow/Amount/index.js @@ -62,7 +62,6 @@ import collectiblesTransferInformation from '../../../../../util/collectibles-tr import { strings } from '../../../../../../locales/i18n'; import Device from '../../../../../util/device'; import { BN } from 'ethereumjs-util'; -import Analytics from '../../../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; import dismissKeyboard from 'react-native/Libraries/Utilities/dismissKeyboard'; import NetworkMainAssetLogo from '../../../../UI/NetworkMainAssetLogo'; @@ -106,6 +105,9 @@ import { swapsUtils } from '@metamask/swaps-controller'; import { regex } from '../../../../../util/regex'; import { AmountViewSelectorsIDs } from '../../../../../../e2e/selectors/SendFlow/AmountView.selectors'; import { isNetworkRampNativeTokenSupported } from '../../../../../components/UI/Ramp/utils'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; +import { selectGasFeeEstimates } from '../../../../../selectors/confirmTransaction'; +import { selectGasFeeControllerEstimateType } from '../../../../../selectors/gasFeeController'; const KEYBOARD_OFFSET = Device.isSmallDevice() ? 80 : 120; @@ -476,6 +478,18 @@ class Amount extends PureComponent { * String that indicates the current chain id */ chainId: PropTypes.string, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, + /** + * Gas fee estimates for the transaction. + */ + gasFeeEstimates: PropTypes.object, + /** + * Type of gas fee estimate provided by the gas fee controller. + */ + gasEstimateType: PropTypes.string, }; state = { @@ -516,6 +530,8 @@ class Amount extends PureComponent { providerType, selectedAsset, isPaymentRequest, + gasEstimateType, + gasFeeEstimates, } = this.props; // For analytics this.updateNavBar(); @@ -530,23 +546,19 @@ class Amount extends PureComponent { this.onInputChange(readableValue); !selectedAsset.tokenId && this.handleSelectedAssetBalance(selectedAsset); - const { GasFeeController } = Engine.context; - const [gasEstimates, gas] = await Promise.all([ - GasFeeController.fetchGasFeeEstimates({ shouldUpdateState: false }), - this.estimateGasLimit(), - ]); + const [gas] = await Promise.all([this.estimateGasLimit()]); - if (gasEstimates.gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) { - const gasFeeEstimates = - gasEstimates.gasFeeEstimates[AppConstants.GAS_OPTIONS.MEDIUM]; + if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) { + const mediumGasFeeEstimates = + gasFeeEstimates[AppConstants.GAS_OPTIONS.MEDIUM]; const estimatedBaseFeeHex = decGWEIToHexWEI( - gasEstimates.gasFeeEstimates.estimatedBaseFee, + gasFeeEstimates.estimatedBaseFee, ); const suggestedMaxPriorityFeePerGasHex = decGWEIToHexWEI( - gasFeeEstimates.suggestedMaxPriorityFeePerGas, + mediumGasFeeEstimates.suggestedMaxPriorityFeePerGas, ); const suggestedMaxFeePerGasHex = decGWEIToHexWEI( - gasFeeEstimates.suggestedMaxFeePerGas, + mediumGasFeeEstimates.suggestedMaxFeePerGas, ); const gasLimitHex = BNToHex(gas); const gasHexes = calculateEIP1559GasFeeHexes({ @@ -558,17 +570,13 @@ class Amount extends PureComponent { this.setState({ estimatedTotalGas: hexToBN(gasHexes.gasFeeMaxHex), }); - } else if (gasEstimates.gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) { + } else if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) { const gasPrice = hexToBN( - decGWEIToHexWEI( - gasEstimates.gasFeeEstimates[AppConstants.GAS_OPTIONS.MEDIUM], - ), + decGWEIToHexWEI(gasFeeEstimates[AppConstants.GAS_OPTIONS.MEDIUM]), ); this.setState({ estimatedTotalGas: gas.mul(gasPrice) }); } else { - const gasPrice = hexToBN( - decGWEIToHexWEI(gasEstimates.gasFeeEstimates.gasPrice), - ); + const gasPrice = hexToBN(decGWEIToHexWEI(gasFeeEstimates.gasPrice)); this.setState({ estimatedTotalGas: gas.mul(gasPrice) }); } @@ -678,11 +686,9 @@ class Amount extends PureComponent { } else { await this.prepareTransaction(value); } - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters( - MetaMetricsEvents.SEND_FLOW_ADDS_AMOUNT, - { network: providerType }, - ); + + this.props.metrics.trackEvent(MetaMetricsEvents.SEND_FLOW_ADDS_AMOUNT, { + network: providerType, }); setSelectedAsset(selectedAsset); @@ -1260,13 +1266,13 @@ class Amount extends PureComponent { const navigateToBuyOrSwaps = () => { if (isSwappable) { - Analytics.trackEventWithParameters(MetaMetricsEvents.LINK_CLICKED, { + this.props.metrics.trackEvent(MetaMetricsEvents.LINK_CLICKED, { location: 'insufficient_funds_warning', text: 'swap_tokens', }); navigateToSwap(); } else if (isNetworkBuyNativeTokenSupported && selectedAsset.isETH) { - Analytics.trackEventWithParameters(MetaMetricsEvents.LINK_CLICKED, { + this.props.metrics.trackEvent(MetaMetricsEvents.LINK_CLICKED, { location: 'insufficient_funds_warning', text: 'buy_more', }); @@ -1512,6 +1518,8 @@ const mapStateToProps = (state, ownProps) => ({ collectibleContracts: collectibleContractsSelector(state), conversionRate: selectConversionRate(state), currentCurrency: selectCurrentCurrency(state), + gasEstimateType: selectGasFeeControllerEstimateType(state), + gasFeeEstimates: selectGasFeeEstimates(state), providerType: selectProviderType(state), primaryCurrency: state.settings.primaryCurrency, selectedAddress: selectSelectedAddress(state), @@ -1538,4 +1546,7 @@ const mapDispatchToProps = (dispatch) => ({ resetTransaction: () => dispatch(resetTransaction()), }); -export default connect(mapStateToProps, mapDispatchToProps)(Amount); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(Amount)); diff --git a/app/components/Views/confirmations/SendFlow/Confirm/components/CustomGasModal/CustomGasModal.tsx b/app/components/Views/confirmations/SendFlow/Confirm/components/CustomGasModal/CustomGasModal.tsx index 225683325c2..0527864bcb8 100644 --- a/app/components/Views/confirmations/SendFlow/Confirm/components/CustomGasModal/CustomGasModal.tsx +++ b/app/components/Views/confirmations/SendFlow/Confirm/components/CustomGasModal/CustomGasModal.tsx @@ -9,6 +9,7 @@ import EditGasFee1559 from '../../../../components/EditGasFee1559Update'; import EditGasFeeLegacy from '../../../../components/EditGasFeeLegacyUpdate'; import createStyles from './CustomGasModal.styles'; import { CustomGasModalProps } from './CustomGasModal.types'; +import { selectGasFeeEstimates } from '../../../../../../../selectors/confirmTransaction'; const CustomGasModal = ({ gasSelected, @@ -27,9 +28,8 @@ const CustomGasModal = ({ const { colors } = useAppThemeFromContext(); const styles = createStyles(); const transaction = useSelector((state: any) => state.transaction); - const gasFeeEstimate = useSelector( - (state: any) => - state.engine.backgroundState.GasFeeController.gasFeeEstimates, + const gasFeeEstimate = useSelector((state: any) => + selectGasFeeEstimates(state), ); const primaryCurrency = useSelector( (state: any) => state.settings.primaryCurrency, diff --git a/app/components/Views/confirmations/SendFlow/Confirm/index.js b/app/components/Views/confirmations/SendFlow/Confirm/index.js index 0d0037f4c0f..cda0b937a53 100644 --- a/app/components/Views/confirmations/SendFlow/Confirm/index.js +++ b/app/components/Views/confirmations/SendFlow/Confirm/index.js @@ -36,6 +36,7 @@ import { resetTransaction, setNonce, setProposedNonce, + setTransactionId, } from '../../../../../actions/transaction'; import { getGasLimit } from '../../../../../util/custom-gas'; import Engine from '../../../../../core/Engine'; @@ -48,7 +49,6 @@ import CollectibleMedia from '../../../../UI/CollectibleMedia'; import Modal from 'react-native-modal'; import IonicIcon from 'react-native-vector-icons/Ionicons'; import TransactionTypes from '../../../../../core/TransactionTypes'; -import Analytics from '../../../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; import { shallowEqual, renderShortText } from '../../../../../util/general'; import { @@ -62,7 +62,6 @@ import { getDecimalChainId, } from '../../../../../util/networks'; import Text from '../../../../Base/Text'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import { addHexPrefix } from 'ethereumjs-util'; import { removeFavoriteCollectible } from '../../../../../actions/collectibles'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -116,6 +115,10 @@ import TransactionBlockaidBanner from '../../components/TransactionBlockaidBanne import { createLedgerTransactionModalNavDetails } from '../../../../../components/UI/LedgerModals/LedgerTransactionModal'; import CustomGasModal from './components/CustomGasModal'; import { ResultType } from '../../components/BlockaidBanner/BlockaidBanner.types'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; +import { selectTransactionGasFeeEstimates } from '../../../../../selectors/confirmTransaction'; +import { selectGasFeeControllerEstimateType } from '../../../../../selectors/gasFeeController'; +import { updateTransaction } from '../../../../../util/transaction-controller'; const EDIT = 'edit'; const EDIT_NONCE = 'edit_nonce'; @@ -229,6 +232,14 @@ class Confirm extends PureComponent { * Boolean that indicates if the network supports buy */ isNativeTokenBuySupported: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, + /** + * Set transaction ID + */ + setTransactionId: PropTypes.func, }; state = { @@ -251,7 +262,6 @@ class Confirm extends PureComponent { multiLayerL1FeeTotal: '0x0', result: {}, transactionMeta: {}, - preparedTransaction: {}, }; originIsWalletConnect = this.props.transaction.origin?.startsWith( @@ -326,10 +336,17 @@ class Confirm extends PureComponent { contractBalances, transactionState: { selectedAsset }, } = this.props; + + const { transactionMeta } = this.state; const { TokensController } = Engine.context; await stopGasPolling(this.state.pollToken); clearInterval(intervalIdForEstimatedL1Fee); + Engine.rejectPendingApproval(transactionMeta.id, undefined, { + ignoreMissing: true, + logErrors: false, + }); + /** * Remove token that was added to the account temporarily * Ref.: https://github.com/MetaMask/metamask-mobile/pull/3989#issuecomment-1367558394 @@ -375,8 +392,16 @@ class Confirm extends PureComponent { navigation, providerType, isPaymentRequest, + setTransactionId, } = this.props; + const { + from, + transactionTo: to, + transactionValue: value, + data, + } = this.props.transaction; + this.updateNavBar(); this.getGasLimit(); @@ -385,7 +410,7 @@ class Confirm extends PureComponent { pollToken, }); // For analytics - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.SEND_TRANSACTION_STARTED, this.getAnalyticsParams(), ); @@ -400,9 +425,41 @@ class Confirm extends PureComponent { POLLING_INTERVAL_ESTIMATED_L1_FEE, ); } + // add transaction + const { TransactionController } = Engine.context; + const { result, transactionMeta } = + await TransactionController.addTransaction(this.props.transaction, { + deviceConfirmedOn: WalletDevice.MM_MOBILE, + origin: TransactionTypes.MMM, + }); + + setTransactionId(transactionMeta.id); + + this.setState({ result, transactionMeta }); + + if (isBlockaidFeatureEnabled()) { + // start validate ppom + const id = transactionMeta.id; + const reqObject = { + id, + jsonrpc: '2.0', + method: 'eth_sendTransaction', + origin: TransactionTypes.MMM, + params: [ + { + from, + to, + value, + data, + }, + ], + }; + + ppomUtil.validateRequest(reqObject, id); + } }; - componentDidUpdate = async (prevProps, prevState) => { + componentDidUpdate = (prevProps, prevState) => { const { transactionState: { transactionTo, @@ -438,7 +495,8 @@ class Confirm extends PureComponent { if ( this.props.gasFeeEstimates && gas && - (!shallowEqual(prevProps.gasFeeEstimates, this.props.gasFeeEstimates) || + (!prevProps.gasFeeEstimates || + !shallowEqual(prevProps.gasFeeEstimates, this.props.gasFeeEstimates) || gas !== prevProps?.transactionState?.transaction?.gas) ) { const gasEstimateTypeChanged = @@ -480,52 +538,6 @@ class Confirm extends PureComponent { this.parseTransactionDataHeader(); } } - - const { gasEstimationReady, preparedTransaction } = this.state; - - // only add transaction if gasEstimationReady and preparedTransaction has gas - if (gasEstimationReady && !preparedTransaction.gas) { - const { TransactionController } = Engine.context; - - const preparedTransaction = this.prepareTransactionToSend(); - - // update state only if preparedTransaction has gas - if (preparedTransaction.gas) { - const { from, to, value, data } = preparedTransaction; - - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ preparedTransaction }, async () => { - const { result, transactionMeta } = - await TransactionController.addTransaction(preparedTransaction, { - deviceConfirmedOn: WalletDevice.MM_MOBILE, - origin: TransactionTypes.MMM, - }); - - this.setState({ result, transactionMeta }); - - if (isBlockaidFeatureEnabled()) { - // start validate ppom - const id = transactionMeta.id; - const reqObject = { - id, - jsonrpc: '2.0', - method: 'eth_sendTransaction', - origin: TransactionTypes.MMM, - params: [ - { - from, - to, - value, - data, - }, - ], - }; - - ppomUtil.validateRequest(reqObject, id); - } - }); - } - } }; setScrollViewRef = (ref) => { @@ -540,11 +552,9 @@ class Confirm extends PureComponent { onModeChange = (mode) => { this.setState({ mode }); if (mode === EDIT) { - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent( - MetaMetricsEvents.SEND_FLOW_ADJUSTS_TRANSACTION_FEE, - ); - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.SEND_FLOW_ADJUSTS_TRANSACTION_FEE, + ); } }; @@ -768,7 +778,7 @@ class Confirm extends PureComponent { assetType, }); this.checkRemoveCollectible(); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.SEND_TRANSACTION_COMPLETED, gaParams, ); @@ -799,11 +809,7 @@ class Confirm extends PureComponent { if (transactionConfirmed) return; this.setState({ transactionConfirmed: true, stopUpdateGas: true }); try { - const { - result, - transactionMeta, - preparedTransaction: transaction, - } = this.state; + const transaction = this.prepareTransactionToSend(); let error; if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) { @@ -823,6 +829,8 @@ class Confirm extends PureComponent { return; } + const { result, transactionMeta } = this.state; + const isLedgerAccount = isHardwareAccount(transaction.from, [ ExtendedKeyringTypes.ledger, ]); @@ -852,6 +860,7 @@ class Confirm extends PureComponent { return; } + await this.persistTransactionParameters(transaction); await KeyringController.resetQRKeyringState(); await ApprovalController.accept(transactionMeta.id, undefined, { waitForResult: true, @@ -868,10 +877,13 @@ class Confirm extends PureComponent { assetType, }); this.checkRemoveCollectible(); - AnalyticsV2.trackEvent(MetaMetricsEvents.SEND_TRANSACTION_COMPLETED, { - ...this.getAnalyticsParams(), - ...this.withBlockaidMetricsParams(), - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.SEND_TRANSACTION_COMPLETED, + { + ...this.getAnalyticsParams(), + ...this.withBlockaidMetricsParams(), + }, + ); stopGasPolling(); resetTransaction(); navigation && navigation.dangerouslyGetParent()?.pop(); @@ -885,7 +897,7 @@ class Confirm extends PureComponent { ); Logger.error(error, 'error while trying to send transaction (Confirm)'); } else { - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, ); } @@ -939,7 +951,6 @@ class Confirm extends PureComponent { updateTransactionStateWithUpdatedNonce = (nonceValue) => { this.props.setNonce(nonceValue); - this.setState({ preparedTransaction: {} }); }; renderCustomNonceModal = () => { @@ -1019,9 +1030,10 @@ class Confirm extends PureComponent { } catch (error) { Logger.error(error, 'Navigation: Error when navigating to buy ETH.'); } - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST); - }); + + this.props.metrics.trackEvent( + MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST, + ); }; goToFaucet = () => { @@ -1085,7 +1097,7 @@ class Confirm extends PureComponent { ...this.withBlockaidMetricsParams(), external_link_clicked: 'security_alert_support_link', }; - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.CONTRACT_ADDRESS_COPIED, analyticsParams, ); @@ -1119,6 +1131,21 @@ class Confirm extends PureComponent { return confirmButtonStyle; } + async persistTransactionParameters(transactionParams) { + const { TransactionController } = Engine.context; + const { transactionMeta } = this.state; + const { id: transactionId } = transactionMeta; + + const controllerTransactionMeta = + TransactionController.state.transactions.find( + (tx) => tx.id === transactionId, + ); + + controllerTransactionMeta.transaction = transactionParams; + + await updateTransaction(controllerTransactionMeta); + } + render = () => { const { selectedAsset, paymentRequest } = this.props.transactionState; const { @@ -1353,10 +1380,8 @@ const mapStateToProps = (state) => ({ selectedAsset: state.transaction.selectedAsset, transactionState: state.transaction, primaryCurrency: state.settings.primaryCurrency, - gasFeeEstimates: - state.engine.backgroundState.GasFeeController.gasFeeEstimates, - gasEstimateType: - state.engine.backgroundState.GasFeeController.gasEstimateType, + gasFeeEstimates: selectTransactionGasFeeEstimates(state), + gasEstimateType: selectGasFeeControllerEstimateType(state), isPaymentRequest: state.transaction.paymentRequest, securityAlertResponse: state.transaction.currentTransactionSecurityAlertResponse, @@ -1370,6 +1395,8 @@ const mapDispatchToProps = (dispatch) => ({ prepareTransaction: (transaction) => dispatch(prepareTransaction(transaction)), resetTransaction: () => dispatch(resetTransaction()), + setTransactionId: (transactionId) => + dispatch(setTransactionId(transactionId)), setNonce: (nonce) => dispatch(setNonce(nonce)), setProposedNonce: (nonce) => dispatch(setProposedNonce(nonce)), removeFavoriteCollectible: (selectedAddress, chainId, collectible) => @@ -1377,4 +1404,7 @@ const mapDispatchToProps = (dispatch) => ({ showAlert: (config) => dispatch(showAlert(config)), }); -export default connect(mapStateToProps, mapDispatchToProps)(Confirm); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(Confirm)); diff --git a/app/components/Views/confirmations/SendFlow/Confirm/index.test.tsx b/app/components/Views/confirmations/SendFlow/Confirm/index.test.tsx index 63fba6cbb5b..0607097b9ff 100644 --- a/app/components/Views/confirmations/SendFlow/Confirm/index.test.tsx +++ b/app/components/Views/confirmations/SendFlow/Confirm/index.test.tsx @@ -5,7 +5,6 @@ import Confirm from '.'; import { renderScreen } from '../../../../../util/test/renderWithProvider'; import Routes from '../../../../../constants/navigation/Routes'; import initialBackgroundState from '../../../../../util/test/initial-background-state.json'; -import analyticsV2 from '../../../../../util/analyticsV2'; import { TESTID_ACCORDION_CONTENT } from '../../../../../component-library/components/Accordions/Accordion/Accordion.constants'; import { FALSE_POSITIVE_REPOST_LINE_TEST_ID } from '../../components/BlockaidBanner/BlockaidBanner.constants'; @@ -97,6 +96,7 @@ jest.mock('../../../../../lib/ppom/ppom-util', () => ({ })); jest.mock('../../../../../core/Engine', () => ({ + rejectPendingApproval: jest.fn(), context: { TokensController: { addToken: jest.fn(), @@ -151,13 +151,6 @@ describe('Confirm', () => { }); it('displays blockaid banner', async () => { - const trackEventSypy = jest - .spyOn(analyticsV2, 'trackEvent') - .mockImplementation((name, params) => { - expect(name).toBeDefined(); - expect(params).toBeDefined(); - }); - const { queryByText, queryByTestId } = render(Confirm); await waitFor(async () => { @@ -172,8 +165,6 @@ describe('Confirm', () => { await queryByTestId(FALSE_POSITIVE_REPOST_LINE_TEST_ID), ).toBeDefined(); expect(await queryByText('Something doesn’t look right?')).toBeDefined(); - - expect(trackEventSypy).toHaveBeenCalledTimes(1); }); }); }); diff --git a/app/components/Views/confirmations/SendFlow/SendTo/index.js b/app/components/Views/confirmations/SendFlow/SendTo/index.js index ba972537fbf..f6155efeae0 100644 --- a/app/components/Views/confirmations/SendFlow/SendTo/index.js +++ b/app/components/Views/confirmations/SendFlow/SendTo/index.js @@ -1,25 +1,16 @@ import React, { Fragment, PureComponent } from 'react'; -import { - View, - InteractionManager, - ScrollView, - Alert, - Platform, - BackHandler, -} from 'react-native'; +import { View, ScrollView, Alert, Platform, BackHandler } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { toChecksumAddress } from 'ethereumjs-util'; import { SafeAreaView } from 'react-native-safe-area-context'; import Icon from 'react-native-vector-icons/FontAwesome'; -import Analytics from '../../../../../core/Analytics/Analytics'; import AddressList from '../AddressList'; import Text from '../../../../Base/Text'; import WarningMessage from '../WarningMessage'; import { getSendFlowTitle } from '../../../../UI/Navbar'; import StyledButton from '../../../../UI/StyledButton'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import { getDecimalChainId, handleNetworkSwitch, @@ -72,6 +63,7 @@ import SendFlowAddressFrom from '../AddressFrom'; import SendFlowAddressTo from '../AddressTo'; import { includes } from 'lodash'; import { SendViewSelectorsIDs } from '../../../../../../e2e/selectors/SendView.selectors'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; const dummy = () => true; @@ -149,6 +141,10 @@ class SendFlow extends PureComponent { * Object of addresses associated with multiple chains {'id': [address: string]} */ ambiguousAddressEntries: PropTypes.object, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; addressToInputRef = React.createRef(); @@ -300,14 +296,10 @@ class SendFlow extends PureComponent { toEnsName, toSelectedAddressName, ); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters( - MetaMetricsEvents.SEND_FLOW_ADDS_RECIPIENT, - { - network: providerType, - }, - ); + this.props.metrics.trackEvent(MetaMetricsEvents.SEND_FLOW_ADDS_RECIPIENT, { + network: providerType, }); + navigation.navigate('Amount'); }; @@ -318,12 +310,11 @@ class SendFlow extends PureComponent { goToBuy = () => { this.props.navigation.navigate(Routes.RAMP.BUY); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BUY_BUTTON_CLICKED, { - button_location: 'Send Flow warning', - button_copy: 'Buy Native Token', - chain_id_destination: this.props.chainId, - }); + + this.props.metrics.trackEvent(MetaMetricsEvents.BUY_BUTTON_CLICKED, { + button_location: 'Send Flow warning', + button_copy: 'Buy Native Token', + chain_id_destination: this.props.chainId, }); }; @@ -423,7 +414,7 @@ class SendFlow extends PureComponent { const isAmbiguousAddress = includes(currentChain, toAccount); if (isAmbiguousAddress) { this.setState({ showAmbiguousAcountWarning: isAmbiguousAddress }); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.SEND_FLOW_SELECT_DUPLICATE_ADDRESS, { chain_id: getDecimalChainId(this.props.chainId), @@ -712,4 +703,7 @@ const mapDispatchToProps = (dispatch) => ({ resetTransaction: () => dispatch(resetTransaction()), }); -export default connect(mapStateToProps, mapDispatchToProps)(SendFlow); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(SendFlow)); diff --git a/app/components/Views/confirmations/components/ApproveTransactionReview/AddNickname/index.tsx b/app/components/Views/confirmations/components/ApproveTransactionReview/AddNickname/index.tsx index 39fbb85f0fb..833af76b957 100644 --- a/app/components/Views/confirmations/components/ApproveTransactionReview/AddNickname/index.tsx +++ b/app/components/Views/confirmations/components/ApproveTransactionReview/AddNickname/index.tsx @@ -4,7 +4,6 @@ import AntDesignIcon from 'react-native-vector-icons/AntDesign'; import EthereumAddress from '../../../../../UI/EthereumAddress'; import Engine from '../../../../../../core/Engine'; import { MetaMetricsEvents } from '../../../../../../core/Analytics'; -import AnalyticsV2 from '../../../../../../util/analyticsV2'; import { toChecksumAddress } from 'ethereumjs-util'; import { connect } from 'react-redux'; @@ -39,6 +38,7 @@ import { } from '../../../../../../selectors/networkController'; import { selectIdentities } from '../../../../../../selectors/preferencesController'; import { ContractNickNameViewSelectorsIDs } from '../../../../../../../e2e/selectors/ContractNickNameView.selectors'; +import { useMetrics } from '../../../../../../components/hooks/useMetrics'; const getAnalyticsParams = () => ({}); @@ -64,6 +64,7 @@ const AddNickname = (props: AddNicknameProps) => { const [showFullAddress, setShowFullAddress] = useState(false); const [shouldDisableButton, setShouldDisableButton] = useState(true); const { colors, themeAppearance } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const chooseToContinue = () => { @@ -109,10 +110,7 @@ const AddNickname = (props: AddNicknameProps) => { data: { msg: strings('transactions.address_copied_to_clipboard') }, }); - AnalyticsV2.trackEvent( - MetaMetricsEvents.CONTRACT_ADDRESS_COPIED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.CONTRACT_ADDRESS_COPIED, getAnalyticsParams()); }; const saveTokenNickname = () => { @@ -124,7 +122,7 @@ const AddNickname = (props: AddNicknameProps) => { providerChainId, ); closeModal(); - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.CONTRACT_ADDRESS_NICKNAME, getAnalyticsParams(), ); diff --git a/app/components/Views/confirmations/components/ApproveTransactionReview/index.js b/app/components/Views/confirmations/components/ApproveTransactionReview/index.js index 967a06da650..0609d11c210 100644 --- a/app/components/Views/confirmations/components/ApproveTransactionReview/index.js +++ b/app/components/Views/confirmations/components/ApproveTransactionReview/index.js @@ -45,9 +45,7 @@ import Avatar, { import Identicon from '../../../../UI/Identicon'; import TransactionTypes from '../../../../../core/TransactionTypes'; import { showAlert } from '../../../../../actions/alert'; -import Analytics from '../../../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import TransactionHeader from '../../../../UI/TransactionHeader'; import TransactionReviewDetailsCard from '../TransactionReview/TransactionReviewDetailsCard'; import AppConstants from '../../../../../core/AppConstants'; @@ -98,6 +96,7 @@ import InfoModal from '../../../../UI/Swaps/components/InfoModal'; import { ResultType } from '../BlockaidBanner/BlockaidBanner.types'; import TransactionBlockaidBanner from '../TransactionBlockaidBanner/TransactionBlockaidBanner'; import { regex } from '../../../../../util/regex'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; const { ORIGIN_DEEPLINK, ORIGIN_QR_CODE } = AppConstants.DEEPLINKS; const POLLING_INTERVAL_ESTIMATED_L1_FEE = 30000; @@ -272,6 +271,10 @@ class ApproveTransactionReview extends PureComponent { * Boolean that indicates gas estimated value is confirmed before approving */ isGasEstimateStatusIn: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -462,7 +465,7 @@ class ApproveTransactionReview extends PureComponent { spendLimitCustomValue: minTokenAllowance, }, () => { - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.APPROVAL_STARTED, this.getAnalyticsParams(), ); @@ -563,13 +566,12 @@ class ApproveTransactionReview extends PureComponent { trackApproveEvent = (event) => { const { transaction, tokensLength, accountsLength, providerType } = this.props; - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(event, { - view: transaction.origin, - numberOfTokens: tokensLength, - numberOfAccounts: accountsLength, - network: providerType, - }); + + this.props.metrics.trackEvent(event, { + view: transaction.origin, + numberOfTokens: tokensLength, + numberOfAccounts: accountsLength, + network: providerType, }); }; @@ -580,7 +582,9 @@ class ApproveTransactionReview extends PureComponent { toggleViewDetails = () => { const { viewDetails } = this.state; - Analytics.trackEvent(MetaMetricsEvents.DAPP_APPROVE_SCREEN_VIEW_DETAILS); + this.props.metrics.trackEvent( + MetaMetricsEvents.DAPP_APPROVE_SCREEN_VIEW_DETAILS, + ); this.setState({ viewDetails: !viewDetails }); }; @@ -592,7 +596,7 @@ class ApproveTransactionReview extends PureComponent { content: 'clipboard-alert', data: { msg: strings('transactions.address_copied_to_clipboard') }, }); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.CONTRACT_ADDRESS_COPIED, this.getAnalyticsParams(), ); @@ -611,7 +615,9 @@ class ApproveTransactionReview extends PureComponent { tokenSpendValue, originalApproveAmount, } = this.state; - Analytics.trackEvent(MetaMetricsEvents.TRANSACTIONS_EDIT_TRANSACTION); + this.props.metrics.trackEvent( + MetaMetricsEvents.TRANSACTIONS_EDIT_TRANSACTION, + ); updateTokenAllowanceState({ tokenStandard, @@ -696,7 +702,7 @@ class ApproveTransactionReview extends PureComponent { ...this.withBlockaidMetricsParams(), external_link_clicked: 'security_alert_support_link', }; - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.CONTRACT_ADDRESS_COPIED, analyticsParams, ); @@ -1164,18 +1170,22 @@ class ApproveTransactionReview extends PureComponent { } catch (error) { Logger.error(error, 'Navigation: Error when navigating to buy ETH.'); } - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST); - }); + + this.props.metrics.trackEvent( + MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST, + ); }; onCancelPress = () => { const { onCancel } = this.props; onCancel && onCancel(); - AnalyticsV2.trackEvent(MetaMetricsEvents.APPROVAL_PERMISSION_UPDATED, { - ...this.getAnalyticsParams(), - ...this.withBlockaidMetricsParams(), - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.APPROVAL_PERMISSION_UPDATED, + { + ...this.getAnalyticsParams(), + ...this.withBlockaidMetricsParams(), + }, + ); }; onConfirmPress = () => { @@ -1186,10 +1196,13 @@ class ApproveTransactionReview extends PureComponent { const { onConfirm } = this.props; if (tokenStandard === ERC20 && !isReadyToApprove) { - AnalyticsV2.trackEvent(MetaMetricsEvents.APPROVAL_PERMISSION_UPDATED, { - ...this.getAnalyticsParams(), - ...this.withBlockaidMetricsParams(), - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.APPROVAL_PERMISSION_UPDATED, + { + ...this.getAnalyticsParams(), + ...this.withBlockaidMetricsParams(), + }, + ); return this.setState({ isReadyToApprove: true }); } @@ -1288,4 +1301,8 @@ ApproveTransactionReview.contextType = ThemeContext; export default connect( mapStateToProps, mapDispatchToProps, -)(withNavigation(withQRHardwareAwareness(ApproveTransactionReview))); +)( + withNavigation( + withQRHardwareAwareness(withMetricsAwareness(ApproveTransactionReview)), + ), +); diff --git a/app/components/Views/confirmations/components/EditGasFee1559Update/index.tsx b/app/components/Views/confirmations/components/EditGasFee1559Update/index.tsx index ef4ee040a38..537f7fb9961 100644 --- a/app/components/Views/confirmations/components/EditGasFee1559Update/index.tsx +++ b/app/components/Views/confirmations/components/EditGasFee1559Update/index.tsx @@ -22,7 +22,6 @@ import { import BigNumber from 'bignumber.js'; import FadeAnimationView from '../../../../UI/FadeAnimationView'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import TimeEstimateInfoModal from '../../../../UI/TimeEstimateInfoModal'; import useModalHandler from '../../../../Base/hooks/useModalHandler'; @@ -42,6 +41,7 @@ import { GAS_LIMIT_MIN, GAS_PRICE_MIN as GAS_MIN, } from '../../../../../util/gasUtils'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; const EditGasFee1559Update = ({ selectedGasValue, @@ -91,6 +91,7 @@ const EditGasFee1559Update = ({ hideTimeEstimateInfoModal, ] = useModalHandler(false); const { colors } = useAppThemeFromContext() || mockTheme; + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const gasTransaction = useGasTransaction({ @@ -133,13 +134,13 @@ const EditGasFee1559Update = ({ const toggleAdvancedOptions = useCallback(() => { if (!showAdvancedOptions) { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.GAS_ADVANCED_OPTIONS_CLICKED, getAnalyticsParams(), ); } setShowAdvancedOptions(!showAdvancedOptions); - }, [getAnalyticsParams, showAdvancedOptions]); + }, [getAnalyticsParams, showAdvancedOptions, trackEvent]); const toggleLearnMoreModal = useCallback(() => { setShowLearnMoreModal(!showLearnMoreModal); @@ -153,10 +154,7 @@ const EditGasFee1559Update = ({ ); const save = useCallback(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.GAS_FEE_CHANGED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.GAS_FEE_CHANGED, getAnalyticsParams()); const newGasPriceObject = { suggestedMaxFeePerGas: gasObject?.suggestedMaxFeePerGas, @@ -165,7 +163,7 @@ const EditGasFee1559Update = ({ }; onSave(gasTransaction, newGasPriceObject); - }, [getAnalyticsParams, onSave, gasTransaction, gasObject]); + }, [getAnalyticsParams, onSave, gasTransaction, gasObject, trackEvent]); const changeGas = useCallback( (gas, option) => { diff --git a/app/components/Views/confirmations/components/EditGasFeeLegacyUpdate/index.tsx b/app/components/Views/confirmations/components/EditGasFeeLegacyUpdate/index.tsx index c4a3757c57b..901d351ab33 100644 --- a/app/components/Views/confirmations/components/EditGasFeeLegacyUpdate/index.tsx +++ b/app/components/Views/confirmations/components/EditGasFeeLegacyUpdate/index.tsx @@ -21,7 +21,6 @@ import Text, { import { MetaMetricsEvents } from '../../../../../core/Analytics'; import { useGasTransaction } from '../../../../../core/GasPolling/GasPolling'; import { selectChainId } from '../../../../../selectors/networkController'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import { getDecimalChainId, isMainnetByChainId, @@ -40,6 +39,8 @@ import { GAS_LIMIT_MIN, GAS_PRICE_MIN, } from '../../../../../util/gasUtils'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; +import { selectGasFeeEstimates } from '../../../../../selectors/confirmTransaction'; const EditGasFeeLegacy = ({ onCancel, @@ -56,6 +57,7 @@ const EditGasFeeLegacy = ({ selectedGasObject, hasDappSuggestedGas, }: EditGasFeeLegacyUpdateProps) => { + const { trackEvent } = useMetrics(); const [showRangeInfoModal, setShowRangeInfoModal] = useState(false); const [infoText, setInfoText] = useState(''); const [gasPriceError, setGasPriceError] = useState(''); @@ -73,11 +75,7 @@ const EditGasFeeLegacy = ({ const { colors } = useTheme(); const styles = createStyles(colors); - - const gasFeeEstimate = useSelector( - (state: any) => - state.engine.backgroundState.GasFeeController.gasFeeEstimates, - ); + const gasFeeEstimate = useSelector(selectGasFeeEstimates); const primaryCurrency = useSelector( (state: any) => state.settings.primaryCurrency, @@ -97,7 +95,7 @@ const EditGasFeeLegacy = ({ }); const save = useCallback(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.GAS_FEE_CHANGED, { + trackEvent(MetaMetricsEvents.GAS_FEE_CHANGED, { ...analyticsParams, chain_id: getDecimalChainId(chainId), function_type: view, @@ -109,7 +107,15 @@ const EditGasFeeLegacy = ({ legacyGasLimit: gasObjectLegacy?.legacyGasLimit, }; onSave(gasTransaction, newGasPriceObject); - }, [onSave, gasTransaction, gasObjectLegacy, analyticsParams, chainId, view]); + }, [ + onSave, + gasTransaction, + gasObjectLegacy, + analyticsParams, + chainId, + view, + trackEvent, + ]); const changeGas = useCallback((gas) => { updateGasObjectLegacy({ diff --git a/app/components/Views/confirmations/components/MessageSign/MessageSign.tsx b/app/components/Views/confirmations/components/MessageSign/MessageSign.tsx index 2a3ac444079..19156226ebf 100644 --- a/app/components/Views/confirmations/components/MessageSign/MessageSign.tsx +++ b/app/components/Views/confirmations/components/MessageSign/MessageSign.tsx @@ -6,7 +6,6 @@ import SignatureRequest from '../SignatureRequest'; import ExpandedMessage from '../SignatureRequest/ExpandedMessage'; import { KEYSTONE_TX_CANCELED } from '../../../../../constants/error'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import { useTheme } from '../../../../../util/theme'; import { getAnalyticsParams, @@ -19,6 +18,7 @@ import createExternalSignModelNav from '../../../../../util/hardwareWallet/signa import { SigningModalSelectorsIDs } from '../../../../../../e2e/selectors/Modals/SigningModal.selectors'; import { useNavigation } from '@react-navigation/native'; import Engine from '../../../../../core/Engine'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; interface MessageSignProps { /** @@ -75,6 +75,7 @@ const MessageSign = ({ showExpandedMessage, }: MessageSignProps) => { const navigation = useNavigation(); + const { trackEvent } = useMetrics(); const [truncateMessage, setTruncateMessage] = useState(false); const { securityAlertResponse } = useSelector( (reduxState: any) => reduxState.signatureRequest, @@ -84,14 +85,14 @@ const MessageSign = ({ const styles = createStyles(colors); useEffect(() => { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.SIGNATURE_REQUESTED, getAnalyticsParams(messageParams, 'eth_sign'), ); const onSignatureError = ({ error }: any) => { if (error?.message.startsWith(KEYSTONE_TX_CANCELED)) { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, getAnalyticsParams(messageParams, 'eth_sign'), ); @@ -108,7 +109,7 @@ const MessageSign = ({ onSignatureError, ); }; - }, [messageParams]); + }, [messageParams, trackEvent]); const shouldTruncateMessage = (e: any) => { if (e.nativeEvent.lines.length > 5) { diff --git a/app/components/Views/confirmations/components/MessageSign/index.test.tsx b/app/components/Views/confirmations/components/MessageSign/index.test.tsx index 208ba1ece0a..14c23daa1f8 100644 --- a/app/components/Views/confirmations/components/MessageSign/index.test.tsx +++ b/app/components/Views/confirmations/components/MessageSign/index.test.tsx @@ -7,17 +7,17 @@ import { InteractionManager } from 'react-native'; import AppConstants from '../../../../../core/AppConstants'; import { strings } from '../../../../../../locales/i18n'; import initialBackgroundState from '../../../../../util/test/initial-background-state.json'; -import analyticsV2 from '../../../../../util/analyticsV2'; import renderWithProvider from '../../../../../util/test/renderWithProvider'; import { act, waitFor } from '@testing-library/react-native'; -import { KEYSTONE_TX_CANCELED } from '../../../../../constants/error'; -import { MetaMetricsEvents } from '../../../../../core/Analytics'; // eslint-disable-next-line import/no-namespace import * as addressUtils from '../../../../../util/address'; import createExternalSignModelNav from '../../../../../util/hardwareWallet/signatureUtils'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; const fakeAddress = '0xE413f7dB07f9B93936189867588B1440D823e651'; +jest.mock('../../../../../components/hooks/useMetrics'); + jest.mock('../../../../../core/Engine', () => ({ acceptPendingApproval: jest.fn(), rejectPendingApproval: jest.fn(), @@ -36,14 +36,10 @@ jest.mock('../../../../../core/Engine', () => ({ }, })); -const EngineMock = Engine as jest.Mocked; - jest.mock('../../../../../core/NotificationManager', () => ({ showSimpleNotification: jest.fn(), })); -jest.mock('../../../../../util/analyticsV2'); - jest.mock('../../../../../util/address', () => ({ ...jest.requireActual('../../../../../util/address'), isExternalHardwareAccount: jest.fn(), @@ -129,6 +125,11 @@ function createContainer({ ); } +const mockTrackEvent = jest.fn(); +(useMetrics as jest.Mock).mockReturnValue({ + trackEvent: mockTrackEvent, +}); + describe('MessageSign', () => { beforeEach(() => { jest.clearAllMocks(); @@ -145,7 +146,6 @@ describe('MessageSign', () => { 'TestMessageId:signError', expect.any(Function), ); - expect(analyticsV2.trackEvent).toHaveBeenCalledTimes(1); expect( Engine.context.SignatureController.hub.removeListener, ).toHaveBeenCalledTimes(0); @@ -243,46 +243,6 @@ describe('MessageSign', () => { }); }); - describe('trackEvent', () => { - it('tracks event for rejected requests', async () => { - const container = createContainer(); - await container.getByTestId('SignatureRequest').props.onReject(); - - expect((analyticsV2.trackEvent as jest.Mock).mock.calls[1][0]).toEqual({ - category: 'Signature Rejected', - }); - expect((analyticsV2.trackEvent as jest.Mock).mock.calls[1][1]).toEqual({ - account_type: 'MetaMask', - dapp_host_name: undefined, - chain_id: undefined, - signature_type: 'eth_sign', - security_alert_response: 'Benign', - security_alert_reason: '', - ppom_eth_chainId_count: 1, - version: undefined, - }); - }); - - it('tracks event for approved requests', async () => { - const container = createContainer(); - await container.getByTestId('SignatureRequest').props.onConfirm(); - - expect((analyticsV2.trackEvent as jest.Mock).mock.calls[1][0]).toEqual({ - category: 'Signature Approved', - }); - expect((analyticsV2.trackEvent as jest.Mock).mock.calls[1][1]).toEqual({ - account_type: 'MetaMask', - dapp_host_name: undefined, - chain_id: undefined, - signature_type: 'eth_sign', - security_alert_response: 'Benign', - security_alert_reason: '', - ppom_eth_chainId_count: 1, - version: undefined, - }); - }); - }); - describe('shouldTruncateMessage', () => { it('sets truncateMessage to true if message is more then 5 characters', async () => { const container = createContainer(); @@ -333,7 +293,6 @@ describe('MessageSign', () => { 'TestMessageId:signError', expect.any(Function), ); - expect(analyticsV2.trackEvent).toHaveBeenCalledTimes(1); expect( Engine.context.SignatureController.hub.removeListener, ).toHaveBeenCalledTimes(0); @@ -361,30 +320,4 @@ describe('MessageSign', () => { ).toHaveBeenCalledWith('TestMessageId:signError', expect.any(Function)); }); }); - - describe('onSignatureError', () => { - let events: any; - beforeEach(() => { - events = {}; - - EngineMock.context.SignatureController.hub.on.mockImplementationOnce( - (event: any, callback: any) => { - events[event] = callback; - }, - ); - }); - - it('track has been called when error message starts with KeystoneError#Tx_canceled', async () => { - createContainer(); - events['TestMessageId:signError']({ - error: new Error(KEYSTONE_TX_CANCELED), - }); - await waitFor(() => { - expect(analyticsV2.trackEvent).toHaveBeenCalledTimes(2); - expect((analyticsV2.trackEvent as jest.Mock).mock.calls[1][0]).toEqual( - MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, - ); - }); - }); - }); }); diff --git a/app/components/Views/confirmations/components/PersonalSign/PersonalSign.tsx b/app/components/Views/confirmations/components/PersonalSign/PersonalSign.tsx index c0986c88f71..8a516e84aef 100644 --- a/app/components/Views/confirmations/components/PersonalSign/PersonalSign.tsx +++ b/app/components/Views/confirmations/components/PersonalSign/PersonalSign.tsx @@ -8,7 +8,6 @@ import NotificationManager from '../../../../../core/NotificationManager'; import { strings } from '../../../../../../locales/i18n'; import { WALLET_CONNECT_ORIGIN } from '../../../../../util/walletconnect'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import { getAddressAccountType, isExternalHardwareAccount, @@ -30,6 +29,7 @@ import { SecurityAlertResponse } from '../BlockaidBanner/BlockaidBanner.types'; import { SigningModalSelectorsIDs } from '../../../../../../e2e/selectors/Modals/SigningModal.selectors'; import { getDecimalChainId } from '../../../../../util/networks'; import Logger from '../../../../../util/Logger'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; /** * Converts a hexadecimal string to a utf8 string. @@ -61,6 +61,7 @@ const PersonalSign = ({ showExpandedMessage, }: PersonalSignProps) => { const navigation = useNavigation(); + const { trackEvent } = useMetrics(); const [truncateMessage, setTruncateMessage] = useState(false); const { securityAlertResponse } = useSelector( (reduxState: any) => reduxState.signatureRequest, @@ -107,7 +108,7 @@ const PersonalSign = ({ useEffect(() => { const onSignatureError = ({ error }: { error: Error }) => { if (error?.message.startsWith(KEYSTONE_TX_CANCELED)) { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, getAnalyticsParams(), ); @@ -123,7 +124,7 @@ const PersonalSign = ({ onSignatureError, ); }; - }, [getAnalyticsParams, messageParams.metamaskId]); + }, [getAnalyticsParams, messageParams.metamaskId, trackEvent]); const showWalletConnectNotification = (confirmation = false) => { InteractionManager.runAfterInteractions(() => { @@ -146,20 +147,14 @@ const PersonalSign = ({ const rejectSignature = async () => { await onReject(); showWalletConnectNotification(false); - AnalyticsV2.trackEvent( - MetaMetricsEvents.SIGNATURE_REJECTED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.SIGNATURE_REJECTED, getAnalyticsParams()); }; const confirmSignature = async () => { if (!isExternalHardwareAccount(messageParams.from)) { await onConfirm(); showWalletConnectNotification(true); - AnalyticsV2.trackEvent( - MetaMetricsEvents.SIGNATURE_APPROVED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.SIGNATURE_APPROVED, getAnalyticsParams()); } else { navigation.navigate( ...(await createExternalSignModelNav( diff --git a/app/components/Views/confirmations/components/PersonalSign/index.test.tsx b/app/components/Views/confirmations/components/PersonalSign/index.test.tsx index b2793039d9f..c64557f57c6 100644 --- a/app/components/Views/confirmations/components/PersonalSign/index.test.tsx +++ b/app/components/Views/confirmations/components/PersonalSign/index.test.tsx @@ -11,8 +11,9 @@ import { InteractionManager } from 'react-native'; import AppConstants from '../../../../../core/AppConstants'; import { strings } from '../../../../../../locales/i18n'; import initialBackgroundState from '../../../../../util/test/initial-background-state.json'; -import analyticsV2 from '../../../../../util/analyticsV2'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; +jest.mock('../../../../../components/hooks/useMetrics'); jest.mock('../../../../../core/Engine', () => ({ acceptPendingApproval: jest.fn(), rejectPendingApproval: jest.fn(), @@ -69,13 +70,16 @@ jest.mock('react-redux', () => ({ }), })); -jest.mock('../../../../../util/analyticsV2'); - jest.mock('../../../../../util/address', () => ({ getAddressAccountType: jest.fn().mockReturnValue('Metamask'), isExternalHardwareAccount: jest.fn().mockReturnValue(false), })); +const mockTrackEvent = jest.fn(); +(useMetrics as jest.Mock).mockReturnValue({ + trackEvent: mockTrackEvent, +}); + function createWrapper({ origin = messageParamsMock.origin, mockConfirm = jest.fn(), @@ -178,9 +182,9 @@ describe('PersonalSign', () => { const wrapper = createWrapper().dive(); await (wrapper.find(SignatureRequest).props() as any).onReject(); - const rejectedMocks = ( - analyticsV2.trackEvent as jest.Mock - ).mock.calls.filter((call) => call[0].category === 'Signature Rejected'); + const rejectedMocks = (mockTrackEvent as jest.Mock).mock.calls.filter( + (call) => call[0].category === 'Signature Rejected', + ); const mockCallsLength = rejectedMocks.length; @@ -202,9 +206,9 @@ describe('PersonalSign', () => { const wrapper = createWrapper().dive(); await (wrapper.find(SignatureRequest).props() as any).onConfirm(); - const signedMocks = ( - analyticsV2.trackEvent as jest.Mock - ).mock.calls.filter((call) => call[0].category === 'Signature Approved'); + const signedMocks = (mockTrackEvent as jest.Mock).mock.calls.filter( + (call) => call[0].category === 'Signature Approved', + ); const mockCallsLength = signedMocks.length; diff --git a/app/components/Views/confirmations/components/SignatureRequest/index.js b/app/components/Views/confirmations/components/SignatureRequest/index.js index 27accc98ff6..a52c26d179b 100644 --- a/app/components/Views/confirmations/components/SignatureRequest/index.js +++ b/app/components/Views/confirmations/components/SignatureRequest/index.js @@ -7,11 +7,9 @@ import { SigningModalSelectorsIDs } from '../../../../../../e2e/selectors/Modals import { strings } from '../../../../../../locales/i18n'; import ExtendedKeyringTypes from '../../../../../constants/keyringTypes'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import Analytics from '../../../../../core/Analytics/Analytics'; import { selectProviderType } from '../../../../../selectors/networkController'; import { fontStyles } from '../../../../../styles/common'; import { isHardwareAccount } from '../../../../../util/address'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import { getHost } from '../../../../../util/browser'; import { getAnalyticsParams } from '../../../../../util/confirmation/signatureUtils'; import Device from '../../../../../util/device'; @@ -24,6 +22,7 @@ import QRSigningDetails from '../../../../UI/QRHardware/QRSigningDetails'; import withQRHardwareAwareness from '../../../../UI/QRHardware/withQRHardwareAwareness'; import WebsiteIcon from '../../../../UI/WebsiteIcon'; import { ResultType } from '../BlockaidBanner/BlockaidBanner.types'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -172,6 +171,10 @@ class SignatureRequest extends PureComponent { selectedAddress: PropTypes.string, testID: PropTypes.string, securityAlertResponse: PropTypes.object, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; /** @@ -179,7 +182,7 @@ class SignatureRequest extends PureComponent { */ onReject = () => { this.props.onReject(); - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_CANCEL_SIGNATURE, this.getTrackingParams(), ); @@ -190,7 +193,7 @@ class SignatureRequest extends PureComponent { */ onConfirm = () => { this.props.onConfirm(); - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_CONFIRM_SIGNATURE, this.getTrackingParams(), ); @@ -313,7 +316,7 @@ class SignatureRequest extends PureComponent { ), external_link_clicked: 'security_alert_support_link', }; - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.SIGNATURE_REQUESTED, analyticsParams, ); @@ -423,5 +426,5 @@ const mapStateToProps = (state) => ({ SignatureRequest.contextType = ThemeContext; export default connect(mapStateToProps)( - withQRHardwareAwareness(SignatureRequest), + withQRHardwareAwareness(withMetricsAwareness(SignatureRequest)), ); diff --git a/app/components/Views/confirmations/components/TransactionReview/TransactionReviewInformation/index.js b/app/components/Views/confirmations/components/TransactionReview/TransactionReviewInformation/index.js index 07c1ed16cb9..6ab721aa806 100644 --- a/app/components/Views/confirmations/components/TransactionReview/TransactionReviewInformation/index.js +++ b/app/components/Views/confirmations/components/TransactionReview/TransactionReviewInformation/index.js @@ -29,7 +29,6 @@ import { calculateERC20EIP1559, } from '../../../../../../util/transactions'; import { sumHexWEIs } from '../../../../../../util/conversions'; -import Analytics from '../../../../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../../../../core/Analytics'; import { TESTNET_FAUCETS, @@ -63,6 +62,7 @@ import { createBrowserNavDetails } from '../../../../Browser'; import { isNetworkRampNativeTokenSupported } from '../../../../../../components/UI/Ramp/utils'; import { getRampNetworks } from '../../../../../../reducers/fiatOrders'; import Routes from '../../../../../../constants/navigation/Routes'; +import { withMetricsAwareness } from '../../../../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -234,6 +234,10 @@ class TransactionReviewInformation extends PureComponent { * Boolean that indicates if the network supports buy */ isNativeTokenBuySupported: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -300,9 +304,10 @@ class TransactionReviewInformation extends PureComponent { } catch (error) { Logger.error(error, 'Navigation: Error when navigating to buy ETH.'); } - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST); - }); + + this.props.metrics.trackEvent( + MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST, + ); }; edit = () => { @@ -757,4 +762,4 @@ TransactionReviewInformation.contextType = ThemeContext; export default connect( mapStateToProps, mapDispatchToProps, -)(TransactionReviewInformation); +)(withMetricsAwareness(TransactionReviewInformation)); diff --git a/app/components/Views/confirmations/components/TransactionReview/index.js b/app/components/Views/confirmations/components/TransactionReview/index.js index 2293e2c310a..a0a7e98d608 100644 --- a/app/components/Views/confirmations/components/TransactionReview/index.js +++ b/app/components/Views/confirmations/components/TransactionReview/index.js @@ -1,12 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { - StyleSheet, - View, - InteractionManager, - Animated, - ScrollView, -} from 'react-native'; +import { StyleSheet, View, Animated, ScrollView } from 'react-native'; import Eth from 'ethjs-query'; import { isMultiLayerFeeNetwork, @@ -38,8 +32,6 @@ import { getBlockaidMetricsParams } from '../../../../../util/blockaid'; import TransactionReviewInformation from './TransactionReviewInformation'; import TransactionReviewSummary from './TransactionReviewSummary'; import TransactionReviewData from './TransactionReviewData'; -import Analytics from '../../../../../core/Analytics/Analytics'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; import TransactionHeader from '../../../../UI/TransactionHeader'; import AccountFromToInfoCard from '../../../../UI/AccountFromToInfoCard'; @@ -64,6 +56,7 @@ import ApproveTransactionHeader from '../ApproveTransactionHeader'; import AppConstants from '../../../../../core/AppConstants'; import TransactionBlockaidBanner from '../TransactionBlockaidBanner/TransactionBlockaidBanner'; import { ResultType } from '../BlockaidBanner/BlockaidBanner.types'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; const POLLING_INTERVAL_ESTIMATED_L1_FEE = 30000; @@ -250,6 +243,10 @@ class TransactionReview extends PureComponent { * @returns {string} */ gasSelected: PropTypes.string, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -294,6 +291,7 @@ class TransactionReview extends PureComponent { tokens, chainId, tokenList, + metrics, } = this.props; let { showHexData } = this.props; let assetAmount, conversionRate, fiatValue; @@ -324,9 +322,9 @@ class TransactionReview extends PureComponent { fiatValue, approveTransaction, }); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.TRANSACTIONS_CONFIRM_STARTED); - }); + + metrics.trackEvent(MetaMetricsEvents.TRANSACTIONS_CONFIRM_STARTED); + if (isMultiLayerFeeNetwork(chainId)) { this.fetchEstimatedL1Fee(); intervalIdForEstimatedL1Fee = setInterval( @@ -337,14 +335,14 @@ class TransactionReview extends PureComponent { }; onContactUsClicked = () => { - const { transaction } = this.props; + const { transaction, metrics } = this.props; const additionalParams = { ...getBlockaidMetricsParams( transaction?.currentTransactionSecurityAlertResponse, ), external_link_clicked: 'security_alert_support_link', }; - AnalyticsV2.trackEvent( + metrics.trackEvent( MetaMetricsEvents.TRANSACTIONS_CONFIRM_STARTED, additionalParams, ); @@ -394,8 +392,8 @@ class TransactionReview extends PureComponent { }; edit = () => { - const { onModeChange } = this.props; - Analytics.trackEvent(MetaMetricsEvents.TRANSACTIONS_EDIT_TRANSACTION); + const { onModeChange, metrics } = this.props; + metrics.trackEvent(MetaMetricsEvents.TRANSACTIONS_EDIT_TRANSACTION); onModeChange && onModeChange('edit'); }; @@ -663,5 +661,7 @@ const mapStateToProps = (state) => ({ TransactionReview.contextType = ThemeContext; export default connect(mapStateToProps)( - withNavigation(withQRHardwareAwareness(TransactionReview)), + withNavigation( + withQRHardwareAwareness(withMetricsAwareness(TransactionReview)), + ), ); diff --git a/app/components/Views/confirmations/components/TransactionReview/index.test.tsx b/app/components/Views/confirmations/components/TransactionReview/index.test.tsx index 1ec7f51a0c3..a617618c7b7 100644 --- a/app/components/Views/confirmations/components/TransactionReview/index.test.tsx +++ b/app/components/Views/confirmations/components/TransactionReview/index.test.tsx @@ -7,7 +7,6 @@ import { Provider } from 'react-redux'; import * as TransactionUtils from '../../../../../util/transactions'; // eslint-disable-next-line import/no-namespace import * as BlockaidUtils from '../../../../../util/blockaid'; -import analyticsV2 from '../../../../../util/analyticsV2'; import renderWithProvider from '../../../../../util/test/renderWithProvider'; import initialBackgroundState from '../../../../../util/test/initial-background-state.json'; import { fireEvent } from '@testing-library/react-native'; @@ -185,12 +184,6 @@ describe('TransactionReview', () => { req: {}, chainId: '0x1', }; - const trackEventSypy = jest - .spyOn(analyticsV2, 'trackEvent') - .mockImplementation((name, params) => { - expect(name).toBeDefined(); - expect(params).toBeDefined(); - }); const blockaidMetricsParamsSpy = jest .spyOn(BlockaidUtils, 'getBlockaidMetricsParams') @@ -239,7 +232,6 @@ describe('TransactionReview', () => { fireEvent.press(await getByText('Report an issue')); - expect(trackEventSypy).toHaveBeenCalledTimes(1); expect(blockaidMetricsParamsSpy).toHaveBeenCalledTimes(1); expect(blockaidMetricsParamsSpy).toHaveBeenCalledWith({ id: '123', diff --git a/app/components/Views/confirmations/components/TypedSign/index.js b/app/components/Views/confirmations/components/TypedSign/index.js index 8f518edae9d..ab992817d54 100644 --- a/app/components/Views/confirmations/components/TypedSign/index.js +++ b/app/components/Views/confirmations/components/TypedSign/index.js @@ -7,7 +7,6 @@ import SignatureRequest from '../SignatureRequest'; import ExpandedMessage from '../SignatureRequest/ExpandedMessage'; import Device from '../../../../../util/device'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import { KEYSTONE_TX_CANCELED } from '../../../../../constants/error'; import { ThemeContext, mockTheme } from '../../../../../util/theme'; import sanitizeString from '../../../../../util/string'; @@ -24,6 +23,7 @@ import { import { isExternalHardwareAccount } from '../../../../../util/address'; import createExternalSignModelNav from '../../../../../util/hardwareWallet/signatureUtils'; import { SigningModalSelectorsIDs } from '../../../../../../e2e/selectors/Modals/SigningModal.selectors'; +import { withMetricsAwareness } from '../../../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -87,6 +87,10 @@ class TypedSign extends PureComponent { * Security alert response object */ securityAlertResponse: PropTypes.object, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -97,9 +101,10 @@ class TypedSign extends PureComponent { const { messageParams: { metamaskId }, messageParams, + metrics, } = this.props; - AnalyticsV2.trackEvent( + metrics.trackEvent( MetaMetricsEvents.SIGNATURE_REQUESTED, getAnalyticsParams(messageParams, 'typed_sign'), ); @@ -114,8 +119,9 @@ class TypedSign extends PureComponent { }; onSignatureError = ({ error }) => { + const { metrics } = this.props; if (error?.message.startsWith(KEYSTONE_TX_CANCELED)) { - AnalyticsV2.trackEvent( + metrics.trackEvent( MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, getAnalyticsParams(), ); @@ -282,4 +288,4 @@ const mapStateToProps = (state) => ({ securityAlertResponse: state.signatureRequest.securityAlertResponse, }); -export default connect(mapStateToProps)(TypedSign); +export default connect(mapStateToProps)(withMetricsAwareness(TypedSign)); diff --git a/app/components/Views/confirmations/components/TypedSign/index.test.tsx b/app/components/Views/confirmations/components/TypedSign/index.test.tsx index 147e21527df..d743adca9da 100644 --- a/app/components/Views/confirmations/components/TypedSign/index.test.tsx +++ b/app/components/Views/confirmations/components/TypedSign/index.test.tsx @@ -12,7 +12,15 @@ import AppConstants from '../../../../../core/AppConstants'; import initialBackgroundState from '../../../../../util/test/initial-background-state.json'; import renderWithProvider from '../../../../../util/test/renderWithProvider'; import { fireEvent, waitFor } from '@testing-library/react-native'; -import analyticsV2 from '../../../../../util/analyticsV2'; +import { MetaMetrics } from '../../../../../core/Analytics'; + +jest.mock('../../../../../core/Analytics/MetaMetrics'); + +const mockMetrics = { + trackEvent: jest.fn(), +}; + +(MetaMetrics.getInstance as jest.Mock).mockReturnValue(mockMetrics); jest.mock('../../../../../core/Engine', () => ({ acceptPendingApproval: jest.fn(), @@ -43,8 +51,6 @@ jest.mock('../../../../../util/address', () => ({ getAddressAccountType: jest.fn().mockReturnValue('Metamask'), })); -jest.mock('../../../../../util/analyticsV2'); - const messageParamsMock = { data: { type: 'string', name: 'Message', value: 'Hi, Alice!' }, origin: 'example.com', @@ -324,9 +330,9 @@ describe('TypedSign', () => { expect(mockReject).toHaveBeenCalledTimes(1); - const rejectedMocks = ( - analyticsV2.trackEvent as jest.Mock - ).mock.calls.filter((call) => call[0].category === 'Signature Rejected'); + const rejectedMocks = mockMetrics.trackEvent.mock.calls.filter( + (call) => call[0].category === 'Signature Rejected', + ); const mockCallsLength = rejectedMocks.length; @@ -364,9 +370,9 @@ describe('TypedSign', () => { ); fireEvent.press(signButton); - const signedMocks = ( - analyticsV2.trackEvent as jest.Mock - ).mock.calls.filter((call) => call[0].category === 'Signature Approved'); + const signedMocks = mockMetrics.trackEvent.mock.calls.filter( + (call) => call[0].category === 'Signature Approved', + ); const mockCallsLength = signedMocks.length; diff --git a/app/components/Views/confirmations/components/UpdateEIP1559Tx/index.tsx b/app/components/Views/confirmations/components/UpdateEIP1559Tx/index.tsx index 162cbca019b..ac360427d59 100644 --- a/app/components/Views/confirmations/components/UpdateEIP1559Tx/index.tsx +++ b/app/components/Views/confirmations/components/UpdateEIP1559Tx/index.tsx @@ -22,6 +22,8 @@ import { import { selectAccounts } from '../../../../../selectors/accountTrackerController'; import { selectSelectedAddress } from '../../../../../selectors/preferencesController'; import { getDecimalChainId } from '../../../../../util/networks'; +import { selectGasFeeEstimates } from '../../../../../selectors/confirmTransaction'; +import { selectGasFeeControllerEstimateType } from '../../../../../selectors/gasFeeController'; const UpdateEIP1559Tx = ({ gas, @@ -255,10 +257,8 @@ const mapStateToProps = (state: any) => ({ accounts: selectAccounts(state), selectedAddress: selectSelectedAddress(state), ticker: selectTicker(state), - gasFeeEstimates: - state.engine.backgroundState.GasFeeController.gasFeeEstimates, - gasEstimateType: - state.engine.backgroundState.GasFeeController.gasEstimateType, + gasFeeEstimates: selectGasFeeEstimates(state), + gasEstimateType: selectGasFeeControllerEstimateType(state), primaryCurrency: state.settings.primaryCurrency, chainId: selectChainId(state), }); diff --git a/app/components/Views/confirmations/components/WatchAssetRequest/index.js b/app/components/Views/confirmations/components/WatchAssetRequest/index.js index 83bfc439e0c..e4235b62af9 100644 --- a/app/components/Views/confirmations/components/WatchAssetRequest/index.js +++ b/app/components/Views/confirmations/components/WatchAssetRequest/index.js @@ -10,7 +10,6 @@ import { renderFromTokenMinimalUnit } from '../../../../../util/number'; import TokenImage from '../../../../UI/TokenImage'; import Device from '../../../../../util/device'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; -import AnalyticsV2 from '../../../../../util/analyticsV2'; import useTokenBalance from '../../../../hooks/useTokenBalance'; import { useTheme } from '../../../../../util/theme'; @@ -21,6 +20,7 @@ import { getActiveTabUrl } from '../../../../../util/transactions'; import { isEqual } from 'lodash'; import { SigningModalSelectorsIDs } from '../../../../../../e2e/selectors/Modals/SigningModal.selectors'; import { getDecimalChainId } from '../../../../../util/networks'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -104,6 +104,7 @@ const WatchAssetRequest = ({ const { asset, interactingAddress } = suggestedAssetMeta; // TODO - Once TokensController is updated, interactingAddress should always be defined const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const [balance, , error] = useTokenBalance(asset.address, interactingAddress); const chainId = useSelector(selectChainId); @@ -132,10 +133,7 @@ const WatchAssetRequest = ({ const onConfirmPress = async () => { await onConfirm(); InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.TOKEN_ADDED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.TOKEN_ADDED, getAnalyticsParams()); NotificationManager.showSimpleNotification({ status: `simple_notification`, duration: 5000, diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 309394444cd..792a6ea3d58 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -934,6 +934,7 @@ class Engine { // @ts-expect-error at this point in time the provider will be defined by the `networkController.initializeProvider` blockTracker: networkController.getProviderAndBlockTracker().blockTracker, + getGasFeeEstimates: () => gasFeeController.fetchGasFeeEstimates(), getNetworkState: () => networkController.state, getSelectedAddress: () => preferencesController.state.selectedAddress, incomingTransactions: { diff --git a/app/core/GasPolling/GasPolling.ts b/app/core/GasPolling/GasPolling.ts index 6d1455960cb..ddf1866f1d5 100644 --- a/app/core/GasPolling/GasPolling.ts +++ b/app/core/GasPolling/GasPolling.ts @@ -23,6 +23,7 @@ import { LegacyProps, UseGasTransactionProps, } from './types'; +import { selectGasFeeEstimates } from '../../selectors/confirmTransaction'; /** * @@ -61,7 +62,7 @@ export const useDataStore = () => { showCustomNonce, ] = useSelector( (state: any) => [ - state.engine.backgroundState.GasFeeController.gasFeeEstimates, + selectGasFeeEstimates(state), state.engine.backgroundState.GasFeeController.gasEstimateType, selectContractExchangeRates(state), selectConversionRate(state), diff --git a/app/core/redux/slices/engine/index.ts b/app/core/redux/slices/engine/index.ts index 700f13624cd..522cf5e705a 100644 --- a/app/core/redux/slices/engine/index.ts +++ b/app/core/redux/slices/engine/index.ts @@ -1,3 +1,4 @@ +import { cloneDeep } from 'lodash'; import Engine from '../../../Engine'; import { createAction, PayloadAction } from '@reduxjs/toolkit'; @@ -5,6 +6,8 @@ const initialState = { backgroundState: {} as any, }; +const legacyControllers = ['TransactionController']; + // Create an action to initialize the background state export const initBgState = createAction('INIT_BG_STATE'); @@ -25,10 +28,24 @@ const engineReducer = ( } case updateBgState.type: { const newState = { ...state }; + if (action.payload) { - newState.backgroundState[action.payload?.key] = + const newControllerState = Engine.state[action.payload.key as keyof typeof Engine.state]; + + // The BaseControllerV1 controllers modify the original state object on update, + // rather than replacing it as done in BaseControllerV2. + // This introduces two issues: + // - Memoized selectors do not fire on nested objects since the references don't change. + // - Deep comparison selectors do not fire since the cached objects are references to the original + // state object which has been mutated. + // This is resolved by doing a deep clone in this scenario to force an entirely new object. + newState.backgroundState[action.payload?.key] = + legacyControllers.includes(action.payload.key) + ? cloneDeep(newControllerState) + : newControllerState; } + return newState; } default: diff --git a/app/reducers/transaction/index.js b/app/reducers/transaction/index.js index fca004b898d..191f9bfe218 100644 --- a/app/reducers/transaction/index.js +++ b/app/reducers/transaction/index.js @@ -160,6 +160,13 @@ const transactionReducer = (state = initialState, action) => { }, }; } + case 'SET_TRANSACTION_ID': { + const { transactionId } = action; + return { + ...state, + id: transactionId, + }; + } default: return state; } diff --git a/app/selectors/confirmTransaction.test.ts b/app/selectors/confirmTransaction.test.ts new file mode 100644 index 00000000000..06ee8c2f2b9 --- /dev/null +++ b/app/selectors/confirmTransaction.test.ts @@ -0,0 +1,134 @@ +import { mergeGasFeeEstimates } from '@metamask/transaction-controller'; +import { RootState } from '../reducers'; +import { + selectCurrentTransactionGasFeeEstimates, + selectCurrentTransactionMetadata, + selectGasFeeEstimates, +} from './confirmTransaction'; +import { + GAS_ESTIMATE_TYPES, + GasFeeEstimates, +} from '@metamask/gas-fee-controller'; + +jest.mock('@metamask/transaction-controller', () => ({ + ...jest.requireActual('@metamask/transaction-controller'), + mergeGasFeeEstimates: jest.fn(), +})); + +const GAS_FEE_ESTIMATES_MOCK = { low: '1', medium: '2', high: '3' }; + +const TRANSACTION_GAS_FEE_ESTIMATES_MOCK = { + low: { suggestedMaxFeePerGas: '0x1', suggestedMaxPriorityFeePerGas: '0x2' }, + medium: { + suggestedMaxFeePerGas: '0x3', + suggestedMaxPriorityFeePerGas: '0x4', + }, + high: { suggestedMaxFeePerGas: '0x5', suggestedMaxPriorityFeePerGas: '0x6' }, +}; + +describe('Confirm Transaction Selectors', () => { + const mergeGasFeeEstimatesMock = jest.mocked(mergeGasFeeEstimates); + + describe('selectCurrentTransactionMetadata', () => { + it('returns the current transaction metadata', () => { + const transactions = [{ id: 1 }, { id: 2 }, { id: 3, chainId: '123' }]; + const currentTransaction = { id: 3 }; + + const state = { + transaction: currentTransaction, + engine: { + backgroundState: { TransactionController: { transactions } }, + }, + }; + + expect( + selectCurrentTransactionMetadata(state as unknown as RootState), + ).toStrictEqual(transactions[2]); + }); + }); + + describe('selectCurrentTransactionGasFeeEstimates', () => { + it('returns the gas fee estimates from current transaction metadata', () => { + const transactions = [ + { id: 1 }, + { id: 2 }, + { id: 3, chainId: '123', gasFeeEstimates: GAS_FEE_ESTIMATES_MOCK }, + ]; + + const currentTransaction = { id: 3 }; + + const state = { + transaction: currentTransaction, + engine: { + backgroundState: { TransactionController: { transactions } }, + }, + }; + + expect( + selectCurrentTransactionGasFeeEstimates(state as unknown as RootState), + ).toStrictEqual(GAS_FEE_ESTIMATES_MOCK); + }); + }); + + describe('selectGasFeeEstimates', () => { + it('returns GasFeeController estimates if no transaction estimates', () => { + const transactions = [{ id: 1 }, { id: 2 }, { id: 3, chainId: '123' }]; + const currentTransaction = { id: 3 }; + + const state = { + transaction: currentTransaction, + engine: { + backgroundState: { + GasFeeController: { gasFeeEstimates: GAS_FEE_ESTIMATES_MOCK }, + TransactionController: { transactions }, + }, + }, + }; + + expect( + selectGasFeeEstimates(state as unknown as RootState), + ).toStrictEqual(GAS_FEE_ESTIMATES_MOCK); + }); + + it('returns merged estimates if GasFeeController estimates and transaction estimates exist', () => { + const transactions = [ + { id: 1 }, + { id: 2 }, + { + id: 3, + chainId: '123', + gasFeeEstimates: TRANSACTION_GAS_FEE_ESTIMATES_MOCK, + }, + ]; + const currentTransaction = { id: 3 }; + + const state = { + transaction: currentTransaction, + engine: { + backgroundState: { + GasFeeController: { + gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET, + gasFeeEstimates: GAS_FEE_ESTIMATES_MOCK, + }, + TransactionController: { transactions }, + }, + }, + }; + + mergeGasFeeEstimatesMock.mockReturnValue( + TRANSACTION_GAS_FEE_ESTIMATES_MOCK as GasFeeEstimates, + ); + + expect( + selectGasFeeEstimates(state as unknown as RootState), + ).toStrictEqual(TRANSACTION_GAS_FEE_ESTIMATES_MOCK); + + expect(mergeGasFeeEstimatesMock).toHaveBeenCalledTimes(1); + expect(mergeGasFeeEstimatesMock).toHaveBeenCalledWith({ + gasFeeControllerEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET, + gasFeeControllerEstimates: GAS_FEE_ESTIMATES_MOCK, + transactionGasFeeEstimates: TRANSACTION_GAS_FEE_ESTIMATES_MOCK, + }); + }); + }); +}); diff --git a/app/selectors/confirmTransaction.ts b/app/selectors/confirmTransaction.ts new file mode 100644 index 00000000000..366657f3551 --- /dev/null +++ b/app/selectors/confirmTransaction.ts @@ -0,0 +1,61 @@ +import { selectTransactions } from './transactionController'; +import { RootState } from '../reducers'; +import { + selectGasFeeControllerEstimateType, + selectGasFeeControllerEstimates, +} from './gasFeeController'; +import { mergeGasFeeEstimates } from '@metamask/transaction-controller'; +import { createSelector } from 'reselect'; +import { createDeepEqualSelector } from './util'; + +const selectCurrentTransactionId = (state: RootState) => state.transaction?.id; + +export const selectCurrentTransactionMetadata = createSelector( + selectTransactions, + selectCurrentTransactionId, + (transactions, currentTransactionId) => + transactions.find((tx) => tx.id === currentTransactionId), +); + +const selectCurrentTransactionGasFeeEstimatesStrict = createSelector( + selectCurrentTransactionMetadata, + (transactionMetadata) => transactionMetadata?.gasFeeEstimates, +); + +const selectCurrentTransactionGasFeeEstimatesLoaded = createSelector( + selectCurrentTransactionMetadata, + (transactionMetadata) => transactionMetadata?.gasFeeEstimatesLoaded, +); + +export const selectCurrentTransactionGasFeeEstimates = createDeepEqualSelector( + selectCurrentTransactionGasFeeEstimatesStrict, + (gasFeeEstimates) => gasFeeEstimates, +); + +export const selectGasFeeEstimates = createSelector( + selectGasFeeControllerEstimateType, + selectGasFeeControllerEstimates, + selectCurrentTransactionGasFeeEstimates, + ( + gasFeeControllerEstimateType, + gasFeeControllerEstimates, + transactionGasFeeEstimates, + ) => { + if (transactionGasFeeEstimates) { + return mergeGasFeeEstimates({ + gasFeeControllerEstimateType: gasFeeControllerEstimateType as any, + gasFeeControllerEstimates: gasFeeControllerEstimates as any, + transactionGasFeeEstimates, + }); + } + + return gasFeeControllerEstimates; + }, +); + +export const selectTransactionGasFeeEstimates = createSelector( + selectCurrentTransactionGasFeeEstimatesLoaded, + selectGasFeeEstimates, + (transactionGasFeeEstimatesLoaded, gasFeeEstimates) => + transactionGasFeeEstimatesLoaded ? gasFeeEstimates : undefined, +); diff --git a/app/selectors/gasFeeController.test.ts b/app/selectors/gasFeeController.test.ts new file mode 100644 index 00000000000..b0159bb4374 --- /dev/null +++ b/app/selectors/gasFeeController.test.ts @@ -0,0 +1,48 @@ +import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller'; +import { + selectGasFeeControllerEstimateType, + selectGasFeeControllerEstimates, +} from './gasFeeController'; +import { RootState } from '../reducers'; + +describe('GasFeeController Selectors', () => { + describe('selectGasFeeControllerEstimates', () => { + it('returns the gas estimate type from GasFeeController state', () => { + const gasFeeEstimates = { low: '1', medium: '2', high: '3' }; + + const state = { + engine: { + backgroundState: { + GasFeeController: { + gasFeeEstimates, + }, + }, + }, + }; + + expect( + selectGasFeeControllerEstimates(state as unknown as RootState), + ).toStrictEqual(gasFeeEstimates); + }); + }); + + describe('selectGasFeeControllerEstimateType', () => { + it('returns the gas estimate type from GasFeeController state', () => { + const gasEstimateType = GAS_ESTIMATE_TYPES.FEE_MARKET; + + const state = { + engine: { + backgroundState: { + GasFeeController: { + gasEstimateType, + }, + }, + }, + }; + + expect( + selectGasFeeControllerEstimateType(state as RootState), + ).toStrictEqual(gasEstimateType); + }); + }); +}); diff --git a/app/selectors/gasFeeController.ts b/app/selectors/gasFeeController.ts new file mode 100644 index 00000000000..6e7da2e9bb3 --- /dev/null +++ b/app/selectors/gasFeeController.ts @@ -0,0 +1,21 @@ +import { createSelector } from 'reselect'; +import { RootState } from '../reducers'; +import { createDeepEqualSelector } from './util'; + +const selectGasFeeControllerState = (state: RootState) => + state.engine.backgroundState.GasFeeController; + +const selectGasFeeControllerEstimatesStrict = createSelector( + selectGasFeeControllerState, + (gasFeeControllerState) => gasFeeControllerState.gasFeeEstimates, +); + +export const selectGasFeeControllerEstimates = createDeepEqualSelector( + selectGasFeeControllerEstimatesStrict, + (gasFeeEstimates) => gasFeeEstimates, +); + +export const selectGasFeeControllerEstimateType = createSelector( + selectGasFeeControllerState, + (gasFeeControllerState) => gasFeeControllerState.gasEstimateType, +); diff --git a/app/selectors/transactionController.test.ts b/app/selectors/transactionController.test.ts new file mode 100644 index 00000000000..b4b78876f4e --- /dev/null +++ b/app/selectors/transactionController.test.ts @@ -0,0 +1,24 @@ +import { RootState } from '../reducers'; +import { selectTransactions } from './transactionController'; + +describe('TransactionController Selectors', () => { + describe('selectTransactions', () => { + it('returns transactions from TransactionController state', () => { + const transactions = [{ id: 1 }, { id: 2 }]; + + const state = { + engine: { + backgroundState: { + TransactionController: { + transactions, + }, + }, + }, + }; + + expect(selectTransactions(state as unknown as RootState)).toStrictEqual( + transactions, + ); + }); + }); +}); diff --git a/app/selectors/transactionController.ts b/app/selectors/transactionController.ts new file mode 100644 index 00000000000..a86514680af --- /dev/null +++ b/app/selectors/transactionController.ts @@ -0,0 +1,17 @@ +import { createSelector } from 'reselect'; +import { RootState } from '../reducers'; +import { createDeepEqualSelector } from './util'; + +const selectTransactionControllerState = (state: RootState) => + state.engine.backgroundState.TransactionController; + +const selectTransactionsStrict = createSelector( + selectTransactionControllerState, + (transactionControllerState) => transactionControllerState.transactions, +); + +// eslint-disable-next-line import/prefer-default-export +export const selectTransactions = createDeepEqualSelector( + selectTransactionsStrict, + (transactions) => transactions, +); diff --git a/app/selectors/util.ts b/app/selectors/util.ts new file mode 100644 index 00000000000..ce59abd821c --- /dev/null +++ b/app/selectors/util.ts @@ -0,0 +1,8 @@ +import { isEqual } from 'lodash'; +import { createSelectorCreator, defaultMemoize } from 'reselect'; + +// eslint-disable-next-line import/prefer-default-export +export const createDeepEqualSelector = createSelectorCreator( + defaultMemoize, + isEqual, +); diff --git a/app/util/confirmation/signatureUtils.js b/app/util/confirmation/signatureUtils.js index f6331b044a8..7365b2464df 100644 --- a/app/util/confirmation/signatureUtils.js +++ b/app/util/confirmation/signatureUtils.js @@ -1,6 +1,5 @@ import Engine from '../../core/Engine'; -import { MetaMetricsEvents } from '../../core/Analytics'; -import AnalyticsV2 from '../analyticsV2'; +import { MetaMetrics, MetaMetricsEvents } from '../../core/Analytics'; import { getAddressAccountType } from '../address'; import NotificationManager from '../../core/NotificationManager'; import { WALLET_CONNECT_ORIGIN } from '../walletconnect'; @@ -95,7 +94,7 @@ export const handleSignatureAction = async ( ) => { await onAction(); showWalletConnectNotification(messageParams, confirmation); - AnalyticsV2.trackEvent( + MetaMetrics.getInstance().trackEvent( confirmation ? MetaMetricsEvents.SIGNATURE_APPROVED : MetaMetricsEvents.SIGNATURE_REJECTED, diff --git a/app/util/metrics/index.ts b/app/util/metrics/index.ts index 3bc681cb1c7..6942d325ebc 100644 --- a/app/util/metrics/index.ts +++ b/app/util/metrics/index.ts @@ -1,7 +1,9 @@ import DeviceAnalyticsMetaData from './DeviceAnalyticsMetaData/generateDeviceAnalyticsMetaData'; import UserSettingsAnalyticsMetaData from './UserSettingsAnalyticsMetaData/generateUserProfileAnalyticsMetaData'; import TrackAfterInteractions from './TrackAfterInteraction/trackAfterInteractions'; +import trackDappVisitedEvent from './trackDappVisitedEvent'; export default DeviceAnalyticsMetaData; export { UserSettingsAnalyticsMetaData }; export { TrackAfterInteractions }; +export { trackDappVisitedEvent }; diff --git a/app/analytics/index.test.ts b/app/util/metrics/trackDappVisited/index.test.ts similarity index 86% rename from app/analytics/index.test.ts rename to app/util/metrics/trackDappVisited/index.test.ts index fc3a9f375de..8c5896d68a6 100644 --- a/app/analytics/index.test.ts +++ b/app/util/metrics/trackDappVisited/index.test.ts @@ -1,15 +1,17 @@ -import { trackDappVisitedEvent } from './index'; -import { MetaMetricsEvents } from '../core/Analytics'; -import AnalyticsV2 from '../util/analyticsV2'; +import trackDappVisitedEvent from './index'; +import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics'; -// Mock AnalyticsV2 -jest.mock('../util/analyticsV2', () => ({ +jest.mock('../../../core/Analytics/MetaMetrics'); + +const mockMetrics = { trackEvent: jest.fn(), -})); +}; + +(MetaMetrics.getInstance as jest.Mock).mockReturnValue(mockMetrics); // Mock store.getState let mockGetState: jest.Mock; -jest.mock('../store', () => { +jest.mock('../../../store', () => { mockGetState = jest.fn(); mockGetState.mockImplementation(() => ({ browser: { @@ -34,7 +36,7 @@ jest.mock('../store', () => { describe('trackDappVisitedEvent', () => { afterEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); it('should track with isFirstVisit = true', () => { @@ -63,7 +65,7 @@ describe('trackDappVisitedEvent', () => { numberOfConnectedAccounts: 1, }); - expect(AnalyticsV2.trackEvent).toBeCalledWith( + expect(mockMetrics.trackEvent).toBeCalledWith( MetaMetricsEvents.DAPP_VISITED, expectedMetrics, ); @@ -95,7 +97,7 @@ describe('trackDappVisitedEvent', () => { numberOfConnectedAccounts: 1, }); - expect(AnalyticsV2.trackEvent).toBeCalledWith( + expect(mockMetrics.trackEvent).toBeCalledWith( MetaMetricsEvents.DAPP_VISITED, expectedMetrics, ); @@ -127,7 +129,7 @@ describe('trackDappVisitedEvent', () => { numberOfConnectedAccounts: 1, }); - expect(AnalyticsV2.trackEvent).toBeCalledWith( + expect(mockMetrics.trackEvent).toBeCalledWith( MetaMetricsEvents.DAPP_VISITED, expectedMetrics, ); @@ -159,7 +161,7 @@ describe('trackDappVisitedEvent', () => { numberOfConnectedAccounts: 1, }); - expect(AnalyticsV2.trackEvent).toBeCalledWith( + expect(mockMetrics.trackEvent).toBeCalledWith( MetaMetricsEvents.DAPP_VISITED, expectedMetrics, ); diff --git a/app/analytics/index.ts b/app/util/metrics/trackDappVisited/index.ts similarity index 50% rename from app/analytics/index.ts rename to app/util/metrics/trackDappVisited/index.ts index 10d35787e4d..58993a279af 100644 --- a/app/analytics/index.ts +++ b/app/util/metrics/trackDappVisited/index.ts @@ -1,18 +1,18 @@ -import { selectIdentities } from '../selectors/preferencesController'; -import { store } from '../store'; -import { MetaMetricsEvents } from '../core/Analytics'; -import AnalyticsV2 from '../util/analyticsV2'; -import { addToVisitedDapp } from '../actions/browser'; +import { store } from '../../../store'; +import { selectIdentities } from '../../../selectors/preferencesController'; +import { addToVisitedDapp } from '../../../actions/browser'; +import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics'; /** * Tracks Dapp visited event * - * @param hostname - Hostname of the Dapp - * @param numberOfConnectedAccounts - Number of connected accounts that are connected to the Dapp + * This is used to track when a user visits a Dapp in the in-app browser + * + * @param params - The parameter object for the tracking function + * @param params.hostname - Hostname of the Dapp + * @param params.numberOfConnectedAccounts - Number of connected accounts that are connected to the Dapp */ -// This file will export more events in the future. -// eslint-disable-next-line import/prefer-default-export -export const trackDappVisitedEvent = ({ +const trackDappVisitedEvent = ({ hostname, numberOfConnectedAccounts, }: { @@ -28,11 +28,12 @@ export const trackDappVisitedEvent = ({ // Add Dapp hostname to visited dapps store.dispatch(addToVisitedDapp(hostname)); - // Track DAPP_VISITED event - AnalyticsV2.trackEvent(MetaMetricsEvents.DAPP_VISITED, { + MetaMetrics.getInstance().trackEvent(MetaMetricsEvents.DAPP_VISITED, { is_first_visit: isFirstVisit, number_of_accounts: numberOfWalletAccounts, number_of_accounts_connected: numberOfConnectedAccounts, source: 'in-app browser', }); }; + +export default trackDappVisitedEvent; diff --git a/app/util/metrics/trackDappVisitedEvent/index.test.ts b/app/util/metrics/trackDappVisitedEvent/index.test.ts new file mode 100644 index 00000000000..8c5896d68a6 --- /dev/null +++ b/app/util/metrics/trackDappVisitedEvent/index.test.ts @@ -0,0 +1,169 @@ +import trackDappVisitedEvent from './index'; +import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics'; + +jest.mock('../../../core/Analytics/MetaMetrics'); + +const mockMetrics = { + trackEvent: jest.fn(), +}; + +(MetaMetrics.getInstance as jest.Mock).mockReturnValue(mockMetrics); + +// Mock store.getState +let mockGetState: jest.Mock; +jest.mock('../../../store', () => { + mockGetState = jest.fn(); + mockGetState.mockImplementation(() => ({ + browser: { + visitedDappsByHostname: {}, + }, + engine: { + backgroundState: { + PreferencesController: { + identities: { '0x1': true, '0x2': true }, + }, + }, + }, + })); + + return { + store: { + getState: mockGetState, + dispatch: jest.fn(), + }, + }; +}); + +describe('trackDappVisitedEvent', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should track with isFirstVisit = true', () => { + mockGetState.mockImplementation(() => ({ + browser: { + visitedDappsByHostname: {}, + }, + engine: { + backgroundState: { + PreferencesController: { + identities: { '0x1': true, '0x2': true }, + }, + }, + }, + })); + + const expectedMetrics = { + is_first_visit: true, + number_of_accounts: 2, + number_of_accounts_connected: 1, + source: 'in-app browser', + }; + + trackDappVisitedEvent({ + hostname: 'uniswap.org', + numberOfConnectedAccounts: 1, + }); + + expect(mockMetrics.trackEvent).toBeCalledWith( + MetaMetricsEvents.DAPP_VISITED, + expectedMetrics, + ); + }); + + it('should track with isFirstVisit = false', () => { + mockGetState.mockImplementation(() => ({ + browser: { + visitedDappsByHostname: { 'uniswap.org': true }, + }, + engine: { + backgroundState: { + PreferencesController: { + identities: { '0x1': true, '0x2': true }, + }, + }, + }, + })); + + const expectedMetrics = { + is_first_visit: false, + number_of_accounts: 2, + number_of_accounts_connected: 1, + source: 'in-app browser', + }; + + trackDappVisitedEvent({ + hostname: 'uniswap.org', + numberOfConnectedAccounts: 1, + }); + + expect(mockMetrics.trackEvent).toBeCalledWith( + MetaMetricsEvents.DAPP_VISITED, + expectedMetrics, + ); + }); + + it('should track with the correct number of connected accounts', () => { + mockGetState.mockImplementation(() => ({ + browser: { + visitedDappsByHostname: { 'uniswap.org': true }, + }, + engine: { + backgroundState: { + PreferencesController: { + identities: { '0x1': true, '0x2': true }, + }, + }, + }, + })); + + const expectedMetrics = { + is_first_visit: false, + number_of_accounts: 2, + number_of_accounts_connected: 1, + source: 'in-app browser', + }; + + trackDappVisitedEvent({ + hostname: 'uniswap.org', + numberOfConnectedAccounts: 1, + }); + + expect(mockMetrics.trackEvent).toBeCalledWith( + MetaMetricsEvents.DAPP_VISITED, + expectedMetrics, + ); + }); + + it('should track with the correct number of wallet accounts', () => { + mockGetState.mockImplementation(() => ({ + browser: { + visitedDappsByHostname: { 'uniswap.org': true }, + }, + engine: { + backgroundState: { + PreferencesController: { + identities: { '0x1': true }, + }, + }, + }, + })); + + const expectedMetrics = { + is_first_visit: false, + number_of_accounts: 1, + number_of_accounts_connected: 1, + source: 'in-app browser', + }; + + trackDappVisitedEvent({ + hostname: 'uniswap.org', + numberOfConnectedAccounts: 1, + }); + + expect(mockMetrics.trackEvent).toBeCalledWith( + MetaMetricsEvents.DAPP_VISITED, + expectedMetrics, + ); + }); +}); diff --git a/app/util/metrics/trackDappVisitedEvent/index.ts b/app/util/metrics/trackDappVisitedEvent/index.ts new file mode 100644 index 00000000000..58993a279af --- /dev/null +++ b/app/util/metrics/trackDappVisitedEvent/index.ts @@ -0,0 +1,39 @@ +import { store } from '../../../store'; +import { selectIdentities } from '../../../selectors/preferencesController'; +import { addToVisitedDapp } from '../../../actions/browser'; +import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics'; + +/** + * Tracks Dapp visited event + * + * This is used to track when a user visits a Dapp in the in-app browser + * + * @param params - The parameter object for the tracking function + * @param params.hostname - Hostname of the Dapp + * @param params.numberOfConnectedAccounts - Number of connected accounts that are connected to the Dapp + */ +const trackDappVisitedEvent = ({ + hostname, + numberOfConnectedAccounts, +}: { + hostname: string; + numberOfConnectedAccounts: number; +}) => { + const visitedDappsByHostname = + store.getState().browser.visitedDappsByHostname; + const isFirstVisit = !visitedDappsByHostname[hostname]; + const accountByAddress = selectIdentities(store.getState()); + const numberOfWalletAccounts = Object.keys(accountByAddress).length; + + // Add Dapp hostname to visited dapps + store.dispatch(addToVisitedDapp(hostname)); + + MetaMetrics.getInstance().trackEvent(MetaMetricsEvents.DAPP_VISITED, { + is_first_visit: isFirstVisit, + number_of_accounts: numberOfWalletAccounts, + number_of_accounts_connected: numberOfConnectedAccounts, + source: 'in-app browser', + }); +}; + +export default trackDappVisitedEvent; diff --git a/app/util/middlewares.js b/app/util/middlewares.js index fe60f6c4f0b..d2378482551 100644 --- a/app/util/middlewares.js +++ b/app/util/middlewares.js @@ -1,5 +1,5 @@ import Logger from './Logger'; -import { trackErrorAsAnalytics } from './analyticsV2'; +import trackErrorAsAnalytics from './metrics/TrackError/trackErrorAsAnalytics'; /** * List of rpc errors caused by the user rejecting a certain action. diff --git a/app/util/sentry/utils.js b/app/util/sentry/utils.js index ffcc573e8a8..78d0c48465c 100644 --- a/app/util/sentry/utils.js +++ b/app/util/sentry/utils.js @@ -83,8 +83,25 @@ function removeDeviceName(report) { report.contexts.device.name = null; } +/** + * Removes SES from the Sentry error event stack trace. + * By default, SES is shown as the top level frame, which can obscure errors. + * We filter it out by identifying the SES stack trace frame simply by 'filename', + * since the 'context_line' is rather verbose. + * @param {*} report - the error event + */ +function removeSES(report) { + const stacktraceFrames = report.exception.values[0].stacktrace.frames; + const filteredFrames = stacktraceFrames.filter( + (frame) => frame.filename !== 'app:///ses.cjs', + ); + report.exception.values[0].stacktrace.frames = filteredFrames; +} + function rewriteReport(report) { try { + // filter out SES from error stack trace + removeSES(report); // simplify certain complex error messages (e.g. Ethjs) simplifyErrorMessages(report); // remove urls from error message diff --git a/patches/@metamask+transaction-controller+6.1.0.patch b/patches/@metamask+transaction-controller+6.1.0.patch index 6a42705ee04..6dc9dd5e7cb 100644 --- a/patches/@metamask+transaction-controller+6.1.0.patch +++ b/patches/@metamask+transaction-controller+6.1.0.patch @@ -1,6 +1,6 @@ diff --git a/node_modules/@metamask/transaction-controller/dist/.patch.txt b/node_modules/@metamask/transaction-controller/dist/.patch.txt new file mode 100644 -index 0000000..e8035ba +index 0000000..550de56 --- /dev/null +++ b/node_modules/@metamask/transaction-controller/dist/.patch.txt @@ -0,0 +1,7 @@ @@ -367,10 +367,10 @@ index 0000000..18bc7af +//# sourceMappingURL=IncomingTransactionHelper.js.map \ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/TransactionController.d.ts b/node_modules/@metamask/transaction-controller/dist/TransactionController.d.ts -index 2c9675f..46df86c 100644 +index 2c9675f..7aeceec 100644 --- a/node_modules/@metamask/transaction-controller/dist/TransactionController.d.ts +++ b/node_modules/@metamask/transaction-controller/dist/TransactionController.d.ts -@@ -2,10 +2,11 @@ +@@ -2,10 +2,12 @@ import { EventEmitter } from 'events'; import Common from '@ethereumjs/common'; import { TypedTransaction } from '@ethereumjs/tx'; @@ -380,11 +380,12 @@ index 2c9675f..46df86c 100644 -import { AcceptRequest as AcceptApprovalRequest, AddApprovalRequest, RejectRequest as RejectApprovalRequest } from '@metamask/approval-controller'; +import { AddApprovalRequest } from '@metamask/approval-controller'; +import { NonceLock } from 'nonce-tracker/dist/NonceTracker'; ++import { GasFeeState } from '@metamask/gas-fee-controller'; +import { SecurityAlertResponse, SubmitHistoryEntry, Transaction, TransactionMeta, WalletDevice } from './types'; /** * @type Result * @property result - Promise resolving to a new transaction hash -@@ -15,44 +16,6 @@ export interface Result { +@@ -15,44 +17,6 @@ export interface Result { result: Promise; transactionMeta: TransactionMeta; } @@ -429,7 +430,7 @@ index 2c9675f..46df86c 100644 export interface GasPriceValue { gasPrice: string; } -@@ -60,115 +23,6 @@ export interface FeeMarketEIP1559Values { +@@ -60,115 +24,6 @@ export interface FeeMarketEIP1559Values { maxFeePerGas: string; maxPriorityFeePerGas: string; } @@ -545,7 +546,7 @@ index 2c9675f..46df86c 100644 /** * @type TransactionConfig * -@@ -205,11 +59,15 @@ export interface TransactionState extends BaseState { +@@ -205,11 +60,15 @@ export interface TransactionState extends BaseState { methodData: { [key: string]: MethodData; }; @@ -562,7 +563,7 @@ index 2c9675f..46df86c 100644 /** * Multiplier used to determine a transaction's increased gas fee during speed up */ -@@ -221,7 +79,7 @@ declare const controllerName = "TransactionController"; +@@ -221,7 +80,7 @@ declare const controllerName = "TransactionController"; /** * The external actions available to the {@link TransactionController}. */ @@ -571,8 +572,12 @@ index 2c9675f..46df86c 100644 /** * The messenger of the {@link TransactionController}. */ -@@ -238,19 +96,9 @@ export declare class TransactionController extends BaseController Promise; getNetworkState: () => NetworkState; + getSelectedAddress: () => string; + incomingTransactions: { @@ -631,7 +638,7 @@ index 2c9675f..46df86c 100644 }, config?: Partial, state?: Partial); /** * Starts a new polling interval. -@@ -301,11 +162,20 @@ export declare class TransactionController extends BaseController:unapproved` hub event will be emitted once added. * * @param transaction - The transaction object to add. @@ -655,7 +662,7 @@ index 2c9675f..46df86c 100644 prepareUnsignedEthTx(txParams: Record): TypedTransaction; /** * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for -@@ -317,22 +187,6 @@ export declare class TransactionController extends BaseController:finished` hub event. -@@ -374,6 +228,13 @@ export declare class TransactionController extends BaseController { @@ -925,7 +936,7 @@ index 3edd9c2..28a1c63 100644 /** * EventEmitter instance used to listen to specific transactional events */ -@@ -126,6 +84,8 @@ class TransactionController extends base_controller_1.BaseController { +@@ -126,19 +87,51 @@ class TransactionController extends base_controller_1.BaseController { this.defaultState = { methodData: {}, transactions: [], @@ -934,7 +945,8 @@ index 3edd9c2..28a1c63 100644 }; this.initialize(); this.provider = provider; -@@ -133,12 +93,29 @@ class TransactionController extends base_controller_1.BaseController { + this.messagingSystem = messenger; ++ this.getGasFeeEstimates = getGasFeeEstimates; this.getNetworkState = getNetworkState; this.ethQuery = new eth_query_1.default(provider); this.registry = new eth_method_registry_1.default({ provider }); @@ -960,13 +972,25 @@ index 3edd9c2..28a1c63 100644 + }), + transactionLimit: this.config.txHistoryLimit, + updateTransactions: incomingTransactions.updateTransactions, - }); ++ }); + this.incomingTransactionHelper.hub.on('transactions', this.onIncomingTransactions.bind(this)); + this.incomingTransactionHelper.hub.on('updatedLastFetchedBlockNumbers', this.onUpdatedLastFetchedBlockNumbers.bind(this)); ++ this.gasFeeFlows = this.getGasFeeFlows(); ++ const gasFeePoller = new GasFeePoller_1.GasFeePoller({ ++ gasFeeFlows: this.gasFeeFlows, ++ getChainIds: () => [this.getNetworkState().providerConfig.chainId], ++ getEthQuery: () => this.ethQuery, ++ getGasFeeControllerEstimates: this.getGasFeeEstimates, ++ getTransactions: () => this.state.transactions, ++ onStateChange: (listener) => { ++ this.subscribe(listener); ++ }, + }); ++ gasFeePoller.hub.on('transaction-updated', this.updateTransaction.bind(this)); onNetworkStateChange(() => { this.ethQuery = new eth_query_1.default(this.provider); this.registry = new eth_method_registry_1.default({ provider: this.provider }); -@@ -146,7 +123,7 @@ class TransactionController extends base_controller_1.BaseController { +@@ -146,7 +139,7 @@ class TransactionController extends base_controller_1.BaseController { this.poll(); } failTransaction(transactionMeta, error) { @@ -975,7 +999,7 @@ index 3edd9c2..28a1c63 100644 this.updateTransaction(newTransactionMeta); this.hub.emit(`${transactionMeta.id}:finished`, newTransactionMeta); } -@@ -157,43 +134,6 @@ class TransactionController extends base_controller_1.BaseController { +@@ -157,43 +150,6 @@ class TransactionController extends base_controller_1.BaseController { return { registryMethod, parsedRegistryMethod }; }); } @@ -1019,7 +1043,7 @@ index 3edd9c2..28a1c63 100644 /** * Starts a new polling interval. * -@@ -241,11 +181,13 @@ class TransactionController extends base_controller_1.BaseController { +@@ -241,11 +197,13 @@ class TransactionController extends base_controller_1.BaseController { * if not provided. If A `:unapproved` hub event will be emitted once added. * * @param transaction - The transaction object to add. @@ -1036,7 +1060,7 @@ index 3edd9c2..28a1c63 100644 return __awaiter(this, void 0, void 0, function* () { const { providerConfig, networkId } = this.getNetworkState(); const { transactions } = this.state; -@@ -256,11 +198,12 @@ class TransactionController extends base_controller_1.BaseController { +@@ -256,11 +214,12 @@ class TransactionController extends base_controller_1.BaseController { networkID: networkId !== null && networkId !== void 0 ? networkId : undefined, chainId: providerConfig.chainId, origin, @@ -1050,7 +1074,7 @@ index 3edd9c2..28a1c63 100644 }; try { const { gas, estimateGasError } = yield this.estimateGas(transaction); -@@ -271,28 +214,24 @@ class TransactionController extends base_controller_1.BaseController { +@@ -271,28 +230,24 @@ class TransactionController extends base_controller_1.BaseController { this.failTransaction(transactionMeta, error); return Promise.reject(error); } @@ -1094,7 +1118,7 @@ index 3edd9c2..28a1c63 100644 }); } prepareUnsignedEthTx(txParams) { -@@ -312,7 +251,9 @@ class TransactionController extends base_controller_1.BaseController { +@@ -312,7 +267,9 @@ class TransactionController extends base_controller_1.BaseController { */ getCommonConfiguration() { const { networkId, providerConfig: { type: chain, chainId, nickname: name }, } = this.getNetworkState(); @@ -1105,7 +1129,7 @@ index 3edd9c2..28a1c63 100644 return new common_1.default({ chain, hardfork: HARDFORK }); } const customChainParams = { -@@ -322,104 +263,6 @@ class TransactionController extends base_controller_1.BaseController { +@@ -322,104 +279,6 @@ class TransactionController extends base_controller_1.BaseController { }; return common_1.default.forCustomChain(controller_utils_1.NetworkType.mainnet, customChainParams, HARDFORK); } @@ -1210,7 +1234,7 @@ index 3edd9c2..28a1c63 100644 /** * Attempts to cancel a transaction based on its ID by setting its status to "rejected" * and emitting a `:finished` hub event. -@@ -482,10 +325,9 @@ class TransactionController extends base_controller_1.BaseController { +@@ -482,10 +341,9 @@ class TransactionController extends base_controller_1.BaseController { const unsignedEthTx = this.prepareUnsignedEthTx(txParams); const signedTx = yield this.sign(unsignedEthTx, transactionMeta.transaction.from); const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize()); @@ -1223,7 +1247,7 @@ index 3edd9c2..28a1c63 100644 }); } /** -@@ -535,9 +377,7 @@ class TransactionController extends base_controller_1.BaseController { +@@ -535,9 +393,7 @@ class TransactionController extends base_controller_1.BaseController { const unsignedEthTx = this.prepareUnsignedEthTx(txParams); const signedTx = yield this.sign(unsignedEthTx, transactionMeta.transaction.from); const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize()); @@ -1234,7 +1258,16 @@ index 3edd9c2..28a1c63 100644 const baseTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { id: (0, uuid_1.v1)(), time: Date.now(), transactionHash }); const newTransactionMeta = newMaxFeePerGas && newMaxPriorityFeePerGas ? Object.assign(Object.assign({}, baseTransactionMeta), { transaction: Object.assign(Object.assign({}, transactionMeta.transaction), { maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas }) }) : Object.assign(Object.assign({}, baseTransactionMeta), { transaction: Object.assign(Object.assign({}, transactionMeta.transaction), { gasPrice: newGasPrice }) }); -@@ -661,6 +501,23 @@ class TransactionController extends base_controller_1.BaseController { +@@ -596,6 +452,8 @@ class TransactionController extends base_controller_1.BaseController { + } + catch (error) { + estimateGasError = utils_1.ESTIMATE_GAS_ERROR; ++ // Fallback to 95% of the block gasLimit. ++ gasHex = estimatedTransaction.gas; + } + // 4. Pad estimated gas without exceeding the most recent block gasLimit. If the network is a + // a custom network then return the eth_estimateGas value. +@@ -661,6 +519,23 @@ class TransactionController extends base_controller_1.BaseController { transactions[index] = transactionMeta; this.update({ transactions: this.trimTransactionsForState(transactions) }); } @@ -1258,7 +1291,7 @@ index 3edd9c2..28a1c63 100644 /** * Removes all transactions from state, optionally based on the current network. * -@@ -686,67 +543,15 @@ class TransactionController extends base_controller_1.BaseController { +@@ -686,67 +561,15 @@ class TransactionController extends base_controller_1.BaseController { }); } /** @@ -1332,7 +1365,7 @@ index 3edd9c2..28a1c63 100644 }); } /** -@@ -765,7 +570,9 @@ class TransactionController extends base_controller_1.BaseController { +@@ -765,7 +588,9 @@ class TransactionController extends base_controller_1.BaseController { */ trimTransactionsForState(transactions) { const nonceNetworkSet = new Set(); @@ -1343,7 +1376,7 @@ index 3edd9c2..28a1c63 100644 const { chainId, networkID, status, transaction, time } = tx; if (transaction) { const key = `${transaction.nonce}-${chainId ? (0, controller_utils_1.convertHexToDecimal)(chainId) : networkID}-${new Date(time).toDateString()}`; -@@ -780,7 +587,7 @@ class TransactionController extends base_controller_1.BaseController { +@@ -780,7 +605,7 @@ class TransactionController extends base_controller_1.BaseController { } return false; }); @@ -1352,7 +1385,7 @@ index 3edd9c2..28a1c63 100644 return txsToKeep; } /** -@@ -790,10 +597,10 @@ class TransactionController extends base_controller_1.BaseController { +@@ -790,10 +615,10 @@ class TransactionController extends base_controller_1.BaseController { * @returns Whether the transaction is in a final state. */ isFinalState(status) { @@ -1367,7 +1400,7 @@ index 3edd9c2..28a1c63 100644 } /** * Method to verify the state of a transaction using the Blockchain as a source of truth. -@@ -805,7 +612,7 @@ class TransactionController extends base_controller_1.BaseController { +@@ -805,7 +630,7 @@ class TransactionController extends base_controller_1.BaseController { return __awaiter(this, void 0, void 0, function* () { const { status, transactionHash } = meta; switch (status) { @@ -1376,7 +1409,7 @@ index 3edd9c2..28a1c63 100644 const txReceipt = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionReceipt', [ transactionHash, ]); -@@ -822,7 +629,7 @@ class TransactionController extends base_controller_1.BaseController { +@@ -822,7 +647,7 @@ class TransactionController extends base_controller_1.BaseController { return [meta, false]; } return [meta, true]; @@ -1385,7 +1418,7 @@ index 3edd9c2..28a1c63 100644 const txObj = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionByHash', [ transactionHash, ]); -@@ -837,9 +644,17 @@ class TransactionController extends base_controller_1.BaseController { +@@ -837,9 +662,17 @@ class TransactionController extends base_controller_1.BaseController { } /* istanbul ignore next */ if (txObj === null || txObj === void 0 ? void 0 : txObj.blockNumber) { @@ -1406,7 +1439,7 @@ index 3edd9c2..28a1c63 100644 } return [meta, false]; default: -@@ -868,128 +683,233 @@ class TransactionController extends base_controller_1.BaseController { +@@ -868,128 +701,236 @@ class TransactionController extends base_controller_1.BaseController { return Number(txReceipt.status) === 0; }); } @@ -1735,10 +1768,12 @@ index 3edd9c2..28a1c63 100644 + if (submitHistory.length > SUBMIT_HISTORY_LIMIT) { + submitHistory.pop(); } -- } ++ this.update({ submitHistory }); + } - getApprovalId(txMeta) { - return String(txMeta.id); -+ this.update({ submitHistory }); ++ getGasFeeFlows() { ++ return [new LineaGasFeeFlow_1.LineaGasFeeFlow()]; } } exports.TransactionController = TransactionController; @@ -2203,14 +2238,496 @@ index 0000000..0757847 +} +//# sourceMappingURL=etherscan.js.map \ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.d.ts b/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.d.ts +new file mode 100644 +index 0000000..325802b +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.d.ts +@@ -0,0 +1,10 @@ ++import type { GasFeeFlow, GasFeeFlowRequest, GasFeeFlowResponse, TransactionMeta } from '../types'; ++/** ++ * The standard implementation of a gas fee flow that obtains gas fee estimates using only the GasFeeController. ++ */ ++export declare class DefaultGasFeeFlow implements GasFeeFlow { ++ #private; ++ matchesTransaction(_transactionMeta: TransactionMeta): boolean; ++ getGasFees(request: GasFeeFlowRequest): Promise; ++} ++//# sourceMappingURL=DefaultGasFeeFlow.d.ts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.d.ts.map b/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.d.ts.map +new file mode 100644 +index 0000000..1a801e5 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.d.ts.map +@@ -0,0 +1 @@ ++{"version":3,"file":"DefaultGasFeeFlow.d.ts","sourceRoot":"","sources":["../../src/gas-flows/DefaultGasFeeFlow.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAGV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAChB,MAAM,UAAU,CAAC;AAiBlB;;GAEG;AACH,qBAAa,iBAAkB,YAAW,UAAU;;IAClD,kBAAkB,CAAC,gBAAgB,EAAE,eAAe,GAAG,OAAO;IAIxD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CA4E1E"} +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.js b/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.js +new file mode 100644 +index 0000000..2b4ffd9 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.js +@@ -0,0 +1,79 @@ ++"use strict"; ++var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { ++ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } ++ return new (P || (P = Promise))(function (resolve, reject) { ++ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } ++ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } ++ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } ++ step((generator = generator.apply(thisArg, _arguments || [])).next()); ++ }); ++}; ++var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); ++ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); ++ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); ++}; ++var _DefaultGasFeeFlow_instances, _DefaultGasFeeFlow_getEstimateLevel, _DefaultGasFeeFlow_getFeeMarketLevel, _DefaultGasFeeFlow_getLegacyLevel, _DefaultGasFeeFlow_gweiDecimalToWeiHex; ++Object.defineProperty(exports, "__esModule", { value: true }); ++exports.DefaultGasFeeFlow = void 0; ++const gas_fee_controller_1 = require("@metamask/gas-fee-controller"); ++const utils_1 = require("@metamask/utils"); ++const controller_utils_1 = require("@metamask/controller-utils"); ++const logger_1 = require("../logger"); ++const types_1 = require("../types"); ++const log = (0, utils_1.createModuleLogger)(logger_1.projectLogger, 'default-gas-fee-flow'); ++/** ++ * The standard implementation of a gas fee flow that obtains gas fee estimates using only the GasFeeController. ++ */ ++class DefaultGasFeeFlow { ++ constructor() { ++ _DefaultGasFeeFlow_instances.add(this); ++ } ++ matchesTransaction(_transactionMeta) { ++ return true; ++ } ++ getGasFees(request) { ++ return __awaiter(this, void 0, void 0, function* () { ++ const { getGasFeeControllerEstimates } = request; ++ const { gasEstimateType, gasFeeEstimates } = yield getGasFeeControllerEstimates(); ++ if (gasEstimateType === gas_fee_controller_1.GAS_ESTIMATE_TYPES.FEE_MARKET) { ++ log('Using fee market estimates', gasFeeEstimates); ++ } ++ else if (gasEstimateType === gas_fee_controller_1.GAS_ESTIMATE_TYPES.LEGACY) { ++ log('Using legacy estimates', gasFeeEstimates); ++ } ++ else { ++ throw new Error(`'No gas fee estimates available`); ++ } ++ const estimates = Object.values(types_1.GasFeeEstimateLevel).reduce((result, level) => (Object.assign(Object.assign({}, result), { [level]: __classPrivateFieldGet(this, _DefaultGasFeeFlow_instances, "m", _DefaultGasFeeFlow_getEstimateLevel).call(this, { ++ gasEstimateType, ++ gasFeeEstimates, ++ level, ++ }) })), {}); ++ return { estimates }; ++ }); ++ } ++} ++exports.DefaultGasFeeFlow = DefaultGasFeeFlow; ++_DefaultGasFeeFlow_instances = new WeakSet(), _DefaultGasFeeFlow_getEstimateLevel = function _DefaultGasFeeFlow_getEstimateLevel({ gasEstimateType, gasFeeEstimates, level, }) { ++ if (gasEstimateType === gas_fee_controller_1.GAS_ESTIMATE_TYPES.FEE_MARKET) { ++ return __classPrivateFieldGet(this, _DefaultGasFeeFlow_instances, "m", _DefaultGasFeeFlow_getFeeMarketLevel).call(this, gasFeeEstimates, level); ++ } ++ return __classPrivateFieldGet(this, _DefaultGasFeeFlow_instances, "m", _DefaultGasFeeFlow_getLegacyLevel).call(this, gasFeeEstimates, level); ++}, _DefaultGasFeeFlow_getFeeMarketLevel = function _DefaultGasFeeFlow_getFeeMarketLevel(gasFeeEstimates, level) { ++ const maxFeePerGas = __classPrivateFieldGet(this, _DefaultGasFeeFlow_instances, "m", _DefaultGasFeeFlow_gweiDecimalToWeiHex).call(this, gasFeeEstimates[level].suggestedMaxFeePerGas); ++ const maxPriorityFeePerGas = __classPrivateFieldGet(this, _DefaultGasFeeFlow_instances, "m", _DefaultGasFeeFlow_gweiDecimalToWeiHex).call(this, gasFeeEstimates[level].suggestedMaxPriorityFeePerGas); ++ return { ++ maxFeePerGas, ++ maxPriorityFeePerGas, ++ }; ++}, _DefaultGasFeeFlow_getLegacyLevel = function _DefaultGasFeeFlow_getLegacyLevel(gasFeeEstimates, level) { ++ const gasPrice = __classPrivateFieldGet(this, _DefaultGasFeeFlow_instances, "m", _DefaultGasFeeFlow_gweiDecimalToWeiHex).call(this, gasFeeEstimates[level]); ++ return { ++ maxFeePerGas: gasPrice, ++ maxPriorityFeePerGas: gasPrice, ++ }; ++}, _DefaultGasFeeFlow_gweiDecimalToWeiHex = function _DefaultGasFeeFlow_gweiDecimalToWeiHex(gweiDecimal) { ++ return (0, controller_utils_1.toHex)((0, controller_utils_1.gweiDecToWEIBN)(gweiDecimal)); ++}; ++//# sourceMappingURL=DefaultGasFeeFlow.js.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.js.map b/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.js.map +new file mode 100644 +index 0000000..f95c0a5 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/gas-flows/DefaultGasFeeFlow.js.map +@@ -0,0 +1 @@ ++{"version":3,"file":"DefaultGasFeeFlow.js","sourceRoot":"","sources":["../../src/gas-flows/DefaultGasFeeFlow.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAIA,qEAAkE;AAClE,2CAA0D;AAE1D,iEAAmE;AACnE,sCAA0C;AAS1C,oCAA+C;AAE/C,MAAM,GAAG,GAAG,IAAA,0BAAkB,EAAC,sBAAa,EAAE,sBAAsB,CAAC,CAAC;AActE;;GAEG;AACH,MAAa,iBAAiB;IAA9B;;IAiFA,CAAC;IAhFC,kBAAkB,CAAC,gBAAiC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IAEK,UAAU,CAAC,OAA0B;;YACzC,MAAM,EAAE,4BAA4B,EAAE,GAAG,OAAO,CAAC;YAEjD,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,GACxC,MAAM,4BAA4B,EAAE,CAAC;YAEvC,IAAI,eAAe,KAAK,uCAAkB,CAAC,UAAU,EAAE;gBACrD,GAAG,CAAC,4BAA4B,EAAE,eAAe,CAAC,CAAC;aACpD;iBAAM,IAAI,eAAe,KAAK,uCAAkB,CAAC,MAAM,EAAE;gBACxD,GAAG,CAAC,wBAAwB,EAAE,eAAe,CAAC,CAAC;aAChD;iBAAM;gBACL,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;aACpD;YAED,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,2BAAmB,CAAC,CAAC,MAAM,CACzD,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,iCACd,MAAM,KACT,CAAC,KAAK,CAAC,EAAE,uBAAA,IAAI,yEAAkB,MAAtB,IAAI,EAAmB;oBAC9B,eAAe;oBACf,eAAe;oBACf,KAAK;iBAC8D,CAAC,IACtE,EACF,EAAqB,CACtB,CAAC;YAEF,OAAO,EAAE,SAAS,EAAE,CAAC;QACvB,CAAC;KAAA;CAiDF;AAjFD,8CAiFC;iIA/CmB,EAChB,eAAe,EACf,eAAe,EACf,KAAK,GAG0B;IAC/B,IAAI,eAAe,KAAK,uCAAkB,CAAC,UAAU,EAAE;QACrD,OAAO,uBAAA,IAAI,0EAAmB,MAAvB,IAAI,EAAoB,eAAe,EAAE,KAAK,CAAC,CAAC;KACxD;IAED,OAAO,uBAAA,IAAI,uEAAgB,MAApB,IAAI,EAAiB,eAAe,EAAE,KAAK,CAAC,CAAC;AACtD,CAAC,uFAGC,eAA0C,EAC1C,KAA0B;IAE1B,MAAM,YAAY,GAAG,uBAAA,IAAI,4EAAqB,MAAzB,IAAI,EACvB,eAAe,CAAC,KAAK,CAAC,CAAC,qBAAqB,CAC7C,CAAC;IAEF,MAAM,oBAAoB,GAAG,uBAAA,IAAI,4EAAqB,MAAzB,IAAI,EAC/B,eAAe,CAAC,KAAK,CAAC,CAAC,6BAA6B,CACrD,CAAC;IAEF,OAAO;QACL,YAAY;QACZ,oBAAoB;KACrB,CAAC;AACJ,CAAC,iFAGC,eAAuC,EACvC,KAA0B;IAE1B,MAAM,QAAQ,GAAG,uBAAA,IAAI,4EAAqB,MAAzB,IAAI,EAAsB,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;IAEnE,OAAO;QACL,YAAY,EAAE,QAAQ;QACtB,oBAAoB,EAAE,QAAQ;KAC/B,CAAC;AACJ,CAAC,2FAEoB,WAAmB;IACtC,OAAO,IAAA,wBAAK,EAAC,IAAA,iCAAc,EAAC,WAAW,CAAC,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["import type {\n LegacyGasPriceEstimate,\n GasFeeEstimates as FeeMarketGasPriceEstimate,\n} from '@metamask/gas-fee-controller';\nimport { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller';\nimport { createModuleLogger, Hex } from '@metamask/utils';\n\nimport { gweiDecToWEIBN, toHex } from '@metamask/controller-utils';\nimport { projectLogger } from '../logger';\nimport type {\n GasFeeEstimates,\n GasFeeEstimatesForLevel,\n GasFeeFlow,\n GasFeeFlowRequest,\n GasFeeFlowResponse,\n TransactionMeta,\n} from '../types';\nimport { GasFeeEstimateLevel } from '../types';\n\nconst log = createModuleLogger(projectLogger, 'default-gas-fee-flow');\n\ntype FeeMarketGetEstimateLevelRequest = {\n gasEstimateType: 'fee-market';\n gasFeeEstimates: FeeMarketGasPriceEstimate;\n level: GasFeeEstimateLevel;\n};\n\ntype LegacyGetEstimateLevelRequest = {\n gasEstimateType: 'legacy';\n gasFeeEstimates: LegacyGasPriceEstimate;\n level: GasFeeEstimateLevel;\n};\n\n/**\n * The standard implementation of a gas fee flow that obtains gas fee estimates using only the GasFeeController.\n */\nexport class DefaultGasFeeFlow implements GasFeeFlow {\n matchesTransaction(_transactionMeta: TransactionMeta): boolean {\n return true;\n }\n\n async getGasFees(request: GasFeeFlowRequest): Promise {\n const { getGasFeeControllerEstimates } = request;\n\n const { gasEstimateType, gasFeeEstimates } =\n await getGasFeeControllerEstimates();\n\n if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {\n log('Using fee market estimates', gasFeeEstimates);\n } else if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {\n log('Using legacy estimates', gasFeeEstimates);\n } else {\n throw new Error(`'No gas fee estimates available`);\n }\n\n const estimates = Object.values(GasFeeEstimateLevel).reduce(\n (result, level) => ({\n ...result,\n [level]: this.#getEstimateLevel({\n gasEstimateType,\n gasFeeEstimates,\n level,\n } as FeeMarketGetEstimateLevelRequest | LegacyGetEstimateLevelRequest),\n }),\n {} as GasFeeEstimates,\n );\n\n return { estimates };\n }\n\n #getEstimateLevel({\n gasEstimateType,\n gasFeeEstimates,\n level,\n }:\n | FeeMarketGetEstimateLevelRequest\n | LegacyGetEstimateLevelRequest): GasFeeEstimatesForLevel {\n if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {\n return this.#getFeeMarketLevel(gasFeeEstimates, level);\n }\n\n return this.#getLegacyLevel(gasFeeEstimates, level);\n }\n\n #getFeeMarketLevel(\n gasFeeEstimates: FeeMarketGasPriceEstimate,\n level: GasFeeEstimateLevel,\n ): GasFeeEstimatesForLevel {\n const maxFeePerGas = this.#gweiDecimalToWeiHex(\n gasFeeEstimates[level].suggestedMaxFeePerGas,\n );\n\n const maxPriorityFeePerGas = this.#gweiDecimalToWeiHex(\n gasFeeEstimates[level].suggestedMaxPriorityFeePerGas,\n );\n\n return {\n maxFeePerGas,\n maxPriorityFeePerGas,\n };\n }\n\n #getLegacyLevel(\n gasFeeEstimates: LegacyGasPriceEstimate,\n level: GasFeeEstimateLevel,\n ): GasFeeEstimatesForLevel {\n const gasPrice = this.#gweiDecimalToWeiHex(gasFeeEstimates[level]);\n\n return {\n maxFeePerGas: gasPrice,\n maxPriorityFeePerGas: gasPrice,\n };\n }\n\n #gweiDecimalToWeiHex(gweiDecimal: string): Hex {\n return toHex(gweiDecToWEIBN(gweiDecimal));\n }\n}\n"]} +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.d.ts b/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.d.ts +new file mode 100644 +index 0000000..ce728db +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.d.ts +@@ -0,0 +1,12 @@ ++import type { GasFeeFlow, GasFeeFlowRequest, GasFeeFlowResponse, TransactionMeta } from '../types'; ++/** ++ * Implementation of a gas fee flow specific to Linea networks that obtains gas fee estimates using: ++ * - The `linea_estimateGas` RPC method to obtain the base fee and lowest priority fee. ++ * - Static multipliers to increase the base and priority fees. ++ */ ++export declare class LineaGasFeeFlow implements GasFeeFlow { ++ #private; ++ matchesTransaction(transactionMeta: TransactionMeta): boolean; ++ getGasFees(request: GasFeeFlowRequest): Promise; ++} ++//# sourceMappingURL=LineaGasFeeFlow.d.ts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.d.ts.map b/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.d.ts.map +new file mode 100644 +index 0000000..56042e1 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.d.ts.map +@@ -0,0 +1 @@ ++{"version":3,"file":"LineaGasFeeFlow.d.ts","sourceRoot":"","sources":["../../src/gas-flows/LineaGasFeeFlow.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAChB,MAAM,UAAU,CAAC;AAgClB;;;;GAIG;AACH,qBAAa,eAAgB,YAAW,UAAU;;IAChD,kBAAkB,CAAC,eAAe,EAAE,eAAe,GAAG,OAAO;IAIvD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAqG1E"} +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.js b/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.js +new file mode 100644 +index 0000000..da048be +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.js +@@ -0,0 +1,111 @@ ++"use strict"; ++var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { ++ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } ++ return new (P || (P = Promise))(function (resolve, reject) { ++ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } ++ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } ++ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } ++ step((generator = generator.apply(thisArg, _arguments || [])).next()); ++ }); ++}; ++var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); ++ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); ++ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); ++}; ++var _LineaGasFeeFlow_instances, _LineaGasFeeFlow_getLineaGasFees, _LineaGasFeeFlow_getLineaResponse, _LineaGasFeeFlow_getValuesFromMultipliers, _LineaGasFeeFlow_getMaxFees, _LineaGasFeeFlow_feesToString; ++Object.defineProperty(exports, "__esModule", { value: true }); ++exports.LineaGasFeeFlow = void 0; ++const controller_utils_1 = require("@metamask/controller-utils"); ++const utils_1 = require("@metamask/utils"); ++const logger_1 = require("../logger"); ++const types_1 = require("../types"); ++const DefaultGasFeeFlow_1 = require("./DefaultGasFeeFlow"); ++const log = (0, utils_1.createModuleLogger)(logger_1.projectLogger, 'linea-gas-fee-flow'); ++const LINEA_CHAIN_IDS = [ ++ controller_utils_1.ChainId['linea-mainnet'], ++ controller_utils_1.ChainId['linea-goerli'], ++]; ++const BASE_FEE_MULTIPLIERS = { ++ low: 1, ++ medium: 1.35, ++ high: 1.7, ++}; ++const PRIORITY_FEE_MULTIPLIERS = { ++ low: 1, ++ medium: 1.05, ++ high: 1.1, ++}; ++/** ++ * Implementation of a gas fee flow specific to Linea networks that obtains gas fee estimates using: ++ * - The `linea_estimateGas` RPC method to obtain the base fee and lowest priority fee. ++ * - Static multipliers to increase the base and priority fees. ++ */ ++class LineaGasFeeFlow { ++ constructor() { ++ _LineaGasFeeFlow_instances.add(this); ++ } ++ matchesTransaction(transactionMeta) { ++ return LINEA_CHAIN_IDS.includes(transactionMeta.chainId); ++ } ++ getGasFees(request) { ++ return __awaiter(this, void 0, void 0, function* () { ++ try { ++ return yield __classPrivateFieldGet(this, _LineaGasFeeFlow_instances, "m", _LineaGasFeeFlow_getLineaGasFees).call(this, request); ++ } ++ catch (error) { ++ log('Using default flow as fallback due to error', error); ++ return new DefaultGasFeeFlow_1.DefaultGasFeeFlow().getGasFees(request); ++ } ++ }); ++ } ++} ++exports.LineaGasFeeFlow = LineaGasFeeFlow; ++_LineaGasFeeFlow_instances = new WeakSet(), _LineaGasFeeFlow_getLineaGasFees = function _LineaGasFeeFlow_getLineaGasFees(request) { ++ return __awaiter(this, void 0, void 0, function* () { ++ const { ethQuery, transactionMeta } = request; ++ const lineaResponse = yield __classPrivateFieldGet(this, _LineaGasFeeFlow_instances, "m", _LineaGasFeeFlow_getLineaResponse).call(this, transactionMeta, ethQuery); ++ log('Received Linea response', lineaResponse); ++ const baseFees = __classPrivateFieldGet(this, _LineaGasFeeFlow_instances, "m", _LineaGasFeeFlow_getValuesFromMultipliers).call(this, lineaResponse.baseFeePerGas, BASE_FEE_MULTIPLIERS); ++ log('Generated base fees', __classPrivateFieldGet(this, _LineaGasFeeFlow_instances, "m", _LineaGasFeeFlow_feesToString).call(this, baseFees)); ++ const priorityFees = __classPrivateFieldGet(this, _LineaGasFeeFlow_instances, "m", _LineaGasFeeFlow_getValuesFromMultipliers).call(this, lineaResponse.priorityFeePerGas, PRIORITY_FEE_MULTIPLIERS); ++ log('Generated priority fees', __classPrivateFieldGet(this, _LineaGasFeeFlow_instances, "m", _LineaGasFeeFlow_feesToString).call(this, priorityFees)); ++ const maxFees = __classPrivateFieldGet(this, _LineaGasFeeFlow_instances, "m", _LineaGasFeeFlow_getMaxFees).call(this, baseFees, priorityFees); ++ log('Generated max fees', __classPrivateFieldGet(this, _LineaGasFeeFlow_instances, "m", _LineaGasFeeFlow_feesToString).call(this, maxFees)); ++ const estimates = Object.values(types_1.GasFeeEstimateLevel).reduce((result, level) => (Object.assign(Object.assign({}, result), { [level]: { ++ maxFeePerGas: (0, controller_utils_1.toHex)(maxFees[level]), ++ maxPriorityFeePerGas: (0, controller_utils_1.toHex)(priorityFees[level]), ++ } })), {}); ++ return { estimates }; ++ }); ++}, _LineaGasFeeFlow_getLineaResponse = function _LineaGasFeeFlow_getLineaResponse(transactionMeta, ethQuery) { ++ return (0, controller_utils_1.query)(ethQuery, 'linea_estimateGas', [ ++ { ++ from: transactionMeta.transaction.from, ++ to: transactionMeta.transaction.to, ++ value: transactionMeta.transaction.value, ++ input: transactionMeta.transaction.data, ++ // Required in request but no impact on response. ++ gasPrice: '0x100000000', ++ }, ++ ]); ++}, _LineaGasFeeFlow_getValuesFromMultipliers = function _LineaGasFeeFlow_getValuesFromMultipliers(value, multipliers) { ++ const base = (0, controller_utils_1.hexToBN)(value); ++ const low = base.muln(multipliers.low); ++ const medium = base.muln(multipliers.medium); ++ const high = base.muln(multipliers.high); ++ return { ++ low, ++ medium, ++ high, ++ }; ++}, _LineaGasFeeFlow_getMaxFees = function _LineaGasFeeFlow_getMaxFees(baseFees, priorityFees) { ++ return { ++ low: baseFees.low.add(priorityFees.low), ++ medium: baseFees.medium.add(priorityFees.medium), ++ high: baseFees.high.add(priorityFees.high), ++ }; ++}, _LineaGasFeeFlow_feesToString = function _LineaGasFeeFlow_feesToString(fees) { ++ return Object.values(types_1.GasFeeEstimateLevel).map((level) => fees[level].toString(10)); ++}; ++//# sourceMappingURL=LineaGasFeeFlow.js.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.js.map b/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.js.map +new file mode 100644 +index 0000000..4742145 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/gas-flows/LineaGasFeeFlow.js.map +@@ -0,0 +1 @@ ++{"version":3,"file":"LineaGasFeeFlow.js","sourceRoot":"","sources":["../../src/gas-flows/LineaGasFeeFlow.ts"],"names":[],"mappings":"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","sourcesContent":["import { ChainId, hexToBN, query, toHex } from '@metamask/controller-utils';\nimport { createModuleLogger, type Hex } from '@metamask/utils';\nimport type { BN } from 'ethereumjs-util';\n\nimport { projectLogger } from '../logger';\nimport type {\n GasFeeEstimates,\n GasFeeFlow,\n GasFeeFlowRequest,\n GasFeeFlowResponse,\n TransactionMeta,\n} from '../types';\nimport { GasFeeEstimateLevel } from '../types';\nimport { DefaultGasFeeFlow } from './DefaultGasFeeFlow';\n\ntype LineaEstimateGasResponse = {\n baseFeePerGas: Hex;\n priorityFeePerGas: Hex;\n};\n\ntype FeesByLevel = {\n [key in GasFeeEstimateLevel]: BN;\n};\n\nconst log = createModuleLogger(projectLogger, 'linea-gas-fee-flow');\n\nconst LINEA_CHAIN_IDS: Hex[] = [\n ChainId['linea-mainnet'],\n ChainId['linea-goerli'],\n];\n\nconst BASE_FEE_MULTIPLIERS = {\n low: 1,\n medium: 1.35,\n high: 1.7,\n};\n\nconst PRIORITY_FEE_MULTIPLIERS = {\n low: 1,\n medium: 1.05,\n high: 1.1,\n};\n\n/**\n * Implementation of a gas fee flow specific to Linea networks that obtains gas fee estimates using:\n * - The `linea_estimateGas` RPC method to obtain the base fee and lowest priority fee.\n * - Static multipliers to increase the base and priority fees.\n */\nexport class LineaGasFeeFlow implements GasFeeFlow {\n matchesTransaction(transactionMeta: TransactionMeta): boolean {\n return LINEA_CHAIN_IDS.includes(transactionMeta.chainId as Hex);\n }\n\n async getGasFees(request: GasFeeFlowRequest): Promise {\n try {\n return await this.#getLineaGasFees(request);\n } catch (error) {\n log('Using default flow as fallback due to error', error);\n return new DefaultGasFeeFlow().getGasFees(request);\n }\n }\n\n async #getLineaGasFees(\n request: GasFeeFlowRequest,\n ): Promise {\n const { ethQuery, transactionMeta } = request;\n\n const lineaResponse = await this.#getLineaResponse(\n transactionMeta,\n ethQuery,\n );\n\n log('Received Linea response', lineaResponse);\n\n const baseFees = this.#getValuesFromMultipliers(\n lineaResponse.baseFeePerGas,\n BASE_FEE_MULTIPLIERS,\n );\n\n log('Generated base fees', this.#feesToString(baseFees));\n\n const priorityFees = this.#getValuesFromMultipliers(\n lineaResponse.priorityFeePerGas,\n PRIORITY_FEE_MULTIPLIERS,\n );\n\n log('Generated priority fees', this.#feesToString(priorityFees));\n\n const maxFees = this.#getMaxFees(baseFees, priorityFees);\n\n log('Generated max fees', this.#feesToString(maxFees));\n\n const estimates = Object.values(GasFeeEstimateLevel).reduce(\n (result, level) => ({\n ...result,\n [level]: {\n maxFeePerGas: toHex(maxFees[level]),\n maxPriorityFeePerGas: toHex(priorityFees[level]),\n },\n }),\n {} as GasFeeEstimates,\n );\n\n return { estimates };\n }\n\n #getLineaResponse(\n transactionMeta: TransactionMeta,\n ethQuery: any,\n ): Promise {\n return query(ethQuery, 'linea_estimateGas', [\n {\n from: transactionMeta.transaction.from,\n to: transactionMeta.transaction.to,\n value: transactionMeta.transaction.value,\n input: transactionMeta.transaction.data,\n // Required in request but no impact on response.\n gasPrice: '0x100000000',\n },\n ]);\n }\n\n #getValuesFromMultipliers(\n value: Hex,\n multipliers: { low: number; medium: number; high: number },\n ): FeesByLevel {\n const base = hexToBN(value);\n const low = base.muln(multipliers.low);\n const medium = base.muln(multipliers.medium);\n const high = base.muln(multipliers.high);\n\n return {\n low,\n medium,\n high,\n };\n }\n\n #getMaxFees(\n baseFees: Record,\n priorityFees: Record,\n ): FeesByLevel {\n return {\n low: baseFees.low.add(priorityFees.low),\n medium: baseFees.medium.add(priorityFees.medium),\n high: baseFees.high.add(priorityFees.high),\n };\n }\n\n #feesToString(fees: FeesByLevel) {\n return Object.values(GasFeeEstimateLevel).map((level) =>\n fees[level].toString(10),\n );\n }\n}\n"]} +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.d.ts b/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.d.ts +new file mode 100644 +index 0000000..dfaddc5 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.d.ts +@@ -0,0 +1,32 @@ ++/// ++import EventEmitter from 'events'; ++import type { GasFeeState } from '@metamask/gas-fee-controller'; ++import type { GasFeeFlow } from '../types'; ++import { type TransactionMeta } from '../types'; ++/** ++ * Automatically polls and updates suggested gas fees on unapproved transactions. ++ */ ++export declare class GasFeePoller { ++ #private; ++ hub: EventEmitter; ++ /** ++ * Constructs a new instance of the GasFeePoller. ++ * ++ * @param options - The options for this instance. ++ * @param options.gasFeeFlows - The gas fee flows to use to obtain suitable gas fees. ++ * @param options.getChainIds - Callback to specify the chain IDs to monitor. ++ * @param options.getEthQuery - Callback to obtain an EthQuery instance. ++ * @param options.getGasFeeControllerEstimates - Callback to obtain the default fee estimates. ++ * @param options.getTransactions - Callback to obtain the transaction data. ++ * @param options.onStateChange - Callback to register a listener for controller state changes. ++ */ ++ constructor({ gasFeeFlows, getChainIds, getEthQuery, getGasFeeControllerEstimates, getTransactions, onStateChange, }: { ++ gasFeeFlows: GasFeeFlow[]; ++ getChainIds: () => string[]; ++ getEthQuery: () => any; ++ getGasFeeControllerEstimates: () => Promise; ++ getTransactions: () => TransactionMeta[]; ++ onStateChange: (listener: () => void) => void; ++ }); ++} ++//# sourceMappingURL=GasFeePoller.d.ts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.d.ts.map b/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.d.ts.map +new file mode 100644 +index 0000000..8ccb3d4 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.d.ts.map +@@ -0,0 +1 @@ ++{"version":3,"file":"GasFeePoller.d.ts","sourceRoot":"","sources":["../../src/helpers/GasFeePoller.ts"],"names":[],"mappings":";AAAA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAIhE,OAAO,KAAK,EAAE,UAAU,EAAqB,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAOnE;;GAEG;AACH,qBAAa,YAAY;;IACvB,GAAG,EAAE,YAAY,CAAsB;IAgBvC;;;;;;;;;;OAUG;gBACS,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,4BAA4B,EAC5B,eAAe,EACf,aAAa,GACd,EAAE;QACD,WAAW,EAAE,UAAU,EAAE,CAAC;QAC1B,WAAW,EAAE,MAAM,MAAM,EAAE,CAAC;QAC5B,WAAW,EAAE,MAAM,GAAG,CAAC;QACvB,4BAA4B,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;QACzD,eAAe,EAAE,MAAM,eAAe,EAAE,CAAC;QACzC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;KAC/C;CA+HF"} +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.js b/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.js +new file mode 100644 +index 0000000..00676ac +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.js +@@ -0,0 +1,147 @@ ++"use strict"; ++var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { ++ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } ++ return new (P || (P = Promise))(function (resolve, reject) { ++ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } ++ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } ++ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } ++ step((generator = generator.apply(thisArg, _arguments || [])).next()); ++ }); ++}; ++var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { ++ if (kind === "m") throw new TypeError("Private method is not writable"); ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); ++ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); ++ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; ++}; ++var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); ++ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); ++ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); ++}; ++var __importDefault = (this && this.__importDefault) || function (mod) { ++ return (mod && mod.__esModule) ? mod : { "default": mod }; ++}; ++var _GasFeePoller_instances, _GasFeePoller_gasFeeFlows, _GasFeePoller_getChainIds, _GasFeePoller_getEthQuery, _GasFeePoller_getGasFeeControllerEstimates, _GasFeePoller_getTransactions, _GasFeePoller_timeout, _GasFeePoller_running, _GasFeePoller_start, _GasFeePoller_stop, _GasFeePoller_onTimeout, _GasFeePoller_updateUnapprovedTransactions, _GasFeePoller_updateTransactionSuggestedFees, _GasFeePoller_getUnapprovedTransactions; ++Object.defineProperty(exports, "__esModule", { value: true }); ++exports.GasFeePoller = void 0; ++const events_1 = __importDefault(require("events")); ++const utils_1 = require("@metamask/utils"); ++const logger_1 = require("../logger"); ++const types_1 = require("../types"); ++const gas_flow_1 = require("../utils/gas-flow"); ++const log = (0, utils_1.createModuleLogger)(logger_1.projectLogger, 'gas-fee-poller'); ++const INTERVAL_MILLISECONDS = 10000; ++/** ++ * Automatically polls and updates suggested gas fees on unapproved transactions. ++ */ ++class GasFeePoller { ++ /** ++ * Constructs a new instance of the GasFeePoller. ++ * ++ * @param options - The options for this instance. ++ * @param options.gasFeeFlows - The gas fee flows to use to obtain suitable gas fees. ++ * @param options.getChainIds - Callback to specify the chain IDs to monitor. ++ * @param options.getEthQuery - Callback to obtain an EthQuery instance. ++ * @param options.getGasFeeControllerEstimates - Callback to obtain the default fee estimates. ++ * @param options.getTransactions - Callback to obtain the transaction data. ++ * @param options.onStateChange - Callback to register a listener for controller state changes. ++ */ ++ constructor({ gasFeeFlows, getChainIds, getEthQuery, getGasFeeControllerEstimates, getTransactions, onStateChange, }) { ++ _GasFeePoller_instances.add(this); ++ this.hub = new events_1.default(); ++ _GasFeePoller_gasFeeFlows.set(this, void 0); ++ _GasFeePoller_getChainIds.set(this, void 0); ++ _GasFeePoller_getEthQuery.set(this, void 0); ++ _GasFeePoller_getGasFeeControllerEstimates.set(this, void 0); ++ _GasFeePoller_getTransactions.set(this, void 0); ++ _GasFeePoller_timeout.set(this, void 0); ++ _GasFeePoller_running.set(this, false); ++ __classPrivateFieldSet(this, _GasFeePoller_gasFeeFlows, gasFeeFlows, "f"); ++ __classPrivateFieldSet(this, _GasFeePoller_getChainIds, getChainIds, "f"); ++ __classPrivateFieldSet(this, _GasFeePoller_getEthQuery, getEthQuery, "f"); ++ __classPrivateFieldSet(this, _GasFeePoller_getGasFeeControllerEstimates, getGasFeeControllerEstimates, "f"); ++ __classPrivateFieldSet(this, _GasFeePoller_getTransactions, getTransactions, "f"); ++ onStateChange(() => { ++ const unapprovedTransactions = __classPrivateFieldGet(this, _GasFeePoller_instances, "m", _GasFeePoller_getUnapprovedTransactions).call(this); ++ if (unapprovedTransactions.length) { ++ __classPrivateFieldGet(this, _GasFeePoller_instances, "m", _GasFeePoller_start).call(this); ++ } ++ else { ++ __classPrivateFieldGet(this, _GasFeePoller_instances, "m", _GasFeePoller_stop).call(this); ++ } ++ }); ++ } ++} ++exports.GasFeePoller = GasFeePoller; ++_GasFeePoller_gasFeeFlows = new WeakMap(), _GasFeePoller_getChainIds = new WeakMap(), _GasFeePoller_getEthQuery = new WeakMap(), _GasFeePoller_getGasFeeControllerEstimates = new WeakMap(), _GasFeePoller_getTransactions = new WeakMap(), _GasFeePoller_timeout = new WeakMap(), _GasFeePoller_running = new WeakMap(), _GasFeePoller_instances = new WeakSet(), _GasFeePoller_start = function _GasFeePoller_start() { ++ if (__classPrivateFieldGet(this, _GasFeePoller_running, "f")) { ++ return; ++ } ++ // Intentionally not awaiting since this starts the timeout chain. ++ // eslint-disable-next-line @typescript-eslint/no-floating-promises ++ __classPrivateFieldGet(this, _GasFeePoller_instances, "m", _GasFeePoller_onTimeout).call(this); ++ __classPrivateFieldSet(this, _GasFeePoller_running, true, "f"); ++ log('Started polling'); ++}, _GasFeePoller_stop = function _GasFeePoller_stop() { ++ if (!__classPrivateFieldGet(this, _GasFeePoller_running, "f")) { ++ return; ++ } ++ clearTimeout(__classPrivateFieldGet(this, _GasFeePoller_timeout, "f")); ++ __classPrivateFieldSet(this, _GasFeePoller_timeout, undefined, "f"); ++ __classPrivateFieldSet(this, _GasFeePoller_running, false, "f"); ++ log('Stopped polling'); ++}, _GasFeePoller_onTimeout = function _GasFeePoller_onTimeout() { ++ return __awaiter(this, void 0, void 0, function* () { ++ yield __classPrivateFieldGet(this, _GasFeePoller_instances, "m", _GasFeePoller_updateUnapprovedTransactions).call(this); ++ // eslint-disable-next-line @typescript-eslint/no-misused-promises ++ __classPrivateFieldSet(this, _GasFeePoller_timeout, setTimeout(() => __classPrivateFieldGet(this, _GasFeePoller_instances, "m", _GasFeePoller_onTimeout).call(this), INTERVAL_MILLISECONDS), "f"); ++ }); ++}, _GasFeePoller_updateUnapprovedTransactions = function _GasFeePoller_updateUnapprovedTransactions() { ++ return __awaiter(this, void 0, void 0, function* () { ++ const unapprovedTransactions = __classPrivateFieldGet(this, _GasFeePoller_instances, "m", _GasFeePoller_getUnapprovedTransactions).call(this); ++ log('Found unapproved transactions', { ++ count: unapprovedTransactions.length, ++ }); ++ const ethQuery = __classPrivateFieldGet(this, _GasFeePoller_getEthQuery, "f").call(this); ++ yield Promise.all(unapprovedTransactions.map((tx) => __classPrivateFieldGet(this, _GasFeePoller_instances, "m", _GasFeePoller_updateTransactionSuggestedFees).call(this, tx, ethQuery))); ++ }); ++}, _GasFeePoller_updateTransactionSuggestedFees = function _GasFeePoller_updateTransactionSuggestedFees(transactionMeta, ethQuery) { ++ return __awaiter(this, void 0, void 0, function* () { ++ const gasFeeFlow = (0, gas_flow_1.getGasFeeFlow)(transactionMeta, __classPrivateFieldGet(this, _GasFeePoller_gasFeeFlows, "f")); ++ if (!gasFeeFlow) { ++ log('No gas fee flow found', transactionMeta.id); ++ } ++ else { ++ log('Found gas fee flow', gasFeeFlow.constructor.name, transactionMeta.id); ++ } ++ const request = { ++ ethQuery, ++ getGasFeeControllerEstimates: __classPrivateFieldGet(this, _GasFeePoller_getGasFeeControllerEstimates, "f"), ++ transactionMeta, ++ }; ++ if (gasFeeFlow) { ++ try { ++ const response = yield gasFeeFlow.getGasFees(request); ++ transactionMeta.gasFeeEstimates = response.estimates; ++ } ++ catch (error) { ++ log('Failed to get suggested gas fees', transactionMeta.id, error); ++ } ++ } ++ if (!gasFeeFlow && transactionMeta.gasFeeEstimatesLoaded) { ++ return; ++ } ++ transactionMeta.gasFeeEstimatesLoaded = true; ++ this.hub.emit('transaction-updated', transactionMeta, 'GasFeePoller - Suggested gas fees updated'); ++ log('Updated suggested gas fees', { ++ gasFeeEstimates: transactionMeta.gasFeeEstimates, ++ transaction: transactionMeta.id, ++ }); ++ }); ++}, _GasFeePoller_getUnapprovedTransactions = function _GasFeePoller_getUnapprovedTransactions() { ++ const chainIds = __classPrivateFieldGet(this, _GasFeePoller_getChainIds, "f").call(this); ++ return __classPrivateFieldGet(this, _GasFeePoller_getTransactions, "f").call(this).filter((tx) => chainIds.includes(tx.chainId) && ++ tx.status === types_1.TransactionStatus.unapproved); ++}; ++//# sourceMappingURL=GasFeePoller.js.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.js.map b/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.js.map +new file mode 100644 +index 0000000..08b3514 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/helpers/GasFeePoller.js.map +@@ -0,0 +1 @@ ++{"version":3,"file":"GasFeePoller.js","sourceRoot":"","sources":["../../src/helpers/GasFeePoller.ts"],"names":[],"mappings":"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","sourcesContent":["import EventEmitter from 'events';\nimport type { GasFeeState } from '@metamask/gas-fee-controller';\nimport { createModuleLogger } from '@metamask/utils';\n\nimport { projectLogger } from '../logger';\nimport type { GasFeeFlow, GasFeeFlowRequest } from '../types';\nimport { TransactionStatus, type TransactionMeta } from '../types';\nimport { getGasFeeFlow } from '../utils/gas-flow';\n\nconst log = createModuleLogger(projectLogger, 'gas-fee-poller');\n\nconst INTERVAL_MILLISECONDS = 10000;\n\n/**\n * Automatically polls and updates suggested gas fees on unapproved transactions.\n */\nexport class GasFeePoller {\n hub: EventEmitter = new EventEmitter();\n\n #gasFeeFlows: GasFeeFlow[];\n\n #getChainIds: () => string[];\n\n #getEthQuery: () => any;\n\n #getGasFeeControllerEstimates: () => Promise;\n\n #getTransactions: () => TransactionMeta[];\n\n #timeout: any;\n\n #running = false;\n\n /**\n * Constructs a new instance of the GasFeePoller.\n *\n * @param options - The options for this instance.\n * @param options.gasFeeFlows - The gas fee flows to use to obtain suitable gas fees.\n * @param options.getChainIds - Callback to specify the chain IDs to monitor.\n * @param options.getEthQuery - Callback to obtain an EthQuery instance.\n * @param options.getGasFeeControllerEstimates - Callback to obtain the default fee estimates.\n * @param options.getTransactions - Callback to obtain the transaction data.\n * @param options.onStateChange - Callback to register a listener for controller state changes.\n */\n constructor({\n gasFeeFlows,\n getChainIds,\n getEthQuery,\n getGasFeeControllerEstimates,\n getTransactions,\n onStateChange,\n }: {\n gasFeeFlows: GasFeeFlow[];\n getChainIds: () => string[];\n getEthQuery: () => any;\n getGasFeeControllerEstimates: () => Promise;\n getTransactions: () => TransactionMeta[];\n onStateChange: (listener: () => void) => void;\n }) {\n this.#gasFeeFlows = gasFeeFlows;\n this.#getChainIds = getChainIds;\n this.#getEthQuery = getEthQuery;\n this.#getGasFeeControllerEstimates = getGasFeeControllerEstimates;\n this.#getTransactions = getTransactions;\n\n onStateChange(() => {\n const unapprovedTransactions = this.#getUnapprovedTransactions();\n\n if (unapprovedTransactions.length) {\n this.#start();\n } else {\n this.#stop();\n }\n });\n }\n\n #start() {\n if (this.#running) {\n return;\n }\n\n // Intentionally not awaiting since this starts the timeout chain.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#onTimeout();\n\n this.#running = true;\n\n log('Started polling');\n }\n\n #stop() {\n if (!this.#running) {\n return;\n }\n\n clearTimeout(this.#timeout);\n\n this.#timeout = undefined;\n this.#running = false;\n\n log('Stopped polling');\n }\n\n async #onTimeout() {\n await this.#updateUnapprovedTransactions();\n\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n this.#timeout = setTimeout(() => this.#onTimeout(), INTERVAL_MILLISECONDS);\n }\n\n async #updateUnapprovedTransactions() {\n const unapprovedTransactions = this.#getUnapprovedTransactions();\n\n log('Found unapproved transactions', {\n count: unapprovedTransactions.length,\n });\n\n const ethQuery = this.#getEthQuery();\n\n await Promise.all(\n unapprovedTransactions.map((tx) =>\n this.#updateTransactionSuggestedFees(tx, ethQuery),\n ),\n );\n }\n\n async #updateTransactionSuggestedFees(\n transactionMeta: TransactionMeta,\n ethQuery: any,\n ) {\n const gasFeeFlow = getGasFeeFlow(transactionMeta, this.#gasFeeFlows);\n\n if (!gasFeeFlow) {\n log('No gas fee flow found', transactionMeta.id);\n } else {\n log(\n 'Found gas fee flow',\n gasFeeFlow.constructor.name,\n transactionMeta.id,\n );\n }\n\n const request: GasFeeFlowRequest = {\n ethQuery,\n getGasFeeControllerEstimates: this.#getGasFeeControllerEstimates,\n transactionMeta,\n };\n\n if (gasFeeFlow) {\n try {\n const response = await gasFeeFlow.getGasFees(request);\n\n transactionMeta.gasFeeEstimates = response.estimates;\n } catch (error) {\n log('Failed to get suggested gas fees', transactionMeta.id, error);\n }\n }\n\n if (!gasFeeFlow && transactionMeta.gasFeeEstimatesLoaded) {\n return;\n }\n\n transactionMeta.gasFeeEstimatesLoaded = true;\n\n this.hub.emit(\n 'transaction-updated',\n transactionMeta,\n 'GasFeePoller - Suggested gas fees updated',\n );\n\n log('Updated suggested gas fees', {\n gasFeeEstimates: transactionMeta.gasFeeEstimates,\n transaction: transactionMeta.id,\n });\n }\n\n #getUnapprovedTransactions() {\n const chainIds = this.#getChainIds();\n\n return this.#getTransactions().filter(\n (tx) =>\n chainIds.includes(tx.chainId as string) &&\n tx.status === TransactionStatus.unapproved,\n );\n }\n}\n"]} +\ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/index.d.ts b/node_modules/@metamask/transaction-controller/dist/index.d.ts -index fc7f49b..cd9486a 100644 +index fc7f49b..52e424e 100644 --- a/node_modules/@metamask/transaction-controller/dist/index.d.ts +++ b/node_modules/@metamask/transaction-controller/dist/index.d.ts -@@ -1,3 +1,4 @@ +@@ -1,3 +1,5 @@ export * from './TransactionController'; export { isEIP1559Transaction } from './utils'; +export * from './types'; ++export { mergeGasFeeEstimates, getGasFeeFlow } from './utils/gas-flow'; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/index.d.ts.map b/node_modules/@metamask/transaction-controller/dist/index.d.ts.map @@ -2222,14 +2739,22 @@ index 2175387..0000000 -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC"} \ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/index.js b/node_modules/@metamask/transaction-controller/dist/index.js -index a1c07c8..602f51c 100644 +index a1c07c8..99cea84 100644 --- a/node_modules/@metamask/transaction-controller/dist/index.js +++ b/node_modules/@metamask/transaction-controller/dist/index.js -@@ -18,4 +18,5 @@ exports.isEIP1559Transaction = void 0; +@@ -14,8 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); + }; + Object.defineProperty(exports, "__esModule", { value: true }); +-exports.isEIP1559Transaction = void 0; ++exports.getGasFeeFlow = exports.mergeGasFeeEstimates = exports.isEIP1559Transaction = void 0; __exportStar(require("./TransactionController"), exports); var utils_1 = require("./utils"); Object.defineProperty(exports, "isEIP1559Transaction", { enumerable: true, get: function () { return utils_1.isEIP1559Transaction; } }); +__exportStar(require("./types"), exports); ++var gas_flow_1 = require("./utils/gas-flow"); ++Object.defineProperty(exports, "mergeGasFeeEstimates", { enumerable: true, get: function () { return gas_flow_1.mergeGasFeeEstimates; } }); ++Object.defineProperty(exports, "getGasFeeFlow", { enumerable: true, get: function () { return gas_flow_1.getGasFeeFlow; } }); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/index.js.map b/node_modules/@metamask/transaction-controller/dist/index.js.map @@ -2240,6 +2765,35 @@ index a4460fa..0000000 @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,iCAA+C;AAAtC,6GAAA,oBAAoB,OAAA","sourcesContent":["export * from './TransactionController';\nexport { isEIP1559Transaction } from './utils';\n"]} \ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/logger.d.ts b/node_modules/@metamask/transaction-controller/dist/logger.d.ts +new file mode 100644 +index 0000000..43ad63f +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/logger.d.ts +@@ -0,0 +1,6 @@ ++/// ++import { createModuleLogger } from '@metamask/utils'; ++export declare const projectLogger: import("debug").Debugger; ++export declare const incomingTransactionsLogger: import("debug").Debugger; ++export { createModuleLogger }; ++//# sourceMappingURL=logger.d.ts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/logger.js b/node_modules/@metamask/transaction-controller/dist/logger.js +new file mode 100644 +index 0000000..bb55b17 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/logger.js +@@ -0,0 +1,9 @@ ++"use strict"; ++/* istanbul ignore file */ ++Object.defineProperty(exports, "__esModule", { value: true }); ++exports.createModuleLogger = exports.incomingTransactionsLogger = exports.projectLogger = void 0; ++const utils_1 = require("@metamask/utils"); ++Object.defineProperty(exports, "createModuleLogger", { enumerable: true, get: function () { return utils_1.createModuleLogger; } }); ++exports.projectLogger = (0, utils_1.createProjectLogger)('transaction-controller'); ++exports.incomingTransactionsLogger = (0, utils_1.createModuleLogger)(exports.projectLogger, 'incoming-transactions'); ++//# sourceMappingURL=logger.js.map +\ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/mocks/txsMock.d.ts b/node_modules/@metamask/transaction-controller/dist/mocks/txsMock.d.ts deleted file mode 100644 index 688af82..0000000 @@ -2852,11 +3406,12 @@ index 99e8f02..0000000 \ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/types.d.ts b/node_modules/@metamask/transaction-controller/dist/types.d.ts new file mode 100644 -index 0000000..3f922df +index 0000000..39cc136 --- /dev/null +++ b/node_modules/@metamask/transaction-controller/dist/types.d.ts -@@ -0,0 +1,174 @@ +@@ -0,0 +1,232 @@ +import { Hex } from '@metamask/utils'; ++import { GasFeeState } from '@metamask/gas-fee-controller'; +/** + * @type TransactionMeta + * @@ -2899,6 +3454,10 @@ index 0000000..3f922df + blockNumber?: string; + deviceConfirmedOn?: WalletDevice; + verifiedOnBlockchain?: boolean; ++ /** Alternate EIP-1559 gas fee estimates for multiple priority levels. */ ++ gasFeeEstimates?: GasFeeEstimates; ++ /** Whether the gas fee estimates have been checked at least once. */ ++ gasFeeEstimatesLoaded?: boolean; + /** + * Response from security validator. + */ @@ -3028,18 +3587,71 @@ index 0000000..3f922df + /** The transaction parameters that were submitted. */ + transaction: Record; +}; ++/** Gas fee estimates for a specific priority level. */ ++export declare type GasFeeEstimatesForLevel = { ++ /** Maximum amount to pay per gas. */ ++ maxFeePerGas: Hex; ++ /** Maximum amount per gas to give to the validator as an incentive. */ ++ maxPriorityFeePerGas: Hex; ++}; ++/** Alternate priority levels for which values are provided in gas fee estimates. */ ++export declare enum GasFeeEstimateLevel { ++ low = "low", ++ medium = "medium", ++ high = "high" ++} ++/** Gas fee estimates for a transaction. */ ++export declare type GasFeeEstimates = { ++ /** The gas fee estimate for a low priority transaction. */ ++ [GasFeeEstimateLevel.low]: GasFeeEstimatesForLevel; ++ /** The gas fee estimate for a medium priority transaction. */ ++ [GasFeeEstimateLevel.medium]: GasFeeEstimatesForLevel; ++ /** The gas fee estimate for a high priority transaction. */ ++ [GasFeeEstimateLevel.high]: GasFeeEstimatesForLevel; ++}; ++/** Request to a gas fee flow to obtain gas fee estimates. */ ++export declare type GasFeeFlowRequest = { ++ /** An EthQuery instance to enable queries to the associated RPC provider. */ ++ ethQuery: any; ++ /** Callback to get the GasFeeController estimates. */ ++ getGasFeeControllerEstimates: () => Promise; ++ /** The metadata of the transaction to obtain estimates for. */ ++ transactionMeta: TransactionMeta; ++}; ++/** Response from a gas fee flow containing gas fee estimates. */ ++export declare type GasFeeFlowResponse = { ++ /** The gas fee estimates for the transaction. */ ++ estimates: GasFeeEstimates; ++}; ++/** A method of obtaining gas fee estimates for a specific transaction. */ ++export declare type GasFeeFlow = { ++ /** ++ * Determine if the gas fee flow supports the specified transaction. ++ * ++ * @param transactionMeta - The transaction metadata. ++ * @returns Whether the gas fee flow supports the transaction. ++ */ ++ matchesTransaction(transactionMeta: TransactionMeta): boolean; ++ /** ++ * Get gas fee estimates for a specific transaction. ++ * ++ * @param request - The gas fee flow request. ++ * @returns The gas fee flow response containing the gas fee estimates. ++ */ ++ getGasFees: (request: GasFeeFlowRequest) => Promise; ++}; +export {}; +//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/types.js b/node_modules/@metamask/transaction-controller/dist/types.js new file mode 100644 -index 0000000..1cddb49 +index 0000000..2cc85ee --- /dev/null +++ b/node_modules/@metamask/transaction-controller/dist/types.js -@@ -0,0 +1,29 @@ +@@ -0,0 +1,36 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); -+exports.WalletDevice = exports.TransactionStatus = void 0; ++exports.GasFeeEstimateLevel = exports.WalletDevice = exports.TransactionStatus = void 0; +/** + * The status of the transaction. Each status represents the state of the transaction internally + * in the wallet. Some of these correspond with the state of the transaction on the network, but @@ -3065,6 +3677,13 @@ index 0000000..1cddb49 + WalletDevice["MM_EXTENSION"] = "metamask_extension"; + WalletDevice["OTHER"] = "other_device"; +})(WalletDevice = exports.WalletDevice || (exports.WalletDevice = {})); ++/** Alternate priority levels for which values are provided in gas fee estimates. */ ++var GasFeeEstimateLevel; ++(function (GasFeeEstimateLevel) { ++ GasFeeEstimateLevel["low"] = "low"; ++ GasFeeEstimateLevel["medium"] = "medium"; ++ GasFeeEstimateLevel["high"] = "high"; ++})(GasFeeEstimateLevel = exports.GasFeeEstimateLevel || (exports.GasFeeEstimateLevel = {})); +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/utils.d.ts b/node_modules/@metamask/transaction-controller/dist/utils.d.ts @@ -3231,3 +3850,122 @@ index 8f2f6e3..0000000 @@ -1 +0,0 @@ -{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { addHexPrefix, isHexString } from 'ethereumjs-util';\nimport {\n NetworkType,\n convertHexToDecimal,\n handleFetch,\n isValidHexAddress,\n} from '@metamask/controller-utils';\nimport type { Transaction as NonceTrackerTransaction } from 'nonce-tracker/dist/NonceTracker';\nimport {\n Transaction,\n FetchAllOptions,\n GasPriceValue,\n FeeMarketEIP1559Values,\n TransactionStatus,\n} from './TransactionController';\nimport type { TransactionMeta } from './TransactionController';\n\nexport const ESTIMATE_GAS_ERROR = 'eth_estimateGas rpc method error';\n\nconst NORMALIZERS: { [param in keyof Transaction]: any } = {\n data: (data: string) => addHexPrefix(data),\n from: (from: string) => addHexPrefix(from).toLowerCase(),\n gas: (gas: string) => addHexPrefix(gas),\n gasPrice: (gasPrice: string) => addHexPrefix(gasPrice),\n nonce: (nonce: string) => addHexPrefix(nonce),\n to: (to: string) => addHexPrefix(to).toLowerCase(),\n value: (value: string) => addHexPrefix(value),\n maxFeePerGas: (maxFeePerGas: string) => addHexPrefix(maxFeePerGas),\n maxPriorityFeePerGas: (maxPriorityFeePerGas: string) =>\n addHexPrefix(maxPriorityFeePerGas),\n estimatedBaseFee: (maxPriorityFeePerGas: string) =>\n addHexPrefix(maxPriorityFeePerGas),\n};\n\n/**\n * Return a URL that can be used to fetch ETH transactions.\n *\n * @param networkType - Network type of desired network.\n * @param urlParams - The parameters used to construct the URL.\n * @returns URL to fetch the access the endpoint.\n */\nexport function getEtherscanApiUrl(\n networkType: string,\n urlParams: any,\n): string {\n let etherscanSubdomain = 'api';\n if (networkType !== NetworkType.mainnet) {\n etherscanSubdomain = `api-${networkType}`;\n }\n const apiUrl = `https://${etherscanSubdomain}.etherscan.io`;\n let url = `${apiUrl}/api?`;\n\n for (const paramKey in urlParams) {\n if (urlParams[paramKey]) {\n url += `${paramKey}=${urlParams[paramKey]}&`;\n }\n }\n url += 'tag=latest&page=1';\n return url;\n}\n\n/**\n * Normalizes properties on a Transaction object.\n *\n * @param transaction - Transaction object to normalize.\n * @returns Normalized Transaction object.\n */\nexport function normalizeTransaction(transaction: Transaction) {\n const normalizedTransaction: Transaction = { from: '' };\n let key: keyof Transaction;\n for (key in NORMALIZERS) {\n if (transaction[key as keyof Transaction]) {\n normalizedTransaction[key] = NORMALIZERS[key](transaction[key]) as never;\n }\n }\n return normalizedTransaction;\n}\n\n/**\n * Validates a Transaction object for required properties and throws in\n * the event of any validation error.\n *\n * @param transaction - Transaction object to validate.\n */\nexport function validateTransaction(transaction: Transaction) {\n if (\n !transaction.from ||\n typeof transaction.from !== 'string' ||\n !isValidHexAddress(transaction.from)\n ) {\n throw new Error(\n `Invalid \"from\" address: ${transaction.from} must be a valid string.`,\n );\n }\n\n if (transaction.to === '0x' || transaction.to === undefined) {\n if (transaction.data) {\n delete transaction.to;\n } else {\n throw new Error(\n `Invalid \"to\" address: ${transaction.to} must be a valid string.`,\n );\n }\n } else if (\n transaction.to !== undefined &&\n !isValidHexAddress(transaction.to)\n ) {\n throw new Error(\n `Invalid \"to\" address: ${transaction.to} must be a valid string.`,\n );\n }\n\n if (transaction.value !== undefined) {\n const value = transaction.value.toString();\n if (value.includes('-')) {\n throw new Error(`Invalid \"value\": ${value} is not a positive number.`);\n }\n\n if (value.includes('.')) {\n throw new Error(\n `Invalid \"value\": ${value} number must be denominated in wei.`,\n );\n }\n const intValue = parseInt(transaction.value, 10);\n const isValid =\n Number.isFinite(intValue) &&\n !Number.isNaN(intValue) &&\n !isNaN(Number(value)) &&\n Number.isSafeInteger(intValue);\n if (!isValid) {\n throw new Error(\n `Invalid \"value\": ${value} number must be a valid number.`,\n );\n }\n }\n}\n\n/**\n * Checks if a transaction is EIP-1559 by checking for the existence of\n * maxFeePerGas and maxPriorityFeePerGas within its parameters.\n *\n * @param transaction - Transaction object to add.\n * @returns Boolean that is true if the transaction is EIP-1559 (has maxFeePerGas and maxPriorityFeePerGas), otherwise returns false.\n */\nexport const isEIP1559Transaction = (transaction: Transaction): boolean => {\n const hasOwnProp = (obj: Transaction, key: string) =>\n Object.prototype.hasOwnProperty.call(obj, key);\n return (\n hasOwnProp(transaction, 'maxFeePerGas') &&\n hasOwnProp(transaction, 'maxPriorityFeePerGas')\n );\n};\n\n/**\n * Handles the fetch of incoming transactions.\n *\n * @param networkType - Network type of desired network.\n * @param address - Address to get the transactions from.\n * @param txHistoryLimit - The maximum number of transactions to fetch.\n * @param opt - Object that can contain fromBlock and Etherscan service API key.\n * @returns Responses for both ETH and ERC20 token transactions.\n */\nexport async function handleTransactionFetch(\n networkType: string,\n address: string,\n txHistoryLimit: number,\n opt?: FetchAllOptions,\n): Promise<[{ [result: string]: [] }, { [result: string]: [] }]> {\n // transactions\n const urlParams = {\n module: 'account',\n address,\n startBlock: opt?.fromBlock,\n apikey: opt?.etherscanApiKey,\n offset: txHistoryLimit.toString(),\n order: 'desc',\n };\n const etherscanTxUrl = getEtherscanApiUrl(networkType, {\n ...urlParams,\n action: 'txlist',\n });\n const etherscanTxResponsePromise = handleFetch(etherscanTxUrl);\n\n // tokens\n const etherscanTokenUrl = getEtherscanApiUrl(networkType, {\n ...urlParams,\n action: 'tokentx',\n });\n const etherscanTokenResponsePromise = handleFetch(etherscanTokenUrl);\n\n let [etherscanTxResponse, etherscanTokenResponse] = await Promise.all([\n etherscanTxResponsePromise,\n etherscanTokenResponsePromise,\n ]);\n\n if (\n etherscanTxResponse.status === '0' ||\n etherscanTxResponse.result.length <= 0\n ) {\n etherscanTxResponse = { status: etherscanTxResponse.status, result: [] };\n }\n\n if (\n etherscanTokenResponse.status === '0' ||\n etherscanTokenResponse.result.length <= 0\n ) {\n etherscanTokenResponse = {\n status: etherscanTokenResponse.status,\n result: [],\n };\n }\n\n return [etherscanTxResponse, etherscanTokenResponse];\n}\n\nexport const validateGasValues = (\n gasValues: GasPriceValue | FeeMarketEIP1559Values,\n) => {\n Object.keys(gasValues).forEach((key) => {\n const value = (gasValues as any)[key];\n if (typeof value !== 'string' || !isHexString(value)) {\n throw new TypeError(\n `expected hex string for ${key} but received: ${value}`,\n );\n }\n });\n};\n\nexport const isFeeMarketEIP1559Values = (\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n): gasValues is FeeMarketEIP1559Values =>\n (gasValues as FeeMarketEIP1559Values)?.maxFeePerGas !== undefined ||\n (gasValues as FeeMarketEIP1559Values)?.maxPriorityFeePerGas !== undefined;\n\nexport const isGasPriceValue = (\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n): gasValues is GasPriceValue =>\n (gasValues as GasPriceValue)?.gasPrice !== undefined;\n\nexport const getIncreasedPriceHex = (value: number, rate: number): string =>\n addHexPrefix(`${parseInt(`${value * rate}`, 10).toString(16)}`);\n\nexport const getIncreasedPriceFromExisting = (\n value: string | undefined,\n rate: number,\n): string => {\n return getIncreasedPriceHex(convertHexToDecimal(value), rate);\n};\n\n/**\n * Validates that the proposed value is greater than or equal to the minimum value.\n *\n * @param proposed - The proposed value.\n * @param min - The minimum value.\n * @returns The proposed value.\n * @throws Will throw if the proposed value is too low.\n */\nexport function validateMinimumIncrease(proposed: string, min: string) {\n const proposedDecimal = convertHexToDecimal(proposed);\n const minDecimal = convertHexToDecimal(min);\n if (proposedDecimal >= minDecimal) {\n return proposed;\n }\n const errorMsg = `The proposed value: ${proposedDecimal} should meet or exceed the minimum value: ${minDecimal}`;\n throw new Error(errorMsg);\n}\n\n/**\n * Helper function to filter and format transactions for the nonce tracker.\n *\n * @param fromAddress - Address of the account from which the transactions to filter from are sent.\n * @param transactionStatus - Status of the transactions for which to filter.\n * @param transactions - Array of transactionMeta objects that have been prefiltered.\n * @returns Array of transactions formatted for the nonce tracker.\n */\nexport function getAndFormatTransactionsForNonceTracker(\n fromAddress: string,\n transactionStatus: TransactionStatus,\n transactions: TransactionMeta[],\n): NonceTrackerTransaction[] {\n return transactions\n .filter(\n ({ status, transaction: { from } }) =>\n status === transactionStatus &&\n from.toLowerCase() === fromAddress.toLowerCase(),\n )\n .map(({ status, transaction: { from, gas, value, nonce } }) => {\n // the only value we care about is the nonce\n // but we need to return the other values to satisfy the type\n // TODO: refactor nonceTracker to not require this\n return {\n status,\n history: [{}],\n txParams: {\n from: from ?? '',\n gas: gas ?? '',\n value: value ?? '',\n nonce: nonce ?? '',\n },\n };\n });\n}\n"]} \ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.d.ts b/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.d.ts +new file mode 100644 +index 0000000..59837f9 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.d.ts +@@ -0,0 +1,33 @@ ++import type { GasFeeEstimates, LegacyGasPriceEstimate } from '@metamask/gas-fee-controller'; ++import { type GasFeeState } from '@metamask/gas-fee-controller'; ++import { type GasFeeEstimates as TransactionGasFeeEstimates, type GasFeeFlow, type TransactionMeta } from '../types'; ++/** ++ * Returns the first gas fee flow that matches the transaction. ++ * ++ * @param transactionMeta - The transaction metadata to find a gas fee flow for. ++ * @param gasFeeFlows - The gas fee flows to search. ++ * @returns The first gas fee flow that matches the transaction, or undefined if none match. ++ */ ++export declare function getGasFeeFlow(transactionMeta: TransactionMeta, gasFeeFlows: GasFeeFlow[]): GasFeeFlow | undefined; ++declare type FeeMarketMergeGasFeeEstimatesRequest = { ++ gasFeeControllerEstimateType: 'fee-market'; ++ gasFeeControllerEstimates: GasFeeEstimates; ++ transactionGasFeeEstimates: TransactionGasFeeEstimates; ++}; ++declare type LegacyMergeGasFeeEstimatesRequest = { ++ gasFeeControllerEstimateType: 'legacy'; ++ gasFeeControllerEstimates: LegacyGasPriceEstimate; ++ transactionGasFeeEstimates: TransactionGasFeeEstimates; ++}; ++/** ++ * Merge the gas fee estimates from the gas fee controller with the gas fee estimates from a transaction. ++ * ++ * @param request - Data required to merge gas fee estimates. ++ * @param request.gasFeeControllerEstimateType - Gas fee estimate type from the gas fee controller. ++ * @param request.gasFeeControllerEstimates - Gas fee estimates from the GasFeeController. ++ * @param request.transactionGasFeeEstimates - Gas fee estimates from the transaction. ++ * @returns The merged gas fee estimates. ++ */ ++export declare function mergeGasFeeEstimates({ gasFeeControllerEstimateType, gasFeeControllerEstimates, transactionGasFeeEstimates, }: FeeMarketMergeGasFeeEstimatesRequest | LegacyMergeGasFeeEstimatesRequest): GasFeeState['gasFeeEstimates']; ++export {}; ++//# sourceMappingURL=gas-flow.d.ts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.d.ts.map b/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.d.ts.map +new file mode 100644 +index 0000000..46c3e5a +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.d.ts.map +@@ -0,0 +1 @@ ++{"version":3,"file":"gas-flow.d.ts","sourceRoot":"","sources":["../../src/utils/gas-flow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,EACf,sBAAsB,EACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,KAAK,eAAe,IAAI,0BAA0B,EAClD,KAAK,UAAU,EACf,KAAK,eAAe,EAGrB,MAAM,UAAU,CAAC;AAElB;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,eAAe,EAChC,WAAW,EAAE,UAAU,EAAE,GACxB,UAAU,GAAG,SAAS,CAIxB;AAED,aAAK,oCAAoC,GAAG;IAC1C,4BAA4B,EAAE,YAAY,CAAC;IAC3C,yBAAyB,EAAE,eAAe,CAAC;IAC3C,0BAA0B,EAAE,0BAA0B,CAAC;CACxD,CAAC;AAEF,aAAK,iCAAiC,GAAG;IACvC,4BAA4B,EAAE,QAAQ,CAAC;IACvC,yBAAyB,EAAE,sBAAsB,CAAC;IAClD,0BAA0B,EAAE,0BAA0B,CAAC;CACxD,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,4BAA4B,EAC5B,yBAAyB,EACzB,0BAA0B,GAC3B,EACG,oCAAoC,GACpC,iCAAiC,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAyBrE"} +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.js b/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.js +new file mode 100644 +index 0000000..7f4e8a9 +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.js +@@ -0,0 +1,56 @@ ++"use strict"; ++Object.defineProperty(exports, "__esModule", { value: true }); ++exports.mergeGasFeeEstimates = exports.getGasFeeFlow = void 0; ++const controller_utils_1 = require("@metamask/controller-utils"); ++const gas_fee_controller_1 = require("@metamask/gas-fee-controller"); ++const types_1 = require("../types"); ++/** ++ * Returns the first gas fee flow that matches the transaction. ++ * ++ * @param transactionMeta - The transaction metadata to find a gas fee flow for. ++ * @param gasFeeFlows - The gas fee flows to search. ++ * @returns The first gas fee flow that matches the transaction, or undefined if none match. ++ */ ++function getGasFeeFlow(transactionMeta, gasFeeFlows) { ++ return gasFeeFlows.find((gasFeeFlow) => gasFeeFlow.matchesTransaction(transactionMeta)); ++} ++exports.getGasFeeFlow = getGasFeeFlow; ++/** ++ * Merge the gas fee estimates from the gas fee controller with the gas fee estimates from a transaction. ++ * ++ * @param request - Data required to merge gas fee estimates. ++ * @param request.gasFeeControllerEstimateType - Gas fee estimate type from the gas fee controller. ++ * @param request.gasFeeControllerEstimates - Gas fee estimates from the GasFeeController. ++ * @param request.transactionGasFeeEstimates - Gas fee estimates from the transaction. ++ * @returns The merged gas fee estimates. ++ */ ++function mergeGasFeeEstimates({ gasFeeControllerEstimateType, gasFeeControllerEstimates, transactionGasFeeEstimates, }) { ++ if (gasFeeControllerEstimateType === gas_fee_controller_1.GAS_ESTIMATE_TYPES.FEE_MARKET) { ++ return Object.values(types_1.GasFeeEstimateLevel).reduce((result, level) => (Object.assign(Object.assign({}, result), { [level]: mergeFeeMarketEstimate(gasFeeControllerEstimates[level], transactionGasFeeEstimates[level]) })), Object.assign({}, gasFeeControllerEstimates)); ++ } ++ if (gasFeeControllerEstimateType === gas_fee_controller_1.GAS_ESTIMATE_TYPES.LEGACY) { ++ return Object.values(types_1.GasFeeEstimateLevel).reduce((result, level) => (Object.assign(Object.assign({}, result), { [level]: getLegacyEstimate(transactionGasFeeEstimates[level]) })), {}); ++ } ++ return gasFeeControllerEstimates; ++} ++exports.mergeGasFeeEstimates = mergeGasFeeEstimates; ++/** ++ * Merge a specific priority level of EIP-1559 gas fee estimates. ++ * ++ * @param gasFeeControllerEstimate - The gas fee estimate from the gas fee controller. ++ * @param transactionGasFeeEstimate - The gas fee estimate from the transaction. ++ * @returns The merged gas fee estimate. ++ */ ++function mergeFeeMarketEstimate(gasFeeControllerEstimate, transactionGasFeeEstimate) { ++ return Object.assign(Object.assign({}, gasFeeControllerEstimate), { suggestedMaxFeePerGas: (0, controller_utils_1.weiHexToGweiDec)(transactionGasFeeEstimate.maxFeePerGas), suggestedMaxPriorityFeePerGas: (0, controller_utils_1.weiHexToGweiDec)(transactionGasFeeEstimate.maxPriorityFeePerGas) }); ++} ++/** ++ * Generate a specific priority level for a legacy gas fee estimate. ++ * ++ * @param transactionGasFeeEstimate - The gas fee estimate from the transaction. ++ * @returns The legacy gas fee estimate. ++ */ ++function getLegacyEstimate(transactionGasFeeEstimate) { ++ return (0, controller_utils_1.weiHexToGweiDec)(transactionGasFeeEstimate.maxFeePerGas); ++} ++//# sourceMappingURL=gas-flow.js.map +\ No newline at end of file +diff --git a/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.js.map b/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.js.map +new file mode 100644 +index 0000000..fe93a4a +--- /dev/null ++++ b/node_modules/@metamask/transaction-controller/dist/utils/gas-flow.js.map +@@ -0,0 +1 @@ ++{"version":3,"file":"gas-flow.js","sourceRoot":"","sources":["../../src/utils/gas-flow.ts"],"names":[],"mappings":";;;AAAA,iEAA6D;AAM7D,qEAGsC;AAEtC,oCAMkB;AAElB;;;;;;GAMG;AACH,SAAgB,aAAa,CAC3B,eAAgC,EAChC,WAAyB;IAEzB,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CACrC,UAAU,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAC/C,CAAC;AACJ,CAAC;AAPD,sCAOC;AAcD;;;;;;;;GAQG;AACH,SAAgB,oBAAoB,CAAC,EACnC,4BAA4B,EAC5B,yBAAyB,EACzB,0BAA0B,GAGS;IACnC,IAAI,4BAA4B,KAAK,uCAAkB,CAAC,UAAU,EAAE;QAClE,OAAO,MAAM,CAAC,MAAM,CAAC,2BAAmB,CAAC,CAAC,MAAM,CAC9C,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,iCACd,MAAM,KACT,CAAC,KAAK,CAAC,EAAE,sBAAsB,CAC7B,yBAAyB,CAAC,KAAK,CAAC,EAChC,0BAA0B,CAAC,KAAK,CAAC,CAClC,IACD,EACF,kBAAK,yBAAyB,CAAqB,CACpD,CAAC;KACH;IAED,IAAI,4BAA4B,KAAK,uCAAkB,CAAC,MAAM,EAAE;QAC9D,OAAO,MAAM,CAAC,MAAM,CAAC,2BAAmB,CAAC,CAAC,MAAM,CAC9C,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,iCACd,MAAM,KACT,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC,IAC7D,EACF,EAA4B,CAC7B,CAAC;KACH;IAED,OAAO,yBAAyB,CAAC;AACnC,CAAC;AA/BD,oDA+BC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAC7B,wBAAuC,EACvC,yBAAkD;IAElD,uCACK,wBAAwB,KAC3B,qBAAqB,EAAE,IAAA,kCAAe,EACpC,yBAAyB,CAAC,YAAY,CACvC,EACD,6BAA6B,EAAE,IAAA,kCAAe,EAC5C,yBAAyB,CAAC,oBAAoB,CAC/C,IACD;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CACxB,yBAAkD;IAElD,OAAO,IAAA,kCAAe,EAAC,yBAAyB,CAAC,YAAY,CAAC,CAAC;AACjE,CAAC","sourcesContent":["import { weiHexToGweiDec } from '@metamask/controller-utils';\nimport type {\n Eip1559GasFee,\n GasFeeEstimates,\n LegacyGasPriceEstimate,\n} from '@metamask/gas-fee-controller';\nimport {\n GAS_ESTIMATE_TYPES,\n type GasFeeState,\n} from '@metamask/gas-fee-controller';\n\nimport {\n type GasFeeEstimates as TransactionGasFeeEstimates,\n type GasFeeFlow,\n type TransactionMeta,\n type GasFeeEstimatesForLevel,\n GasFeeEstimateLevel,\n} from '../types';\n\n/**\n * Returns the first gas fee flow that matches the transaction.\n *\n * @param transactionMeta - The transaction metadata to find a gas fee flow for.\n * @param gasFeeFlows - The gas fee flows to search.\n * @returns The first gas fee flow that matches the transaction, or undefined if none match.\n */\nexport function getGasFeeFlow(\n transactionMeta: TransactionMeta,\n gasFeeFlows: GasFeeFlow[],\n): GasFeeFlow | undefined {\n return gasFeeFlows.find((gasFeeFlow) =>\n gasFeeFlow.matchesTransaction(transactionMeta),\n );\n}\n\ntype FeeMarketMergeGasFeeEstimatesRequest = {\n gasFeeControllerEstimateType: 'fee-market';\n gasFeeControllerEstimates: GasFeeEstimates;\n transactionGasFeeEstimates: TransactionGasFeeEstimates;\n};\n\ntype LegacyMergeGasFeeEstimatesRequest = {\n gasFeeControllerEstimateType: 'legacy';\n gasFeeControllerEstimates: LegacyGasPriceEstimate;\n transactionGasFeeEstimates: TransactionGasFeeEstimates;\n};\n\n/**\n * Merge the gas fee estimates from the gas fee controller with the gas fee estimates from a transaction.\n *\n * @param request - Data required to merge gas fee estimates.\n * @param request.gasFeeControllerEstimateType - Gas fee estimate type from the gas fee controller.\n * @param request.gasFeeControllerEstimates - Gas fee estimates from the GasFeeController.\n * @param request.transactionGasFeeEstimates - Gas fee estimates from the transaction.\n * @returns The merged gas fee estimates.\n */\nexport function mergeGasFeeEstimates({\n gasFeeControllerEstimateType,\n gasFeeControllerEstimates,\n transactionGasFeeEstimates,\n}:\n | FeeMarketMergeGasFeeEstimatesRequest\n | LegacyMergeGasFeeEstimatesRequest): GasFeeState['gasFeeEstimates'] {\n if (gasFeeControllerEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {\n return Object.values(GasFeeEstimateLevel).reduce(\n (result, level) => ({\n ...result,\n [level]: mergeFeeMarketEstimate(\n gasFeeControllerEstimates[level],\n transactionGasFeeEstimates[level],\n ),\n }),\n { ...gasFeeControllerEstimates } as GasFeeEstimates,\n );\n }\n\n if (gasFeeControllerEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {\n return Object.values(GasFeeEstimateLevel).reduce(\n (result, level) => ({\n ...result,\n [level]: getLegacyEstimate(transactionGasFeeEstimates[level]),\n }),\n {} as LegacyGasPriceEstimate,\n );\n }\n\n return gasFeeControllerEstimates;\n}\n\n/**\n * Merge a specific priority level of EIP-1559 gas fee estimates.\n *\n * @param gasFeeControllerEstimate - The gas fee estimate from the gas fee controller.\n * @param transactionGasFeeEstimate - The gas fee estimate from the transaction.\n * @returns The merged gas fee estimate.\n */\nfunction mergeFeeMarketEstimate(\n gasFeeControllerEstimate: Eip1559GasFee,\n transactionGasFeeEstimate: GasFeeEstimatesForLevel,\n): Eip1559GasFee {\n return {\n ...gasFeeControllerEstimate,\n suggestedMaxFeePerGas: weiHexToGweiDec(\n transactionGasFeeEstimate.maxFeePerGas,\n ),\n suggestedMaxPriorityFeePerGas: weiHexToGweiDec(\n transactionGasFeeEstimate.maxPriorityFeePerGas,\n ),\n };\n}\n\n/**\n * Generate a specific priority level for a legacy gas fee estimate.\n *\n * @param transactionGasFeeEstimate - The gas fee estimate from the transaction.\n * @returns The legacy gas fee estimate.\n */\nfunction getLegacyEstimate(\n transactionGasFeeEstimate: GasFeeEstimatesForLevel,\n): string {\n return weiHexToGweiDec(transactionGasFeeEstimate.maxFeePerGas);\n}\n"]} +\ No newline at end of file