From 6841a912954ab42a7c175b519c0725c504bb450b Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:37:59 +0000 Subject: [PATCH 1/7] fix: filter SES from Sentry stack trace frames (#8584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** SES is shown at the top of the stack trace, but we don't have a lockdown (repairIntrinsics) option to hide it yet this simply removes the SES frame from the Sentry error event stack trace we identify this with ease by the `filename`, rather than the verbose `context_line` here's an example of a SES Sentry error event stack trace frame that we remove: ``` { "colno":25, "context_line":" error= construct(FERAL_ERROR, rest, new.target);", "filename":"app:///ses.cjs", "function":"Error", "in_app":true, "lineno":7575, "platform":"javascript", "post_context":[ "Array" ], "pre_context":[ "Array" ] }, ``` - https://docs.sentry.io/platforms/javascript/configuration/filtering - https://github.com/endojs/endo/blob/master/packages/ses/docs/reference.md#options-quick-reference ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/8586 ## **Manual testing steps** - create personal Sentry account - run Sentry wizard to add/update `sentry.properties` files - update `app/util/sentry/utils.js` `Sentry.init({dsn: xxx})` with your own + for local testing, enable SES in debug-mode ([here](https://github.com/MetaMask/metamask-mobile/blob/main/patches/react-native%2B0.71.15.patch#L94)) + test a handled `Sentry.captureException('test')` + test a `Sentry.nativeCrash()` + test an unhandled `throw new Error('test')` - observe error on sentry.io - `Error(ses)` no longer seen - `app:///ses.cjs` frame no longer present in stack trace - `app:///ses.cjs` still present in breadcrumbs ## **Screenshots/Recordings** ### **Before** Screenshot 2024-02-14 at 9 07 15 pm Screenshot 2024-02-14 at 9 08 27 pm ### **After** Screenshot 2024-02-14 at 9 07 37 pm Screenshot 2024-02-14 at 9 08 53 pm ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [x] I've included manual testing steps - [x] I've included screenshots/recordings if applicable - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [x] In case it's not yet "ready for review", I've set it to "draft". - [x] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/util/sentry/utils.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 From baed762fe049ffca16d685b73262de9533ba7363 Mon Sep 17 00:00:00 2001 From: Nico MASSART Date: Mon, 26 Feb 2024 17:19:03 +0100 Subject: [PATCH 2/7] feat: segment migration of utils (#8705) - Migrate utils - move trackDappVisitedEvent to utils - update unit tests - update imports Progresses https://github.com/MetaMask/mobile-planning/issues/1129 --- .../Views/AccountConnect/AccountConnect.tsx | 2 +- app/components/Views/BrowserTab/index.js | 2 +- .../components/MessageSign/index.test.tsx | 75 ------------------- .../components/TypedSign/index.test.tsx | 24 +++--- app/util/confirmation/signatureUtils.js | 5 +- app/util/metrics/index.ts | 2 + .../trackDappVisitedEvent}/index.test.ts | 26 ++++--- .../metrics/trackDappVisitedEvent}/index.ts | 25 ++++--- app/util/middlewares.js | 2 +- 9 files changed, 49 insertions(+), 114 deletions(-) rename app/{analytics => util/metrics/trackDappVisitedEvent}/index.test.ts (86%) rename app/{analytics => util/metrics/trackDappVisitedEvent}/index.ts (50%) diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index c99a0f926a5..d9615ce4f8f 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -54,7 +54,7 @@ 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'; const AccountConnect = (props: AccountConnectProps) => { const Engine = UntypedEngine as any; diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index cea53ff6d75..83036bfb355 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -101,7 +101,7 @@ 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 { trackDappVisitedEvent } from '../../../util/metrics'; const { HOMEPAGE_URL, NOTIFICATION_NAMES } = AppConstants; const HOMEPAGE_HOST = new URL(HOMEPAGE_URL)?.hostname; diff --git a/app/components/Views/confirmations/components/MessageSign/index.test.tsx b/app/components/Views/confirmations/components/MessageSign/index.test.tsx index 208ba1ece0a..8f8c91f817a 100644 --- a/app/components/Views/confirmations/components/MessageSign/index.test.tsx +++ b/app/components/Views/confirmations/components/MessageSign/index.test.tsx @@ -7,11 +7,8 @@ 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'; @@ -36,14 +33,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(), @@ -145,7 +138,6 @@ describe('MessageSign', () => { 'TestMessageId:signError', expect.any(Function), ); - expect(analyticsV2.trackEvent).toHaveBeenCalledTimes(1); expect( Engine.context.SignatureController.hub.removeListener, ).toHaveBeenCalledTimes(0); @@ -243,46 +235,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 +285,6 @@ describe('MessageSign', () => { 'TestMessageId:signError', expect.any(Function), ); - expect(analyticsV2.trackEvent).toHaveBeenCalledTimes(1); expect( Engine.context.SignatureController.hub.removeListener, ).toHaveBeenCalledTimes(0); @@ -361,30 +312,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/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/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/trackDappVisitedEvent/index.test.ts similarity index 86% rename from app/analytics/index.test.ts rename to app/util/metrics/trackDappVisitedEvent/index.test.ts index fc3a9f375de..8c5896d68a6 100644 --- a/app/analytics/index.test.ts +++ b/app/util/metrics/trackDappVisitedEvent/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/trackDappVisitedEvent/index.ts similarity index 50% rename from app/analytics/index.ts rename to app/util/metrics/trackDappVisitedEvent/index.ts index 10d35787e4d..58993a279af 100644 --- a/app/analytics/index.ts +++ b/app/util/metrics/trackDappVisitedEvent/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/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. From 2809783448fa8c2e995a4f5e8a74597bba3a5d87 Mon Sep 17 00:00:00 2001 From: tommasini <46944231+tommasini@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:37:14 +0000 Subject: [PATCH 3/7] feat: c-a views components events migration to segment (#8711) C-A views component events migration to segment --- .../Views/AccountActions/AccountActions.tsx | 16 +- .../Views/AccountBackupStep1/index.js | 7 +- .../Views/AccountBackupStep1B/index.js | 7 +- .../Views/AccountConnect/AccountConnect.tsx | 25 ++- .../AccountPermissions/AccountPermissions.tsx | 23 ++- .../AccountPermissionsConnected.tsx | 7 +- .../AccountPermissionsRevoke.tsx | 31 ++-- .../Views/AccountSelector/AccountSelector.tsx | 18 +- app/components/Views/ActivityView/index.js | 7 +- .../AddAccountActions/AddAccountActions.tsx | 15 +- app/components/Views/Asset/index.js | 22 +-- app/components/Views/AssetDetails/index.tsx | 5 +- app/components/Views/Browser/index.js | 7 +- app/components/Views/BrowserTab/index.js | 47 +++-- app/components/Views/ChoosePassword/index.js | 6 +- .../metrics/trackDappVisited/index.test.ts | 169 ++++++++++++++++++ app/util/metrics/trackDappVisited/index.ts | 39 ++++ 17 files changed, 318 insertions(+), 133 deletions(-) create mode 100644 app/util/metrics/trackDappVisited/index.test.ts create mode 100644 app/util/metrics/trackDappVisited/index.ts 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 d9615ce4f8f..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 { @@ -55,12 +54,14 @@ import AccountConnectMultiSelector from './AccountConnectMultiSelector'; import useFavicon from '../../hooks/useFavicon/useFavicon'; import URLParse from 'url-parse'; 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 83036bfb355..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 '../../../util/metrics'; +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/util/metrics/trackDappVisited/index.test.ts b/app/util/metrics/trackDappVisited/index.test.ts new file mode 100644 index 00000000000..8c5896d68a6 --- /dev/null +++ b/app/util/metrics/trackDappVisited/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/trackDappVisited/index.ts b/app/util/metrics/trackDappVisited/index.ts new file mode 100644 index 00000000000..58993a279af --- /dev/null +++ b/app/util/metrics/trackDappVisited/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; From 88a04ac24f7da31a0dc86d7b47d22e4bb9fbac62 Mon Sep 17 00:00:00 2001 From: tommasini <46944231+tommasini@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:39:32 +0000 Subject: [PATCH 4/7] feat: Edit gas, Drawer, DeleteWallet, ComponentErrorBoundary, CollectilbleContracts, BrowsserBottomBar events migration (#8656) Edit gas, Drawer, DeleteWallet, ComponentErrorBoundary, CollectilbleContracts, BrowsserBottomBar components under UI folder, with new Segment metrics. Co-authored-by: sethkfman <10342624+sethkfman@users.noreply.github.com> --- .../__snapshots__/index.test.tsx.snap | 218 ++---------------- app/components/UI/BrowserBottomBar/index.js | 13 +- .../UI/CollectibleContracts/index.js | 12 +- .../UI/ComponentErrorBoundary/index.js | 2 +- app/components/UI/DeleteWalletModal/index.tsx | 3 +- app/components/UI/DrawerView/index.js | 37 +-- app/components/UI/EditGasFee1559/index.js | 15 +- app/components/UI/EditGasFeeLegacy/index.js | 14 +- 8 files changed, 71 insertions(+), 243 deletions(-) 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) => { From 0e2c734f8c1393a11b9c223bf2b9f805cfa973e2 Mon Sep 17 00:00:00 2001 From: tommasini <46944231+tommasini@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:42:11 +0000 Subject: [PATCH 5/7] feat: confirmations views components events migration (#8712) Confirmations folder junder Views folder events migration Co-authored-by: Nico MASSART --- .../Views/confirmations/Approval/index.js | 54 +++++++++------ .../ApproveView/Approve/index.js | 55 ++++++++------- .../Views/confirmations/Send/index.js | 22 +++--- .../confirmations/SendFlow/Amount/index.js | 23 ++++--- .../confirmations/SendFlow/Confirm/index.js | 46 ++++++++----- .../SendFlow/Confirm/index.test.tsx | 10 --- .../confirmations/SendFlow/SendTo/index.js | 44 +++++------- .../AddNickname/index.tsx | 10 ++- .../ApproveTransactionReview/index.js | 69 ++++++++++++------- .../components/EditGasFee1559Update/index.tsx | 14 ++-- .../EditGasFeeLegacyUpdate/index.tsx | 15 +++- .../components/MessageSign/MessageSign.tsx | 9 +-- .../components/MessageSign/index.test.tsx | 8 +++ .../components/PersonalSign/PersonalSign.tsx | 17 ++--- .../components/PersonalSign/index.test.tsx | 22 +++--- .../components/SignatureRequest/index.js | 15 ++-- .../TransactionReviewInformation/index.js | 15 ++-- .../components/TransactionReview/index.js | 34 ++++----- .../TransactionReview/index.test.tsx | 8 --- .../components/TypedSign/index.js | 14 ++-- .../components/WatchAssetRequest/index.js | 8 +-- 21 files changed, 282 insertions(+), 230 deletions(-) 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..bdda770b338 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,7 @@ 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'; const EDIT = 'edit'; const REVIEW = 'review'; @@ -166,6 +166,10 @@ class Approve extends PureComponent { * Object that represents the navigator */ navigation: PropTypes.object, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -357,15 +361,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 +469,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 +482,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 +492,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 +568,7 @@ class Approve extends PureComponent { waitForResult: true, }); - AnalyticsV2.trackEvent( + metrics.trackEvent( MetaMetricsEvents.APPROVAL_COMPLETED, this.getAnalyticsParams(), ); @@ -577,9 +581,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 +589,7 @@ class Approve extends PureComponent { }; onCancel = () => { + const { metrics, hideModal } = this.props; Engine.rejectPendingApproval( this.props.transaction.id, ethErrors.provider.userRejectedRequest(), @@ -595,11 +598,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 +617,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); } }; @@ -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..56ed1faaaf3 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,7 @@ 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'; const KEYBOARD_OFFSET = Device.isSmallDevice() ? 80 : 120; @@ -476,6 +476,10 @@ class Amount extends PureComponent { * String that indicates the current chain id */ chainId: PropTypes.string, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -678,11 +682,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 +1262,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', }); @@ -1538,4 +1540,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/index.js b/app/components/Views/confirmations/SendFlow/Confirm/index.js index 0d0037f4c0f..7d7ec47760a 100644 --- a/app/components/Views/confirmations/SendFlow/Confirm/index.js +++ b/app/components/Views/confirmations/SendFlow/Confirm/index.js @@ -48,7 +48,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 +61,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 +114,7 @@ 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'; const EDIT = 'edit'; const EDIT_NONCE = 'edit_nonce'; @@ -229,6 +228,10 @@ class Confirm extends PureComponent { * Boolean that indicates if the network supports buy */ isNativeTokenBuySupported: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -385,7 +388,7 @@ class Confirm extends PureComponent { pollToken, }); // For analytics - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.SEND_TRANSACTION_STARTED, this.getAnalyticsParams(), ); @@ -540,11 +543,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 +769,7 @@ class Confirm extends PureComponent { assetType, }); this.checkRemoveCollectible(); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.SEND_TRANSACTION_COMPLETED, gaParams, ); @@ -868,10 +869,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 +889,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, ); } @@ -1019,9 +1023,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 +1090,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, ); @@ -1377,4 +1382,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..15225b74cf0 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'; @@ -151,13 +150,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 +164,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..bf3e29e5252 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,7 @@ import { GAS_LIMIT_MIN, GAS_PRICE_MIN, } from '../../../../../util/gasUtils'; +import { useMetrics } from '../../../../../components/hooks/useMetrics'; const EditGasFeeLegacy = ({ onCancel, @@ -56,6 +56,7 @@ const EditGasFeeLegacy = ({ selectedGasObject, hasDappSuggestedGas, }: EditGasFeeLegacyUpdateProps) => { + const { trackEvent } = useMetrics(); const [showRangeInfoModal, setShowRangeInfoModal] = useState(false); const [infoText, setInfoText] = useState(''); const [gasPriceError, setGasPriceError] = useState(''); @@ -97,7 +98,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 +110,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 8f8c91f817a..14c23daa1f8 100644 --- a/app/components/Views/confirmations/components/MessageSign/index.test.tsx +++ b/app/components/Views/confirmations/components/MessageSign/index.test.tsx @@ -12,9 +12,12 @@ import { act, waitFor } from '@testing-library/react-native'; // 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(), @@ -122,6 +125,11 @@ function createContainer({ ); } +const mockTrackEvent = jest.fn(); +(useMetrics as jest.Mock).mockReturnValue({ + trackEvent: mockTrackEvent, +}); + describe('MessageSign', () => { beforeEach(() => { jest.clearAllMocks(); 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/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, From 48696900e915eb8c7356a31eb2438559f07a677c Mon Sep 17 00:00:00 2001 From: Derek Brans Date: Mon, 26 Feb 2024 11:43:48 -0500 Subject: [PATCH 6/7] fix: patch transaction controller in mobile to add fallback gas estimation (#8707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Diff in core repo corresponding to this patch: https://github.com/MetaMask/core/commit/0c09517b4abb8578e8c9551241514199db0b010b This patches transaction-controller 6.1.0, introducing a fallback for gas estimations. In cases of failed gas estimation, the transaction will default to using 95% of the latest block gas limit. This ensures that transactions have viable gas. Previously, if gas estimation for a transaction failed (for example, due to the transaction being reverted), the gasHex value would remain undefined. This issue led to the transaction being assigned zero gas, causing it to be submitted to the mempool where it would get "stuck". This change is specific to version 6.1.0 of the transaction-controller. Later versions of the TransactionController include this fix. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/8533 ## **Manual testing steps** 1. Go to https://mmsdk-polygon-swap.vercel.app/ 2. Add Polygon chain and switch to network 3. Click "Send Transaction" 4. Try again if you get a JSON-RPC error (unrelated issue) Transaction should submit and fail... but not get stuck in "submitted" state as in https://github.com/MetaMask/metamask-mobile/issues/8533. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've clearly explained what problem this PR is solving and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ...etamask+transaction-controller+6.1.0.patch | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/patches/@metamask+transaction-controller+6.1.0.patch b/patches/@metamask+transaction-controller+6.1.0.patch index 6a42705ee04..d5acc95582a 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 @@ @@ -811,7 +811,7 @@ index 236aaf1..0000000 -{"version":3,"file":"TransactionController.d.ts","sourceRoot":"","sources":["../src/TransactionController.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/node_modules/@metamask/transaction-controller/dist/TransactionController.js b/node_modules/@metamask/transaction-controller/dist/TransactionController.js -index 3edd9c2..28a1c63 100644 +index 3edd9c2..f173f21 100644 --- a/node_modules/@metamask/transaction-controller/dist/TransactionController.js +++ b/node_modules/@metamask/transaction-controller/dist/TransactionController.js @@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { @@ -1234,7 +1234,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 +436,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 +503,23 @@ class TransactionController extends base_controller_1.BaseController { transactions[index] = transactionMeta; this.update({ transactions: this.trimTransactionsForState(transactions) }); } @@ -1258,7 +1267,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 +545,15 @@ class TransactionController extends base_controller_1.BaseController { }); } /** @@ -1332,7 +1341,7 @@ index 3edd9c2..28a1c63 100644 }); } /** -@@ -765,7 +570,9 @@ class TransactionController extends base_controller_1.BaseController { +@@ -765,7 +572,9 @@ class TransactionController extends base_controller_1.BaseController { */ trimTransactionsForState(transactions) { const nonceNetworkSet = new Set(); @@ -1343,7 +1352,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 +589,7 @@ class TransactionController extends base_controller_1.BaseController { } return false; }); @@ -1352,7 +1361,7 @@ index 3edd9c2..28a1c63 100644 return txsToKeep; } /** -@@ -790,10 +597,10 @@ class TransactionController extends base_controller_1.BaseController { +@@ -790,10 +599,10 @@ class TransactionController extends base_controller_1.BaseController { * @returns Whether the transaction is in a final state. */ isFinalState(status) { @@ -1367,7 +1376,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 +614,7 @@ class TransactionController extends base_controller_1.BaseController { return __awaiter(this, void 0, void 0, function* () { const { status, transactionHash } = meta; switch (status) { @@ -1376,7 +1385,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 +631,7 @@ class TransactionController extends base_controller_1.BaseController { return [meta, false]; } return [meta, true]; @@ -1385,7 +1394,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 +646,17 @@ class TransactionController extends base_controller_1.BaseController { } /* istanbul ignore next */ if (txObj === null || txObj === void 0 ? void 0 : txObj.blockNumber) { @@ -1406,7 +1415,7 @@ index 3edd9c2..28a1c63 100644 } return [meta, false]; default: -@@ -868,128 +683,233 @@ class TransactionController extends base_controller_1.BaseController { +@@ -868,128 +685,233 @@ class TransactionController extends base_controller_1.BaseController { return Number(txReceipt.status) === 0; }); } From 9e4c51d17423971385b2a20a0ababbf93d6cfb97 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Mon, 26 Feb 2024 18:59:41 +0000 Subject: [PATCH 7/7] feat: support updated Linea gas fee estimation (#8581) When creating transactions on a Linea network, use the updated gas fee estimates provided by the `linea_estimateGas` RPC method. Create selectors for the `GasFeeController`, `TransactionController`, and for general transaction confirmation. Use these new selectors in place of any direct state references to ensure the transaction specific estimates are used whenever they are available. Resolve a Redux / selector limitation of `BaseControllerV1` controllers. Fix saving custom gas fee values in the send flow. Reject approval request when closing send confirm component. --- app/actions/transaction/index.js | 12 + app/components/UI/Swaps/QuotesView.js | 4 +- app/components/UI/Transactions/index.js | 4 +- .../components/TransactionEditor/index.js | 8 +- .../ApproveView/Approve/index.js | 8 +- .../confirmations/SendFlow/Amount/index.js | 42 +- .../CustomGasModal/CustomGasModal.tsx | 6 +- .../confirmations/SendFlow/Confirm/index.js | 140 +-- .../SendFlow/Confirm/index.test.tsx | 1 + .../EditGasFeeLegacyUpdate/index.tsx | 7 +- .../components/UpdateEIP1559Tx/index.tsx | 8 +- app/core/Engine.ts | 1 + app/core/GasPolling/GasPolling.ts | 3 +- app/core/redux/slices/engine/index.ts | 19 +- app/reducers/transaction/index.js | 7 + app/selectors/confirmTransaction.test.ts | 134 +++ app/selectors/confirmTransaction.ts | 61 ++ app/selectors/gasFeeController.test.ts | 48 + app/selectors/gasFeeController.ts | 21 + app/selectors/transactionController.test.ts | 24 + app/selectors/transactionController.ts | 17 + app/selectors/util.ts | 8 + ...etamask+transaction-controller+6.1.0.patch | 831 ++++++++++++++++-- 23 files changed, 1260 insertions(+), 154 deletions(-) create mode 100644 app/selectors/confirmTransaction.test.ts create mode 100644 app/selectors/confirmTransaction.ts create mode 100644 app/selectors/gasFeeController.test.ts create mode 100644 app/selectors/gasFeeController.ts create mode 100644 app/selectors/transactionController.test.ts create mode 100644 app/selectors/transactionController.ts create mode 100644 app/selectors/util.ts 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/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/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/ApproveView/Approve/index.js b/app/components/Views/confirmations/ApproveView/Approve/index.js index bdda770b338..879b5bc5266 100644 --- a/app/components/Views/confirmations/ApproveView/Approve/index.js +++ b/app/components/Views/confirmations/ApproveView/Approve/index.js @@ -74,6 +74,8 @@ 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'; @@ -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), diff --git a/app/components/Views/confirmations/SendFlow/Amount/index.js b/app/components/Views/confirmations/SendFlow/Amount/index.js index 56ed1faaaf3..79251004053 100644 --- a/app/components/Views/confirmations/SendFlow/Amount/index.js +++ b/app/components/Views/confirmations/SendFlow/Amount/index.js @@ -106,6 +106,8 @@ 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; @@ -480,6 +482,14 @@ class Amount extends PureComponent { * 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 = { @@ -520,6 +530,8 @@ class Amount extends PureComponent { providerType, selectedAsset, isPaymentRequest, + gasEstimateType, + gasFeeEstimates, } = this.props; // For analytics this.updateNavBar(); @@ -534,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({ @@ -562,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) }); } @@ -1514,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), 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 7d7ec47760a..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'; @@ -115,6 +116,9 @@ import { createLedgerTransactionModalNavDetails } from '../../../../../component 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'; @@ -232,6 +236,10 @@ class Confirm extends PureComponent { * Metrics injected by withMetricsAwareness HOC */ metrics: PropTypes.object, + /** + * Set transaction ID + */ + setTransactionId: PropTypes.func, }; state = { @@ -254,7 +262,6 @@ class Confirm extends PureComponent { multiLayerL1FeeTotal: '0x0', result: {}, transactionMeta: {}, - preparedTransaction: {}, }; originIsWalletConnect = this.props.transaction.origin?.startsWith( @@ -329,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 @@ -378,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(); @@ -403,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, @@ -441,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 = @@ -483,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) => { @@ -800,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) { @@ -824,6 +829,8 @@ class Confirm extends PureComponent { return; } + const { result, transactionMeta } = this.state; + const isLedgerAccount = isHardwareAccount(transaction.from, [ ExtendedKeyringTypes.ledger, ]); @@ -853,6 +860,7 @@ class Confirm extends PureComponent { return; } + await this.persistTransactionParameters(transaction); await KeyringController.resetQRKeyringState(); await ApprovalController.accept(transactionMeta.id, undefined, { waitForResult: true, @@ -943,7 +951,6 @@ class Confirm extends PureComponent { updateTransactionStateWithUpdatedNonce = (nonceValue) => { this.props.setNonce(nonceValue); - this.setState({ preparedTransaction: {} }); }; renderCustomNonceModal = () => { @@ -1124,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 { @@ -1358,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, @@ -1375,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) => diff --git a/app/components/Views/confirmations/SendFlow/Confirm/index.test.tsx b/app/components/Views/confirmations/SendFlow/Confirm/index.test.tsx index 15225b74cf0..0607097b9ff 100644 --- a/app/components/Views/confirmations/SendFlow/Confirm/index.test.tsx +++ b/app/components/Views/confirmations/SendFlow/Confirm/index.test.tsx @@ -96,6 +96,7 @@ jest.mock('../../../../../lib/ppom/ppom-util', () => ({ })); jest.mock('../../../../../core/Engine', () => ({ + rejectPendingApproval: jest.fn(), context: { TokensController: { addToken: jest.fn(), diff --git a/app/components/Views/confirmations/components/EditGasFeeLegacyUpdate/index.tsx b/app/components/Views/confirmations/components/EditGasFeeLegacyUpdate/index.tsx index bf3e29e5252..901d351ab33 100644 --- a/app/components/Views/confirmations/components/EditGasFeeLegacyUpdate/index.tsx +++ b/app/components/Views/confirmations/components/EditGasFeeLegacyUpdate/index.tsx @@ -40,6 +40,7 @@ import { GAS_PRICE_MIN, } from '../../../../../util/gasUtils'; import { useMetrics } from '../../../../../components/hooks/useMetrics'; +import { selectGasFeeEstimates } from '../../../../../selectors/confirmTransaction'; const EditGasFeeLegacy = ({ onCancel, @@ -74,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, 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/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/patches/@metamask+transaction-controller+6.1.0.patch b/patches/@metamask+transaction-controller+6.1.0.patch index d5acc95582a..6dc9dd5e7cb 100644 --- a/patches/@metamask+transaction-controller+6.1.0.patch +++ b/patches/@metamask+transaction-controller+6.1.0.patch @@ -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..f173f21 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..f173f21 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..f173f21 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..f173f21 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..f173f21 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..f173f21 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..f173f21 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..f173f21 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..f173f21 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..f173f21 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..f173f21 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,7 @@ index 3edd9c2..f173f21 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 }) }); -@@ -596,6 +436,8 @@ 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; @@ -1243,7 +1267,7 @@ index 3edd9c2..f173f21 100644 } // 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 +503,23 @@ class TransactionController extends base_controller_1.BaseController { +@@ -661,6 +519,23 @@ class TransactionController extends base_controller_1.BaseController { transactions[index] = transactionMeta; this.update({ transactions: this.trimTransactionsForState(transactions) }); } @@ -1267,7 +1291,7 @@ index 3edd9c2..f173f21 100644 /** * Removes all transactions from state, optionally based on the current network. * -@@ -686,67 +545,15 @@ class TransactionController extends base_controller_1.BaseController { +@@ -686,67 +561,15 @@ class TransactionController extends base_controller_1.BaseController { }); } /** @@ -1341,7 +1365,7 @@ index 3edd9c2..f173f21 100644 }); } /** -@@ -765,7 +572,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(); @@ -1352,7 +1376,7 @@ index 3edd9c2..f173f21 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 +589,7 @@ class TransactionController extends base_controller_1.BaseController { +@@ -780,7 +605,7 @@ class TransactionController extends base_controller_1.BaseController { } return false; }); @@ -1361,7 +1385,7 @@ index 3edd9c2..f173f21 100644 return txsToKeep; } /** -@@ -790,10 +599,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) { @@ -1376,7 +1400,7 @@ index 3edd9c2..f173f21 100644 } /** * Method to verify the state of a transaction using the Blockchain as a source of truth. -@@ -805,7 +614,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) { @@ -1385,7 +1409,7 @@ index 3edd9c2..f173f21 100644 const txReceipt = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionReceipt', [ transactionHash, ]); -@@ -822,7 +631,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]; @@ -1394,7 +1418,7 @@ index 3edd9c2..f173f21 100644 const txObj = yield (0, controller_utils_1.query)(this.ethQuery, 'getTransactionByHash', [ transactionHash, ]); -@@ -837,9 +646,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) { @@ -1415,7 +1439,7 @@ index 3edd9c2..f173f21 100644 } return [meta, false]; default: -@@ -868,128 +685,233 @@ class TransactionController extends base_controller_1.BaseController { +@@ -868,128 +701,236 @@ class TransactionController extends base_controller_1.BaseController { return Number(txReceipt.status) === 0; }); } @@ -1744,10 +1768,12 @@ index 3edd9c2..f173f21 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; @@ -2212,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 @@ -2231,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 @@ -2249,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 @@ -2861,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 + * @@ -2908,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. + */ @@ -3037,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 @@ -3074,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 @@ -3240,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