diff --git a/.github/workflows/create-cherry-pick-pr.yml b/.github/workflows/create-cherry-pick-pr.yml index cc4d1abdb06..edeec25e8f3 100644 --- a/.github/workflows/create-cherry-pick-pr.yml +++ b/.github/workflows/create-cherry-pick-pr.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: branch_name: - description: 'Branch name you want the cherry-pick branch to be based from' + description: 'Target branch name to merge the cherry-picked branch into' required: true commit_hash: description: 'Commit Hash' diff --git a/.github/workflows/security-code-scanner.yml b/.github/workflows/security-code-scanner.yml new file mode 100644 index 00000000000..729f6f4e31e --- /dev/null +++ b/.github/workflows/security-code-scanner.yml @@ -0,0 +1,35 @@ +name: "MetaMask Security Code Scanner" + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + run-security-scan: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + steps: + - name: MetaMask Security Code Scanner + uses: MetaMask/Security-Code-Scanner@main + with: + repo: ${{ github.repository }} + paths_ignored: | + e2e/ + docs/ + storybook/ + .storybook/ + '**/*.test.js' + '**/*.test.ts' + '**/*.test.jsx' + '**/*.test.tsx' + '**/*.stories.tsx' + '**/*.stories.js' + '**/*.snap' + rules_excluded: example + mixpanel_project_token: ${{secrets.SECURITY_CODE_SCANNER_MIXPANEL_TOKEN}} + slack_webhook: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }} diff --git a/.js.env.example b/.js.env.example index fd36d5d5e62..6a76c00af6c 100644 --- a/.js.env.example +++ b/.js.env.example @@ -53,4 +53,10 @@ export WATCHER_PORT=8081 export METAMASK_ENVIRONMENT="" # Build type: "main" or "flask" -export METAMASK_BUILD_TYPE="" \ No newline at end of file +export METAMASK_BUILD_TYPE="" + +# Segment SDK proxy endpoint and write key +export SEGMENT_WRITE_KEY="" +export SEGMENT_PROXY_URL="" +export SEGMENT_DELETE_API_SOURCE_ID="" +export SEGMENT_REGULATIONS_ENDPOINT="" diff --git a/.storybook/main.js b/.storybook/main.js index de82113ddcf..b218e7f96fd 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,6 +1,7 @@ module.exports = { stories: [ '../app/component-library/components/**/*.stories.?(ts|tsx|js|jsx)', + '../app/component-library/components-temp/TagColored/**/*.stories.?(ts|tsx|js|jsx)', ], addons: ['@storybook/addon-ondevice-controls'], framework: '@storybook/react-native', diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js index c0afadd4a0c..32cee332bf3 100644 --- a/.storybook/storybook.requires.js +++ b/.storybook/storybook.requires.js @@ -16,6 +16,13 @@ global.STORIES = [ importPathMatcher: "^\\.[\\\\/](?:app\\/component-library\\/components(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$", }, + { + titlePrefix: "", + directory: "./app/component-library/components-temp/TagColored", + files: "**/*.stories.?(ts|tsx|js|jsx)", + importPathMatcher: + "^\\.[\\\\/](?:app\\/component-library\\/components-temp\\/TagColored(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$", + }, ]; import "@storybook/addon-ondevice-controls/register"; @@ -87,8 +94,8 @@ const getStories = () => { "./app/component-library/components/Pickers/PickerAccount/PickerAccount.stories.tsx": require("../app/component-library/components/Pickers/PickerAccount/PickerAccount.stories.tsx"), "./app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx": require("../app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx"), "./app/component-library/components/RadioButton/RadioButton.stories.tsx": require("../app/component-library/components/RadioButton/RadioButton.stories.tsx"), - "./app/component-library/components/Select/SelectOption/SelectOption.stories.tsx": require("../app/component-library/components/Select/SelectOption/SelectOption.stories.tsx"), "./app/component-library/components/Select/SelectButton/SelectButton.stories.tsx": require("../app/component-library/components/Select/SelectButton/SelectButton.stories.tsx"), + "./app/component-library/components/Select/SelectOption/SelectOption.stories.tsx": require("../app/component-library/components/Select/SelectOption/SelectOption.stories.tsx"), "./app/component-library/components/Select/SelectValue/SelectValue.stories.tsx": require("../app/component-library/components/Select/SelectValue/SelectValue.stories.tsx"), "./app/component-library/components/Sheet/SheetBottom/SheetBottom.stories.tsx": require("../app/component-library/components/Sheet/SheetBottom/SheetBottom.stories.tsx"), "./app/component-library/components/Sheet/SheetHeader/SheetHeader.stories.tsx": require("../app/component-library/components/Sheet/SheetHeader/SheetHeader.stories.tsx"), @@ -97,6 +104,7 @@ const getStories = () => { "./app/component-library/components/Texts/Text/Text.stories.tsx": require("../app/component-library/components/Texts/Text/Text.stories.tsx"), "./app/component-library/components/Texts/TextWithPrefixIcon/TextWithPrefixIcon.stories.tsx": require("../app/component-library/components/Texts/TextWithPrefixIcon/TextWithPrefixIcon.stories.tsx"), "./app/component-library/components/Toast/Toast.stories.tsx": require("../app/component-library/components/Toast/Toast.stories.tsx"), + "./app/component-library/components-temp/TagColored/TagColored.stories.tsx": require("../app/component-library/components-temp/TagColored/TagColored.stories.tsx"), }; }; diff --git a/README.md b/README.md index f6e20357017..89cb782993f 100644 --- a/README.md +++ b/README.md @@ -10,365 +10,59 @@ For up to the minute news, follow our [Twitter](https://twitter.com/metamask) or To learn how to develop MetaMask-compatible applications, visit our [Developer Docs](https://docs.metamask.io). -## MetaMask Mobile +## Documentation +- [Architecture](./docs/readme/architecture.md) +- [Development Environment Setup](./docs/readme/environment.md) +- [Build Troubleshooting](./docs/readme/troubleshooting.md) +- [Testing](./docs/readme/testing.md) +- [Debugging](./docs/readme/debugging.md) +- [Storybook](./docs/readme/storybook.md) +- [Miscellaneous](./docs/readme/miscellaneous.md) -### Environment Setup +## Getting started -The code is built using React-Native and running code locally requires a Mac or Linux OS. +### Environment setup -- Install [sentry-cli](https://github.com/getsentry/sentry-cli) tools: `brew install getsentry/tools/sentry-cli` +Before running the app, make sure your development environment has all the required tools. -<<<<<<< HEAD -- Install [Node.js](https://nodejs.org) **version 18** -======= - - Auto upload for source maps and debug files is disabled by default. To enable it, set the `SENTRY_DISABLE_AUTO_UPLOAD` environment variable to `false`. ->>>>>>> 7c51abce6 (fix: Fix/8352 source map stack trace (#8509)) +[Setup your development environment](./docs/readme/environment.md) -- Install [Node.js](https://nodejs.org) **version 18.18** - - - If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you. - -- Install [Yarn v1](https://yarnpkg.com/en/docs/install) - - One way to install Yarn v1 is by using brew: - - ```bash - brew install yarn@1.22.19 - ``` - -<<<<<<< HEAD -- Install the shared [React Native dependencies](https://reactnative.dev/docs/environment-setup#installing-dependencies) (`React Native CLI`, _not_ `Expo CLI`) - - XCode version `15.0.1` or below -======= - To check you've installed the right version: ->>>>>>> 7c51abce6 (fix: Fix/8352 source map stack trace (#8509)) - - ```bash - yarn --version - ``` - -- Install the shared [React Native dependencies](https://reactnative.dev/docs/environment-setup#installing-dependencies) (`React Native CLI`, _not_ `Expo CLI`) - - -- Install [cocoapods](https://guides.cocoapods.org/using/getting-started.html) by running: - - ```bash - sudo gem install cocoapods -v 1.12.1 - ``` - -- Install [Python](https://www.python.org/downloads/) **version ^3.10** - - _Note_: M1 User might need to stay with **version 3.10** - -### Device Environment Setup - -#### Android - -- Install [Java](https://www.java.com/en/download/). To check if Java is already installed, run: - ``` - java -version - ``` -- Install the Android SDK, via [Android Studio](https://developer.android.com/studio). - - _MetaMask Only:_ To create production builds, you need to install Google Play Licensing Library via the SDK Manager in Android Studio. -- Install the Android NDK (version `21.4.7075529`), via [Android Studio](https://developer.android.com/studio)'s SDK Manager. - - Go to Settings > Appearance & Behavior > System Settings > Android SDK - - Shortcut: Selecting `More Actions` > `SDK Manager` from the "Welcome to Android Studio" page will also bring you here. - - Select `SDK Tools` tab - - Check `Show Package Details` option below the tools list to show available versions - - Locate `NDK (Side-by-side)` option in the tools list - - Check NDK version `21.4.7075529` - - Locate `CMake` option in the tools list - - Check CMake version `3.22.1` - - Click "Apply" or "OK" to download -- Linux only: - - Ensure that you have the `secret-tool` binary on your machine. - - Part of the [libsecret-tools](https://launchpad.net/ubuntu/bionic/+package/libsecret-tools) package on Debian/Ubuntu based distributions. -- Install the correct emulator - - Follow the instructions at: - - [React Native Getting Started - Android](https://reactnative.dev/docs/environment-setup#installing-dependencies) _(React Native CLI Quickstart -> [your OS] -> Android)_ - - FYI: as of today (7/18/23) there is currently an issue when running detox on android 12 and 13 (API 32/33) which prevents the tests from running. The issue is, the tap() action is treated like a tapAndHold() action. See the open issue in wix/detox [here](https://github.com/wix/Detox/issues/3762) - - More details can be found [on the Android Developer site](https://developer.android.com/studio/run/emulator) - - You should use the following: - - **Android OS Version:** Latest, unless told otherwise - - **Device:** Google Pixel 5 -- Finally, start the emulator from Android Studio: - - Open "Virtual Device Manager" - - Launch emulator for "Pixel 5 " - -#### iOS - -- Install the iOS dependencies - - [React Native Getting Started - iOS](https://reactnative.dev/docs/environment-setup#installing-dependencies) _(React Native CLI Quickstart -> [your OS] -> iOS)_ -- Install the correct simulator - - **iOS OS Version:** Latest, unless told otherwise - - **Device:** iPhone 13 Pro - -### Building Locally - -- Clone this repo: +### Building the app +**Clone the project** ```bash -git clone ... +git clone git@github.com:MetaMask/metamask-mobile.git && \ cd metamask-mobile ``` -- _MetaMask Only:_ Rename the `.*.env.example` files (remove the `.example`) in the root of the project and fill in the appropriate values for each key. Get the values from another MetaMask Mobile developer. -- _Non-MetaMask Only:_ In the project root folder run -- If you intend to use WalletConnect v2 during your development, you should register to get a projectId from WalletConnect website and set the `WALLET_CONNECT_PROJECT_ID` value accordingly in .js.env file. - -``` - cp .ios.env.example .ios.env && \ - cp .android.env.example .android.env && \ - cp .js.env.example .js.env -``` - -- _Non-MetaMask Only:_ Create an account and generate your own API key at [Infura](https://infura.io) in order to connect to main and test nets. Fill `MM_INFURA_PROJECT_ID` in `.js.env`. (App will run without it, but will not be able to connect to actual network.) -- _Non-MetaMask Only:_ Fill `MM_SENTRY_DSN` in `.js.env` if you want the app to emit logs to your own Sentry project. -- Note: after updating env vars, make sure to run `source .js.env` on the root of the project - -- Install the app: - -``` -yarn setup # not the usual install command, this will run a lengthy postinstall flow -``` - -- Then, in one terminal, run: - -```bash -yarn watch -``` - -- You can change the default port (8081) from metro using the WATCHER_PORT environment variable. For example: - -```bash -WATCHER_PORT=8082 yarn watch -# This value can also be set directly inside .js.env file -``` - -#### Android +**Install dependencies** ```bash -yarn start:android +yarn setup ``` +_Not the usual install command, this will run scripts and a lengthy postinstall flow_ -#### iOS - +**Setup environment variables** ```bash -yarn start:ios +cp .ios.env.example .ios.env && \ +cp .android.env.example .android.env && \ +cp .js.env.example .js.env ``` -#### Build Troubleshooting - -Unfortunately, the build system may fail to pick up local changes, such as installing new NPM packages or `yarn link`ing a dependency. -If the app is behaving strangely or not picking up your local changes, it may be due to build issues. -To ensure that you're starting with a clean slate, close all emulators/simulators, stop the `yarn watch` process, and run: - -```bash -yarn clean - -# if you're going to `yarn link` any packages, -# do that here, before the next command - -yarn watch:clean - -# ...and then, in another terminal - -yarn start:ios - -# or - -yarn start:android -``` - -If `yarn link` fails after going through these steps, try directly `yarn add`ing the local files instead. - -### Debugging - -First, make sure you have the following running: - -- `yarn watch` -- Your Android emulator or iOS simulator -- `yarn start:android` or `yarn start:ios` - -Next, install the [Flipper](https://fbflipper.com/) desktop app (verified working with v0.127.0) - -- Once Flipper is installed, configure your system as follows: - - Install react-devtools: `npm i -g react-devtools@4.22.1` - - Update Android SDK location settings by accessing Flipper's settings via the `Gear Icon` -> `Settings` - - Example SDK path: `/Users//Library/Android/sdk` - -Finally, check that the debugger is working: - -- Open your emulator or simulator alongside the Flipper app -- Flipper should auto-detect the device and the application to debug -- You should now be able to access features such as `Logs` - -#### Debugging Physical iOS devices - -- Debugging physical iOS devices requires `idb` to be installed, which consists of 2 parts -- Install the two idb parts: - 1. `brew tap facebook/fb` & `brew install idb-companion` - 2. `pip3.9 install fb-idb` (This step may require that you install python3 via `python -m pip3 install --upgrade pip`) - -#### Debug a website inside the WebView (in-app browser) - -Android - -- Run the app in debug mode (for example, in a simulator) -- Open Chrome on your desktop -- Go to `chrome://inspect/#devices` -- Look for the device and click inspect - -iOS - -- Run the app in debug mode (for example, in a simulator) -- Open Safari on your desktop -- Go to the menu Develop -> [Your device] -> [Website] - -You should see the console for the website that is running inside the WebView - -#### Miscellaneous - -- [Troubleshooting for React Native](https://facebook.github.io/react-native/docs/troubleshooting#content) -- [Flipper Documentation](https://fbflipper.com/docs/features/react-native/) - -### Running Tests - -#### Unit Tests - -```bash -yarn test:unit -``` - -#### E2E Tests - -##### Platforms - -For both iOS and Android platforms, our chosen E2E test framework is Detox. We also utilize Appium for Android (wdio folder). - -##### Test wallet - -E2E tests use a wallet able to access testnet and mainnet. -On Bitrise CI, the wallet is created using the secret recovery phrase from secret env var. -For local testing, the wallet is created using the secret recovery phrase from the `.e2e.env` file. - -##### Detox - -All tests live within the e2e/specs folder. - -### iOS - -Prerequisites for running tests: - -- Make sure to install `detox-cli` by referring to the instructions mentioned [here](https://wix.github.io/Detox/docs/introduction/getting-started/#detox-prerequisites). -- Additionally, install `applesimutils` by following the guidelines provided [here](https://github.com/wix/AppleSimulatorUtils). -- Before running any tests, it's recommended to refer to the `iOS section` above and check the latest simulator device specified under `Install the correct simulator`. -- The default device for iOS is the iPhone 13 Pro and Android the Pixel 5. Ensure you have these set up. -- Make sure that Metro is running. Use this command to launch the metro server: +### Running the app +**Run Metro bundler** ```bash yarn watch ``` +_Like a local server for the app_ -You can trigger the tests against a `release` or `debug` build. It recommended that you trigger the tests against a debug build. - -To trigger the tests on a debug build run this command: - -For iOS - +**Run on a iOS device** ```bash -yarn test:e2e:ios:debug +yarn start:ios ``` -and on Android: - +**Run on an Android device** ```bash -yarn test:e2e:android:debug -``` - -If you choose to run tests against a release build, you can do so by running this command: - -For iOS - -```bash -yarn test:e2e:ios -``` - -and on Android: - -```bash -yarn test:e2e:android -``` - -If you have already built the application for Detox and want to run a specific test from the test folder, you can use this command: - -For iOS - -```bash -yarn test:e2e:ios:debug:single e2e/specs/TEST_NAME.spec.js -``` - -and on Android: - -```bash -yarn test:e2e:android:debug:single e2e/specs/TEST_NAME.spec.js -``` - -To run tests associated with a certain tag, you can do so using the `--testNamePattern` flag. For example: - -```bash -yarn test:e2e:ios:debug --testNamePattern="Smoke" -``` - -```bash -yarn test:e2e:android:debug --testNamePattern="Smoke" -``` - -This runs all tests that are tagged "Smoke" - -##### Appium - -The appium tests lives within the wdio/feature folder. - -By default the tests use an avd named `Android 11 - Pixel 4a API 31`, with API `Level 30` (Android 11). You can modify the emulator and platform version by navigating to `wdio/config/android.config.debug.js` and adjusting the values of `deviceName` to match your emulator's name, and `platformVersion` to match your operating system's version. Make sure to verify that the config file accurately represents your emulator settings before executing any tests. - -The sequence in which you should run tests: - -create a test build using this command: - -```bash -yarn start:android:qa -``` - -Then run tests using this command: - -```bash -yarn test:wdio:android -``` - -If you want to run a specific test, you can include the `--spec` flag in the aforementioned command. For example: - -```bash -yarn test:wdio:android --spec ./wdio/features/Onboarding/CreateNewWallet.feature -``` - -### Changing dependencies - -Whenever you change dependencies (adding, removing, or updating, either in `package.json` or `yarn.lock`), there are various files that must be kept up-to-date. - -- `yarn.lock`: - - Run `yarn setup` again after your changes to ensure `yarn.lock` has been properly updated. -- The `allow-scripts` configuration in `package.json` - - Run `yarn allow-scripts auto` to update the `allow-scripts` configuration automatically. This config determines whether the package's install/postinstall scripts are allowed to run. Review each new package to determine whether the install script needs to run or not, testing if necessary. - - Unfortunately, `yarn allow-scripts auto` will behave inconsistently on different platforms. macOS and Windows users may see extraneous changes relating to optional dependencies. - -### Architecture - -To get a better understanding of the internal architecture of this app take a look at [this diagram](https://github.com/MetaMask/metamask-mobile/blob/main/architecture.svg). - -### Storybook - -We have begun documenting our components using Storybook. Please read the [Documentation Guidelines](./storybook/DOCUMENTATION_GUIDELINES.md) to get up and running. - -### Other Docs - -- [Adding Confirmations](./docs/confirmations.md) +yarn start:android +``` \ No newline at end of file diff --git a/app/actions/browser/index.js b/app/actions/browser/index.js index 0f63d097776..64c3a2fd522 100644 --- a/app/actions/browser/index.js +++ b/app/actions/browser/index.js @@ -1,3 +1,23 @@ +/** + * Browser actions for Redux + */ +export const BrowserActionTypes = { + ADD_TO_VISITED_DAPP: 'ADD_TO_VISITED_DAPP', +}; + +/** + * Adds a new entry to visited dapps + * + * @param {string} hostname - Dapp hostname + * @returns + */ +export function addToVisitedDapp(hostname) { + return { + type: BrowserActionTypes.ADD_TO_VISITED_DAPP, + hostname, + }; +} + /** * Adds a new entry to the browser history * diff --git a/app/actions/settings/index.js b/app/actions/settings/index.js index d4a3bb39fc7..ea64074d2d1 100644 --- a/app/actions/settings/index.js +++ b/app/actions/settings/index.js @@ -46,10 +46,3 @@ export function setUseBlockieIcon(useBlockieIcon) { useBlockieIcon, }; } - -export function setLedgerBetaEnabled(ledgerBetaEnabled) { - return { - type: 'SET_LEDGER_BETA_ENABLED', - ledgerBetaEnabled, - }; -} diff --git a/app/actions/signatureRequest/index.ts b/app/actions/signatureRequest/index.ts index c29eb5c217d..db042fef16d 100644 --- a/app/actions/signatureRequest/index.ts +++ b/app/actions/signatureRequest/index.ts @@ -1,4 +1,4 @@ -import { SecurityAlertResponse } from '../../components/UI/BlockaidBanner/BlockaidBanner.types'; +import { SecurityAlertResponse } from '../../components/Views/confirmations/components/BlockaidBanner/BlockaidBanner.types'; /** * Clears transaction object completely 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/component-library/components-temp/TagColored/README.md b/app/component-library/components-temp/TagColored/README.md new file mode 100644 index 00000000000..40bd865b461 --- /dev/null +++ b/app/component-library/components-temp/TagColored/README.md @@ -0,0 +1,40 @@ +# TagColored + +## TagColored Props + +This component extends React Native's [ViewProps](https://reactnative.dev/docs/view) component. + +### `color` + +Optional prop to configure the color of the TagColored + +| TYPE | REQUIRED | DEFAULT | +| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- | +| [TagColor](./TagColored.types.ts) | No | TagColor.Default | + +### `children` + +Content to wrap to display. + +| TYPE | REQUIRED | +| :-------------------------------------------------- | :------------------------------------------------------ | +| string or ReactNode | Yes | + +## Usage + +```javascript +// Passing text to children +SAMPLE TAGCOLORED TEXT; + +// Passing node to children + + SAMPLE TAGCOLORED TEXT +; + +// Configuring different colors +SAMPLE TAGCOLORED TEXT; +SAMPLE TAGCOLORED TEXT; +SAMPLE TAGCOLORED TEXT; +SAMPLE TAGCOLORED TEXT; +SAMPLE TAGCOLORED TEXT; +``` diff --git a/app/component-library/components-temp/TagColored/TagColored.constants.ts b/app/component-library/components-temp/TagColored/TagColored.constants.ts new file mode 100644 index 00000000000..f4468459326 --- /dev/null +++ b/app/component-library/components-temp/TagColored/TagColored.constants.ts @@ -0,0 +1,20 @@ +/* eslint-disable import/prefer-default-export */ +// External dependencies +import { TextVariant } from '../../components/Texts/Text'; + +// Internal dependencies +import { TagColor, TagColoredProps } from './TagColored.types'; + +// Defaults +export const DEFAULT_TAGCOLORED_COLOR = TagColor.Default; +export const DEFAULT_TAGCOLORED_TEXTVARIANT = TextVariant.BodyXS; + +// Test IDs +export const TAGCOLORED_TESTID = 'tagcolored'; +export const TAGCOLORED_TEXT_TESTID = 'tagcolored-text'; + +// Sample consts +export const SAMPLE_TAGCOLORED_PROPS: TagColoredProps = { + color: DEFAULT_TAGCOLORED_COLOR, + children: 'Sample TagColored text', +}; diff --git a/app/component-library/components-temp/TagColored/TagColored.stories.tsx b/app/component-library/components-temp/TagColored/TagColored.stories.tsx new file mode 100644 index 00000000000..65df02d9cb1 --- /dev/null +++ b/app/component-library/components-temp/TagColored/TagColored.stories.tsx @@ -0,0 +1,29 @@ +// Internal dependencies. +import { default as TagColoredComponent } from './TagColored'; +import { TagColor } from './TagColored.types'; +import { SAMPLE_TAGCOLORED_PROPS } from './TagColored.constants'; + +const TagColoredStoryMeta = { + title: 'Components Temp / TagColored', + component: TagColoredComponent, + argTypes: { + color: { + options: TagColor, + control: { + type: 'select', + }, + }, + children: { + control: { type: 'text' }, + }, + }, +}; + +export default TagColoredStoryMeta; + +export const TagColored = { + args: { + color: SAMPLE_TAGCOLORED_PROPS.color, + children: SAMPLE_TAGCOLORED_PROPS.children, + }, +}; diff --git a/app/component-library/components-temp/TagColored/TagColored.styles.ts b/app/component-library/components-temp/TagColored/TagColored.styles.ts new file mode 100644 index 00000000000..f10fa27a669 --- /dev/null +++ b/app/component-library/components-temp/TagColored/TagColored.styles.ts @@ -0,0 +1,72 @@ +// Third party dependencies. +import { StyleSheet, ViewStyle } from 'react-native'; + +// External dependencies. +import { Theme } from '../../../util/theme/models'; + +// Internal dependencies. +import { TagColoredStyleSheetVars, TagColor } from './TagColored.types'; + +/** + * Style sheet function for TagColored component. + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @param params.vars Inputs that the style sheet depends on. + * @returns StyleSheet object. + */ +const styleSheet = (params: { + theme: Theme; + vars: TagColoredStyleSheetVars; +}) => { + const { vars, theme } = params; + const { style, color } = vars; + let backgroundColor, textColor; + switch (color) { + case TagColor.Default: + backgroundColor = theme.colors.background.alternative; + textColor = theme.colors.text.alternative; + break; + case TagColor.Success: + backgroundColor = theme.colors.success.muted; + textColor = theme.colors.success.default; + break; + case TagColor.Info: + backgroundColor = theme.colors.primary.muted; + textColor = theme.colors.primary.default; + break; + case TagColor.Danger: + backgroundColor = theme.colors.error.muted; + textColor = theme.colors.error.default; + break; + case TagColor.Warning: + backgroundColor = theme.colors.warning.muted; + textColor = theme.colors.warning.default; + break; + default: + backgroundColor = theme.colors.background.alternative; + textColor = theme.colors.text.alternative; + } + + return StyleSheet.create({ + base: Object.assign( + { + height: 20, + backgroundColor, + borderRadius: 4, + paddingHorizontal: 8, + alignSelf: 'flex-start', + alignItems: 'center', + justifyContent: 'center', + } as ViewStyle, + style, + ) as ViewStyle, + text: { + fontWeight: 'bold', + textTransform: 'uppercase', + color: textColor, + }, + }); +}; + +export default styleSheet; diff --git a/app/component-library/components-temp/TagColored/TagColored.test.tsx b/app/component-library/components-temp/TagColored/TagColored.test.tsx new file mode 100644 index 00000000000..67e26caa966 --- /dev/null +++ b/app/component-library/components-temp/TagColored/TagColored.test.tsx @@ -0,0 +1,144 @@ +// Third party dependencies +import React from 'react'; +import { render } from '@testing-library/react-native'; + +// External dependencies +import Text from '../../components/Texts/Text'; +import { mockTheme } from '../../../util/theme'; + +// Internal dependencies +import TagColored from './TagColored'; +import { + DEFAULT_TAGCOLORED_TEXTVARIANT, + SAMPLE_TAGCOLORED_PROPS, + TAGCOLORED_TESTID, + TAGCOLORED_TEXT_TESTID, +} from './TagColored.constants'; +import { TagColor } from './TagColored.types'; + +describe('TagColored', () => { + it('should render TagColored', () => { + const wrapper = render(); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.queryByTestId(TAGCOLORED_TESTID)).not.toBe(null); + }); + + it('should render children correctly when provided', () => { + const testText = 'TagColored'; + const ChildrenComponent = () => {testText}; + + const { getByText } = render( + + + , + ); + + expect(getByText(testText)).toBeDefined(); + }); + + it('should render children correctly when a string is provided', () => { + const testText = 'TagColored'; + + const { getByText } = render({testText}); + + expect(getByText(testText)).toBeDefined(); + }); + + it('should render children with the right text variant if typeof children === string', () => { + const testText = 'TagColored'; + + const { getByText } = render({testText}); + + expect(getByText(testText).props.style.fontSize).toBe( + mockTheme.typography[DEFAULT_TAGCOLORED_TEXTVARIANT].fontSize, + ); + }); + + it('should render the correct default color on default', () => { + const testText = 'TagColored'; + + const { getByTestId } = render({testText}); + + expect(getByTestId(TAGCOLORED_TESTID).props.style.backgroundColor).toBe( + mockTheme.colors.background.alternative, + ); + expect(getByTestId(TAGCOLORED_TEXT_TESTID).props.style.color).toBe( + mockTheme.colors.text.alternative, + ); + }); + + it('should render the correct default color when given', () => { + const testText = 'TagColored'; + + const { getByTestId } = render( + {testText}, + ); + + expect(getByTestId(TAGCOLORED_TESTID).props.style.backgroundColor).toBe( + mockTheme.colors.background.alternative, + ); + expect(getByTestId(TAGCOLORED_TEXT_TESTID).props.style.color).toBe( + mockTheme.colors.text.alternative, + ); + }); + + it('should render the correct success color when given', () => { + const testText = 'TagColored'; + + const { getByTestId } = render( + {testText}, + ); + + expect(getByTestId(TAGCOLORED_TESTID).props.style.backgroundColor).toBe( + mockTheme.colors.success.muted, + ); + expect(getByTestId(TAGCOLORED_TEXT_TESTID).props.style.color).toBe( + mockTheme.colors.success.default, + ); + }); + + it('should render the correct info color when given', () => { + const testText = 'TagColored'; + + const { getByTestId } = render( + {testText}, + ); + + expect(getByTestId(TAGCOLORED_TESTID).props.style.backgroundColor).toBe( + mockTheme.colors.primary.muted, + ); + expect(getByTestId(TAGCOLORED_TEXT_TESTID).props.style.color).toBe( + mockTheme.colors.primary.default, + ); + }); + + it('should render the correct danger color when given', () => { + const testText = 'TagColored'; + + const { getByTestId } = render( + {testText}, + ); + + expect(getByTestId(TAGCOLORED_TESTID).props.style.backgroundColor).toBe( + mockTheme.colors.error.muted, + ); + expect(getByTestId(TAGCOLORED_TEXT_TESTID).props.style.color).toBe( + mockTheme.colors.error.default, + ); + }); + + it('should render the correct warning color when given', () => { + const testText = 'TagColored'; + + const { getByTestId } = render( + {testText}, + ); + + expect(getByTestId(TAGCOLORED_TESTID).props.style.backgroundColor).toBe( + mockTheme.colors.warning.muted, + ); + expect(getByTestId(TAGCOLORED_TEXT_TESTID).props.style.color).toBe( + mockTheme.colors.warning.default, + ); + }); +}); diff --git a/app/component-library/components-temp/TagColored/TagColored.tsx b/app/component-library/components-temp/TagColored/TagColored.tsx new file mode 100644 index 00000000000..b50a4b289c3 --- /dev/null +++ b/app/component-library/components-temp/TagColored/TagColored.tsx @@ -0,0 +1,44 @@ +/* eslint-disable react/prop-types */ + +// Third party dependencies. +import React from 'react'; +import { View } from 'react-native'; + +// External dependencies. +import { useStyles } from '../../hooks'; +import Text from '../../../component-library/components/Texts/Text'; + +// Internal dependencies. +import styleSheet from './TagColored.styles'; +import { TagColoredProps } from './TagColored.types'; +import { + DEFAULT_TAGCOLORED_COLOR, + DEFAULT_TAGCOLORED_TEXTVARIANT, + TAGCOLORED_TESTID, + TAGCOLORED_TEXT_TESTID, +} from './TagColored.constants'; + +const TagColored: React.FC = ({ + style, + color = DEFAULT_TAGCOLORED_COLOR, + children, +}) => { + const { styles } = useStyles(styleSheet, { style, color }); + return ( + + {typeof children === 'string' ? ( + + {children} + + ) : ( + children + )} + + ); +}; + +export default TagColored; diff --git a/app/component-library/components-temp/TagColored/TagColored.types.ts b/app/component-library/components-temp/TagColored/TagColored.types.ts new file mode 100644 index 00000000000..3556386d933 --- /dev/null +++ b/app/component-library/components-temp/TagColored/TagColored.types.ts @@ -0,0 +1,34 @@ +// Third party dependencies +import { ViewProps } from 'react-native'; + +/** + * Tag Color. + */ +export enum TagColor { + Default = 'Default', + Success = 'Success', + Info = 'Info', + Danger = 'Danger', + Warning = 'Warning', +} + +/** + * TagColored component props. + */ +export interface TagColoredProps extends ViewProps { + /** + * Content to wrap to display. + */ + children: React.ReactNode | string; + /** + * Optional prop to configure the color of the TagColored + * @default TagColor.Default + */ + color?: TagColor; +} +/** + * Style sheet input parameters. + */ +export type TagColoredStyleSheetVars = Pick & { + color: TagColor; +}; diff --git a/app/component-library/components-temp/TagColored/__snapshots__/TagColored.test.tsx.snap b/app/component-library/components-temp/TagColored/__snapshots__/TagColored.test.tsx.snap new file mode 100644 index 00000000000..bfa4d5ea7b9 --- /dev/null +++ b/app/component-library/components-temp/TagColored/__snapshots__/TagColored.test.tsx.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TagColored should render TagColored 1`] = ` + + + Sample TagColored text + + +`; diff --git a/app/component-library/components-temp/TagColored/index.ts b/app/component-library/components-temp/TagColored/index.ts new file mode 100644 index 00000000000..6c18384b817 --- /dev/null +++ b/app/component-library/components-temp/TagColored/index.ts @@ -0,0 +1,2 @@ +export { default } from './TagColored'; +export { TagColor } from './TagColored.types'; diff --git a/app/component-library/components/Accordions/Accordion/README.md b/app/component-library/components/Accordions/Accordion/README.md index 7284bfebc49..b03af34bbdc 100644 --- a/app/component-library/components/Accordions/Accordion/README.md +++ b/app/component-library/components/Accordions/Accordion/README.md @@ -1,4 +1,4 @@ -# Accordion +# [Accordion](https://metamask-consensys.notion.site/Accordion-f43e500b92754d9bb06bc72553c44494) ![Accordion](./Accordion.png) diff --git a/app/component-library/components/Avatars/Avatar/README.md b/app/component-library/components/Avatars/Avatar/README.md index f4f560333f9..bdcce39668e 100644 --- a/app/component-library/components/Avatars/Avatar/README.md +++ b/app/component-library/components/Avatars/Avatar/README.md @@ -1,4 +1,4 @@ -# Avatar +# [Avatar](https://metamask-consensys.notion.site/Avatar-3c55358131914c589ed1e6d21d14a04f) ![Avatar](./Avatar.png) diff --git a/app/component-library/components/Badges/Badge/README.md b/app/component-library/components/Badges/Badge/README.md index 3deed9285ec..823ee6eb3e2 100644 --- a/app/component-library/components/Badges/Badge/README.md +++ b/app/component-library/components/Badges/Badge/README.md @@ -1,6 +1,7 @@ # Badge - +[BadgeNetwork](https://metamask-consensys.notion.site/Badge-Network-94a679c50cb446f4844dc624b4f74946) ![BadgeNetwork](./variants/BadgeNetwork/BadgeNetwork.png) +[BadgeStatus](https://metamask-consensys.notion.site/Badge-Status-5caf000de32549f8ad67c0b89469ce4d) ![BadgeStatus](./variants/BadgeStatus/BadgeStatus.png) Badge is a union component, which currently only consist of [BadgeNetwork](./variants/BadgeNetwork/BadgeNetwork.tsx) diff --git a/app/component-library/components/Badges/Badge/variants/BadgeNetwork/README.md b/app/component-library/components/Badges/Badge/variants/BadgeNetwork/README.md index 57a2cb2185d..39b019216cb 100644 --- a/app/component-library/components/Badges/Badge/variants/BadgeNetwork/README.md +++ b/app/component-library/components/Badges/Badge/variants/BadgeNetwork/README.md @@ -1,4 +1,4 @@ -# BadgeNetwork +# [BadgeNetwork](https://metamask-consensys.notion.site/Badge-Network-94a679c50cb446f4844dc624b4f74946) ![BadgeNetwork](./BadgeNetwork.png) diff --git a/app/component-library/components/Badges/Badge/variants/BadgeStatus/README.md b/app/component-library/components/Badges/Badge/variants/BadgeStatus/README.md index a4cadf17e6f..5b294c62942 100644 --- a/app/component-library/components/Badges/Badge/variants/BadgeStatus/README.md +++ b/app/component-library/components/Badges/Badge/variants/BadgeStatus/README.md @@ -1,4 +1,4 @@ -# BadgeStatus +# [BadgeStatus](https://metamask-consensys.notion.site/Badge-Status-5caf000de32549f8ad67c0b89469ce4d) ![BadgeStatus](./BadgeStatus.png) diff --git a/app/component-library/components/Banners/Banner/README.md b/app/component-library/components/Banners/Banner/README.md index d02b563be93..984ffa65425 100644 --- a/app/component-library/components/Banners/Banner/README.md +++ b/app/component-library/components/Banners/Banner/README.md @@ -1,6 +1,7 @@ # Banner - +[BannerAlert](https://metamask-consensys.notion.site/Banner-Alert-56177453644641ed8ef5c96db6a7681f) ![BannerAlert](./variants/BannerAlert/BannerAlert.png) +[BannerTip](https://metamask-consensys.notion.site/Banner-Tip-67fdb01ab850472f90abc6f4127395cb) ![BannerTip](./variants/BannerTip/BannerTip.png) This component is a union Banner component, which consists of [BannerAlert](../BannerAlert/BannerAlert.tsx) and [BannerTip](../BannerTip/BannerTip.tsx). diff --git a/app/component-library/components/Banners/Banner/variants/BannerAlert/README.md b/app/component-library/components/Banners/Banner/variants/BannerAlert/README.md index 46136bd4b2a..3e4f9a07142 100644 --- a/app/component-library/components/Banners/Banner/variants/BannerAlert/README.md +++ b/app/component-library/components/Banners/Banner/variants/BannerAlert/README.md @@ -1,4 +1,4 @@ -# BannerAlert +# [BannerAlert](https://metamask-consensys.notion.site/Banner-Alert-56177453644641ed8ef5c96db6a7681f) ![BannerAlert](./BannerAlert.png) diff --git a/app/component-library/components/Banners/Banner/variants/BannerTip/README.md b/app/component-library/components/Banners/Banner/variants/BannerTip/README.md index 8ba12117fd2..a19ba8b7894 100644 --- a/app/component-library/components/Banners/Banner/variants/BannerTip/README.md +++ b/app/component-library/components/Banners/Banner/variants/BannerTip/README.md @@ -1,4 +1,4 @@ -# BannerTip +# [BannerTip](https://metamask-consensys.notion.site/Banner-Tip-67fdb01ab850472f90abc6f4127395cb) ![BannerTip](./BannerTip.png) diff --git a/app/component-library/components/BottomSheets/BottomSheet/README.md b/app/component-library/components/BottomSheets/BottomSheet/README.md index 8694cefc13d..c858d5319b6 100644 --- a/app/component-library/components/BottomSheets/BottomSheet/README.md +++ b/app/component-library/components/BottomSheets/BottomSheet/README.md @@ -1,4 +1,4 @@ -# BottomSheet +# [BottomSheet](https://metamask-consensys.notion.site/Bottom-Sheet-302164aff8a74b6eb721f49586f88ecd) ![BottomSheet](./BottomSheet.png) diff --git a/app/component-library/components/BottomSheets/BottomSheetFooter/README.md b/app/component-library/components/BottomSheets/BottomSheetFooter/README.md index 24969991fb8..a4e7d187e3b 100644 --- a/app/component-library/components/BottomSheets/BottomSheetFooter/README.md +++ b/app/component-library/components/BottomSheets/BottomSheetFooter/README.md @@ -1,5 +1,4 @@ -# BottomSheetFooter - +# [BottomSheetFooter](https://metamask-consensys.notion.site/Footer-b0eacc3951bf4962b75f6c4f95a8c2df) ![BottomSheetFooter](./BottomSheetFooter.png) BottomSheetFooter is a Footer component specifically used for BottomSheets. diff --git a/app/component-library/components/BottomSheets/BottomSheetFooter/__snapshots__/BottomSheetFooter.test.tsx.snap b/app/component-library/components/BottomSheets/BottomSheetFooter/__snapshots__/BottomSheetFooter.test.tsx.snap index 2a12274955b..48120778bff 100644 --- a/app/component-library/components/BottomSheets/BottomSheetFooter/__snapshots__/BottomSheetFooter.test.tsx.snap +++ b/app/component-library/components/BottomSheets/BottomSheetFooter/__snapshots__/BottomSheetFooter.test.tsx.snap @@ -78,7 +78,7 @@ exports[`BottomSheetFooter should render snapshot correctly 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", diff --git a/app/component-library/components/Buttons/Button/README.md b/app/component-library/components/Buttons/Button/README.md index 5ea2d3dc0e2..12d3563a188 100644 --- a/app/component-library/components/Buttons/Button/README.md +++ b/app/component-library/components/Buttons/Button/README.md @@ -1,4 +1,4 @@ -# Button +# [Button](https://metamask-consensys.notion.site/Button-88af1ddc075b40e3bb38a6d0c098d9b6) ![Button](./Button.png) diff --git a/app/component-library/components/Buttons/ButtonIcon/README.md b/app/component-library/components/Buttons/ButtonIcon/README.md index 4e87e3fac80..6313fd85b13 100644 --- a/app/component-library/components/Buttons/ButtonIcon/README.md +++ b/app/component-library/components/Buttons/ButtonIcon/README.md @@ -1,4 +1,4 @@ -# ButtonIcon +# [ButtonIcon](https://metamask-consensys.notion.site/Button-Icon-52fa285ebd8b4d56a22b6eabd08a8cf0) ![ButtonIcon](./ButtonIcon.png) diff --git a/app/component-library/components/Checkbox/README.md b/app/component-library/components/Checkbox/README.md index b5a14884f2d..71da58f812a 100644 --- a/app/component-library/components/Checkbox/README.md +++ b/app/component-library/components/Checkbox/README.md @@ -1,4 +1,4 @@ -# Checkbox +# [Checkbox](https://metamask-consensys.notion.site/Checkbox-359c236367eb4058876fc12189c06824) ![Checkbox](./Checkbox.png) diff --git a/app/component-library/components/Form/HelpText/README.md b/app/component-library/components/Form/HelpText/README.md index 8d32b3d84c2..fecb7f35685 100644 --- a/app/component-library/components/Form/HelpText/README.md +++ b/app/component-library/components/Form/HelpText/README.md @@ -1,4 +1,4 @@ -# HelpText +# [HelpText](https://metamask-consensys.notion.site/Help-Text-d3c5f6284df84d598e2c2d586083eef0) ![HelpText](./HelpText.png) diff --git a/app/component-library/components/Form/Label/README.md b/app/component-library/components/Form/Label/README.md index dd85166181d..1e025a04d92 100644 --- a/app/component-library/components/Form/Label/README.md +++ b/app/component-library/components/Form/Label/README.md @@ -1,4 +1,4 @@ -# Label +# [Label](https://metamask-consensys.notion.site/Label-d6aa56ac40044195a5078d37d3118223) ![Label](./Label.png) diff --git a/app/component-library/components/Form/TextField/README.md b/app/component-library/components/Form/TextField/README.md index 11c976c1bc4..e4518b8979e 100644 --- a/app/component-library/components/Form/TextField/README.md +++ b/app/component-library/components/Form/TextField/README.md @@ -1,4 +1,4 @@ -# TextField +# [TextField](https://metamask-consensys.notion.site/Text-Field-73e2b9b748404901ab2c9fba13eb7785) ![TextField](./TextField.png) diff --git a/app/component-library/components/Form/TextFieldSearch/README.md b/app/component-library/components/Form/TextFieldSearch/README.md index a2e256125dd..0eadc0378aa 100644 --- a/app/component-library/components/Form/TextFieldSearch/README.md +++ b/app/component-library/components/Form/TextFieldSearch/README.md @@ -1,4 +1,4 @@ -# TextFieldSearch +# [TextFieldSearch](https://metamask-consensys.notion.site/Text-Field-Search-de685317eeaf413f9682363128ffe673) ![TextFieldSearch](./TextFieldSearch.png) diff --git a/app/component-library/components/HeaderBase/README.md b/app/component-library/components/HeaderBase/README.md index de83961f28a..c5766b54932 100644 --- a/app/component-library/components/HeaderBase/README.md +++ b/app/component-library/components/HeaderBase/README.md @@ -1,4 +1,4 @@ -# HeaderBase +# [HeaderBase](https://metamask-consensys.notion.site/Header-Base-2e96e1ac4c2b4816bb7bd3c594c72538) ![HeaderBase](./HeaderBase.png) diff --git a/app/component-library/components/Icons/Icon/README.md b/app/component-library/components/Icons/Icon/README.md index df26b39e04d..9df7fff346a 100644 --- a/app/component-library/components/Icons/Icon/README.md +++ b/app/component-library/components/Icons/Icon/README.md @@ -1,4 +1,4 @@ -# Icon +# [Icon](https://metamask-consensys.notion.site/Icon-140cbe6ea9044c189e75b795fef99dd6) ![Icon](./Icon.png) diff --git a/app/component-library/components/List/ListItem/README.md b/app/component-library/components/List/ListItem/README.md index cd15c1130cc..dfb736636ad 100644 --- a/app/component-library/components/List/ListItem/README.md +++ b/app/component-library/components/List/ListItem/README.md @@ -1,4 +1,4 @@ -# ListItem +# [ListItem](https://metamask-consensys.notion.site/List-Item-e3ddf0999a1844288b0a152e9d2a54b3) ![ListItem](./ListItem.png) diff --git a/app/component-library/components/List/ListItemMultiSelect/README.md b/app/component-library/components/List/ListItemMultiSelect/README.md index 0e083dad054..9c46dcc2f8f 100644 --- a/app/component-library/components/List/ListItemMultiSelect/README.md +++ b/app/component-library/components/List/ListItemMultiSelect/README.md @@ -1,4 +1,4 @@ -# ListItemMultiSelect +# [ListItemMultiSelect](https://metamask-consensys.notion.site/List-Item-MultiSelect-8ffafd9e99114e588c5c28ee5e4f0b10) ![ListItemMultiSelect](./ListItemMultiSelect.png) diff --git a/app/component-library/components/List/ListItemSelect/README.md b/app/component-library/components/List/ListItemSelect/README.md index 3ffa4fdb7e4..da661f535fb 100644 --- a/app/component-library/components/List/ListItemSelect/README.md +++ b/app/component-library/components/List/ListItemSelect/README.md @@ -1,4 +1,4 @@ -# ListItemSelect +# [ListItemSelect](https://metamask-consensys.notion.site/List-Item-Select-e9173552eb1c4723b9549a021624c536) ![ListItemSelect](./ListItemSelect.png) diff --git a/app/component-library/components/Navigation/TabBar/TabBar.tsx b/app/component-library/components/Navigation/TabBar/TabBar.tsx index 817a41d665c..40bb34e29f3 100644 --- a/app/component-library/components/Navigation/TabBar/TabBar.tsx +++ b/app/component-library/components/Navigation/TabBar/TabBar.tsx @@ -15,6 +15,7 @@ import { useTheme } from '../../../../util/theme'; import Analytics from '../../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import { selectChainId } from '../../../../selectors/networkController'; +import { getDecimalChainId } from '../../../../util/networks'; // Internal dependencies. import { TabBarProps } from './TabBar.types'; @@ -76,7 +77,7 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { MetaMetricsEvents.ACTIONS_BUTTON_CLICKED, { text: '', - chain_id: chainId, + chain_id: getDecimalChainId(chainId), }, ); break; diff --git a/app/component-library/components/Navigation/TabBar/__snapshots__/TabBar.test.tsx.snap b/app/component-library/components/Navigation/TabBar/__snapshots__/TabBar.test.tsx.snap index d5dde280fdd..f658bbc2270 100644 --- a/app/component-library/components/Navigation/TabBar/__snapshots__/TabBar.test.tsx.snap +++ b/app/component-library/components/Navigation/TabBar/__snapshots__/TabBar.test.tsx.snap @@ -127,7 +127,7 @@ Array [ } > + StyleSheet.create({ + actionsContainer: { + alignItems: 'flex-start', + justifyContent: 'center', + }, + }); + +export default styleSheet; diff --git a/app/components/Approvals/AddChainApproval/AddChainApproval.test.tsx b/app/components/Approvals/AddChainApproval/AddChainApproval.test.tsx index 79da3bde5b3..ab165ddae69 100644 --- a/app/components/Approvals/AddChainApproval/AddChainApproval.test.tsx +++ b/app/components/Approvals/AddChainApproval/AddChainApproval.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; import AddChainApproval from './AddChainApproval'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; -jest.mock('../../hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); const mockApprovalRequest = (approvalRequest?: ApprovalRequest) => { ( diff --git a/app/components/Approvals/AddChainApproval/AddChainApproval.tsx b/app/components/Approvals/AddChainApproval/AddChainApproval.tsx index 30084afc69e..a0798e0155b 100644 --- a/app/components/Approvals/AddChainApproval/AddChainApproval.tsx +++ b/app/components/Approvals/AddChainApproval/AddChainApproval.tsx @@ -1,56 +1,29 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import NetworkVerificationInfo from '../../UI/NetworkVerificationInfo'; import BottomSheet from '../../../component-library/components/BottomSheets/BottomSheet'; -import BottomSheetHeader from '../../../component-library/components/BottomSheets/BottomSheetHeader'; -import Text, { - TextVariant, -} from '../../../component-library/components/Texts/Text'; -import BottomSheetFooter, { - ButtonsAlignment, -} from '../../../component-library/components/BottomSheets/BottomSheetFooter'; -import { - ButtonSize, - ButtonVariants, -} from '../../../component-library/components/Buttons/Button'; -import { strings } from '../../../../locales/i18n'; -import { CommonSelectorsIDs } from '../../../../e2e/selectors/Common.selectors'; +import { useStyles } from '../../../component-library/hooks'; +import { View } from 'react-native'; + +// Internal dependencies +import styleSheet from './AddChainApproval.styles'; const AddChainApproval = () => { const { approvalRequest, onConfirm, onReject } = useApprovalRequest(); + const { styles } = useStyles(styleSheet, {}); if (approvalRequest?.type !== ApprovalTypes.ADD_ETHEREUM_CHAIN) return null; return ( - - - {strings('add_custom_network.title')} - - - - + + + ); }; diff --git a/app/components/Approvals/AddChainApproval/__snapshots__/AddChainApproval.test.tsx.snap b/app/components/Approvals/AddChainApproval/__snapshots__/AddChainApproval.test.tsx.snap index 33e674a7bdf..c47a70a4136 100644 --- a/app/components/Approvals/AddChainApproval/__snapshots__/AddChainApproval.test.tsx.snap +++ b/app/components/Approvals/AddChainApproval/__snapshots__/AddChainApproval.test.tsx.snap @@ -4,35 +4,16 @@ exports[`AddChainApproval renders 1`] = ` - - - Allow this site to add a network? - - - - + > + + `; diff --git a/app/components/Approvals/ConnectApproval/ConnectApproval.test.tsx b/app/components/Approvals/ConnectApproval/ConnectApproval.test.tsx index b3761df3bea..2fed0ef1ce9 100644 --- a/app/components/Approvals/ConnectApproval/ConnectApproval.test.tsx +++ b/app/components/Approvals/ConnectApproval/ConnectApproval.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; import ConnectApproval from './ConnectApproval'; -jest.mock('../../hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); const mockApprovalRequest = (approvalRequest?: ApprovalRequest) => { ( diff --git a/app/components/Approvals/ConnectApproval/ConnectApproval.tsx b/app/components/Approvals/ConnectApproval/ConnectApproval.tsx index a9ed10e17b5..a95ee20254c 100644 --- a/app/components/Approvals/ConnectApproval/ConnectApproval.tsx +++ b/app/components/Approvals/ConnectApproval/ConnectApproval.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import ApprovalModal from '../ApprovalModal'; import AccountApproval from '../../UI/AccountApproval'; diff --git a/app/components/Approvals/FlowLoaderModal/FlowLoaderModal.test.tsx b/app/components/Approvals/FlowLoaderModal/FlowLoaderModal.test.tsx index 57f4789d8e1..bc4ac05ca3d 100644 --- a/app/components/Approvals/FlowLoaderModal/FlowLoaderModal.test.tsx +++ b/app/components/Approvals/FlowLoaderModal/FlowLoaderModal.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { @@ -7,10 +7,10 @@ import { ApprovalRequest, } from '@metamask/approval-controller'; import FlowLoaderModal from './FlowLoaderModal'; -import useApprovalFlow from '../../hooks/useApprovalFlow'; +import useApprovalFlow from '../../Views/confirmations/hooks/useApprovalFlow'; -jest.mock('../../hooks/useApprovalRequest'); -jest.mock('../../hooks/useApprovalFlow'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalFlow'); const APPROVAL_FLOW_MOCK: ApprovalFlowState = { id: 'testId1', diff --git a/app/components/Approvals/FlowLoaderModal/FlowLoaderModal.tsx b/app/components/Approvals/FlowLoaderModal/FlowLoaderModal.tsx index 6a579dfecee..302f5ea2725 100644 --- a/app/components/Approvals/FlowLoaderModal/FlowLoaderModal.tsx +++ b/app/components/Approvals/FlowLoaderModal/FlowLoaderModal.tsx @@ -1,8 +1,8 @@ import React, { useCallback } from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import ApprovalModal from '../ApprovalModal'; -import useApprovalFlow from '../../hooks/useApprovalFlow'; -import ApprovalFlowLoader from '../../UI/Approval/ApprovalFlowLoader'; +import useApprovalFlow from '../../Views/confirmations/hooks/useApprovalFlow'; +import ApprovalFlowLoader from '../../Views/confirmations/components/Approval/ApprovalFlowLoader'; const FlowLoaderModal = () => { const { approvalRequest } = useApprovalRequest(); diff --git a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx index ab9ef322a6d..a4b0ee63132 100644 --- a/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx +++ b/app/components/Approvals/InstallSnapApproval/InstallSnapApproval.tsx @@ -1,7 +1,7 @@ ///: BEGIN:ONLY_INCLUDE_IF(snaps) import React, { useEffect, useState } from 'react'; import ApprovalModal from '../ApprovalModal'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import Logger from '../../../util/Logger'; import { SnapInstallState } from './InstallSnapApproval.types'; diff --git a/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx b/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx index a8df01d6320..965dce1b3c2 100644 --- a/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx +++ b/app/components/Approvals/InstallSnapApproval/test/InstallSnapApproval.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react-native'; import InstallSnapApproval from '../InstallSnapApproval'; import { ApprovalRequest } from '@metamask/approval-controller'; -import useApprovalRequest from '../../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../../Views/confirmations/hooks/useApprovalRequest'; import { SNAP_INSTALL_CANCEL, SNAP_INSTALL_CONNECTION_REQUEST, @@ -15,7 +15,7 @@ import { import SNAP_INSTALL_SUCCESS from '../components/InstallSnapSuccess/InstallSnapSuccess.constants'; import SNAP_INSTALL_ERROR from '../components/InstallSnapError/InstallSnapError.constants'; -jest.mock('../../../hooks/useApprovalRequest'); +jest.mock('../../../Views/confirmations/hooks/useApprovalRequest'); const onConfirm = jest.fn(); const onReject = jest.fn(); diff --git a/app/components/Approvals/PermissionApproval/PermissionApproval.test.tsx b/app/components/Approvals/PermissionApproval/PermissionApproval.test.tsx index b22b6c8d366..86ad746e141 100644 --- a/app/components/Approvals/PermissionApproval/PermissionApproval.test.tsx +++ b/app/components/Approvals/PermissionApproval/PermissionApproval.test.tsx @@ -1,17 +1,17 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; import PermissionApproval from './PermissionApproval'; import { createAccountConnectNavDetails } from '../../Views/AccountConnect'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { useSelector } from 'react-redux'; import { MetaMetricsEvents } from '../../../core/Analytics'; import initialBackgroundState from '../../../util/test/initial-background-state.json'; import { render } from '@testing-library/react-native'; +import { useMetrics } from '../../../components/hooks/useMetrics'; -jest.mock('../../hooks/useApprovalRequest'); -jest.mock('../../../util/analyticsV2'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); +jest.mock('../../../components/hooks/useMetrics'); jest.mock('../../Views/AccountConnect', () => ({ createAccountConnectNavDetails: jest.fn(), @@ -58,9 +58,24 @@ const mockSelectorState = (state: any) => { ); }; +const mockTrackEvent = jest.fn(); + describe('PermissionApproval', () => { beforeEach(() => { jest.resetAllMocks(); + (useMetrics as jest.MockedFn).mockReturnValue({ + trackEvent: mockTrackEvent, + trackAnonymousEvent: jest.fn(), + enable: jest.fn(), + addTraitsToUser: jest.fn(), + createDataDeletionTask: jest.fn(), + checkDataDeleteStatus: jest.fn(), + getDeleteRegulationCreationDate: jest.fn(), + getDeleteRegulationId: jest.fn(), + isDataRecorded: jest.fn(), + isEnabled: jest.fn(), + getMetaMetricsId: jest.fn(), + }); }); it('navigates', async () => { @@ -116,8 +131,8 @@ describe('PermissionApproval', () => { render(); - expect(AnalyticsV2.trackEvent).toHaveBeenCalledTimes(1); - expect(AnalyticsV2.trackEvent).toHaveBeenCalledWith( + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + expect(mockTrackEvent).toHaveBeenCalledWith( MetaMetricsEvents.CONNECT_REQUEST_STARTED, { number_of_accounts: 3, diff --git a/app/components/Approvals/PermissionApproval/PermissionApproval.tsx b/app/components/Approvals/PermissionApproval/PermissionApproval.tsx index f70db700caa..4a12bdae5d8 100644 --- a/app/components/Approvals/PermissionApproval/PermissionApproval.tsx +++ b/app/components/Approvals/PermissionApproval/PermissionApproval.tsx @@ -1,18 +1,19 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import { useEffect, useRef } from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { createAccountConnectNavDetails } from '../../Views/AccountConnect'; import { useSelector } from 'react-redux'; import { selectAccountsLength } from '../../../selectors/accountTrackerController'; +import { useMetrics } from '../../../components/hooks/useMetrics'; export interface PermissionApprovalProps { navigation: any; } const PermissionApproval = (props: PermissionApprovalProps) => { + const { trackEvent } = useMetrics(); const { approvalRequest } = useApprovalRequest(); const totalAccounts = useSelector(selectAccountsLength); const isProcessing = useRef(false); @@ -35,7 +36,7 @@ const PermissionApproval = (props: PermissionApprovalProps) => { isProcessing.current = true; - AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_REQUEST_STARTED, { + trackEvent(MetaMetricsEvents.CONNECT_REQUEST_STARTED, { number_of_accounts: totalAccounts, source: 'PERMISSION SYSTEM', }); @@ -46,7 +47,7 @@ const PermissionApproval = (props: PermissionApprovalProps) => { permissionRequestId: id, }), ); - }, [approvalRequest, totalAccounts, props.navigation]); + }, [approvalRequest, totalAccounts, props.navigation, trackEvent]); return null; }; diff --git a/app/components/Approvals/SignatureApproval/SignatureApproval.test.tsx b/app/components/Approvals/SignatureApproval/SignatureApproval.test.tsx index 2e30b54ff87..baaaf4a9573 100644 --- a/app/components/Approvals/SignatureApproval/SignatureApproval.test.tsx +++ b/app/components/Approvals/SignatureApproval/SignatureApproval.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; import SignatureApproval from './SignatureApproval'; -jest.mock('../../hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); const mockApprovalRequest = (approvalRequest?: ApprovalRequest) => { ( diff --git a/app/components/Approvals/SignatureApproval/SignatureApproval.tsx b/app/components/Approvals/SignatureApproval/SignatureApproval.tsx index 38ec7daa721..a22a06cd04d 100644 --- a/app/components/Approvals/SignatureApproval/SignatureApproval.tsx +++ b/app/components/Approvals/SignatureApproval/SignatureApproval.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; -import SignatureRequestRoot from '../../UI/SignatureRequest/Root'; +import SignatureRequestRoot from '../../Views/confirmations/components/SignatureRequest/Root'; const SignatureApproval = () => { const { approvalRequest, onReject, onConfirm } = useApprovalRequest(); diff --git a/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.test.tsx b/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.test.tsx index 85884bcace2..ac57bba0e20 100644 --- a/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.test.tsx +++ b/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.test.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; import SwitchChainApproval from './SwitchChainApproval'; import { networkSwitched } from '../../../actions/onboardNetwork'; -jest.mock('../../hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); jest.mock('../../../actions/onboardNetwork'); jest.mock('react-redux', () => ({ diff --git a/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx b/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx index 7a97187c082..9a3310addf9 100644 --- a/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx +++ b/app/components/Approvals/SwitchChainApproval/SwitchChainApproval.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import ApprovalModal from '../ApprovalModal'; import SwitchCustomNetwork from '../../UI/SwitchCustomNetwork'; diff --git a/app/components/Approvals/TemplateConfirmationModal/TemplateConfirmationModal.test.tsx b/app/components/Approvals/TemplateConfirmationModal/TemplateConfirmationModal.test.tsx index 49ee66f0334..5f8b8097da8 100644 --- a/app/components/Approvals/TemplateConfirmationModal/TemplateConfirmationModal.test.tsx +++ b/app/components/Approvals/TemplateConfirmationModal/TemplateConfirmationModal.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; import TemplateConfirmationModal from './TemplateConfirmationModal'; -jest.mock('../../hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); const mockApprovalRequest = (approvalRequest?: ApprovalRequest) => { ( diff --git a/app/components/Approvals/TemplateConfirmationModal/TemplateConfirmationModal.tsx b/app/components/Approvals/TemplateConfirmationModal/TemplateConfirmationModal.tsx index 25795c64e64..f527fb52b41 100644 --- a/app/components/Approvals/TemplateConfirmationModal/TemplateConfirmationModal.tsx +++ b/app/components/Approvals/TemplateConfirmationModal/TemplateConfirmationModal.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import ApprovalModal from '../ApprovalModal'; -import TemplateConfirmation from '../../UI/Approval/TemplateConfirmation/TemplateConfirmation'; -import { TEMPLATED_CONFIRMATION_APPROVAL_TYPES } from '../../UI/Approval/TemplateConfirmation/Templates'; +import TemplateConfirmation from '../../Views/confirmations/components/Approval/TemplateConfirmation/TemplateConfirmation'; +import { TEMPLATED_CONFIRMATION_APPROVAL_TYPES } from '../../Views/confirmations/components/Approval/TemplateConfirmation/Templates'; const TemplateConfirmationModal = () => { const { approvalRequest, onConfirm, onReject } = useApprovalRequest(); diff --git a/app/components/Approvals/TransactionApproval/TransactionApproval.test.tsx b/app/components/Approvals/TransactionApproval/TransactionApproval.test.tsx index 715a0f3b0dc..3c9eafa35b2 100644 --- a/app/components/Approvals/TransactionApproval/TransactionApproval.test.tsx +++ b/app/components/Approvals/TransactionApproval/TransactionApproval.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; @@ -8,7 +8,7 @@ import { TransactionModalType, } from './TransactionApproval'; -jest.mock('../../hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); jest.mock('../../UI/QRHardware/withQRHardwareAwareness', () => jest.fn((component) => component), diff --git a/app/components/Approvals/TransactionApproval/TransactionApproval.tsx b/app/components/Approvals/TransactionApproval/TransactionApproval.tsx index de00e6efbf8..4ee7b9e8d74 100644 --- a/app/components/Approvals/TransactionApproval/TransactionApproval.tsx +++ b/app/components/Approvals/TransactionApproval/TransactionApproval.tsx @@ -1,8 +1,8 @@ import React, { useCallback, useState } from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; -import Approval from '../../Views/Approval'; -import Approve from '../../Views/ApproveView/Approve'; +import Approval from '../../Views/confirmations/Approval'; +import Approve from '../../Views/confirmations/ApproveView/Approve'; import QRSigningModal from '../../UI/QRHardware/QRSigningModal'; import withQRHardwareAwareness from '../../UI/QRHardware/withQRHardwareAwareness'; import { IQRState } from '../../UI/QRHardware/types'; diff --git a/app/components/Approvals/WalletConnectApproval/WalletConnectApproval.test.tsx b/app/components/Approvals/WalletConnectApproval/WalletConnectApproval.test.tsx index 8a1218e4238..f773d5bb200 100644 --- a/app/components/Approvals/WalletConnectApproval/WalletConnectApproval.test.tsx +++ b/app/components/Approvals/WalletConnectApproval/WalletConnectApproval.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; import WalletConnectApproval from './WalletConnectApproval'; -jest.mock('../../hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); const mockApprovalRequest = (approvalRequest?: ApprovalRequest) => { ( diff --git a/app/components/Approvals/WalletConnectApproval/WalletConnectApproval.tsx b/app/components/Approvals/WalletConnectApproval/WalletConnectApproval.tsx index 4b9ca9cdb32..b34b35fe4e7 100644 --- a/app/components/Approvals/WalletConnectApproval/WalletConnectApproval.tsx +++ b/app/components/Approvals/WalletConnectApproval/WalletConnectApproval.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import ApprovalModal from '../ApprovalModal'; import AccountApproval from '../../UI/AccountApproval'; diff --git a/app/components/Approvals/WatchAssetApproval/WatchAssetApproval.test.tsx b/app/components/Approvals/WatchAssetApproval/WatchAssetApproval.test.tsx index 1a4d97b8b32..be4cc50719c 100644 --- a/app/components/Approvals/WatchAssetApproval/WatchAssetApproval.test.tsx +++ b/app/components/Approvals/WatchAssetApproval/WatchAssetApproval.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; import { shallow } from 'enzyme'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import { ApprovalRequest } from '@metamask/approval-controller'; import WatchAssetApproval from './WatchAssetApproval'; -jest.mock('../../hooks/useApprovalRequest'); +jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); const mockApprovalRequest = (approvalRequest?: ApprovalRequest) => { ( diff --git a/app/components/Approvals/WatchAssetApproval/WatchAssetApproval.tsx b/app/components/Approvals/WatchAssetApproval/WatchAssetApproval.tsx index 06bfe9d861b..a402c54987f 100644 --- a/app/components/Approvals/WatchAssetApproval/WatchAssetApproval.tsx +++ b/app/components/Approvals/WatchAssetApproval/WatchAssetApproval.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import useApprovalRequest from '../../hooks/useApprovalRequest'; -import WatchAssetRequest from '../../UI/WatchAssetRequest'; +import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest'; +import WatchAssetRequest from '../../Views/confirmations/components/WatchAssetRequest'; import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware'; import ApprovalModal from '../ApprovalModal'; diff --git a/app/components/Base/DetailsModal.js b/app/components/Base/DetailsModal.js index a5ca13c1937..da1b0096359 100644 --- a/app/components/Base/DetailsModal.js +++ b/app/components/Base/DetailsModal.js @@ -5,10 +5,7 @@ import Ionicons from 'react-native-vector-icons/Ionicons'; import { fontStyles } from '../../styles/common'; import Text from './Text'; import { useTheme } from '../../util/theme'; -import { - DETAILS_MODAL_TITLE, - DETAILS_MODAL_CLOSE_ICON, -} from '../../../wdio/screen-objects/testIDs/Components/DetailsModal.js'; +import { TransactionDetailsModalSelectorsIDs } from '../../../e2e/selectors/Modals/TransactionDetailsModal.selectors'; const createStyles = (colors) => StyleSheet.create({ @@ -85,7 +82,7 @@ const DetailsModalTitle = ({ style, ...props }) => { return ( @@ -99,7 +96,7 @@ const DetailsModalCloseIcon = ({ style, ...props }) => { @@ -109,7 +106,13 @@ const DetailsModalBody = ({ style, ...props }) => { const { colors } = useTheme(); const styles = createStyles(colors); - return ; + return ( + + ); }; const DetailsModalSection = ({ style, borderBottom, ...props }) => { const { colors } = useTheme(); diff --git a/app/components/Base/ScreenView.tsx b/app/components/Base/ScreenView.tsx index 3f08008b39e..857f2753c2b 100644 --- a/app/components/Base/ScreenView.tsx +++ b/app/components/Base/ScreenView.tsx @@ -1,4 +1,4 @@ -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; import React from 'react'; import { SafeAreaView, StyleSheet, ScrollView } from 'react-native'; import { useTheme } from '../../util/theme'; diff --git a/app/components/Base/StatusText.js b/app/components/Base/StatusText.js index 83f1ffc1966..0de17d43962 100644 --- a/app/components/Base/StatusText.js +++ b/app/components/Base/StatusText.js @@ -5,7 +5,7 @@ import { StyleSheet } from 'react-native'; import { FIAT_ORDER_STATES } from '../../constants/on-ramp'; import { strings } from '../../../locales/i18n'; import { useTheme } from '../../util/theme'; -import { DETAILS_MODAL_TITLE } from '../../../wdio/screen-objects/testIDs/Components/DetailsModal.js'; +import { CommonSelectorsIDs } from '../../../e2e/selectors/Common.selectors'; const styles = StyleSheet.create({ status: { @@ -17,7 +17,7 @@ const styles = StyleSheet.create({ export const ConfirmedText = (props) => ( { useEffect(() => { const initAnalytics = async () => { + await MetaMetrics.getInstance().configure(); await Analytics.init(); }; diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index 3825e27697a..eb9f1dff629 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -1,11 +1,10 @@ import React, { useRef, useState, useEffect } from 'react'; import { Image, StyleSheet, Keyboard, Platform } from 'react-native'; -import PropTypes from 'prop-types'; import { createStackNavigator } from '@react-navigation/stack'; import { useSelector } from 'react-redux'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import Browser from '../../Views/Browser'; -import { NetworksChainId } from '@metamask/controller-utils'; +import { ChainId } from '@metamask/controller-utils'; import AddBookmark from '../../Views/AddBookmark'; import SimpleWebview from '../../Views/SimpleWebview'; import Settings from '../../Views/Settings'; @@ -21,8 +20,8 @@ import Asset from '../../Views/Asset'; import AssetDetails from '../../Views/AssetDetails'; import AddAsset from '../../Views/AddAsset'; import Collectible from '../../Views/Collectible'; -import Send from '../../Views/Send'; -import SendTo from '../../Views/SendFlow/SendTo'; +import Send from '../../Views/confirmations/Send'; +import SendTo from '../../Views/confirmations/SendFlow/SendTo'; import { RevealPrivateCredential } from '../../Views/RevealPrivateCredential'; import WalletConnectSessions from '../../Views/WalletConnectSessions'; import OfflineMode from '../../Views/OfflineMode'; @@ -37,8 +36,8 @@ import ManualBackupStep2 from '../../Views/ManualBackupStep2'; import ManualBackupStep3 from '../../Views/ManualBackupStep3'; import PaymentRequest from '../../UI/PaymentRequest'; import PaymentRequestSuccess from '../../UI/PaymentRequestSuccess'; -import Amount from '../../Views/SendFlow/Amount'; -import Confirm from '../../Views/SendFlow/Confirm'; +import Amount from '../../Views/confirmations/SendFlow/Amount'; +import Confirm from '../../Views/confirmations/SendFlow/Confirm'; import ContactForm from '../../Views/Settings/Contacts/ContactForm'; import ActivityView from '../../Views/ActivityView'; import SwapsAmountView from '../../UI/Swaps'; @@ -47,17 +46,11 @@ import CollectiblesDetails from '../../UI/CollectibleModal'; import OptinMetrics from '../../UI/OptinMetrics'; import Drawer from '../../UI/Drawer'; -import { RampSDKProvider } from '../../UI/Ramp/sdk'; +import RampRoutes from '../../UI/Ramp/routes'; import { RampType } from '../../UI/Ramp/types'; -import GetStarted from '../../UI/Ramp/Views/GetStarted'; -import PaymentMethods from '../../UI/Ramp/Views/PaymentMethods/PaymentMethods'; -import BuildQuote from '../../UI/Ramp/Views/BuildQuote/BuildQuote'; -import Quotes from '../../UI/Ramp/Views/Quotes'; -import CheckoutWebView from '../../UI/Ramp/Views/Checkout'; import RampSettings from '../../UI/Ramp/Views/Settings'; -import NetworkSwitcher from '../../UI/Ramp/Views/NetworkSwitcher'; import RampAddActivationKey from '../../UI/Ramp/Views/Settings/AddActivationKey'; -import Regions from '../../UI/Ramp/Views/Regions'; + import { colors as importedColors } from '../../../styles/common'; import OrderDetails from '../../UI/Ramp/Views/OrderDetails'; import SendTransaction from '../../UI/Ramp/Views/SendTransaction'; @@ -68,7 +61,6 @@ import { SnapsSettingsList } from '../../Views/Snaps/SnapsSettingsList'; import { SnapSettings } from '../../Views/Snaps/SnapSettings'; ///: END:ONLY_INCLUDE_IF import Routes from '../../../constants/navigation/Routes'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { getActiveTabUrl } from '../../../util/transactions'; import { getPermittedAccountsByHostname } from '../../../core/Permissions'; @@ -80,6 +72,8 @@ import isUrl from 'is-url'; import SDKSessionsManager from '../../Views/SDKSessionsManager/SDKSessionsManager'; import URL from 'url-parse'; import Logger from '../../../util/Logger'; +import { getDecimalChainId } from '../../../util/networks'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const Stack = createStackNavigator(); const Tab = createBottomTabNavigator(); @@ -332,6 +326,7 @@ const SettingsFlow = () => ( ); const HomeTabs = () => { + const { trackEvent } = useMetrics(); const drawerRef = useRef(null); const [isKeyboardHidden, setIsKeyboardHidden] = useState(true); @@ -339,7 +334,7 @@ const HomeTabs = () => { const chainId = useSelector((state) => { const providerConfig = selectProviderConfig(state); - return NetworksChainId[providerConfig.type]; + return ChainId[providerConfig.type]; }); const amountOfBrowserOpenTabs = useSelector( @@ -371,9 +366,9 @@ const HomeTabs = () => { home: { tabBarIconKey: TabBarIconKey.Wallet, callback: () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_OPENED, { + trackEvent(MetaMetricsEvents.WALLET_OPENED, { number_of_accounts: accountsLength, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), }); }, rootScreenName: Routes.WALLET_VIEW, @@ -385,9 +380,9 @@ const HomeTabs = () => { browser: { tabBarIconKey: TabBarIconKey.Browser, callback: () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_OPENED, { + trackEvent(MetaMetricsEvents.BROWSER_OPENED, { number_of_accounts: accountsLength, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), source: 'Navigation Tab', active_connected_dapp: activeConnectedDapp, number_of_open_tabs: amountOfBrowserOpenTabs, @@ -398,16 +393,14 @@ const HomeTabs = () => { activity: { tabBarIconKey: TabBarIconKey.Activity, callback: () => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.NAVIGATION_TAPS_TRANSACTION_HISTORY, - ); + trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_TRANSACTION_HISTORY); }, rootScreenName: Routes.TRANSACTIONS_VIEW, }, settings: { tabBarIconKey: TabBarIconKey.Setting, callback: () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SETTINGS); + trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SETTINGS); }, rootScreenName: Routes.SETTINGS_VIEW, unmountOnBlur: true, @@ -555,46 +548,6 @@ const PaymentRequestView = () => ( ); -const Ramps = ({ rampType }) => ( - - - - - - - - - - - - - - -); - -Ramps.propTypes = { - rampType: PropTypes.string, -}; - const Swaps = () => ( ( - {() => } + {() => } - {() => } + {() => } { + const { trackEvent, trackAnonymousEvent } = useMetrics(); const [transactionModalType, setTransactionModalType] = useState(undefined); const tokenList = useSelector(selectTokenList); const setTransactionObject = props.setTransactionObject; @@ -158,27 +166,28 @@ const RootRPCMethodsUI = (props) => { delete newSwapsTransactions[transactionMeta.id].analytics; delete newSwapsTransactions[transactionMeta.id].paramsForAnalytics; - InteractionManager.runAfterInteractions(() => { - const parameters = { - ...analyticsParams, - time_to_mine: timeToMine, - estimated_vs_used_gasRatio: estimatedVsUsedGasRatio, - quote_vs_executionRatio: quoteVsExecutionRatio, - token_to_amount_received: tokenToAmountReceived.toString(), - }; - Analytics.trackEventWithParameters(event, {}); - Analytics.trackEventWithParameters(event, parameters, true); - }); + const parameters = { + ...analyticsParams, + time_to_mine: timeToMine, + estimated_vs_used_gasRatio: estimatedVsUsedGasRatio, + quote_vs_executionRatio: quoteVsExecutionRatio, + token_to_amount_received: tokenToAmountReceived.toString(), + }; + + trackAnonymousEvent(event, parameters); } catch (e) { Logger.error(e, MetaMetricsEvents.SWAP_TRACKING_FAILED); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.SWAP_TRACKING_FAILED, { - error: e, - }); + trackEvent(MetaMetricsEvents.SWAP_TRACKING_FAILED, { + error: e, }); } }, - [props.selectedAddress, props.swapsTransactions], + [ + props.selectedAddress, + props.swapsTransactions, + trackEvent, + trackAnonymousEvent, + ], ); const autoSign = useCallback( @@ -211,7 +220,27 @@ const RootRPCMethodsUI = (props) => { ); await KeyringController.resetQRKeyringState(); - Engine.acceptPendingApproval(transactionMeta.id); + const isLedgerAccount = isHardwareAccount( + transactionMeta.transaction.from, + [ExtendedKeyringTypes.ledger], + ); + + // For Ledger Accounts we handover the signing to the confirmation flow + if (isLedgerAccount) { + const ledgerKeyring = await getLedgerKeyring(); + + props.navigation.navigate( + ...createLedgerTransactionModalNavDetails({ + transactionId: transactionMeta.id, + deviceId: ledgerKeyring.deviceId, + // eslint-disable-next-line no-empty-function + onConfirmationComplete: () => {}, + type: 'signTransaction', + }), + ); + } else { + Engine.acceptPendingApproval(transactionMeta.id); + } } catch (error) { if (!error?.message.startsWith(KEYSTONE_TX_CANCELED)) { Alert.alert( @@ -221,13 +250,11 @@ const RootRPCMethodsUI = (props) => { ); Logger.error(error, 'error while trying to send transaction (Main)'); } else { - AnalyticsV2.trackEvent( - MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, - ); + trackEvent(MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED); } } }, - [props.swapsTransactions, trackSwaps], + [props.navigation, props.swapsTransactions, trackSwaps, trackEvent], ); const onUnapprovedTransaction = useCallback( diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 97a9557a300..a497ca6e4cf 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -69,10 +69,14 @@ import { selectProviderType, } from '../../../selectors/networkController'; import { selectShowIncomingTransactionNetworks } from '../../../selectors/preferencesController'; -import { addHexPrefix, toHexadecimal } from '../../../util/number'; -import { NETWORKS_CHAIN_ID } from '../../../constants/network'; +import { DEPRECATED_NETWORKS } from '../../../constants/network'; import WarningAlert from '../../../components/UI/WarningAlert'; import { GOERLI_DEPRECATED_ARTICLE } from '../../../constants/urls'; +import { + updateIncomingTransactions, + startIncomingTransactionPolling, + stopIncomingTransactionPolling, +} from '../../../util/transaction-controller'; ///: BEGIN:ONLY_INCLUDE_IF(snaps) import { SnapsExecutionWebView } from '../../UI/SnapsExecutionWebView'; ///: END:ONLY_INCLUDE_IF @@ -111,13 +115,20 @@ const Main = (props) => { useMinimumVersions(); useEffect(() => { - const { TransactionController } = Engine.context; - const currentHexChainId = addHexPrefix(toHexadecimal(props.chainId)); + if (DEPRECATED_NETWORKS.includes(props.chainId)) { + setShowDeprecatedAlert(true); + } else { + setShowDeprecatedAlert(false); + } + }, [props.chainId]); + + useEffect(() => { + const chainId = props.chainId; - if (props.showIncomingTransactionsNetworks[currentHexChainId]) { - TransactionController.startIncomingTransactionPolling(); + if (props.showIncomingTransactionsNetworks[chainId]) { + startIncomingTransactionPolling(); } else { - TransactionController.stopIncomingTransactionPolling(); + stopIncomingTransactionPolling(); } }, [props.showIncomingTransactionsNetworks, props.chainId]); @@ -162,7 +173,6 @@ const Main = (props) => { const handleAppStateChange = useCallback( (appState) => { const newModeIsBackground = appState === 'background'; - const { TransactionController } = Engine.context; // If it was in background and it's not anymore // we need to stop the Background timer @@ -178,7 +188,7 @@ const Main = (props) => { removeNotVisibleNotifications(); BackgroundTimer.runBackgroundTimer(async () => { - await TransactionController.updateIncomingTransactions(); + await updateIncomingTransactions(); }, AppConstants.TX_CHECK_BACKGROUND_FREQUENCY); } }, @@ -334,7 +344,7 @@ const Main = (props) => { }; const renderDeprecatedNetworkAlert = (chainId, backUpSeedphraseVisible) => { - if (chainId === NETWORKS_CHAIN_ID.GOERLI && showDeprecatedAlert) { + if (DEPRECATED_NETWORKS.includes(chainId) && showDeprecatedAlert) { return ( { - AnalyticsV2.trackEvent( - MetaMetricsEvents.CONNECT_REQUEST_STARTED, - this.getAnalyticsParams(), - ); - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.CONNECT_REQUEST_STARTED, + this.getAnalyticsParams(), + ); }; showWalletConnectNotification = (confirmation = false) => { @@ -171,7 +174,7 @@ class AccountApproval extends PureComponent { // onConfirm will close current window by rejecting current approvalRequest. this.props.onCancel(); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.CONNECT_REQUEST_OTPFAILURE, this.getAnalyticsParams(), ); @@ -192,7 +195,7 @@ class AccountApproval extends PureComponent { } this.props.onConfirm(); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.CONNECT_REQUEST_COMPLETED, this.getAnalyticsParams(), ); @@ -203,7 +206,7 @@ class AccountApproval extends PureComponent { * Calls onConfirm callback and analytics to track connect canceled event */ onCancel = () => { - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.CONNECT_REQUEST_CANCELLED, this.getAnalyticsParams(), ); @@ -380,4 +383,4 @@ const mapStateToProps = (state) => ({ AccountApproval.contextType = ThemeContext; -export default connect(mapStateToProps)(AccountApproval); +export default connect(mapStateToProps)(withMetricsAwareness(AccountApproval)); diff --git a/app/components/UI/AccountApproval/styles.ts b/app/components/UI/AccountApproval/styles.ts index 339955dd19d..7416079f9be 100644 --- a/app/components/UI/AccountApproval/styles.ts +++ b/app/components/UI/AccountApproval/styles.ts @@ -1,7 +1,7 @@ import { StyleSheet } from 'react-native'; import Device from '../../../util/device'; -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; -import { ThemeTypography } from '@metamask/design-tokens/dist/js/typography'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; +import type { ThemeTypography } from '@metamask/design-tokens/dist/types/js/typography'; const createStyles = (colors: ThemeColors, typography: ThemeTypography) => StyleSheet.create({ diff --git a/app/components/UI/AccountFromToInfoCard/__snapshots__/AccountFromToInfoCard.test.tsx.snap b/app/components/UI/AccountFromToInfoCard/__snapshots__/AccountFromToInfoCard.test.tsx.snap index 923b2a198ff..31542fbafc7 100644 --- a/app/components/UI/AccountFromToInfoCard/__snapshots__/AccountFromToInfoCard.test.tsx.snap +++ b/app/components/UI/AccountFromToInfoCard/__snapshots__/AccountFromToInfoCard.test.tsx.snap @@ -392,7 +392,7 @@ Array [ style={ Array [ Object { - "color": "#28A745", + "color": "#1C8234", "fontSize": 15, }, undefined, diff --git a/app/components/UI/AccountInfoCard/index.js b/app/components/UI/AccountInfoCard/index.js index 748b2a3b7c3..67a5bf2b5b5 100644 --- a/app/components/UI/AccountInfoCard/index.js +++ b/app/components/UI/AccountInfoCard/index.js @@ -26,7 +26,7 @@ import { } from '../../../selectors/currencyRateController'; import { selectAccounts } from '../../../selectors/accountTrackerController'; import { selectIdentities } from '../../../selectors/preferencesController'; -import ApproveTransactionHeader from '../ApproveTransactionHeader'; +import ApproveTransactionHeader from '../../Views/confirmations/components/ApproveTransactionHeader'; import Text, { TextVariant, } from '../../../component-library/components/Texts/Text'; diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js index 6d122401bde..69f9b0b01a9 100644 --- a/app/components/UI/AccountOverview/index.js +++ b/app/components/UI/AccountOverview/index.js @@ -37,7 +37,6 @@ import { ThemeContext, mockTheme } from '../../../util/theme'; import EthereumAddress from '../EthereumAddress'; import Identicon from '../Identicon'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import Analytics from '../../../core/Analytics/Analytics'; import AppConstants from '../../../core/AppConstants'; import Engine from '../../../core/Engine'; import { selectChainId } from '../../../selectors/networkController'; @@ -51,6 +50,7 @@ import { regex } from '../../../util/regex'; import Text, { TextVariant, } from '../../../component-library/components/Texts/Text'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -207,6 +207,10 @@ class AccountOverview extends PureComponent { * Current opens tabs in browser */ browserTabs: PropTypes.array, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -302,9 +306,8 @@ class AccountOverview extends PureComponent { data: { msg: strings('account_details.account_copied_to_clipboard') }, }); setTimeout(() => this.props.protectWalletModalVisible(), 2000); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.WALLET_COPIED_ADDRESS); - }); + + this.props.metrics.trackEvent(MetaMetricsEvents.WALLET_COPIED_ADDRESS); }; doENSLookup = async () => { @@ -337,7 +340,7 @@ class AccountOverview extends PureComponent { screen: Routes.BROWSER.VIEW, params, }); - Analytics.trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, { + this.props.metrics.trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, { portfolioUrl: AppConstants.PORTFOLIO_URL, }); }; @@ -478,4 +481,7 @@ const mapDispatchToProps = (dispatch) => ({ AccountOverview.contextType = ThemeContext; -export default connect(mapStateToProps, mapDispatchToProps)(AccountOverview); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(AccountOverview)); diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx index 78fbec7ef25..56b73f3b4ba 100644 --- a/app/components/UI/AccountRightButton/index.tsx +++ b/app/components/UI/AccountRightButton/index.tsx @@ -35,8 +35,8 @@ import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrap import { selectProviderConfig } from '../../../selectors/networkController'; import Routes from '../../../constants/navigation/Routes'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import Analytics from '../../../core/Analytics/Analytics'; import { AccountOverviewSelectorsIDs } from '../../../../e2e/selectors/AccountOverview.selectors'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const styles = StyleSheet.create({ leftButton: { @@ -66,6 +66,7 @@ const AccountRightButton = ({ // Placeholder ref for dismissing keyboard. Works when the focused input is within a Webview. const placeholderInputRef = useRef(null); const { navigate } = useNavigation(); + const { trackEvent } = useMetrics(); const [isKeyboardVisible, setIsKeyboardVisible] = useState(false); const accountAvatarType = useSelector((state: any) => @@ -122,12 +123,9 @@ const AccountRightButton = ({ navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.NETWORK_SELECTOR, }); - Analytics.trackEventWithParameters( - MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, - { - chain_id: providerConfig.chainId, - }, - ); + trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { + chain_id: providerConfig.chainId, + }); } else { onPress?.(); } @@ -138,6 +136,7 @@ const AccountRightButton = ({ onPress, navigate, providerConfig.chainId, + trackEvent, ]); const networkName = useMemo( diff --git a/app/components/UI/AccountSelectorList/AccountSelector.test.tsx b/app/components/UI/AccountSelectorList/AccountSelector.test.tsx index 4bdf609e0fc..cbe7eadb5bd 100644 --- a/app/components/UI/AccountSelectorList/AccountSelector.test.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelector.test.tsx @@ -41,7 +41,7 @@ const initialState = { providerConfig: { ticker: 'ETH', type: 'mainnet', - chainId: '1', + chainId: '0x1', }, }, AccountTrackerController: { diff --git a/app/components/UI/ActionView/__snapshots__/index.test.tsx.snap b/app/components/UI/ActionView/__snapshots__/index.test.tsx.snap index 7a89c89fd57..16cd8c27df3 100644 --- a/app/components/UI/ActionView/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/ActionView/__snapshots__/index.test.tsx.snap @@ -86,6 +86,8 @@ exports[`ActionView should render correctly 1`] = ` Object { "marginLeft": 8, }, + Object {}, + Object {}, ] } disabled={false} diff --git a/app/components/UI/ActionView/index.js b/app/components/UI/ActionView/index.js index 24634500430..da9fc1fb566 100644 --- a/app/components/UI/ActionView/index.js +++ b/app/components/UI/ActionView/index.js @@ -13,23 +13,38 @@ import { strings } from '../../../../locales/i18n'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { useTheme } from '../../../util/theme'; -const styles = StyleSheet.create({ - actionContainer: { - flex: 0, - flexDirection: 'row', - paddingVertical: 16, - paddingHorizontal: 24, - }, - button: { - flex: 1, - }, - cancel: { - marginRight: 8, - }, - confirm: { - marginLeft: 8, - }, -}); +export const ConfirmButtonState = { + Error: 'error', + Warning: 'warning', + Normal: 'normal', +}; + +const getStyles = (colors) => + StyleSheet.create({ + actionContainer: { + flex: 0, + flexDirection: 'row', + paddingVertical: 16, + paddingHorizontal: 24, + }, + button: { + flex: 1, + }, + cancel: { + marginRight: 8, + }, + confirm: { + marginLeft: 8, + }, + confirmButtonError: { + backgroundColor: colors.error.default, + borderColor: colors.error.default, + }, + confirmButtonWarning: { + backgroundColor: colors.warning.default, + borderColor: colors.warning.default, + }, + }); /** * PureComponent that renders scrollable content above configurable buttons @@ -51,10 +66,12 @@ export default function ActionView({ loading = false, keyboardShouldPersistTaps = 'never', style = undefined, + confirmButtonState = ConfirmButtonState.Normal, }) { const { colors } = useTheme(); confirmText = confirmText || strings('action_view.confirm'); cancelText = cancelText || strings('action_view.cancel'); + const styles = getStyles(colors); return ( @@ -93,7 +110,16 @@ export default function ActionView({ testID={confirmTestID} type={confirmButtonMode} onPress={onConfirmPress} - containerStyle={[styles.button, styles.confirm]} + containerStyle={[ + styles.button, + styles.confirm, + confirmButtonState === ConfirmButtonState.Error + ? styles.confirmButtonError + : {}, + confirmButtonState === ConfirmButtonState.Warning + ? styles.confirmButtonWarning + : {}, + ]} disabled={confirmed || confirmDisabled || loading} > {confirmed || loading ? ( @@ -189,4 +215,8 @@ ActionView.propTypes = { * Optional View styles. Applies to scroll view */ style: PropTypes.object, + /** + * Optional Confirm button state - this can be Error/Warning/Normal. + */ + confirmButtonState: PropTypes.string, }; diff --git a/app/components/UI/AddCustomCollectible/index.tsx b/app/components/UI/AddCustomCollectible/index.tsx index 1d0c5646810..d2e4b719018 100644 --- a/app/components/UI/AddCustomCollectible/index.tsx +++ b/app/components/UI/AddCustomCollectible/index.tsx @@ -16,7 +16,6 @@ import ActionView from '../ActionView'; import { isSmartContractAddress } from '../../../util/transactions'; import Device from '../../../util/device'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { useTheme } from '../../../util/theme'; import { CUSTOM_TOKEN_CONTAINER_ID } from '../../../../wdio/screen-objects/testIDs/Screens/AddCustomToken.testIds'; @@ -29,6 +28,8 @@ import { } from '../../../../wdio/screen-objects/testIDs/Screens/NFTImportScreen.testIds'; import { selectChainId } from '../../../selectors/networkController'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; +import { getDecimalChainId } from '../../../util/networks'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors: any) => StyleSheet.create({ @@ -81,6 +82,7 @@ const AddCustomCollectible = ({ const [loading, setLoading] = useState(false); const assetTokenIdInput = React.createRef() as any; const { colors, themeAppearance } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const selectedAddress = useSelector(selectSelectedAddress); @@ -102,7 +104,7 @@ const AddCustomCollectible = ({ const getAnalyticsParams = () => { try { return { - chain_id: chainId, + chain_id: getDecimalChainId(chainId), }; } catch (error) { return {}; @@ -189,10 +191,7 @@ const AddCustomCollectible = ({ const { NftController } = Engine.context as any; NftController.addNft(address, tokenId); - AnalyticsV2.trackEvent( - MetaMetricsEvents.COLLECTIBLE_ADDED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.COLLECTIBLE_ADDED, getAnalyticsParams()); setLoading(false); navigation.goBack(); }; diff --git a/app/components/UI/AddCustomToken/__snapshots__/index.test.tsx.snap b/app/components/UI/AddCustomToken/__snapshots__/index.test.tsx.snap index da48b5e2861..9036a3995b0 100644 --- a/app/components/UI/AddCustomToken/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/AddCustomToken/__snapshots__/index.test.tsx.snap @@ -1,220 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`AddCustomToken should render correctly 1`] = ` - - - - - - Token detection is not available on this network yet. Please import token manually and make sure you trust it. Learn about - - - token scams and security risks. - - - - - Token Address - - - - - - - Token Symbol - - - - - - - Token Decimal - - - - - - - +/> `; diff --git a/app/components/UI/AddCustomToken/index.js b/app/components/UI/AddCustomToken/index.js index da39db882fc..8b8d2ef7841 100644 --- a/app/components/UI/AddCustomToken/index.js +++ b/app/components/UI/AddCustomToken/index.js @@ -15,12 +15,11 @@ import { isValidAddress } from 'ethereumjs-util'; import ActionView from '../ActionView'; import { isSmartContractAddress } from '../../../util/transactions'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import AppConstants from '../../../core/AppConstants'; import Alert, { AlertType } from '../../Base/Alert'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; -import WarningMessage from '../../Views/SendFlow/WarningMessage'; +import WarningMessage from '../../Views/confirmations/SendFlow/WarningMessage'; import NotificationManager from '../../../core/NotificationManager'; import { ThemeContext, mockTheme } from '../../../util/theme'; import generateTestId from '../../../../wdio/utils/generateTestId'; @@ -35,6 +34,8 @@ import { import { NFT_IDENTIFIER_INPUT_BOX_ID } from '../../../../wdio/screen-objects/testIDs/Screens/NFTImportScreen.testIds'; import { regex } from '../../../../app/util/regex'; import { AddCustomTokenViewSelectorsIDs } from '../../../../e2e/selectors/AddCustomTokenView.selectors'; +import { getDecimalChainId } from '../../../util/networks'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -78,7 +79,7 @@ const createStyles = (colors) => /** * Copmonent that provides ability to add custom tokens. */ -export default class AddCustomToken extends PureComponent { +class AddCustomToken extends PureComponent { state = { address: '', symbol: '', @@ -103,6 +104,10 @@ export default class AddCustomToken extends PureComponent { * Checks if token detection is supported */ isTokenDetectionSupported: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; getAnalyticsParams = () => { @@ -112,7 +117,7 @@ export default class AddCustomToken extends PureComponent { return { token_address: address, token_symbol: symbol, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), source: 'Custom token', }; } catch (error) { @@ -126,7 +131,7 @@ export default class AddCustomToken extends PureComponent { const { address, symbol, decimals, name } = this.state; await TokensController.addToken(address, symbol, decimals, { name }); - AnalyticsV2.trackEvent( + this.props.metrics.trackEvent( MetaMetricsEvents.TOKEN_ADDED, this.getAnalyticsParams(), ); @@ -478,3 +483,5 @@ export default class AddCustomToken extends PureComponent { } AddCustomToken.contextType = ThemeContext; + +export default withMetricsAwareness(AddCustomToken); diff --git a/app/components/UI/AddToAddressBookWrapper/AddToAddressBookWrapper.test.tsx b/app/components/UI/AddToAddressBookWrapper/AddToAddressBookWrapper.test.tsx index 7e0c018eff5..b2ce68ace60 100644 --- a/app/components/UI/AddToAddressBookWrapper/AddToAddressBookWrapper.test.tsx +++ b/app/components/UI/AddToAddressBookWrapper/AddToAddressBookWrapper.test.tsx @@ -27,7 +27,7 @@ const initialState = { }, AddressBookController: { addressBook: { - 1: { + '0x1': { '0x1': { address: '0x1', name: 'Account 2', diff --git a/app/components/UI/AddressCopy/AddressCopy.tsx b/app/components/UI/AddressCopy/AddressCopy.tsx index 3c9db52a4a8..e25fbd155c0 100644 --- a/app/components/UI/AddressCopy/AddressCopy.tsx +++ b/app/components/UI/AddressCopy/AddressCopy.tsx @@ -18,8 +18,8 @@ import ClipboardManager from '../../../core/ClipboardManager'; import { showAlert } from '../../../actions/alert'; import { protectWalletModalVisible } from '../../../actions/user'; import { strings } from '../../../../locales/i18n'; -import { InteractionManager, Platform, View } from 'react-native'; -import { Analytics, MetaMetricsEvents } from '../../../core/Analytics'; +import { Platform, View } from 'react-native'; +import { MetaMetricsEvents } from '../../../core/Analytics'; import { useStyles } from '../../../component-library/hooks'; import generateTestId from '../../../../wdio/utils/generateTestId'; @@ -30,11 +30,13 @@ import { selectIdentities, selectSelectedAddress, } from '../../../selectors/preferencesController'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const AddressCopy = ({ formatAddressType = 'full' }: AddressCopyProps) => { const { styles } = useStyles(styleSheet, {}); const dispatch = useDispatch(); + const { trackEvent } = useMetrics(); const handleShowAlert = (config: { isVisible: boolean; @@ -70,9 +72,8 @@ const AddressCopy = ({ formatAddressType = 'full' }: AddressCopyProps) => { data: { msg: strings('account_details.account_copied_to_clipboard') }, }); setTimeout(() => handleProtectWalletModalVisible(), 2000); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.WALLET_COPIED_ADDRESS); - }); + + trackEvent(MetaMetricsEvents.WALLET_COPIED_ADDRESS); }; return ( diff --git a/app/components/UI/AddressInputs/__snapshots__/index.test.jsx.snap b/app/components/UI/AddressInputs/__snapshots__/index.test.jsx.snap index 6a8faaa2c0f..aed648215f3 100644 --- a/app/components/UI/AddressInputs/__snapshots__/index.test.jsx.snap +++ b/app/components/UI/AddressInputs/__snapshots__/index.test.jsx.snap @@ -605,7 +605,7 @@ exports[`AddressInputs AddressTo should match default snapshot 1`] = ` style={ Array [ Object { - "color": "#28A745", + "color": "#1C8234", "fontSize": 15, }, undefined, @@ -876,7 +876,7 @@ exports[`AddressInputs AddressTo should match snapshot when layout is vertical 1 style={ Array [ Object { - "color": "#28A745", + "color": "#1C8234", "fontSize": 15, }, undefined, diff --git a/app/components/UI/AddressInputs/index.js b/app/components/UI/AddressInputs/index.js index 90ebe91cae4..cf2e5773561 100644 --- a/app/components/UI/AddressInputs/index.js +++ b/app/components/UI/AddressInputs/index.js @@ -93,7 +93,6 @@ const createStyles = (colors, layout = 'horizontal') => { }, accountNameLabelText: { marginLeft: 4, - horizontalAlign: 'center', textAlign: 'center', paddingHorizontal: 8, color: colors.text.alternative, diff --git a/app/components/UI/AddressInputs/index.test.jsx b/app/components/UI/AddressInputs/index.test.jsx index bd5230f819e..a32e4577c0d 100644 --- a/app/components/UI/AddressInputs/index.test.jsx +++ b/app/components/UI/AddressInputs/index.test.jsx @@ -26,7 +26,7 @@ const initialState = { }, AddressBookController: { addressBook: { - 1: { + '0x1': { '0x519d2CE57898513F676a5C3b66496c3C394c9CC7': { address: '0x519d2CE57898513F676a5C3b66496c3C394c9CC7', name: 'Account 2', diff --git a/app/components/UI/AssetOverview/AboutAsset/AboutAsset.styles.tsx b/app/components/UI/AssetOverview/AboutAsset/AboutAsset.styles.tsx index f5daaad2d1e..2f8436f3bfc 100644 --- a/app/components/UI/AssetOverview/AboutAsset/AboutAsset.styles.tsx +++ b/app/components/UI/AssetOverview/AboutAsset/AboutAsset.styles.tsx @@ -1,4 +1,4 @@ -import { Theme } from '@metamask/design-tokens'; +import type { Theme } from '@metamask/design-tokens'; import { StyleSheet, TextStyle } from 'react-native'; const styleSheet = (params: { theme: Theme }) => { diff --git a/app/components/UI/AssetOverview/AboutAsset/ContentDisplay.styles.tsx b/app/components/UI/AssetOverview/AboutAsset/ContentDisplay.styles.tsx index 6b29dbcf365..c5af1067484 100644 --- a/app/components/UI/AssetOverview/AboutAsset/ContentDisplay.styles.tsx +++ b/app/components/UI/AssetOverview/AboutAsset/ContentDisplay.styles.tsx @@ -1,4 +1,4 @@ -import { Theme } from '@metamask/design-tokens'; +import type { Theme } from '@metamask/design-tokens'; import { StyleSheet, TextStyle } from 'react-native'; const styleSheet = (params: { theme: Theme }) => { diff --git a/app/components/UI/AssetOverview/AssetActionButton/AssetActionButton.styles.tsx b/app/components/UI/AssetOverview/AssetActionButton/AssetActionButton.styles.tsx index 7db9c31b602..850cb3f0ccf 100644 --- a/app/components/UI/AssetOverview/AssetActionButton/AssetActionButton.styles.tsx +++ b/app/components/UI/AssetOverview/AssetActionButton/AssetActionButton.styles.tsx @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; import Device from '../../../../util/device'; -import { Theme } from '@metamask/design-tokens'; +import type { Theme } from '@metamask/design-tokens'; const createStyles = (params: { theme: Theme }) => { const { theme } = params; diff --git a/app/components/UI/AssetOverview/AssetActionButton/__snapshots__/index.test.tsx.snap b/app/components/UI/AssetOverview/AssetActionButton/__snapshots__/index.test.tsx.snap index d195c0fb821..b9f0ca1c80e 100644 --- a/app/components/UI/AssetOverview/AssetActionButton/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/AssetOverview/AssetActionButton/__snapshots__/index.test.tsx.snap @@ -84,7 +84,7 @@ exports[`AssetActionButtons should render type add correctly 1`] = ` style={ Object { "alignContent": "center", - "color": "#FCFCFC", + "color": "#FFFFFF", "justifyContent": "center", "textAlign": "center", } @@ -145,7 +145,7 @@ exports[`AssetActionButtons should render type information correctly 1`] = ` style={ Object { "alignContent": "center", - "color": "#FCFCFC", + "color": "#FFFFFF", "justifyContent": "center", "textAlign": "center", } @@ -201,14 +201,14 @@ exports[`AssetActionButtons should render type receive correctly 1`] = ` > { diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index 94f78f96209..a946a79f9aa 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -26,7 +26,7 @@ import { selectCurrentCurrency, } from '../../../selectors/currencyRateController'; import { selectContractExchangeRates } from '../../../selectors/tokenRatesController'; -import { selectAccounts } from '../../../selectors/accountTrackerController'; +import { selectAccountsByChainId } from '../../../selectors/accountTrackerController'; import { selectContractBalances } from '../../../selectors/tokenBalancesController'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; import Logger from '../../../util/Logger'; @@ -36,6 +36,7 @@ import { hexToBN, renderFromTokenMinimalUnit, renderFromWei, + toHexadecimal, weiToFiat, } from '../../../util/number'; import { getEther } from '../../../util/transactions'; @@ -65,7 +66,7 @@ const AssetOverview: React.FC = ({ const [timePeriod, setTimePeriod] = React.useState('1d'); const currentCurrency = useSelector(selectCurrentCurrency); const conversionRate = useSelector(selectConversionRate); - const accounts = useSelector(selectAccounts); + const accountsByChainId = useSelector(selectAccountsByChainId); const primaryCurrency = useSelector( (state: RootStateOrAny) => state.settings.primaryCurrency, ); @@ -165,9 +166,13 @@ const AssetOverview: React.FC = ({ let balance, balanceFiat; if (asset.isETH) { - balance = renderFromWei(accounts[selectedAddress]?.balance); + balance = renderFromWei( + accountsByChainId[toHexadecimal(chainId)][selectedAddress]?.balance, + ); balanceFiat = weiToFiat( - hexToBN(accounts[selectedAddress].balance), + hexToBN( + accountsByChainId[toHexadecimal(chainId)][selectedAddress]?.balance, + ), conversionRate, currentCurrency, ); diff --git a/app/components/UI/AssetOverview/Balance/Balance.styles.tsx b/app/components/UI/AssetOverview/Balance/Balance.styles.tsx index 97d6431ca33..3944c0da057 100644 --- a/app/components/UI/AssetOverview/Balance/Balance.styles.tsx +++ b/app/components/UI/AssetOverview/Balance/Balance.styles.tsx @@ -1,4 +1,4 @@ -import { Theme } from '@metamask/design-tokens'; +import type { Theme } from '@metamask/design-tokens'; import { StyleSheet, TextStyle } from 'react-native'; const styleSheet = (params: { theme: Theme }) => { diff --git a/app/components/UI/AssetOverview/ChartNavigationButton/ChartNavigationButton.styles.tsx b/app/components/UI/AssetOverview/ChartNavigationButton/ChartNavigationButton.styles.tsx index fbf94b7a0bf..d468e7e2502 100644 --- a/app/components/UI/AssetOverview/ChartNavigationButton/ChartNavigationButton.styles.tsx +++ b/app/components/UI/AssetOverview/ChartNavigationButton/ChartNavigationButton.styles.tsx @@ -1,4 +1,4 @@ -import { Theme } from '@metamask/design-tokens'; +import type { Theme } from '@metamask/design-tokens'; import { StyleSheet, TextStyle } from 'react-native'; const styleSheet = (params: { diff --git a/app/components/UI/AssetOverview/Price/Price.styles.tsx b/app/components/UI/AssetOverview/Price/Price.styles.tsx index 780d9677571..cea4c101423 100644 --- a/app/components/UI/AssetOverview/Price/Price.styles.tsx +++ b/app/components/UI/AssetOverview/Price/Price.styles.tsx @@ -1,4 +1,4 @@ -import { Theme } from '@metamask/design-tokens'; +import type { Theme } from '@metamask/design-tokens'; import { StyleSheet, TextStyle } from 'react-native'; const styleSheet = (params: { diff --git a/app/components/UI/AssetOverview/PriceChart/PriceChart.styles.tsx b/app/components/UI/AssetOverview/PriceChart/PriceChart.styles.tsx index b4b304794c0..d027fbcc803 100644 --- a/app/components/UI/AssetOverview/PriceChart/PriceChart.styles.tsx +++ b/app/components/UI/AssetOverview/PriceChart/PriceChart.styles.tsx @@ -1,4 +1,4 @@ -import { Theme } from '@metamask/design-tokens'; +import type { Theme } from '@metamask/design-tokens'; import { Dimensions, StyleSheet, TextStyle } from 'react-native'; export const CHART_HEIGHT = Dimensions.get('screen').height * 0.44; diff --git a/app/components/UI/BackupAlert/BackupAlert.constants.ts b/app/components/UI/BackupAlert/BackupAlert.constants.ts new file mode 100644 index 00000000000..41de0e70ca6 --- /dev/null +++ b/app/components/UI/BackupAlert/BackupAlert.constants.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export const PROTECT_WALLET_BUTTON = 'protect-your-wallet-button'; diff --git a/app/components/UI/BackupAlert/BackupAlert.styles.ts b/app/components/UI/BackupAlert/BackupAlert.styles.ts new file mode 100644 index 00000000000..cfa88b311b4 --- /dev/null +++ b/app/components/UI/BackupAlert/BackupAlert.styles.ts @@ -0,0 +1,67 @@ +import { StyleSheet } from 'react-native'; +import Device from '../../../util/device'; +import { Theme } from '../../../util/theme/models'; + +/** + * Style sheet function for BackupAlert component. + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @returns StyleSheet object. + */ +const styleSheet = (params: { theme: Theme }) => { + const { theme } = params; + const { colors } = theme; + + return StyleSheet.create({ + container: { + backgroundColor: colors.background.default, + position: 'absolute', + left: 16, + right: 16, + borderRadius: 8, + borderColor: colors.warning.default, + borderWidth: 1, + }, + backupAlertWrapper: { + flex: 1, + backgroundColor: colors.warning.muted, + padding: 14, + }, + backupAlertIconWrapper: { + marginRight: 10, + }, + backupAlertIcon: { + fontSize: 22, + color: colors.text.default, + }, + backupAlertTitle: { + marginBottom: 14, + }, + backupAlertMessage: { + color: colors.primary.default, + marginLeft: 14, + flex: 1, + textAlign: 'right', + }, + touchableView: { + flexDirection: 'row', + }, + modalViewInBrowserView: { + bottom: Device.isIphoneX() ? 180 : 170, + }, + modalViewNotInBrowserView: { + bottom: Device.isIphoneX() ? 120 : 110, + }, + buttonsWrapper: { + flexDirection: 'row-reverse', + alignContent: 'flex-end', + flex: 1, + }, + dismissButton: { + flex: 1, + }, + }); +}; + +export default styleSheet; diff --git a/app/components/UI/BackupAlert/BackupAlert.test.tsx b/app/components/UI/BackupAlert/BackupAlert.test.tsx new file mode 100644 index 00000000000..2526d5ef9fc --- /dev/null +++ b/app/components/UI/BackupAlert/BackupAlert.test.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +import BackupAlert from '.'; +import renderWithProvider from '../../../util/test/renderWithProvider'; +import Engine from '../../../core/Engine'; +import { fireEvent } from '@testing-library/react-native'; + +const mockEngine = Engine; + +const initialState = { + user: { + seedphraseBackedUp: false, + passwordSet: false, + backUpSeedphraseVisible: true, + }, + wizard: { + step: 0, + }, +}; +const mockNavigation = { + navigate: jest.fn(), + dangerouslyGetState: jest.fn(() => ({ routes: [{ name: 'WalletView' }] })), +}; +jest.mock('../../../core/Engine', () => ({ + init: () => mockEngine.init({}), +})); + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest + .fn() + .mockImplementation((callback) => callback(initialState)), +})); + +describe('BackupAlert', () => { + it('should render correctly', () => { + const { toJSON } = renderWithProvider( + null} />, + { + state: initialState, + }, + ); + expect(toJSON()).toMatchSnapshot(); + }); + + it('navigates to backup flow when right button is pressed', () => { + const { getByTestId } = renderWithProvider( + null} />, + { + state: initialState, + }, + ); + const rightButton = getByTestId('protect-your-wallet-button'); + fireEvent.press(rightButton); + expect(mockNavigation.navigate).toHaveBeenCalledWith('SetPasswordFlow', { + screen: 'AccountBackupStep1', + }); + }); +}); diff --git a/app/components/UI/BackupAlert/BackupAlert.tsx b/app/components/UI/BackupAlert/BackupAlert.tsx new file mode 100644 index 00000000000..179cf78357c --- /dev/null +++ b/app/components/UI/BackupAlert/BackupAlert.tsx @@ -0,0 +1,168 @@ +/* eslint-disable react/prop-types */ +import React, { useState, useEffect } from 'react'; +import { View, TouchableOpacity, Platform } from 'react-native'; +import ElevatedView from 'react-native-elevated-view'; +import { strings } from '../../../../locales/i18n'; +import { baseStyles } from '../../../styles/common'; +import { useDispatch, useSelector } from 'react-redux'; +import { backUpSeedphraseAlertNotVisible } from '../../../actions/user'; +import { findRouteNameFromNavigatorState } from '../../../util/general'; +import { MetaMetricsEvents } from '../../../core/Analytics'; +import generateTestId from '../../../../wdio/utils/generateTestId'; +import { + NOTIFICATION_REMIND_ME_LATER_BUTTON_ID, + SECURE_WALLET_BACKUP_ALERT_MODAL, +} from '../../../../wdio/screen-objects/testIDs/Screens/WalletView.testIds'; +import styleSheet from './BackupAlert.styles'; +import { useStyles } from '../../../component-library/hooks'; +import { BackupAlertI } from './BackupAlert.types'; +import { PROTECT_WALLET_BUTTON } from './BackupAlert.constants'; +import Icon, { + IconColor, + IconName, + IconSize, +} from '../../../component-library/components/Icons/Icon'; +import Text, { + TextVariant, +} from '../../../component-library/components/Texts/Text'; +import { useMetrics } from '../../../components/hooks/useMetrics'; + +const BROWSER_ROUTE = 'BrowserView'; + +const BLOCKED_LIST = [ + 'ImportPrivateKey', + 'Send', + 'SendTo', + 'Amount', + 'Confirm', + 'Approval', + 'Approve', + 'AddBookmark', + 'RevealPrivateCredentialView', + 'AccountBackupStep', + 'ManualBackupStep', +]; + +const BackupAlert = ({ navigation, onDismiss }: BackupAlertI) => { + const { styles } = useStyles(styleSheet, {}); + const { trackEvent } = useMetrics(); + const [inBrowserView, setInBrowserView] = useState(false); + const [inBlockedView, setInBlockedView] = useState(false); + const [isVisible, setIsVisible] = useState(true); + + const { seedphraseBackedUp, backUpSeedphraseVisible } = useSelector( + (state: any) => state.user, + ); + + const onboardingWizardStep = useSelector((state: any) => state.wizard.step); + const dispatch = useDispatch(); + + const currentRouteName = findRouteNameFromNavigatorState( + navigation.dangerouslyGetState().routes, + ); + + useEffect(() => { + const isInBrowserView = currentRouteName === BROWSER_ROUTE; + const blockedView = + BLOCKED_LIST.find((path) => currentRouteName.includes(path)) || + currentRouteName === 'SetPasswordFlow'; + + setInBrowserView(isInBrowserView); + setInBlockedView(!!blockedView); + }, [currentRouteName]); + + const goToBackupFlow = () => { + setIsVisible(false); + navigation.navigate('SetPasswordFlow', { + screen: 'AccountBackupStep1', + }); + + trackEvent(MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED, { + wallet_protection_required: false, + source: 'Backup Alert', + }); + }; + + const onDismissAlert = () => { + dispatch(backUpSeedphraseAlertNotVisible()); + + trackEvent(MetaMetricsEvents.WALLET_SECURITY_PROTECT_DISMISSED, { + wallet_protection_required: false, + source: 'Backup Alert', + }); + + if (onDismiss) onDismiss(); + }; + + const shouldNotRenderAlert = + seedphraseBackedUp || + inBlockedView || + !backUpSeedphraseVisible || + onboardingWizardStep !== 0 || + !isVisible; + + return shouldNotRenderAlert ? null : ( + + + + + + + + + {strings('backup_alert.title')} + + + + + {strings('backup_alert.right_button')} + + + + + {strings('backup_alert.left_button')} + + + + + + + + ); +}; + +export default BackupAlert; diff --git a/app/components/UI/BackupAlert/BackupAlert.types.ts b/app/components/UI/BackupAlert/BackupAlert.types.ts new file mode 100644 index 00000000000..2bb40e1c788 --- /dev/null +++ b/app/components/UI/BackupAlert/BackupAlert.types.ts @@ -0,0 +1,4 @@ +export interface BackupAlertI { + navigation: any; + onDismiss: () => void; +} diff --git a/app/components/UI/BackupAlert/__snapshots__/BackupAlert.test.tsx.snap b/app/components/UI/BackupAlert/__snapshots__/BackupAlert.test.tsx.snap new file mode 100644 index 00000000000..8e500d44ceb --- /dev/null +++ b/app/components/UI/BackupAlert/__snapshots__/BackupAlert.test.tsx.snap @@ -0,0 +1,156 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BackupAlert should render correctly 1`] = ` + + + + + + + + + Protect your wallet + + + + + Protect wallet + + + + + Remind me later + + + + + + + +`; diff --git a/app/components/UI/BackupAlert/__snapshots__/index.test.tsx.snap b/app/components/UI/BackupAlert/__snapshots__/index.test.tsx.snap deleted file mode 100644 index c7fd0d7c159..00000000000 --- a/app/components/UI/BackupAlert/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BackupAlert should render correctly 1`] = ` - - - -`; diff --git a/app/components/UI/BackupAlert/index.js b/app/components/UI/BackupAlert/index.js deleted file mode 100644 index a173b803c8e..00000000000 --- a/app/components/UI/BackupAlert/index.js +++ /dev/null @@ -1,268 +0,0 @@ -import React, { PureComponent } from 'react'; -import { - Text, - StyleSheet, - View, - TouchableOpacity, - InteractionManager, - Platform, -} from 'react-native'; -import PropTypes from 'prop-types'; -import EvilIcons from 'react-native-vector-icons/EvilIcons'; -import ElevatedView from 'react-native-elevated-view'; -import { strings } from '../../../../locales/i18n'; -import { fontStyles, baseStyles } from '../../../styles/common'; -import Device from '../../../util/device'; -import { connect } from 'react-redux'; -import { backUpSeedphraseAlertNotVisible } from '../../../actions/user'; -import { findRouteNameFromNavigatorState } from '../../../util/general'; -import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; -import generateTestId from '../../../../wdio/utils/generateTestId'; -import { - NOTIFICATION_REMIND_ME_LATER_BUTTON_ID, - SECURE_WALLET_BACKUP_ALERT_MODAL, -} from '../../../../wdio/screen-objects/testIDs/Screens/WalletView.testIds'; - -import { ThemeContext, mockTheme } from '../../../util/theme'; - -const BROWSER_ROUTE = 'BrowserView'; - -const createStyles = (colors) => - StyleSheet.create({ - container: { - backgroundColor: colors.background.default, - position: 'absolute', - left: 16, - right: 16, - borderRadius: 8, - borderColor: colors.warning.default, - borderWidth: 1, - }, - backupAlertWrapper: { - flex: 1, - backgroundColor: colors.warning.muted, - padding: 14, - }, - backupAlertIconWrapper: { - marginRight: 10, - }, - backupAlertIcon: { - fontSize: 22, - ...fontStyles.bold, - color: colors.text.default, - }, - backupAlertTitle: { - fontSize: 14, - marginBottom: 14, - color: colors.text.default, - ...fontStyles.bold, - }, - backupAlertMessage: { - fontSize: 12, - color: colors.primary.default, - marginLeft: 14, - flex: 1, - textAlign: 'right', - ...fontStyles.normal, - }, - touchableView: { - flexDirection: 'row', - }, - modalViewInBrowserView: { - bottom: Device.isIphoneX() ? 180 : 170, - }, - modalViewNotInBrowserView: { - bottom: Device.isIphoneX() ? 120 : 110, - }, - buttonsWrapper: { - flexDirection: 'row-reverse', - alignContent: 'flex-end', - flex: 1, - }, - dismissButton: { - flex: 1, - }, - }); - -const BLOCKED_LIST = [ - 'ImportPrivateKey', - 'Send', - 'SendTo', - 'Amount', - 'Confirm', - 'Approval', - 'Approve', - 'AddBookmark', - 'RevealPrivateCredentialView', - 'AccountBackupStep', - 'ManualBackupStep', -]; - -/** - * PureComponent that renders an alert shown when the - * seed phrase hasn't been backed up - */ -class BackupAlert extends PureComponent { - static propTypes = { - navigation: PropTypes.object, - /** - * redux flag that indicates if the user - * completed the seed phrase backup flow - */ - seedphraseBackedUp: PropTypes.bool, - /** - * redux flag that indicates if the alert should be shown - */ - backUpSeedphraseVisible: PropTypes.bool, - /** - * Dismisses the alert - */ - backUpSeedphraseAlertNotVisible: PropTypes.func.isRequired, - /** - * A second prop to be used in conjunction with the above - * currently used to toggle the backup reminder modal (a second time) - */ - onDismiss: PropTypes.func, - /** - * Used to determine if onboarding has been completed - * we only want to render the backup alert after onboarding - */ - onboardingWizardStep: PropTypes.number, - }; - - state = { - inBrowserView: false, - inAccountBackupStep: false, - }; - - componentDidUpdate = async (prevProps) => { - if ( - prevProps.navigation.dangerouslyGetState() !== - this.props.navigation.dangerouslyGetState() - ) { - const currentRouteName = findRouteNameFromNavigatorState( - this.props.navigation.dangerouslyGetState().routes, - ); - - const inBrowserView = currentRouteName === BROWSER_ROUTE; - const blockedView = - BLOCKED_LIST.find((path) => currentRouteName.includes(path)) || - currentRouteName === 'SetPasswordFlow'; - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ inBrowserView, blockedView }); - } - }; - - goToBackupFlow = () => { - this.props.navigation.navigate('SetPasswordFlow', { - screen: 'AccountBackupStep1', - }); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED, - { - wallet_protection_required: false, - source: 'Backup Alert', - }, - ); - }); - }; - - onDismiss = () => { - const { onDismiss, backUpSeedphraseAlertNotVisible } = this.props; - backUpSeedphraseAlertNotVisible(); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_DISMISSED, - { - wallet_protection_required: false, - source: 'Backup Alert', - }, - ); - }); - if (onDismiss) onDismiss(); - }; - - render() { - const { - seedphraseBackedUp, - backUpSeedphraseVisible, - onboardingWizardStep, - } = this.props; - const { inBrowserView, blockedView } = this.state; - const colors = this.context.colors || mockTheme.colors; - const styles = createStyles(colors); - - const shouldNotRenderAlert = - seedphraseBackedUp || - blockedView || - !backUpSeedphraseVisible || - onboardingWizardStep !== 0; - - if (shouldNotRenderAlert) return null; - return ( - - - - - - - - - {strings('backup_alert.title')} - - - - - {strings('backup_alert.right_button')} - - - - - {strings('backup_alert.left_button')} - - - - - - - - ); - } -} - -const mapStateToProps = (state) => ({ - seedphraseBackedUp: state.user.seedphraseBackedUp, - backUpSeedphraseVisible: state.user.backUpSeedphraseVisible, - onboardingWizardStep: state.wizard.step, -}); - -const mapDispatchToProps = (dispatch) => ({ - backUpSeedphraseAlertNotVisible: () => - dispatch(backUpSeedphraseAlertNotVisible()), -}); - -BackupAlert.contextType = ThemeContext; - -export default connect(mapStateToProps, mapDispatchToProps)(BackupAlert); diff --git a/app/components/UI/BackupAlert/index.test.tsx b/app/components/UI/BackupAlert/index.test.tsx deleted file mode 100644 index 612f12cc815..00000000000 --- a/app/components/UI/BackupAlert/index.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint-disable react/jsx-no-bind */ -import React from 'react'; -import configureMockStore from 'redux-mock-store'; -import { shallow } from 'enzyme'; -import BackupAlert from './'; -import { Provider } from 'react-redux'; - -const mockStore = configureMockStore(); -const initialState = { - user: { - seedphraseBackedUp: false, - passwordSet: false, - }, - wizard: { - step: 0, - }, -}; -const store = mockStore(initialState); - -describe('BackupAlert', () => { - it('should render correctly', () => { - const wrapper = shallow( - - - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/app/components/UI/BackupAlert/index.ts b/app/components/UI/BackupAlert/index.ts new file mode 100644 index 00000000000..b2fa1af18fc --- /dev/null +++ b/app/components/UI/BackupAlert/index.ts @@ -0,0 +1 @@ +export { default } from './BackupAlert'; diff --git a/app/components/UI/Bridge/utils/useGoToBridge.ts b/app/components/UI/Bridge/utils/useGoToBridge.ts index 64d593a6378..510abb714a5 100644 --- a/app/components/UI/Bridge/utils/useGoToBridge.ts +++ b/app/components/UI/Bridge/utils/useGoToBridge.ts @@ -1,6 +1,6 @@ import Routes from '../../../../constants/navigation/Routes'; import AppConstants from '../../../../core/AppConstants'; -import { Analytics, MetaMetricsEvents } from '../../../../core/Analytics'; +import { MetaMetricsEvents } from '../../../../core/Analytics'; import { useSelector } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; @@ -8,6 +8,8 @@ import { selectChainId } from '../../../../selectors/networkController'; import type { BrowserTab } from '../../Tokens/types'; import type { BrowserParams } from '../../../../components/Views/Browser/Browser.types'; +import { getDecimalChainId } from '../../../../util/networks'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const BRIDGE_URL = `${AppConstants.PORTFOLIO_URL}/bridge`; @@ -20,7 +22,7 @@ export default function useGoToBridge(location: string) { const chainId = useSelector(selectChainId); const browserTabs = useSelector((state: any) => state.browser.tabs); const { navigate } = useNavigation(); - + const { trackEvent } = useMetrics(); return (address?: string) => { const existingBridgeTab = browserTabs.find((tab: BrowserTab) => tab.url.match(new RegExp(BRIDGE_URL)), @@ -34,19 +36,19 @@ export default function useGoToBridge(location: string) { params.newTabUrl = undefined; params.existingTabId = existingBridgeTab.id; } else { - params.newTabUrl = `${BRIDGE_URL}/?metamaskEntry=mobile&srcChain=${chainId}${ - address ? `&token=${address}` : '' - }`; + params.newTabUrl = `${BRIDGE_URL}/?metamaskEntry=mobile&srcChain=${getDecimalChainId( + chainId, + )}${address ? `&token=${address}` : ''}`; } navigate(Routes.BROWSER.HOME, { screen: Routes.BROWSER.VIEW, params, }); - Analytics.trackEventWithParameters(MetaMetricsEvents.BRIDGE_LINK_CLICKED, { + trackEvent(MetaMetricsEvents.BRIDGE_LINK_CLICKED, { bridgeUrl: BRIDGE_URL, location, - chain_id_source: chainId, + chain_id_source: getDecimalChainId(chainId), token_address_source: address, }); }; 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/CollectibleContractElement/index.js b/app/components/UI/CollectibleContractElement/index.js index 574cfcc8911..3474d78e48d 100644 --- a/app/components/UI/CollectibleContractElement/index.js +++ b/app/components/UI/CollectibleContractElement/index.js @@ -92,6 +92,7 @@ function CollectibleContractElement({ chainId, selectedAddress, removeFavoriteCollectible, + toggleRemovingProgress, }) { const [collectiblesGrid, setCollectiblesGrid] = useState([]); const [collectiblesVisible, setCollectiblesVisible] = useState( @@ -149,7 +150,11 @@ function CollectibleContractElement({ const handleMenuAction = (index) => { if (index === 1) { + // set toggle to true + toggleRemovingProgress(); removeNft(); + // set toggle to false to indicate that removing the NFT has finished + toggleRemovingProgress(); } else if (index === 0) { refreshMetadata(); } @@ -301,6 +306,7 @@ CollectibleContractElement.propTypes = { * Dispatch remove collectible from favorites action */ removeFavoriteCollectible: PropTypes.func, + toggleRemovingProgress: PropTypes.func, }; const mapStateToProps = (state) => ({ diff --git a/app/components/UI/CollectibleContracts/index.js b/app/components/UI/CollectibleContracts/index.js index 73fe8e8b052..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,10 +109,16 @@ 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); + const [isRemovingNftInProgress, setIsRemovingNftInProgress] = useState(false); + + const toggleRemovingProgress = () => + setIsRemovingNftInProgress((value) => !value); + const displayNftMedia = useSelector(selectDisplayNftMedia); const isCollectionDetectionBannerVisible = @@ -126,6 +131,25 @@ const CollectibleContracts = ({ [navigation], ); + /** + * Method that checks if the collectible is inside the collectibles array. If it is not it means the + * collectible has been ignored, hence we should not call the updateMetadata which executes the addNft fct + * + * @returns Boolean indicating if the collectible is ignored or not. + */ + const isCollectibleIgnored = useCallback( + (collectible) => { + const found = collectibles.find( + (elm) => + elm.address === collectible.address && + elm.tokenId === collectible.tokenId, + ); + if (found) return false; + return true; + }, + [collectibles], + ); + /** * Method to check the token id data type of the current collectibles. * @@ -133,7 +157,8 @@ const CollectibleContracts = ({ * @returns Boolean indicating if the collectible should be updated. */ const shouldUpdateCollectibleMetadata = (collectible) => - typeof collectible.tokenId === 'number'; + typeof collectible.tokenId === 'number' || + (typeof collectible.tokenId === 'string' && !isNaN(collectible.tokenId)); /** * Method to updated collectible and avoid backwards compatibility issues. @@ -144,14 +169,24 @@ const CollectibleContracts = ({ async (collectible) => { const { NftController } = Engine.context; const { address, tokenId } = collectible; - NftController.removeNft(address, tokenId); - if (String(tokenId).includes('e+')) { - removeFavoriteCollectible(selectedAddress, chainId, collectible); - } else { - await NftController.addNft(address, String(tokenId)); + + const isIgnored = isCollectibleIgnored(collectible); + + if (!isRemovingNftInProgress && !isIgnored) { + if (String(tokenId).includes('e+')) { + removeFavoriteCollectible(selectedAddress, chainId, collectible); + } else { + await NftController.addNft(address, String(tokenId)); + } } }, - [chainId, removeFavoriteCollectible, selectedAddress], + [ + chainId, + removeFavoriteCollectible, + selectedAddress, + isCollectibleIgnored, + isRemovingNftInProgress, + ], ); useEffect(() => { @@ -239,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( () => ( @@ -277,6 +310,7 @@ const CollectibleContracts = ({ key={item.address} contractCollectibles={contractCollectibles} collectiblesVisible={index === 0} + toggleRemovingProgress={toggleRemovingProgress} /> ); }, @@ -299,6 +333,7 @@ const CollectibleContracts = ({ key={'Favorites'} contractCollectibles={filteredCollectibles} collectiblesVisible + toggleRemovingProgress={toggleRemovingProgress} /> ) ); @@ -420,6 +455,7 @@ CollectibleContracts.propTypes = { * Array of collectibles objects */ collectibles: PropTypes.array, + /** * Navigation object required to push * the Asset detail view diff --git a/app/components/UI/CollectibleContracts/index.test.tsx b/app/components/UI/CollectibleContracts/index.test.tsx index 72bf4463ba2..aedf262024e 100644 --- a/app/components/UI/CollectibleContracts/index.test.tsx +++ b/app/components/UI/CollectibleContracts/index.test.tsx @@ -6,6 +6,11 @@ import { Provider } from 'react-redux'; import initialBackgroundState from '../../../util/test/initial-background-state.json'; import renderWithProvider from '../../../util/test/renderWithProvider'; +// eslint-disable-next-line import/no-namespace +import * as allSelectors from '../../../../app/reducers/collectibles/index.js'; +import { cleanup, waitFor } from '@testing-library/react-native'; +import Engine from '../../../core/Engine'; + jest.mock('@react-navigation/native', () => { const actualReactNavigation = jest.requireActual('@react-navigation/native'); return { @@ -22,6 +27,18 @@ jest.mock('@react-navigation/native', () => { }; }); +jest.mock('../../../core/Engine', () => ({ + context: { + NftController: { + addNft: jest.fn(), + checkAndUpdateAllNftsOwnershipStatus: jest.fn(), + }, + NftDetectionController: { + detectNfts: jest.fn(), + }, + }, +})); + const mockStore = configureMockStore(); const initialState = { collectibles: { @@ -34,6 +51,7 @@ const initialState = { const store = mockStore(initialState); describe('CollectibleContracts', () => { + afterEach(cleanup); it('should render correctly', () => { const wrapper = shallow( @@ -57,7 +75,7 @@ describe('CollectibleContracts', () => { providerConfig: { ticker: 'ETH', type: 'mainnet', - chainId: '1', + chainId: '0x1', }, }, AccountTrackerController: { @@ -75,7 +93,7 @@ describe('CollectibleContracts', () => { NftController: { allNfts: { [CURRENT_ACCOUNT]: { - '1': [ + '0x1': [ { address: '0x72b1FDb6443338A158DeC2FbF411B71aeB157A42', description: @@ -107,7 +125,7 @@ describe('CollectibleContracts', () => { }, allNftContracts: { [CURRENT_ACCOUNT]: { - '1': [ + '0x1': [ { address: '0x72b1FDb6443338A158DeC2FbF411B71aeB157A42', name: 'MyToken', @@ -130,4 +148,123 @@ describe('CollectibleContracts', () => { expect(ownedNft).toBeTruthy(); expect(nonOwnedNft).toBeNull(); }); + + it('UI refresh changes NFT image when metadata image changes', async () => { + const CURRENT_ACCOUNT = '0x1a'; + const collectibleData = [ + { + address: '0x72b1FDb6443338A158DeC2FbF411B71aeB157A42', + name: 'MyToken', + symbol: 'MTK', + }, + ]; + const nftItemData = [ + { + address: '0x72b1FDb6443338A158DeC2FbF411B71aeB157A42', + description: + 'Lil Pudgys are a collection of 22,222 randomly generated NFTs minted on Ethereum.', + error: 'Opensea import error', + favorite: false, + image: 'https://api.pudgypenguins.io/lil/image/11222', + isCurrentlyOwned: true, + name: 'Lil Pudgy #113', + standard: 'ERC721', + tokenId: '113', + tokenURI: 'https://api.pudgypenguins.io/lil/113', + }, + ]; + + const nftItemDataUpdated = [ + { + address: '0x72b1FDb6443338A158DeC2FbF411B71aeB157A42', + description: + 'Lil Pudgys are a collection of 22,222 randomly generated NFTs minted on Ethereum.', + error: 'Opensea import error', + favorite: false, + image: 'https://api.pudgypenguins.io/lil/image/updated.png', + isCurrentlyOwned: true, + name: 'Lil Pudgy #113', + standard: 'ERC721', + tokenId: '113', + tokenURI: 'https://api.pudgypenguins.io/lil/113', + }, + ]; + const mockState = { + collectibles: { + favorites: {}, + }, + engine: { + backgroundState: { + ...initialBackgroundState, + NetworkController: { + network: '1', + providerConfig: { + ticker: 'ETH', + type: 'mainnet', + chainId: '1', + }, + }, + AccountTrackerController: { + accounts: { [CURRENT_ACCOUNT]: { balance: '0' } }, + }, + PreferencesController: { + displayNftMedia: true, + selectedAddress: CURRENT_ACCOUNT, + identities: { + [CURRENT_ACCOUNT]: { + address: CURRENT_ACCOUNT, + name: 'Account 1', + }, + }, + }, + NftController: { + addNft: jest.fn(), + allNfts: { + [CURRENT_ACCOUNT]: { + '1': [], + }, + }, + allNftContracts: { + [CURRENT_ACCOUNT]: { + '1': [], + }, + }, + }, + }, + }, + }; + + const spyOnCollectibles = jest + .spyOn(allSelectors, 'collectiblesSelector') + .mockReturnValueOnce(nftItemData) + .mockReturnValueOnce(nftItemDataUpdated); + const spyOnContracts = jest + .spyOn(allSelectors, 'collectibleContractsSelector') + .mockReturnValue(collectibleData); + const spyOnAddNft = jest + .spyOn(Engine.context.NftController, 'addNft') + .mockImplementation(async () => undefined); + + const { getByTestId } = renderWithProvider(, { + state: mockState, + }); + const nftImageBefore = getByTestId('nft-image'); + expect(nftImageBefore.props.source.uri).toEqual(nftItemData[0].image); + + const { queryByTestId } = renderWithProvider(, { + state: mockState, + }); + + await waitFor(() => { + expect(spyOnAddNft).toHaveBeenCalled(); + const nftImageAfter = queryByTestId('nft-image'); + expect(nftImageAfter.props.source.uri).toEqual( + nftItemDataUpdated[0].image, + ); + }); + + spyOnCollectibles.mockRestore(); + spyOnContracts.mockRestore(); + spyOnAddNft.mockRestore(); + }); }); 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/CustomNonceModal/index.test.tsx b/app/components/UI/CustomNonceModal/index.test.tsx deleted file mode 100644 index 84cc225cb5b..00000000000 --- a/app/components/UI/CustomNonceModal/index.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import CustomNonceModal from './'; - -describe('CustomNonceModal', () => { - it('should render correctly', () => { - const proposedNonce = 26; - const customNonce = 28; - const noop = () => ({}); - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); 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 39f8c0ba6f5..fb48b8e3c45 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -20,6 +20,7 @@ import { hasBlockExplorer, findBlockExplorerForRpc, getBlockExplorerName, + getDecimalChainId, } from '../../../util/networks'; import Identicon from '../Identicon'; import StyledButton from '../StyledButton'; @@ -40,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'; @@ -52,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, @@ -89,6 +88,7 @@ import { import { createAccountSelectorNavDetails } from '../../Views/AccountSelector'; import NetworkInfo from '../NetworkInfo'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -451,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 = { @@ -545,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 }); @@ -623,17 +626,15 @@ 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: providerConfig.chainId, + chain_id: getDecimalChainId(providerConfig.chainId), }); }; @@ -702,7 +703,7 @@ class DrawerView extends PureComponent { this.props; if (providerConfig.type === RPC) { const blockExplorer = findBlockExplorerForRpc( - providerConfig.rpcTarget, + providerConfig.rpcUrl, networkConfigurations, ); const url = `${blockExplorer}/address/${selectedAddress}`; @@ -758,10 +759,10 @@ class DrawerView extends PureComponent { const { networkConfigurations } = this.props; if (providerType === RPC) { const { - providerConfig: { rpcTarget }, + providerConfig: { rpcUrl }, } = this.props; const blockExplorer = findBlockExplorerForRpc( - rpcTarget, + rpcUrl, networkConfigurations, ); if (blockExplorer) { @@ -846,12 +847,12 @@ class DrawerView extends PureComponent { getSections = () => { const { - providerConfig: { type, rpcTarget }, + providerConfig: { type, rpcUrl }, networkConfigurations, } = this.props; let blockExplorer, blockExplorerName; if (type === RPC) { - blockExplorer = findBlockExplorerForRpc(rpcTarget, networkConfigurations); + blockExplorer = findBlockExplorerForRpc(rpcUrl, networkConfigurations); blockExplorerName = getBlockExplorerName(blockExplorer); } return [ @@ -927,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, @@ -1272,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 dd73b169c7a..048ac447ef8 100644 --- a/app/components/UI/EditGasFee1559/index.js +++ b/app/components/UI/EditGasFee1559/index.js @@ -18,22 +18,23 @@ import { strings } from '../../../../locales/i18n'; import Alert, { AlertType } from '../../Base/Alert'; import HorizontalSelector from '../../Base/HorizontalSelector'; import Device from '../../../util/device'; -import { isMainnetByChainId } from '../../../util/networks'; +import { getDecimalChainId, isMainnetByChainId } from '../../../util/networks'; 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'; import AppConstants from '../../../core/AppConstants'; import { useTheme } from '../../../util/theme'; - -const GAS_LIMIT_INCREMENT = new BigNumber(1000); -const GAS_INCREMENT = new BigNumber(1); -const GAS_LIMIT_MIN = new BigNumber(21000); -const GAS_MIN = new BigNumber(0); +import { + GAS_LIMIT_INCREMENT, + GAS_PRICE_INCREMENT as GAS_INCREMENT, + GAS_LIMIT_MIN, + GAS_PRICE_MIN as GAS_MIN, +} from '../../../util/gasUtils'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -198,13 +199,15 @@ const EditGasFee1559 = ({ hideTimeEstimateInfoModal, ] = useModalHandler(false); const { colors } = useTheme(); + const { trackEvent } = useMetrics(); + const styles = createStyles(colors); const getAnalyticsParams = useCallback(() => { try { return { ...analyticsParams, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), function_type: view, gas_mode: selectedOption ? 'Basic' : 'Advanced', speed_set: selectedOption || undefined, @@ -216,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 206415def01..4bfcdb2923c 100644 --- a/app/components/UI/EditGasFeeLegacy/index.js +++ b/app/components/UI/EditGasFeeLegacy/index.js @@ -20,18 +20,19 @@ import { strings } from '../../../../locales/i18n'; import Alert, { AlertType } from '../../Base/Alert'; import HorizontalSelector from '../../Base/HorizontalSelector'; import Device from '../../../util/device'; -import { isMainnetByChainId } from '../../../util/networks'; +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'; - -const GAS_LIMIT_INCREMENT = new BigNumber(1000); -const GAS_PRICE_INCREMENT = new BigNumber(1); -const GAS_LIMIT_MIN = new BigNumber(21000); -const GAS_PRICE_MIN = new BigNumber(0); +import { + GAS_LIMIT_INCREMENT, + GAS_PRICE_INCREMENT, + GAS_LIMIT_MIN, + GAS_PRICE_MIN, +} from '../../../util/gasUtils'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -147,13 +148,14 @@ const EditGasFeeLegacy = ({ const [selectedOption, setSelectedOption] = useState(selected); const [gasPriceError, setGasPriceError] = useState(); const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const getAnalyticsParams = useCallback(() => { try { return { ...analyticsParams, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), function_type: view, gas_mode: selectedOption ? 'Basic' : 'Advanced', speed_set: selectedOption || undefined, @@ -165,22 +167,19 @@ const EditGasFeeLegacy = ({ const toggleAdvancedOptions = useCallback(() => { if (!showAdvancedOptions) { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.GAS_ADVANCED_OPTIONS_CLICKED, getAnalyticsParams(), ); } setShowAdvancedOptions((showAdvancedOptions) => !showAdvancedOptions); - }, [getAnalyticsParams, showAdvancedOptions]); + }, [getAnalyticsParams, showAdvancedOptions, trackEvent]); const save = useCallback(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.GAS_FEE_CHANGED, - getAnalyticsParams(), - ); + trackEvent(MetaMetricsEvents.GAS_FEE_CHANGED, getAnalyticsParams()); onSave(selectedOption); - }, [getAnalyticsParams, onSave, selectedOption]); + }, [getAnalyticsParams, onSave, selectedOption, trackEvent]); const changeGas = useCallback( (gas, selectedOption) => { diff --git a/app/components/UI/EnableAutomaticSecurityChecksModal/EnableAutomaticSecurityChecksModal.tsx b/app/components/UI/EnableAutomaticSecurityChecksModal/EnableAutomaticSecurityChecksModal.tsx index fecb9590f22..8f88277cc40 100644 --- a/app/components/UI/EnableAutomaticSecurityChecksModal/EnableAutomaticSecurityChecksModal.tsx +++ b/app/components/UI/EnableAutomaticSecurityChecksModal/EnableAutomaticSecurityChecksModal.tsx @@ -21,7 +21,6 @@ import { userSelectedAutomaticSecurityChecksOptions, } from '../../../actions/security'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { ScrollView } from 'react-native-gesture-handler'; import { @@ -31,6 +30,7 @@ import { import generateTestId from '../../../../wdio/utils/generateTestId'; import generateDeviceAnalyticsMetaData from '../../../util/metrics'; +import { useMetrics } from '../../../components/hooks/useMetrics'; /* eslint-disable import/no-commonjs, @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ const onboardingDeviceImage = require('../../../images/swaps_onboard_device.png'); @@ -43,6 +43,7 @@ export const createEnableAutomaticSecurityChecksModalNavDetails = const EnableAutomaticSecurityChecksModal = () => { const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const modalRef = useRef(null); const dispatch = useDispatch(); @@ -51,11 +52,10 @@ const EnableAutomaticSecurityChecksModal = () => { modalRef?.current?.dismissModal(cb); useEffect(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_PROMPT_VIEWED, - generateDeviceAnalyticsMetaData(), - ); - }, []); + trackEvent(MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_PROMPT_VIEWED, { + ...generateDeviceAnalyticsMetaData(), + }); + }, [trackEvent]); useEffect(() => { dispatch(setAutomaticSecurityChecksModalOpen(true)); @@ -67,25 +67,25 @@ const EnableAutomaticSecurityChecksModal = () => { const triggerCloseAndDisableAutomaticSecurityChecks = useCallback( () => dismissModal(() => { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_DISABLED_FROM_PROMPT, - generateDeviceAnalyticsMetaData(), + { ...generateDeviceAnalyticsMetaData() }, ); dispatch(userSelectedAutomaticSecurityChecksOptions()); }), - [dispatch], + [dispatch, trackEvent], ); const enableAutomaticSecurityChecks = useCallback(() => { dismissModal(() => { - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_ENABLED_FROM_PROMPT, - generateDeviceAnalyticsMetaData(), + { ...generateDeviceAnalyticsMetaData() }, ); dispatch(userSelectedAutomaticSecurityChecksOptions()); dispatch(setAutomaticSecurityChecks(true)); }); - }, [dispatch]); + }, [dispatch, trackEvent]); return ( diff --git a/app/components/UI/LedgerModals/LedgerConfirmationModal.tsx b/app/components/UI/LedgerModals/LedgerConfirmationModal.tsx index 94bc7892a23..65cae14f23a 100644 --- a/app/components/UI/LedgerModals/LedgerConfirmationModal.tsx +++ b/app/components/UI/LedgerModals/LedgerConfirmationModal.tsx @@ -16,7 +16,7 @@ import OpenETHAppStep from './Steps/OpenETHAppStep'; import SearchingForDeviceStep from './Steps/SearchingForDeviceStep'; import { unlockLedgerDefaultAccount } from '../../../core/Ledger/Ledger'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors: Colors) => StyleSheet.create({ @@ -46,6 +46,7 @@ const LedgerConfirmationModal = ({ }: LedgerConfirmationModalProps) => { const { colors } = useAppThemeFromContext() || mockTheme; const styles = useMemo(() => createStyles(colors), [colors]); + const { trackEvent } = useMetrics(); const [permissionErrorShown, setPermissionErrorShown] = useState(false); const { isSendingLedgerCommands, @@ -70,13 +71,13 @@ const LedgerConfirmationModal = ({ const connectLedger = () => { try { ledgerLogicToRun(async () => { - await unlockLedgerDefaultAccount(); + await unlockLedgerDefaultAccount(false); await onConfirmation(); }); } catch (_e) { // Handle a super edge case of the user starting a transaction with the device connected // After arriving to confirmation the ETH app is not installed anymore this causes a crash. - AnalyticsV2.trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { device_type: 'Ledger', error: 'LEDGER_ETH_APP_NOT_INSTALLED', }); @@ -88,12 +89,9 @@ const LedgerConfirmationModal = ({ try { onRejection(); } finally { - AnalyticsV2.trackEvent( - MetaMetricsEvents.LEDGER_HARDWARE_TRANSACTION_CANCELLED, - { - device_type: 'Ledger', - }, - ); + trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_TRANSACTION_CANCELLED, { + device_type: 'Ledger', + }); } }; @@ -180,7 +178,7 @@ const LedgerConfirmationModal = ({ break; } if (ledgerError !== LedgerCommunicationErrors.UserRefusedConfirmation) { - AnalyticsV2.trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { device_type: 'Ledger', error: `${ledgerError}`, }); @@ -203,7 +201,7 @@ const LedgerConfirmationModal = ({ break; } setPermissionErrorShown(true); - AnalyticsV2.trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { device_type: 'Ledger', error: 'LEDGER_BLUETOOTH_PERMISSION_ERR', }); @@ -214,7 +212,7 @@ const LedgerConfirmationModal = ({ title: strings('ledger.bluetooth_off'), subtitle: strings('ledger.bluetooth_off_message'), }); - AnalyticsV2.trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { device_type: 'Ledger', error: 'LEDGER_BLUETOOTH_CONNECTION_ERR', }); diff --git a/app/components/UI/LedgerModals/LedgerTransactionModal.tsx b/app/components/UI/LedgerModals/LedgerTransactionModal.tsx index f47998092bd..6094e4a8c0f 100644 --- a/app/components/UI/LedgerModals/LedgerTransactionModal.tsx +++ b/app/components/UI/LedgerModals/LedgerTransactionModal.tsx @@ -10,6 +10,7 @@ import { } from '../../../util/navigation/navUtils'; import Routes from '../../../constants/navigation/Routes'; import { useAppThemeFromContext, mockTheme } from '../../../util/theme'; +import { speedUpTransaction } from '../../../util/transaction-controller'; export const createLedgerTransactionModalNavDetails = createNavigationDetails( @@ -49,10 +50,7 @@ const LedgerTransactionModal = () => { const executeOnLedger = useCallback(async () => { if (replacementParams?.type === LedgerReplacementTxTypes.SPEED_UP) { - await TransactionController.speedUpTransaction( - transactionId, - replacementParams.eip1559GasFee, - ); + await speedUpTransaction(transactionId, replacementParams.eip1559GasFee); } else if (replacementParams?.type === LedgerReplacementTxTypes.CANCEL) { await TransactionController.stopTransaction( transactionId, diff --git a/app/components/UI/LedgerModals/styles.ts b/app/components/UI/LedgerModals/styles.ts index 81f27f596da..6de560c41a7 100644 --- a/app/components/UI/LedgerModals/styles.ts +++ b/app/components/UI/LedgerModals/styles.ts @@ -6,12 +6,14 @@ export const createStyles = (colors: Colors) => StyleSheet.create({ modal: { justifyContent: 'flex-end', - margin: 0, height: 600, + margin: 0, zIndex: 1000, }, contentWrapper: { zIndex: 1000, + paddingHorizontal: 8, + marginHorizontal: 8, paddingBottom: 32, borderRadius: 20, backgroundColor: colors.background.default, diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index 1de294e0118..322a0947fc9 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -6,7 +6,6 @@ import AccountRightButton from '../AccountRightButton'; import { Alert, Image, - InteractionManager, Platform, StyleSheet, Text, @@ -21,23 +20,20 @@ import { scale } from 'react-native-size-matters'; import { strings } from '../../../../locales/i18n'; import AppConstants from '../../../core/AppConstants'; import DeeplinkManager from '../../../core/DeeplinkManager/SharedDeeplinkManager'; -import Analytics from '../../../core/Analytics/Analytics'; -import { MetaMetricsEvents } from '../../../core/Analytics'; +import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics'; import { importAccountFromPrivateKey } from '../../../util/address'; import Device from '../../../util/device'; import PickerNetwork from '../../../component-library/components/Pickers/PickerNetwork'; import BrowserUrlBar from '../BrowserUrlBar'; import generateTestId from '../../../../wdio/utils/generateTestId'; import { NAVBAR_NETWORK_BUTTON } from '../../../../wdio/screen-objects/testIDs/Screens/WalletView.testIds'; -import { - NAV_ANDROID_BACK_BUTTON, - NETWORK_BACK_ARROW_BUTTON_ID, - NETWORK_SCREEN_CLOSE_ICON, -} from '../../../../wdio/screen-objects/testIDs/Screens/NetworksScreen.testids'; +import { NAV_ANDROID_BACK_BUTTON } from '../../../../wdio/screen-objects/testIDs/Screens/NetworksScreen.testids'; import { SEND_CANCEL_BUTTON } from '../../../../wdio/screen-objects/testIDs/Screens/SendScreen.testIds'; import { ASSET_BACK_BUTTON } from '../../../../wdio/screen-objects/testIDs/Screens/TokenOverviewScreen.testIds'; import { REQUEST_SEARCH_RESULTS_BACK_BUTTON } from '../../../../wdio/screen-objects/testIDs/Screens/RequestToken.testIds'; import { BACK_BUTTON_SIMPLE_WEBVIEW } from '../../../../wdio/screen-objects/testIDs/Components/SimpleWebView.testIds'; +import { EDIT_BUTTON } from '../../../../wdio/screen-objects/testIDs/Common.testIds'; + import ButtonIcon, { ButtonIconSizes, ButtonIconVariants, @@ -46,25 +42,17 @@ import { IconName, IconSize, } from '../../../component-library/components/Icons/Icon'; -import { EDIT_BUTTON } from '../../../../wdio/screen-objects/testIDs/Common.testIds'; -import { SendLinkViewSelectorsIDs } from '../../../../e2e/selectors/SendLinkView.selectors'; import { default as MorphText, TextVariant, } from '../../../component-library/components/Texts/Text'; import { CommonSelectorsIDs } from '../../../../e2e/selectors/Common.selectors'; import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/WalletView.selectors'; +import { NetworksViewSelectorsIDs } from '../../../../e2e/selectors/Settings/NetworksView.selectors'; +import { SendLinkViewSelectorsIDs } from '../../../../e2e/selectors/SendLinkView.selectors'; -const trackEvent = (event) => { - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(event); - }); -}; - -const trackEventWithParameters = (event, params) => { - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(event, params); - }); +const trackEvent = (event, params = {}) => { + MetaMetrics.getInstance().trackEvent(event, params); }; const styles = StyleSheet.create({ @@ -216,7 +204,7 @@ export function getNavigationOptionsTitle( iconName={IconName.Close} onPress={navigationPop} style={innerStyles.accessories} - {...generateTestId(Platform, NETWORK_SCREEN_CLOSE_ICON)} + testID={NetworksViewSelectorsIDs.CLOSE_ICON} /> ) : null, headerLeft: () => @@ -226,7 +214,7 @@ export function getNavigationOptionsTitle( iconName={IconName.ArrowLeft} onPress={navigationPop} style={innerStyles.accessories} - {...generateTestId(Platform, NETWORK_BACK_ARROW_BUTTON_ID)} + testID={CommonSelectorsIDs.BACK_ARROW_BUTTON} /> ), headerTintColor: themeColors.primary.default, @@ -536,7 +524,7 @@ export function getSendFlowTitle( }); const rightAction = () => { const providerType = route?.params?.providerType ?? ''; - trackEventWithParameters(MetaMetricsEvents.SEND_FLOW_CANCEL, { + trackEvent(MetaMetricsEvents.SEND_FLOW_CANCEL, { view: title.split('.')[1], network: providerType, }); @@ -1389,14 +1377,9 @@ export function getSwapsQuotesNavbar(navigation, route, themeColors) { const selectedQuote = route.params?.selectedQuote; const quoteBegin = route.params?.quoteBegin; if (!selectedQuote) { - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters( - MetaMetricsEvents.QUOTES_REQUEST_CANCELLED, - { - ...trade, - responseTime: new Date().getTime() - quoteBegin, - }, - ); + trackEvent(MetaMetricsEvents.QUOTES_REQUEST_CANCELLED, { + ...trade, + responseTime: new Date().getTime() - quoteBegin, }); } navigation.pop(); @@ -1407,14 +1390,9 @@ export function getSwapsQuotesNavbar(navigation, route, themeColors) { const selectedQuote = route.params?.selectedQuote; const quoteBegin = route.params?.quoteBegin; if (!selectedQuote) { - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters( - MetaMetricsEvents.QUOTES_REQUEST_CANCELLED, - { - ...trade, - responseTime: new Date().getTime() - quoteBegin, - }, - ); + trackEvent(MetaMetricsEvents.QUOTES_REQUEST_CANCELLED, { + ...trade, + responseTime: new Date().getTime() - quoteBegin, }); } navigation.dangerouslyGetParent()?.pop(); diff --git a/app/components/UI/NavbarTitle/__snapshots__/index.test.js.snap b/app/components/UI/NavbarTitle/__snapshots__/index.test.js.snap index c0849cce627..86e69b97324 100644 --- a/app/components/UI/NavbarTitle/__snapshots__/index.test.js.snap +++ b/app/components/UI/NavbarTitle/__snapshots__/index.test.js.snap @@ -32,7 +32,7 @@ exports[`NavbarTitle should render correctly 1`] = ` } } > - diff --git a/app/components/UI/NavbarTitle/index.js b/app/components/UI/NavbarTitle/index.js index 8dfc5a9d44a..540ef5fa028 100644 --- a/app/components/UI/NavbarTitle/index.js +++ b/app/components/UI/NavbarTitle/index.js @@ -4,16 +4,16 @@ import { connect } from 'react-redux'; import { scale } from 'react-native-size-matters'; import { TouchableOpacity, View, StyleSheet, Text } from 'react-native'; import { fontStyles, colors as importedColors } from '../../../styles/common'; -import Networks from '../../../util/networks'; +import Networks, { getDecimalChainId } from '../../../util/networks'; import { strings } from '../../../../locales/i18n'; import Device from '../../../util/device'; import { ThemeContext, mockTheme } from '../../../util/theme'; import { NAVBAR_TITLE_NETWORKS_TEXT } from '../../../../wdio/screen-objects/testIDs/Screens/WalletScreen-testIds'; import Routes from '../../../constants/navigation/Routes'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import Analytics from '../../../core/Analytics/Analytics'; import { withNavigation } from '@react-navigation/compat'; import { selectProviderConfig } from '../../../selectors/networkController'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; const createStyles = (colors) => StyleSheet.create({ @@ -75,6 +75,10 @@ class NavbarTitle extends PureComponent { * Object that represents the navigator */ navigation: PropTypes.object, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; static defaultProps = { @@ -91,10 +95,10 @@ class NavbarTitle extends PureComponent { screen: Routes.SHEET.NETWORK_SELECTOR, }); - Analytics.trackEventWithParameters( + this.props.metrics.trackEvent( MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { - chain_id: this.props.providerConfig.chainId, + chain_id: getDecimalChainId(this.props.providerConfig.chainId), }, ); setTimeout(() => { @@ -160,4 +164,6 @@ const mapStateToProps = (state) => ({ providerConfig: selectProviderConfig(state), }); -export default withNavigation(connect(mapStateToProps)(NavbarTitle)); +export default withNavigation( + connect(mapStateToProps)(withMetricsAwareness(NavbarTitle)), +); diff --git a/app/components/UI/NetworkInfo/index.tsx b/app/components/UI/NetworkInfo/index.tsx index e6cabc8cb13..d6617cef5fc 100644 --- a/app/components/UI/NetworkInfo/index.tsx +++ b/app/components/UI/NetworkInfo/index.tsx @@ -117,7 +117,7 @@ interface NetworkInfoProps { const NetworkInfo = (props: NetworkInfoProps) => { const { onClose, ticker, isTokenDetectionEnabled } = props; const providerConfig = useSelector(selectProviderConfig); - const { type, ticker: networkTicker, rpcTarget, chainId } = providerConfig; + const { type, ticker: networkTicker, rpcUrl, chainId } = providerConfig; const { colors } = useTheme(); const styles = createStyles(colors); const isTokenDetectionSupported = @@ -170,9 +170,7 @@ const NetworkInfo = (props: NetworkInfoProps) => { {networkName} - {ticker === undefined && ( - {rpcTarget} - )} + {ticker === undefined && {rpcUrl}} {strings('network_information.things_to_keep_in_mind')}: diff --git a/app/components/UI/NetworkMainAssetLogo/index.js b/app/components/UI/NetworkMainAssetLogo/index.js index 53e265ee244..b85e38b64d1 100644 --- a/app/components/UI/NetworkMainAssetLogo/index.js +++ b/app/components/UI/NetworkMainAssetLogo/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { NetworksChainId } from '@metamask/controller-utils'; +import { ChainId } from '@metamask/controller-utils'; import { connect } from 'react-redux'; import TokenIcon from '../Swaps/components/TokenIcon'; import { @@ -9,7 +9,7 @@ import { } from '../../../selectors/networkController'; function NetworkMainAssetLogo({ chainId, ticker, style, big, biggest }) { - if (chainId === NetworksChainId.mainnet) { + if (chainId === ChainId.mainnet) { return ( ); diff --git a/app/components/UI/NetworkModal/NetworkAdded/index.tsx b/app/components/UI/NetworkModal/NetworkAdded/index.tsx index 2b4039a8f6b..139ae4cbe03 100644 --- a/app/components/UI/NetworkModal/NetworkAdded/index.tsx +++ b/app/components/UI/NetworkModal/NetworkAdded/index.tsx @@ -4,10 +4,8 @@ import StyledButton from '../../StyledButton'; import { strings } from '../../../../../locales/i18n'; import Text from '../../../Base/Text'; import { useTheme } from '../../../../util/theme'; -import { - NEW_NETWORK_ADDED_SWITCH_TO_NETWORK_BUTTON, - NEW_NETWORK_ADDED_CLOSE_BUTTON, -} from '../../../../../wdio/screen-objects/testIDs/Screens/NetworksScreen.testids'; +import { NetworkAddedModalSelectorsIDs } from '../../../../../e2e/selectors/Modals/NetworkAddedModal.selectors'; + const createStyles = (colors: any) => StyleSheet.create({ buttonView: { @@ -47,14 +45,14 @@ const NetworkAdded = (props: NetworkAddedProps) => { {`"${strings('networks.network_name', { - networkName: nickname, + networkName: nickname ?? '', })}"`} {strings('networks.network_added')} @@ -63,7 +61,7 @@ const NetworkAdded = (props: NetworkAddedProps) => { {strings('networks.switch_network')} diff --git a/app/components/UI/NetworkModal/NetworkDetails/index.tsx b/app/components/UI/NetworkModal/NetworkDetails/index.tsx index f9d6a32fe21..1e7d2ed3fa3 100644 --- a/app/components/UI/NetworkModal/NetworkDetails/index.tsx +++ b/app/components/UI/NetworkModal/NetworkDetails/index.tsx @@ -4,6 +4,7 @@ import ConnectHeader from '../../../UI/ConnectHeader'; import { strings } from '../../../../../locales/i18n'; import Text from '../../../Base/Text'; import { useTheme } from '../../../../util/theme'; +import { getDecimalChainId } from '../../../../util/networks'; const createStyles = (colors: any) => StyleSheet.create({ @@ -45,7 +46,7 @@ const NetworkDetails = (props: NetworkDetailsProps) => { }, { title: strings('networks.network_chain_id'), - value: chainId, + value: getDecimalChainId(chainId), }, { title: strings('networks.network_currency_symbol'), diff --git a/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap b/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..84e5c22fe23 --- /dev/null +++ b/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap @@ -0,0 +1,586 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NetworkDetails renders correctly 1`] = ` + + + + + + + + + + + + + + + + + Add custom network + + + + + + + + + + + + + + Test Network + + + + MetaMask doesn’t verify custom networks, so only approve networks you trust. + + + Learn more about network security risks and scams. + + + + + + Display name + + + Test Network + + + Chain ID + + + 1 + + + Network URL + + + https://localhost:8545 + + + + View details + + + + + + + + + + + + + Cancel + + + + + Confirm + + + + + + + + + +`; diff --git a/app/components/UI/NetworkModal/index.styles.ts b/app/components/UI/NetworkModal/index.styles.ts new file mode 100644 index 00000000000..fd2be5dc160 --- /dev/null +++ b/app/components/UI/NetworkModal/index.styles.ts @@ -0,0 +1,75 @@ +import { StyleSheet } from 'react-native'; +import { fontStyles } from '../../../styles/common'; +import scaling from '../../../util/scaling'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; + +const createNetworkModalStyles = (colors: ThemeColors) => + StyleSheet.create({ + root: { + backgroundColor: colors.background.default, + paddingHorizontal: 0, + maxHeight: '85%', + paddingBottom: 20, + }, + bottomModal: { + justifyContent: 'flex-end', + margin: 0, + }, + alertBar: { + width: '100%', + marginBottom: 15, + }, + modalContainer: { + borderRadius: 10, + backgroundColor: colors.background.default, + padding: 4, + paddingTop: 4, + maxHeight: '80%', + }, + title: { + ...fontStyles.bold, + fontSize: scaling.scale(18), + textAlign: 'center', + color: colors.text.default, + lineHeight: 34, + marginVertical: 10, + paddingHorizontal: 16, + }, + bottomSpace: { + marginBottom: 10, + }, + actionContainer: { + flex: 0, + paddingVertical: 16, + justifyContent: 'center', + }, + notch: { + width: 40, + height: 4, + borderRadius: 2, + backgroundColor: colors.border.muted, + }, + notchWrapper: { + alignSelf: 'stretch', + padding: 4, + alignItems: 'center', + }, + textSection: { + marginBottom: 8, + }, + accountCardWrapper: { + borderWidth: 1, + borderColor: colors.border.default, + borderRadius: 10, + padding: 16, + marginVertical: 16, + maxHeight: '70%', + }, + nestedScrollContent: { paddingBottom: 24 }, + networkSection: { marginBottom: 16 }, + textCentred: { + textAlign: 'center', + }, + }); + +export default createNetworkModalStyles; diff --git a/app/components/UI/NetworkModal/index.test.tsx b/app/components/UI/NetworkModal/index.test.tsx new file mode 100644 index 00000000000..d1ae7342112 --- /dev/null +++ b/app/components/UI/NetworkModal/index.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import NetworkModal from './index'; +import { render } from '@testing-library/react-native'; +import { useSelector } from 'react-redux'; + +interface NetworkProps { + isVisible: boolean; + onClose: () => void; + networkConfiguration: any; + navigation: any; + shouldNetworkSwitchPopToWallet: boolean; + onNetworkSwitch?: () => void; +} + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), + useSelector: jest.fn(), +})); +describe('NetworkDetails', () => { + const props: NetworkProps = { + isVisible: true, + onClose: () => ({}), + networkConfiguration: { + chainId: '1', + nickname: 'Test Network', + ticker: 'Test', + rpcUrl: 'https://localhost:8545', + formattedRpcUrl: 'https://localhost:8545', + rpcPrefs: { blockExplorerUrl: 'https://test.com', imageUrl: 'image' }, + }, + navigation: 'navigation', + shouldNetworkSwitchPopToWallet: true, + }; + it('renders correctly', () => { + (useSelector as jest.Mock).mockReturnValue(true); + const { toJSON } = render(); + + expect(toJSON()).toMatchSnapshot(); + }); +}); diff --git a/app/components/UI/NetworkModal/index.tsx b/app/components/UI/NetworkModal/index.tsx index 18f72928e0f..bbc5b1c8d9c 100644 --- a/app/components/UI/NetworkModal/index.tsx +++ b/app/components/UI/NetworkModal/index.tsx @@ -1,102 +1,38 @@ import Modal from 'react-native-modal'; -import React from 'react'; -import { View, StyleSheet, Linking, Platform } from 'react-native'; -import StyledButton from '../StyledButton'; -import { fontStyles } from '../../../styles/common'; +import React, { useCallback, useEffect } from 'react'; +import { View } from 'react-native'; import { strings } from '../../../../locales/i18n'; import Text from '../../Base/Text'; import NetworkDetails from './NetworkDetails'; import NetworkAdded from './NetworkAdded'; import Engine from '../../../core/Engine'; -import { isprivateConnection } from '../../../util/networks'; +import { + isprivateConnection, + toggleUseSafeChainsListValidation, +} from '../../../util/networks'; import getDecimalChainId from '../../../util/networks/getDecimalChainId'; import URLPARSE from 'url-parse'; -import scaling from '../../../util/scaling'; import { isWebUri } from 'valid-url'; -import FAIcon from 'react-native-vector-icons/FontAwesome'; -import InfoModal from '../Swaps/components/InfoModal'; -import ImageIcons from '../../UI/ImageIcon'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; +import { BannerAlertSeverity } from '../../../component-library/components/Banners/Banner'; +import { + ButtonSize, + ButtonVariants, +} from '../../../component-library/components/Buttons/Button'; import { useTheme } from '../../../util/theme'; import { networkSwitched } from '../../../actions/onboardNetwork'; -import generateTestId from '../../../../wdio/utils/generateTestId'; import { NetworkApprovalModalSelectorsIDs } from '../../../../e2e/selectors/Modals/NetworkApprovalModal.selectors'; -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; - -const createStyles = (colors: ThemeColors) => - StyleSheet.create({ - bottomModal: { - justifyContent: 'flex-end', - margin: 0, - }, - modalContainer: { - borderRadius: 10, - backgroundColor: colors.background.default, - padding: 20, - }, - buttonView: { - flexDirection: 'row', - paddingVertical: 16, - }, - button: { - flex: 1, - }, - cancel: { - marginRight: 8, - borderColor: colors.text.muted, - borderWidth: 1, - }, - confirm: { - marginLeft: 8, - }, - networkInformation: { - flexDirection: 'row', - justifyContent: 'flex-start', - borderWidth: 1, - borderColor: colors.text.muted, - borderRadius: 10, - padding: 16, - marginBottom: 10, - }, - title: { - ...fontStyles.bold, - fontSize: scaling.scale(18), - textAlign: 'center', - color: colors.text.default, - lineHeight: 34, - marginVertical: 10, - paddingHorizontal: 16, - }, - bottomSpace: { - marginBottom: 10, - }, - nameWrapper: { - backgroundColor: colors.background.alternative, - marginRight: '15%', - marginLeft: '15%', - paddingVertical: 5, - borderRadius: 40, - justifyContent: 'center', - alignItems: 'center', - flexDirection: 'row', - }, - infoIconContainer: { - paddingHorizontal: 3, - }, - infoIcon: { - fontSize: 12, - color: colors.icon.default, - }, - popularNetworkImage: { - width: 20, - height: 20, - marginRight: 10, - borderRadius: 10, - }, - }); +import { selectUseSafeChainsListValidation } from '../../../selectors/preferencesController'; +import BottomSheetFooter, { + ButtonsAlignment, +} from '../../../component-library/components/BottomSheets/BottomSheetFooter'; +import { ButtonProps } from '../../../component-library/components/Buttons/Button/Button.types'; +import checkSafeNetwork from '../../../core/RPCMethods/networkChecker.util'; +import NetworkVerificationInfo from '../NetworkVerificationInfo'; +import createNetworkModalStyles from './index.styles'; +import { useMetrics } from '../../../components/hooks/useMetrics'; interface NetworkProps { isVisible: boolean; @@ -123,15 +59,24 @@ const NetworkModals = (props: NetworkProps) => { shouldNetworkSwitchPopToWallet, onNetworkSwitch, } = props; - + const { trackEvent } = useMetrics(); const [showDetails, setShowDetails] = React.useState(false); - const [showInfo, setShowInfo] = React.useState(false); const [networkAdded, setNetworkAdded] = React.useState(false); + const [showCheckNetwork, setShowCheckNetwork] = React.useState(false); + const [alerts, setAlerts] = React.useState< + { + alertError: string; + alertSeverity: BannerAlertSeverity; + alertOrigin: string; + }[] + >([]); + const isCustomNetwork = true; const showDetailsModal = () => setShowDetails(!showDetails); + const showCheckNetworkModal = () => setShowCheckNetwork(!showCheckNetwork); const { colors } = useTheme(); - const styles = createStyles(colors); + const styles = createNetworkModalStyles(colors); const dispatch = useDispatch(); @@ -146,19 +91,64 @@ const NetworkModals = (props: NetworkProps) => { setNetworkAdded(validUrl); }; - const showToolTip = () => setShowInfo(!showInfo); + const cancelButtonProps: ButtonProps = { + variant: ButtonVariants.Secondary, + label: strings('accountApproval.cancel'), + size: ButtonSize.Lg, + onPress: showCheckNetworkModal, + testID: NetworkApprovalModalSelectorsIDs.CANCEL_BUTTON, + }; + + const confirmButtonProps: ButtonProps = { + variant: ButtonVariants.Primary, + label: strings('enter_password.confirm_button'), + size: ButtonSize.Lg, + onPress: () => { + toggleUseSafeChainsListValidation(true); + showCheckNetworkModal(); + }, + testID: NetworkApprovalModalSelectorsIDs.CONFIRM_NETWORK_CHECK, + }; + + const useSafeChainsListValidation = useSelector( + selectUseSafeChainsListValidation, + ); + + const customNetworkInformation = { + chainId, + blockExplorerUrl, + chainName: nickname, + rpcUrl, + icon: imageUrl, + ticker, + alerts, + }; + + const checkNetwork = useCallback(async () => { + if (useSafeChainsListValidation) { + const alertsNetwork = await checkSafeNetwork( + getDecimalChainId(chainId), + rpcUrl, + nickname, + ticker, + ); + + setAlerts(alertsNetwork); + } + }, [chainId, rpcUrl, nickname, ticker, useSafeChainsListValidation]); - const goToLink = () => Linking.openURL(strings('networks.security_link')); + useEffect(() => { + checkNetwork(); + }, [checkNetwork]); const closeModal = () => { const { NetworkController } = Engine.context; const url = new URLPARSE(rpcUrl); - const decimalChainId = getDecimalChainId(chainId); !isprivateConnection(url.hostname) && url.set('protocol', 'https:'); NetworkController.upsertNetworkConfiguration( { rpcUrl: url.href, - chainId: decimalChainId, + chainId, ticker, nickname, rpcPrefs: { blockExplorerUrl }, @@ -176,13 +166,12 @@ const NetworkModals = (props: NetworkProps) => { const switchNetwork = () => { const { NetworkController, CurrencyRateController } = Engine.context; const url = new URLPARSE(rpcUrl); - const decimalChainId = getDecimalChainId(chainId); CurrencyRateController.setNativeCurrency(ticker); !isprivateConnection(url.hostname) && url.set('protocol', 'https:'); NetworkController.upsertNetworkConfiguration( { rpcUrl: url.href, - chainId: decimalChainId, + chainId, ticker, nickname, rpcPrefs: { blockExplorerUrl }, @@ -197,12 +186,12 @@ const NetworkModals = (props: NetworkProps) => { ); const analyticsParamsAdd = { - chain_id: decimalChainId, + chain_id: getDecimalChainId(chainId), source: 'Popular network list', symbol: ticker, }; - AnalyticsV2.trackEvent(MetaMetricsEvents.NETWORK_ADDED, analyticsParamsAdd); + trackEvent(MetaMetricsEvents.NETWORK_ADDED, analyticsParamsAdd); closeModal(); if (onNetworkSwitch) { @@ -224,11 +213,35 @@ const NetworkModals = (props: NetworkProps) => { backdropOpacity={0.7} animationInTiming={600} animationOutTiming={600} - swipeDirection={'down'} propagateSwipe > - {showDetails ? ( + {showCheckNetwork ? ( + + + + {strings('wallet.network_details_check')} + + + {strings('app_settings.use_safe_chains_list_validation_desc')} + + + + {strings('networks.network_select_confirm_use_safe_check')} + + {strings('networks.network_settings_security_privacy')} + {' '} + + + + + + + + ) : showDetails ? ( { /> ) : ( - {showInfo && ( - - )} - - - {nickname} + + - - {strings('networks.want_to_add_network')} - - - {strings('networks.network_infomation')} - - - {strings('networks.network_endorsement')} - - - - - - {strings('networks.learn_about')} - - {strings('networks.network_risk')} - - - - - {strings('networks.network_display_name')} - - {nickname} - - {strings('networks.network_chain_id')} - - {chainId} - - {strings('networks.network_rpc_url')} - - {formattedRpcUrl || rpcUrl} - - - - - {strings('networks.view_details')} - - - - {strings('networks.cancel')} - - - {strings('networks.approve')} - + + + )} diff --git a/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.styles.ts b/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.styles.ts index 0873292ea38..da1fa609867 100644 --- a/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.styles.ts +++ b/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.styles.ts @@ -1,10 +1,10 @@ -import { StyleSheet } from 'react-native'; +import { StyleSheet, TextStyle } from 'react-native'; // External dependencies. import { Theme } from '../../../util/theme/models'; const styleSheet = (params: { theme: Theme }) => { const { theme } = params; - const { colors } = theme; + const { colors, typography } = theme; return StyleSheet.create({ root: { @@ -22,7 +22,36 @@ const styleSheet = (params: { theme: Theme }) => { textSection: { marginBottom: 8, }, - + alertBar: { + width: '100%', + marginBottom: 15, + }, + title: { + textAlign: 'center', + marginVertical: 10, + paddingHorizontal: 16, + ...typography.lBodyMDBold, + } as TextStyle, + bottomSpace: { + marginBottom: 10, + }, + textContainer: { + paddingHorizontal: 16, + marginVertical: 10, + }, + warningContainer: { + marginTop: 20, + marginHorizontal: 4, + }, + errorContinue: { + marginVertical: 16, + }, + textCentred: { + textAlign: 'center', + }, + boldText: { + ...typography.sBodyMDBold, + } as TextStyle, networkSection: { marginBottom: 16 }, nestedScrollContent: { paddingBottom: 24 }, }); diff --git a/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.test.tsx b/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.test.tsx index effb2c89e6d..45b0b9f28dc 100644 --- a/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.test.tsx +++ b/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.test.tsx @@ -3,6 +3,7 @@ import NetworkVerificationInfo from './NetworkVerificationInfo'; import { render } from '@testing-library/react-native'; import { BannerAlertSeverity } from '../../../component-library/components/Banners/Banner'; import { strings } from '../../../../locales/i18n'; +import { useSelector } from 'react-redux'; const mockNetworkInfo = { chainName: 'Test Chain', @@ -20,8 +21,17 @@ const mockNetworkInfo = { icon: 'test-icon', }; +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn(), +})); + describe('NetworkVerificationInfo', () => { + beforeEach(() => { + (useSelector as jest.Mock).mockClear(); + }); it('renders correctly', () => { + (useSelector as jest.Mock).mockReturnValue(true); const { toJSON } = render( , ); @@ -29,6 +39,7 @@ describe('NetworkVerificationInfo', () => { expect(toJSON()).toMatchSnapshot(); }); it('renders one alert', () => { + (useSelector as jest.Mock).mockReturnValue(true); const { getByText } = render( , ); @@ -36,4 +47,25 @@ describe('NetworkVerificationInfo', () => { getByText(strings('add_custom_network.unrecognized_chain_name')), ).toBeDefined(); }); + + it('should render the banner', () => { + (useSelector as jest.Mock).mockReturnValue(false); + const { getByText } = render( + , + ); + expect( + getByText(strings('wallet.turn_on_network_check_cta')), + ).toBeDefined(); + }); + + it('should not render alert', () => { + (useSelector as jest.Mock).mockReturnValue(false); + const { getByText } = render( + , + ); + + expect(() => + getByText(strings('add_custom_network.unrecognized_chain_name')), + ).toThrow('Unable to find an element with text'); + }); }); diff --git a/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.tsx b/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.tsx index 4d8b4bd2f7c..0e5ff5b6bdd 100644 --- a/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.tsx +++ b/app/components/UI/NetworkVerificationInfo/NetworkVerificationInfo.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/prop-types */ -import React, { useCallback, useState } from 'react'; -import { Linking } from 'react-native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { View, Linking } from 'react-native'; import { strings } from '../../../../locales/i18n'; import { CommonSelectorsIDs } from '../../../../e2e/selectors/Common.selectors'; import Text, { @@ -13,26 +13,59 @@ import Banner, { BannerAlertSeverity, BannerVariant, } from '../../../component-library/components/Banners/Banner'; -import { DEFAULT_BUTTONLINK_LABEL_COLOR } from '../../../component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.constants'; import { useStyles } from '../../../component-library/hooks'; import styleSheet from './NetworkVerificationInfo.styles'; import { CustomNetworkInformation } from './NetworkVerificationInfo.types'; import { ScrollView } from 'react-native-gesture-handler'; import { ADD_CUSTOM_NETWORK_ARTCILE } from '../../../constants/urls'; +import { useSelector } from 'react-redux'; +import { selectUseSafeChainsListValidation } from '../../../selectors/preferencesController'; +import { + ButtonSize, + ButtonVariants, +} from '../../../component-library/components/Buttons/Button'; +import BottomSheetFooter, { + ButtonsAlignment, +} from '../../../component-library/components/BottomSheets/BottomSheetFooter'; +import BottomSheetHeader from '../../../component-library/components/BottomSheets/BottomSheetHeader'; +import { toggleUseSafeChainsListValidation } from '../../../util/networks'; +import { NetworkApprovalModalSelectorsIDs } from '../../../../e2e/selectors/Modals/NetworkApprovalModal.selectors'; + +interface Alert { + alertError: string; + alertSeverity: BannerAlertSeverity; + alertOrigin: string; +} /** * NetworkVerificationInfo component */ const NetworkVerificationInfo = ({ customNetworkInformation, + onReject, + onConfirm, + isCustomNetwork = false, }: { customNetworkInformation: CustomNetworkInformation; + onReject: () => void; + onConfirm: () => void; + isCustomNetwork?: boolean; }) => { const [networkInfoMaxHeight, setNetworkInfoMaxHeight] = useState< number | null >(null); const [networkDetailsExpanded, setNetworkDetailsExpanded] = useState(false); const { styles } = useStyles(styleSheet, {}); + const safeChainsListValidationEnabled = useSelector( + selectUseSafeChainsListValidation, + ); + const [showCheckNetwork, setShowCheckNetwork] = React.useState(false); + const { alerts: alertsFromProps } = customNetworkInformation; + const [alerts, setAlerts] = React.useState([]); + + const showCheckNetworkModal = () => setShowCheckNetwork(!showCheckNetwork); + + useEffect(() => setAlerts(alertsFromProps), [alertsFromProps]); const renderNetworkInfo = () => ( { + if (!safeChainsListValidationEnabled) { + return ( + + + + ); + } + return null; + }; + const renderAlerts = useCallback(() => { - if (!customNetworkInformation.alerts.length) return null; - return customNetworkInformation.alerts.map( - (networkAlert: { - alertError: string; - alertSeverity: BannerAlertSeverity; - alertOrigin: string; - }) => ( + if (!safeChainsListValidationEnabled) return null; + if (!alerts.length) return null; + return alerts.map( + ( + networkAlert: { + alertError: string; + alertSeverity: BannerAlertSeverity; + alertOrigin: string; + }, + index, + ) => ( { + const newAlerts = [...alerts]; + newAlerts.splice(index, 1); + setAlerts(newAlerts); + }} /> ), ); - }, [customNetworkInformation.alerts, styles.textSection]); - - return ( - - + + + {strings('wallet.network_details_check')} + + + {strings('app_settings.use_safe_chains_list_validation_desc_1')}{' '} + chainid.network{' '} + {strings('app_settings.use_safe_chains_list_validation_desc_2')} + + + + {strings('networks.network_select_confirm_use_safe_check')}{' '} + + {strings('networks.network_settings_security_privacy')} + + + + + { + toggleUseSafeChainsListValidation(true); + showCheckNetworkModal(); + }, + label: strings('confirmation_modal.confirm_cta'), + variant: ButtonVariants.Primary, + size: ButtonSize.Lg, + testID: CommonSelectorsIDs.CONNECT_BUTTON, + }, + ]} + buttonsAlignment={ButtonsAlignment.Horizontal} /> - {renderAlerts()} - - {strings('add_custom_network.warning_subtext_new.1')}{' '} - - {strings('add_custom_network.warning_subtext_new.2')} + + ) : ( + + + + {isCustomNetwork + ? strings('networks.add_custom_network') + : strings('app_settings.network_add_network')} - - {renderNetworkInfo()} - + + + + {renderAlerts()} + {renderBanner()} + + {strings('add_custom_network.warning_subtext_new.1')}{' '} + + {strings('add_custom_network.warning_subtext_new.2')} + + + {renderNetworkInfo()} + + + ); }; diff --git a/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap b/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap index 02f7903ddb4..a9f05c62c14 100644 --- a/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap +++ b/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap @@ -1,269 +1,259 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NetworkVerificationInfo renders correctly 1`] = ` - - - + + + - - + } + > - Test Chain + Add Network - + + + + + + - - - + > + + - It looks like this network's display name doesn't match its chain ID. + Test Chain - - - - MetaMask doesn’t verify custom networks, so only approve networks you trust. - - + - Learn more about network security risks and scams. - - - - - - Display name - - - Test Chain - - + + - Chain ID - - + It looks like this network's display name doesn't match its chain ID. + + + - 1 - - + + + + + - Network URL - + } + > + MetaMask doesn’t verify custom networks, so only approve networks you trust. + - http://test.com + Learn more about network security risks and scams. - + + + + > + Display name + + + Test Chain + + Chain ID + + - View details + 1 - - + + http://test.com + + + - - - - + testID="accordionheader-title" + > + View details + + + + + + + + + + + + + Cancel + + + + + Confirm + + - + `; diff --git a/app/components/UI/Notification/TransactionNotification/index.js b/app/components/UI/Notification/TransactionNotification/index.js index 4c583b8d82b..6543347eea5 100644 --- a/app/components/UI/Notification/TransactionNotification/index.js +++ b/app/components/UI/Notification/TransactionNotification/index.js @@ -35,6 +35,7 @@ import { selectTokensByAddress } from '../../../../selectors/tokensController'; import { selectContractExchangeRates } from '../../../../selectors/tokenRatesController'; import { selectAccounts } from '../../../../selectors/accountTrackerController'; import { selectSelectedAddress } from '../../../../selectors/preferencesController'; +import { speedUpTransaction } from '../../../../util/transaction-controller'; const WINDOW_WIDTH = Dimensions.get('window').width; const ACTION_CANCEL = 'cancel'; @@ -202,10 +203,8 @@ function TransactionNotification(props) { [onActionFinish], ); - const speedUpTransaction = useCallback(() => { - safelyExecute(() => - Engine.context.TransactionController.speedUpTransaction(tx?.id), - ); + const speedUpTx = useCallback(() => { + safelyExecute(() => speedUpTransaction(tx?.id)); }, [safelyExecute, tx]); const stopTransaction = useCallback(() => { @@ -330,7 +329,7 @@ function TransactionNotification(props) { onConfirmPress={ transactionAction === ACTION_CANCEL ? stopTransaction - : speedUpTransaction + : speedUpTx } confirmText={strings('transaction.lets_try')} confirmButtonMode={'confirm'} diff --git a/app/components/UI/OnboardingWizard/Coachmark/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/Coachmark/__snapshots__/index.test.tsx.snap index e8a4b95e504..e3f79c2b8c7 100644 --- a/app/components/UI/OnboardingWizard/Coachmark/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/OnboardingWizard/Coachmark/__snapshots__/index.test.tsx.snap @@ -61,7 +61,7 @@ exports[`Coachmark should render correctly 1`] = ` } > @@ -106,7 +106,7 @@ exports[`Coachmark should render correctly 1`] = ` diff --git a/app/components/UI/OnboardingWizard/Step1/index.js b/app/components/UI/OnboardingWizard/Step1/index.js index 00378bdd4db..cc3ee2ab591 100644 --- a/app/components/UI/OnboardingWizard/Step1/index.js +++ b/app/components/UI/OnboardingWizard/Step1/index.js @@ -1,13 +1,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { - View, - Text, - StyleSheet, - InteractionManager, - Platform, -} from 'react-native'; +import { View, Text, StyleSheet, Platform } from 'react-native'; import Coachmark from '../Coachmark'; import Device from '../../../../util/device'; import setOnboardingWizardStep from '../../../../actions/wizard'; @@ -17,11 +11,11 @@ import { MetaMetricsEvents, ONBOARDING_WIZARD_STEP_DESCRIPTION, } from '../../../../core/Analytics'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { ThemeContext, mockTheme } from '../../../../util/theme'; import generateTestId from '../../../../../wdio/utils/generateTestId'; import { ONBOARDING_WIZARD_STEP_1_CONTAINER_ID } from '../../../../../wdio/screen-objects/testIDs/Components/OnboardingWizard.testIds'; +import { withMetricsAwareness } from '../../../../components/hooks/useMetrics'; const styles = StyleSheet.create({ main: { @@ -49,6 +43,10 @@ class Step1 extends PureComponent { * Dispatch set onboarding wizard step */ setOnboardingWizardStep: PropTypes.func, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; /** @@ -57,11 +55,10 @@ class Step1 extends PureComponent { onNext = () => { const { setOnboardingWizardStep } = this.props; setOnboardingWizardStep && setOnboardingWizardStep(2); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STARTED, { - tutorial_step_count: 1, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[1], - }); + + this.props.metrics.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STARTED, { + tutorial_step_count: 1, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[1], }); }; @@ -118,4 +115,4 @@ const mapDispatchToProps = (dispatch) => ({ Step1.contextType = ThemeContext; -export default connect(null, mapDispatchToProps)(Step1); +export default connect(null, mapDispatchToProps)(withMetricsAwareness(Step1)); diff --git a/app/components/UI/OnboardingWizard/Step2/index.js b/app/components/UI/OnboardingWizard/Step2/index.js index 26bd1d5248f..3bce98fe7d9 100644 --- a/app/components/UI/OnboardingWizard/Step2/index.js +++ b/app/components/UI/OnboardingWizard/Step2/index.js @@ -10,10 +10,10 @@ import { MetaMetricsEvents, ONBOARDING_WIZARD_STEP_DESCRIPTION, } from '../../../../core/Analytics'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { mockTheme, ThemeContext } from '../../../../util/theme'; import generateTestId from '../../../../../wdio/utils/generateTestId'; import { ONBOARDING_WIZARD_SECOND_STEP_CONTENT_ID } from '../../../../../wdio/screen-objects/testIDs/Components/OnboardingWizard.testIds'; +import { withMetricsAwareness } from '../../../../components/hooks/useMetrics'; const styles = StyleSheet.create({ main: { @@ -41,6 +41,10 @@ class Step2 extends PureComponent { * Callback called when closing step */ onClose: PropTypes.func, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -70,10 +74,13 @@ class Step2 extends PureComponent { onNext = () => { const { setOnboardingWizardStep } = this.props; setOnboardingWizardStep && setOnboardingWizardStep(3); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { - tutorial_step_count: 2, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2], - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, + { + tutorial_step_count: 2, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2], + }, + ); }; /** @@ -82,10 +89,13 @@ class Step2 extends PureComponent { onBack = () => { const { setOnboardingWizardStep } = this.props; setOnboardingWizardStep && setOnboardingWizardStep(1); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { - tutorial_step_count: 2, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2], - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, + { + tutorial_step_count: 2, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2], + }, + ); }; getOnboardingStyles = () => { @@ -154,4 +164,4 @@ const mapDispatchToProps = (dispatch) => ({ Step2.contextType = ThemeContext; -export default connect(null, mapDispatchToProps)(Step2); +export default connect(null, mapDispatchToProps)(withMetricsAwareness(Step2)); diff --git a/app/components/UI/OnboardingWizard/Step3/index.js b/app/components/UI/OnboardingWizard/Step3/index.js index 65216a970cf..700ed3d9be0 100644 --- a/app/components/UI/OnboardingWizard/Step3/index.js +++ b/app/components/UI/OnboardingWizard/Step3/index.js @@ -10,7 +10,6 @@ import { MetaMetricsEvents, ONBOARDING_WIZARD_STEP_DESCRIPTION, } from '../../../../core/Analytics'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { useTheme } from '../../../../util/theme'; import generateTestId from '../../../../../wdio/utils/generateTestId'; import { ONBOARDING_WIZARD_THIRD_STEP_CONTENT_ID } from '../../../../../wdio/screen-objects/testIDs/Components/OnboardingWizard.testIds'; @@ -20,6 +19,7 @@ import { selectIdentities, selectSelectedAddress, } from '../../../../selectors/preferencesController'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const styles = StyleSheet.create({ main: { @@ -32,6 +32,8 @@ const styles = StyleSheet.create({ const Step3 = ({ setOnboardingWizardStep, coachmarkRef, onClose }) => { const { colors } = useTheme(); + const { trackEvent } = useMetrics(); + const [coachmarkTop, setCoachmarkTop] = useState(0); const [coachmarkLeft, setCoachmarkLeft] = useState(0); const [coachmarkRight, setCoachmarkRight] = useState(0); @@ -65,7 +67,7 @@ const Step3 = ({ setOnboardingWizardStep, coachmarkRef, onClose }) => { const onNext = () => { setOnboardingWizardStep && setOnboardingWizardStep(4); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { tutorial_step_count: 3, tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[3], }); @@ -73,7 +75,7 @@ const Step3 = ({ setOnboardingWizardStep, coachmarkRef, onClose }) => { const onBack = () => { setOnboardingWizardStep && setOnboardingWizardStep(2); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { tutorial_step_count: 3, tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[3], }); diff --git a/app/components/UI/OnboardingWizard/Step4/index.js b/app/components/UI/OnboardingWizard/Step4/index.js index ad519d674cb..2973c67361f 100644 --- a/app/components/UI/OnboardingWizard/Step4/index.js +++ b/app/components/UI/OnboardingWizard/Step4/index.js @@ -10,10 +10,10 @@ import { MetaMetricsEvents, ONBOARDING_WIZARD_STEP_DESCRIPTION, } from '../../../../core/Analytics'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { useTheme } from '../../../../util/theme'; import generateTestId from '../../../../../wdio/utils/generateTestId'; import { ONBOARDING_WIZARD_FOURTH_STEP_CONTENT_ID } from '../../../../../wdio/screen-objects/testIDs/Components/OnboardingWizard.testIds'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const styles = StyleSheet.create({ main: { @@ -29,6 +29,7 @@ const styles = StyleSheet.create({ const Step4 = (props) => { const { setOnboardingWizardStep, onClose } = props; + const { trackEvent } = useMetrics(); const { colors } = useTheme(); const dynamicOnboardingStyles = onboardingStyles(colors); const [coachmarkBottom, setCoachmarkBottom] = useState(); @@ -50,7 +51,7 @@ const Step4 = (props) => { */ const onNext = () => { setOnboardingWizardStep && setOnboardingWizardStep(5); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { tutorial_step_count: 4, tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[4], }); @@ -61,7 +62,7 @@ const Step4 = (props) => { */ const onBack = () => { setOnboardingWizardStep && setOnboardingWizardStep(3); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { tutorial_step_count: 4, tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[4], }); diff --git a/app/components/UI/OnboardingWizard/Step5/index.js b/app/components/UI/OnboardingWizard/Step5/index.js index 0b3ec670818..9f2450dd66b 100644 --- a/app/components/UI/OnboardingWizard/Step5/index.js +++ b/app/components/UI/OnboardingWizard/Step5/index.js @@ -11,11 +11,11 @@ import { MetaMetricsEvents, ONBOARDING_WIZARD_STEP_DESCRIPTION, } from '../../../../core/Analytics'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { useTheme } from '../../../../util/theme'; import { createBrowserNavDetails } from '../../../Views/Browser'; import generateTestId from '../../../../../wdio/utils/generateTestId'; import { ONBOARDING_WIZARD_FIFTH_STEP_CONTENT_ID } from '../../../../../wdio/screen-objects/testIDs/Components/OnboardingWizard.testIds'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const WIDTH = Dimensions.get('window').width; const styles = StyleSheet.create({ @@ -36,7 +36,7 @@ const styles = StyleSheet.create({ const Step5 = (props) => { const { navigation, setOnboardingWizardStep, onClose } = props; - + const { trackEvent } = useMetrics(); const { colors } = useTheme(); const dynamicOnboardingStyles = onboardingStyles(colors); const [coachmarkBottom, setCoachmarkBottom] = useState(); @@ -47,7 +47,7 @@ const Step5 = (props) => { const onNext = () => { setOnboardingWizardStep && setOnboardingWizardStep(6); navigation && navigation.navigate(...createBrowserNavDetails()); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { tutorial_step_count: 5, tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5], }); @@ -61,7 +61,7 @@ const Step5 = (props) => { setTimeout(() => { setOnboardingWizardStep && setOnboardingWizardStep(4); }, 1); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { tutorial_step_count: 5, tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5], }); diff --git a/app/components/UI/OnboardingWizard/Step6/index.js b/app/components/UI/OnboardingWizard/Step6/index.js index 2c1ec078a46..4a7fbf3c413 100644 --- a/app/components/UI/OnboardingWizard/Step6/index.js +++ b/app/components/UI/OnboardingWizard/Step6/index.js @@ -11,11 +11,11 @@ import { MetaMetricsEvents, ONBOARDING_WIZARD_STEP_DESCRIPTION, } from '../../../../core/Analytics'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { useTheme } from '../../../../util/theme'; import Routes from '../../../../constants/navigation/Routes'; import generateTestId from '../../../../../wdio/utils/generateTestId'; import { ONBOARDING_WIZARD_SIXTH_STEP_CONTENT_ID } from '../../../../../wdio/screen-objects/testIDs/Components/OnboardingWizard.testIds'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const styles = StyleSheet.create({ main: { @@ -32,6 +32,8 @@ const styles = StyleSheet.create({ const Step6 = (props) => { const { setOnboardingWizardStep, onClose, navigation } = props; + const { trackEvent } = useMetrics(); + const [ready, setReady] = useState(false); const [coachmarkTop, setCoachmarkTop] = useState(0); const { colors } = useTheme(); @@ -59,7 +61,7 @@ const Step6 = (props) => { const onBack = () => { navigation?.navigate?.(Routes.WALLET.HOME); setOnboardingWizardStep && setOnboardingWizardStep(5); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { tutorial_step_count: 6, tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[6], }); diff --git a/app/components/UI/OnboardingWizard/index.js b/app/components/UI/OnboardingWizard/index.js index 04f5307b873..97dcd2a8442 100644 --- a/app/components/UI/OnboardingWizard/index.js +++ b/app/components/UI/OnboardingWizard/index.js @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import { View, StyleSheet, InteractionManager } from 'react-native'; +import { View, StyleSheet } from 'react-native'; import { colors as importedColors } from '../../../styles/common'; import { connect } from 'react-redux'; import Step1 from './Step1'; @@ -17,12 +17,12 @@ import { MetaMetricsEvents, ONBOARDING_WIZARD_STEP_DESCRIPTION, } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { DrawerContext } from '../../../components/Nav/Main/MainNavigator'; import { useTheme } from '../../../util/theme'; import Device from '../../../util/device'; import AsyncStorageWrapper from '../../../store/async-storage-wrapper'; import { isTest } from '../../../util/test/utils'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = ({ colors, typography }) => StyleSheet.create({ @@ -77,6 +77,7 @@ const OnboardingWizard = (props) => { } = props; const { drawerRef } = useContext(DrawerContext); const theme = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(theme); /** @@ -86,13 +87,11 @@ const OnboardingWizard = (props) => { await DefaultPreference.set(ONBOARDING_WIZARD, EXPLORED); setOnboardingWizardStep && setOnboardingWizardStep(0); drawerRef?.current?.dismissDrawer?.(); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_SKIPPED, { - tutorial_step_count: step, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[step], - }); - AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_COMPLETED); + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_SKIPPED, { + tutorial_step_count: step, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[step], }); + trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_COMPLETED); }; // Since react-native-default-preference is not covered by the fixtures, diff --git a/app/components/UI/OptinMetrics/index.js b/app/components/UI/OptinMetrics/index.js index f3cc890338e..666f50c094a 100644 --- a/app/components/UI/OptinMetrics/index.js +++ b/app/components/UI/OptinMetrics/index.js @@ -17,17 +17,11 @@ import { getOptinMetricsNavbarOptions } from '../Navbar'; import { strings } from '../../../../locales/i18n'; import setOnboardingWizardStep from '../../../actions/wizard'; import { connect } from 'react-redux'; -import Analytics from '../../../core/Analytics/Analytics'; import { clearOnboardingEvents } from '../../../actions/onboarding'; -import { - ONBOARDING_WIZARD, - METRICS_OPT_IN, - DENIED, - AGREED, -} from '../../../constants/storage'; +import { ONBOARDING_WIZARD } from '../../../constants/storage'; import AppConstants from '../../../core/AppConstants'; -import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; +import { Analytics, MetaMetricsEvents } from '../../../core/Analytics'; +import { withMetricsAwareness } from '../../hooks/useMetrics'; import DefaultPreference from 'react-native-default-preference'; import { ThemeContext } from '../../../util/theme'; @@ -45,6 +39,9 @@ import Button, { } from '../../../component-library/components/Buttons/Button'; import { MAINNET } from '../../../constants/network'; import Routes from '../../../constants/navigation/Routes'; +import generateDeviceAnalyticsMetaData, { + UserSettingsAnalyticsMetaData as generateUserSettingsAnalyticsMetaData, +} from '../../../util/metrics'; const createStyles = ({ colors }) => StyleSheet.create({ @@ -144,6 +141,10 @@ class OptinMetrics 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 = { @@ -260,32 +261,17 @@ class OptinMetrics extends PureComponent { ); }; - /** - * Track the event of opt in or opt out. - * @param AnalyticsOptionSelected - User selected option regarding the tracking of events - */ - trackOptInEvent = (AnalyticsOptionSelected) => { - InteractionManager.runAfterInteractions(async () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, { - analytics_option_selected: AnalyticsOptionSelected, - updated_after_onboarding: false, - }); - }); - }; - /** * Callback on press cancel */ onCancel = async () => { - const { events } = this.props; - const metricsOptionSelected = 'Metrics Opt Out'; setTimeout(async () => { - if (events && events.length) { - events.forEach((eventArgs) => AnalyticsV2.trackEvent(...eventArgs)); - } - this.trackOptInEvent(metricsOptionSelected); - this.props.clearOnboardingEvents(); - await DefaultPreference.set(METRICS_OPT_IN, DENIED); + const { clearOnboardingEvents, metrics } = this.props; + // if users refuses tracking, get rid of the stored events + // and never send them to Segment + // and disable analytics + clearOnboardingEvents(); + await metrics.enable(false); Analytics.disableInstance(); }, 200); this.continue(); @@ -295,17 +281,44 @@ class OptinMetrics extends PureComponent { * Callback on press confirm */ onConfirm = async () => { - const { events } = this.props; - const metricsOptionSelected = 'Metrics Opt In'; - Analytics.enable(); - setTimeout(async () => { + const { events, metrics } = this.props; + await metrics.enable(); + InteractionManager.runAfterInteractions(async () => { + // add traits to user for identification + // consolidate device and user settings traits + const consolidatedTraits = { + ...generateDeviceAnalyticsMetaData(), + ...generateUserSettingsAnalyticsMetaData(), + }; + await metrics.addTraitsToUser(consolidatedTraits); + + // track onboarding events that were stored before user opted in + // only if the user eventually opts in. if (events && events.length) { - events.forEach((eventArgs) => AnalyticsV2.trackEvent(...eventArgs)); + let delay = 0; // Initialize delay + const eventTrackingDelay = 200; // ms delay between each event + events.forEach((eventArgs) => { + // delay each event to prevent them from + // being tracked with the same timestamp + // which would cause them to be grouped together + // by sentAt time in the Segment dashboard + // as precision is only to the milisecond + // and loop seems to runs faster than that + setTimeout(() => { + metrics.trackEvent(...eventArgs); + }, delay); + delay += eventTrackingDelay; + }); } - this.trackOptInEvent(metricsOptionSelected); + this.props.clearOnboardingEvents(); - await DefaultPreference.set(METRICS_OPT_IN, AGREED); - }, 200); + + // track event for user opting in + metrics.trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, { + analytics_option_selected: 'Metrics Opt In', + updated_after_onboarding: false, + }); + }); this.continue(); }; @@ -523,4 +536,7 @@ const mapDispatchToProps = (dispatch) => ({ clearOnboardingEvents: () => dispatch(clearOnboardingEvents()), }); -export default connect(mapStateToProps, mapDispatchToProps)(OptinMetrics); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(OptinMetrics)); diff --git a/app/components/UI/PaymentRequest/index.js b/app/components/UI/PaymentRequest/index.js index 267aa2b0e0a..7eeecdeece4 100644 --- a/app/components/UI/PaymentRequest/index.js +++ b/app/components/UI/PaymentRequest/index.js @@ -40,7 +40,7 @@ import { } from '../../../util/payment-link-generator'; import Device from '../../../util/device'; import currencySymbols from '../../../util/currency-symbols.json'; -import { NetworksChainId } from '@metamask/controller-utils'; +import { ChainId } from '@metamask/controller-utils'; import { getTicker } from '../../../util/transactions'; import { toLowerCaseEquals } from '../../../util/general'; import { utils as ethersUtils } from 'ethers'; @@ -433,13 +433,13 @@ class PaymentRequest extends PureComponent { if (isTDSupportedForNetwork) { const defaults = - chainId === NetworksChainId.mainnet + chainId === ChainId.mainnet ? defaultAssets : [{ ...defaultEth, symbol: getTicker(ticker), name: '' }]; results = this.state.searchInputValue ? this.state.results : defaults; } else if ( //Check to see if it is not a test net ticker symbol - Object.values(NetworksChainId).find((value) => value === chainId) && + Object.values(ChainId).find((value) => value === chainId) && !(parseInt(chainId, 10) > 1 && parseInt(chainId, 10) < 6) ) { results = [defaultEth]; diff --git a/app/components/UI/ProtectYourWalletModal/index.js b/app/components/UI/ProtectYourWalletModal/index.js index 88b72ca8306..53ad4502c1e 100644 --- a/app/components/UI/ProtectYourWalletModal/index.js +++ b/app/components/UI/ProtectYourWalletModal/index.js @@ -1,13 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { - StyleSheet, - View, - Text, - Image, - TouchableOpacity, - InteractionManager, -} from 'react-native'; +import { StyleSheet, View, Text, Image, TouchableOpacity } from 'react-native'; import ActionModal from '../ActionModal'; import { fontStyles } from '../../../styles/common'; import { connect } from 'react-redux'; @@ -16,10 +9,10 @@ import Icon from 'react-native-vector-icons/FontAwesome'; import { strings } from '../../../../locales/i18n'; import scaling from '../../../util/scaling'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { ThemeContext, mockTheme } from '../../../util/theme'; import { ProtectWalletModalSelectorsIDs } from '../../../../e2e/selectors/Modals/ProtectWalletModal.selectors'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; const protectWalletImage = require('../../../images/explain-backup-seedphrase.png'); // eslint-disable-line @@ -96,6 +89,10 @@ class ProtectYourWalletModal extends PureComponent { * Boolean that determines if the user has set a password before */ passwordSet: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; goToBackupFlow = () => { @@ -104,15 +101,13 @@ class ProtectYourWalletModal extends PureComponent { 'SetPasswordFlow', this.props.passwordSet ? { screen: 'AccountBackupStep1' } : undefined, ); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED, - { - wallet_protection_required: false, - source: 'Modal', - }, - ); - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED, + { + wallet_protection_required: false, + source: 'Modal', + }, + ); }; onLearnMore = () => { @@ -128,15 +123,13 @@ class ProtectYourWalletModal extends PureComponent { onDismiss = () => { this.props.protectWalletModalNotVisible(); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_DISMISSED, - { - wallet_protection_required: false, - source: 'Modal', - }, - ); - }); + this.props.metrics.trackEvent( + MetaMetricsEvents.WALLET_SECURITY_PROTECT_DISMISSED, + { + wallet_protection_required: false, + source: 'Modal', + }, + ); }; render() { @@ -209,4 +202,4 @@ ProtectYourWalletModal.contextType = ThemeContext; export default connect( mapStateToProps, mapDispatchToProps, -)(ProtectYourWalletModal); +)(withMetricsAwareness(ProtectYourWalletModal)); diff --git a/app/components/UI/QRHardware/AnimatedQRScanner.tsx b/app/components/UI/QRHardware/AnimatedQRScanner.tsx index b7c97428359..2357e210385 100644 --- a/app/components/UI/QRHardware/AnimatedQRScanner.tsx +++ b/app/components/UI/QRHardware/AnimatedQRScanner.tsx @@ -19,10 +19,10 @@ import { URRegistryDecoder } from '@keystonehq/ur-decoder'; import Modal from 'react-native-modal'; import { UR } from '@ngraveio/bc-ur'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { SUPPORTED_UR_TYPE } from '../../../constants/qr'; import { useTheme } from '../../../util/theme'; import { Theme } from '../../../util/theme/models'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (theme: Theme) => StyleSheet.create({ @@ -102,9 +102,11 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { hideModal, pauseQRCode, } = props; + const [urDecoder, setURDecoder] = useState(new URRegistryDecoder()); const [progress, setProgress] = useState(0); const theme = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(theme); let expectedURTypes: string[]; @@ -141,14 +143,14 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { const onError = useCallback( (error) => { if (onScanError && error) { - AnalyticsV2.trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { purpose, error, }); onScanError(error.message); } }, - [purpose, onScanError], + [purpose, onScanError, trackEvent], ); const onBarCodeRead = useCallback( @@ -164,7 +166,7 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { urDecoder.receivePart(content); setProgress(Math.ceil(urDecoder.getProgress() * 100)); if (urDecoder.isError()) { - AnalyticsV2.trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { purpose, error: urDecoder.resultError(), }); @@ -176,14 +178,14 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { setProgress(0); setURDecoder(new URRegistryDecoder()); } else if (purpose === 'sync') { - AnalyticsV2.trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { purpose, received_ur_type: ur.type, error: 'invalid `sync` qr code', }); onScanError(strings('transaction.invalid_qr_code_sync')); } else { - AnalyticsV2.trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { purpose, received_ur_type: ur.type, error: 'invalid `sign` qr code', @@ -195,7 +197,15 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { onScanError(strings('transaction.unknown_qr_code')); } }, - [visible, urDecoder, onScanError, expectedURTypes, purpose, onScanSuccess], + [ + visible, + urDecoder, + onScanError, + expectedURTypes, + purpose, + onScanSuccess, + trackEvent, + ], ); const onStatusChange = useCallback( diff --git a/app/components/UI/QRHardware/QRSigningDetails.tsx b/app/components/UI/QRHardware/QRSigningDetails.tsx index e8e15b26e8e..2567e571de9 100644 --- a/app/components/UI/QRHardware/QRSigningDetails.tsx +++ b/app/components/UI/QRHardware/QRSigningDetails.tsx @@ -29,11 +29,11 @@ import { ETHSignature } from '@keystonehq/bc-ur-registry-eth'; import { stringify as uuidStringify } from 'uuid'; import Alert, { AlertType } from '../../Base/Alert'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { useNavigation } from '@react-navigation/native'; import { useTheme } from '../../../util/theme'; import Device from '../../../util/device'; +import { useMetrics } from '../../../components/hooks/useMetrics'; interface IQRSigningDetails { QRState: IQRState; @@ -128,6 +128,7 @@ const QRSigningDetails = ({ fromAddress, }: IQRSigningDetails) => { const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const navigation = useNavigation(); const KeyringController = useMemo(() => { @@ -230,7 +231,7 @@ const QRSigningDetails = ({ setSentOrCanceled(true); successCallback?.(); } else { - AnalyticsV2.trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { + trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { error: 'received signature request id is not matched with origin request', }); @@ -243,6 +244,7 @@ const QRSigningDetails = ({ QRState.sign.request?.requestId, failureCallback, successCallback, + trackEvent, ], ); const onScanError = useCallback( diff --git a/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap b/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap index beda4b1db08..fd694600bd4 100644 --- a/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap +++ b/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap @@ -674,7 +674,7 @@ exports[`BuildQuote View Crypto Currency Data renders a special error page if cr "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -1406,7 +1406,7 @@ exports[`BuildQuote View Crypto Currency Data renders a special error page if cr "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -2138,7 +2138,7 @@ exports[`BuildQuote View Crypto Currency Data renders an error page when there i "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -3736,7 +3736,7 @@ exports[`BuildQuote View Fiat Currency Data renders an error page when there is "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -5334,7 +5334,7 @@ exports[`BuildQuote View Payment Method Data renders an error page when there is "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -6932,7 +6932,7 @@ exports[`BuildQuote View Regions data renders an error page when there is a regi "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -9659,7 +9659,7 @@ exports[`BuildQuote View renders correctly 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -10944,7 +10944,7 @@ exports[`BuildQuote View renders correctly 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -12837,7 +12837,7 @@ exports[`BuildQuote View renders correctly 2`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -14187,7 +14187,7 @@ exports[`BuildQuote View renders correctly 2`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -15004,7 +15004,7 @@ exports[`BuildQuote View renders correctly when sdkError is present 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -15707,7 +15707,7 @@ exports[`BuildQuote View renders correctly when sdkError is present 2`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], diff --git a/app/components/UI/Ramp/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap b/app/components/UI/Ramp/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap index bd7f860e121..f08c774fbc4 100644 --- a/app/components/UI/Ramp/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap +++ b/app/components/UI/Ramp/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap @@ -642,7 +642,7 @@ exports[`GetStarted renders correctly 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -1311,7 +1311,7 @@ exports[`GetStarted renders correctly 2`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -2428,7 +2428,7 @@ exports[`GetStarted renders correctly when sdkError is present 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], diff --git a/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.test.tsx b/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.test.tsx index 473285b4fc4..169c962fca7 100644 --- a/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.test.tsx +++ b/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.test.tsx @@ -80,13 +80,13 @@ function render(Component: React.ComponentType, chainId?: string) { NetworkController: { ...initialBackgroundState.NetworkController, providerConfig: { - chainId: chainId ?? '56', + chainId: chainId ?? '0x38', ticker: 'BNB', nickname: 'BNB Smart Chain', }, networkConfigurations: { networkId1: { - chainId: '137', + chainId: '0x89', nickname: 'Polygon Mainnet', rpcPrefs: { blockExplorerUrl: 'https://polygonscan.com' }, rpcUrl: diff --git a/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx b/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx index df410f24eb2..3441401a1d2 100644 --- a/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx +++ b/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigation } from '@react-navigation/native'; import { RefreshControl, TouchableOpacity, View } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; -import { NetworksChainId, NetworkType } from '@metamask/controller-utils'; +import { ChainId, NetworkType, toHex } from '@metamask/controller-utils'; import { useSelector } from 'react-redux'; import LoadingNetworksSkeleton from './LoadingNetworksSkeleton'; @@ -63,10 +63,10 @@ function NetworkSwitcher() { const rampNetworks = useMemo(() => { const activeNetworkDetails: Network[] = []; supportedNetworks.forEach(({ chainId: supportedChainId, active }) => { - const currentChainId = `${supportedChainId}`; + const currentChainId = toHex(supportedChainId); if ( - currentChainId === NetworksChainId['linea-mainnet'] || - currentChainId === NetworksChainId.mainnet || + currentChainId === ChainId['linea-mainnet'] || + currentChainId === ChainId.mainnet || !active ) { return; @@ -82,7 +82,7 @@ function NetworkSwitcher() { } const networkDetail = networksDetails.find( - ({ chainId }) => chainId === currentChainId, + ({ chainId }) => toHex(chainId) === currentChainId, ); if (networkDetail) { activeNetworkDetails.push(networkDetail); @@ -227,10 +227,7 @@ function NetworkSwitcher() { ) : ( <> - {isNetworkRampSupported( - NetworksChainId.mainnet, - supportedNetworks, - ) ? ( + {isNetworkRampSupported(ChainId.mainnet, supportedNetworks) ? ( switchToMainnet('mainnet')} @@ -253,7 +250,7 @@ function NetworkSwitcher() { ) : null} {isNetworkRampSupported( - NetworksChainId['linea-mainnet'], + ChainId['linea-mainnet'], supportedNetworks, ) ? ( @@ -958,828 +944,468 @@ exports[`NetworkSwitcher View renders and dismisses network modal when pressing style={ Object { "alignItems": "center", - "backgroundColor": "#F2F4F6", - "borderRadius": 40, - "flexDirection": "row", - "justifyContent": "center", - "marginLeft": "15%", - "marginRight": "15%", - "paddingVertical": 5, + "alignSelf": "stretch", + "padding": 4, } } - testID="approve-network-modal" - > - - Cronos Mainnet - - - - Want to add this network? - - - This allows this network to be used within MetaMask - - - MetaMask does not endorse custom networks or their security. - -  - - - - - - Learn about - - - - scams and network security risks - - + /> + - - - Display name - - - Cronos Mainnet - - - Chain ID - - + - 25 - - + + + + + Add custom network + + + - Network URL - - + + + + - https://evm.cronos.org - - - - - View details - - - + + + + + + Cronos Mainnet + + + - + MetaMask doesn’t verify custom networks, so only approve networks you trust. + + + Learn more about network security risks and scams. + + + + + + Display name + + + Cronos Mainnet + + + Chain ID + + + 25 + + + Network URL + + + https://evm.cronos.org + + + + View details + + + + + + + + + + - Cancel - - - + - + Cancel + + + + - Approve - - + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 22, + } + } + > + Confirm + + + + @@ -8743,7 +8369,7 @@ exports[`NetworkSwitcher View renders correctly with errors 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -9425,7 +9051,7 @@ exports[`NetworkSwitcher View renders correctly with errors 2`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -10107,7 +9733,7 @@ exports[`NetworkSwitcher View renders correctly with no data 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], diff --git a/app/components/UI/Ramp/Views/OrderDetails/__snapshots__/OrderDetails.test.tsx.snap b/app/components/UI/Ramp/Views/OrderDetails/__snapshots__/OrderDetails.test.tsx.snap index 5cbf28e682a..b7553163a74 100644 --- a/app/components/UI/Ramp/Views/OrderDetails/__snapshots__/OrderDetails.test.tsx.snap +++ b/app/components/UI/Ramp/Views/OrderDetails/__snapshots__/OrderDetails.test.tsx.snap @@ -1910,7 +1910,7 @@ exports[`OrderDetails renders a cancelled order 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -2425,7 +2425,7 @@ exports[`OrderDetails renders a completed order 1`] = ` style={ Array [ Object { - "color": "#28A745", + "color": "#1C8234", "fontSize": 32, }, undefined, @@ -3864,7 +3864,7 @@ exports[`OrderDetails renders a completed order 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -7685,7 +7685,7 @@ exports[`OrderDetails renders a failed order 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -10686,7 +10686,7 @@ exports[`OrderDetails renders an error screen if a CREATED order cannot be polle "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -12678,7 +12678,7 @@ exports[`OrderDetails renders non-transacted orders 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -13194,7 +13194,7 @@ exports[`OrderDetails renders the support links if the provider has them 1`] = ` style={ Array [ Object { - "color": "#28A745", + "color": "#1C8234", "fontSize": 32, }, undefined, @@ -14741,7 +14741,7 @@ exports[`OrderDetails renders the support links if the provider has them 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], diff --git a/app/components/UI/Ramp/Views/OrdersList/OrdersList.test.tsx b/app/components/UI/Ramp/Views/OrdersList/OrdersList.test.tsx index 59b72f8c486..1cf05fc4096 100644 --- a/app/components/UI/Ramp/Views/OrdersList/OrdersList.test.tsx +++ b/app/components/UI/Ramp/Views/OrdersList/OrdersList.test.tsx @@ -125,7 +125,7 @@ function render(Component: React.ReactElement, orders = testOrders) { providerConfig: { ticker: 'ETH', type: 'mainnet', - chainId: '1', + chainId: '0x1', }, }, }, diff --git a/app/components/UI/Ramp/Views/OrdersList/__snapshots__/OrdersList.test.tsx.snap b/app/components/UI/Ramp/Views/OrdersList/__snapshots__/OrdersList.test.tsx.snap index 185b239f73a..eb15ac6f751 100644 --- a/app/components/UI/Ramp/Views/OrdersList/__snapshots__/OrdersList.test.tsx.snap +++ b/app/components/UI/Ramp/Views/OrdersList/__snapshots__/OrdersList.test.tsx.snap @@ -231,7 +231,7 @@ exports[`OrdersList renders buy only correctly when pressing buy filter 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", @@ -464,7 +464,7 @@ exports[`OrdersList renders buy only correctly when pressing buy filter 1`] = ` accessibilityRole="text" style={ Object { - "color": "#28A745", + "color": "#1C8234", "fontFamily": "Euclid Circular B", "fontSize": 12, "fontWeight": "700", @@ -1352,7 +1352,7 @@ exports[`OrdersList renders correctly 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", @@ -1624,7 +1624,7 @@ exports[`OrdersList renders correctly 1`] = ` accessibilityRole="text" style={ Object { - "color": "#28A745", + "color": "#1C8234", "fontFamily": "Euclid Circular B", "fontSize": 12, "fontWeight": "700", @@ -2760,7 +2760,7 @@ exports[`OrdersList renders empty buy message 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", @@ -3050,7 +3050,7 @@ exports[`OrdersList renders empty sell message 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", @@ -3327,7 +3327,7 @@ exports[`OrdersList renders sell only correctly when pressing sell filter 1`] = accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", @@ -3873,7 +3873,7 @@ exports[`OrdersList resets filter to all after other filter was set 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", @@ -4405,7 +4405,7 @@ exports[`OrdersList resets filter to all after other filter was set 2`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", @@ -4677,7 +4677,7 @@ exports[`OrdersList resets filter to all after other filter was set 2`] = ` accessibilityRole="text" style={ Object { - "color": "#28A745", + "color": "#1C8234", "fontFamily": "Euclid Circular B", "fontSize": 12, "fontWeight": "700", diff --git a/app/components/UI/Ramp/Views/PaymentMethods/__snapshots__/PaymentMethods.test.tsx.snap b/app/components/UI/Ramp/Views/PaymentMethods/__snapshots__/PaymentMethods.test.tsx.snap index dd2d5a92c0d..f281af080d5 100644 --- a/app/components/UI/Ramp/Views/PaymentMethods/__snapshots__/PaymentMethods.test.tsx.snap +++ b/app/components/UI/Ramp/Views/PaymentMethods/__snapshots__/PaymentMethods.test.tsx.snap @@ -2030,7 +2030,7 @@ exports[`PaymentMethods View renders correctly 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -4088,7 +4088,7 @@ exports[`PaymentMethods View renders correctly for sell 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -6221,7 +6221,7 @@ If you selected Chile by mistake, click the button below to reset your region. "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -6926,7 +6926,7 @@ If you selected Chile by mistake, click the button below to reset your region. "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -7629,7 +7629,7 @@ exports[`PaymentMethods View renders correctly with error 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -11175,7 +11175,7 @@ exports[`PaymentMethods View renders correctly with payment method with disclaim "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -11877,7 +11877,7 @@ exports[`PaymentMethods View renders correctly with sdkError 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -13913,7 +13913,7 @@ exports[`PaymentMethods View renders correctly with show back button false 1`] = "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], diff --git a/app/components/UI/Ramp/Views/Quotes/Quotes.tsx b/app/components/UI/Ramp/Views/Quotes/Quotes.tsx index ed7e51d25a8..6cbf49eb632 100644 --- a/app/components/UI/Ramp/Views/Quotes/Quotes.tsx +++ b/app/components/UI/Ramp/Views/Quotes/Quotes.tsx @@ -593,6 +593,11 @@ function Quotes() { (link) => link.name === PROVIDER_LINKS.PRIVACY_POLICY, )?.url } + providerTermsOfService={ + selectedProviderInfo?.links?.find( + (link) => link.name === PROVIDER_LINKS.TOS, + )?.url + } providerSupport={ selectedProviderInfo?.links?.find( (link) => link.name === PROVIDER_LINKS.SUPPORT, diff --git a/app/components/UI/Ramp/Views/Quotes/__snapshots__/Quotes.test.tsx.snap b/app/components/UI/Ramp/Views/Quotes/__snapshots__/Quotes.test.tsx.snap index 19b7f536e33..73f38913267 100644 --- a/app/components/UI/Ramp/Views/Quotes/__snapshots__/Quotes.test.tsx.snap +++ b/app/components/UI/Ramp/Views/Quotes/__snapshots__/Quotes.test.tsx.snap @@ -2275,7 +2275,7 @@ exports[`Quotes renders correctly after animation with quotes 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -2615,7 +2615,7 @@ exports[`Quotes renders correctly after animation with quotes 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -3296,7 +3296,7 @@ exports[`Quotes renders correctly after animation without quotes 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -3968,7 +3968,7 @@ exports[`Quotes renders correctly when fetching quotes errors 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -4668,7 +4668,7 @@ exports[`Quotes renders correctly with sdkError 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -5343,7 +5343,7 @@ exports[`Quotes renders quotes expired screen 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], diff --git a/app/components/UI/Ramp/Views/Regions/__snapshots__/Regions.test.tsx.snap b/app/components/UI/Ramp/Views/Regions/__snapshots__/Regions.test.tsx.snap index 247be1fc570..8dad06619da 100644 --- a/app/components/UI/Ramp/Views/Regions/__snapshots__/Regions.test.tsx.snap +++ b/app/components/UI/Ramp/Views/Regions/__snapshots__/Regions.test.tsx.snap @@ -737,7 +737,7 @@ exports[`Regions View renders correctly 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -2081,7 +2081,7 @@ exports[`Regions View renders correctly with error 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -3367,7 +3367,7 @@ exports[`Regions View renders correctly with sdkError 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -4133,7 +4133,7 @@ exports[`Regions View renders correctly with selectedRegion 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -4955,7 +4955,7 @@ exports[`Regions View renders correctly with unsupportedRegion 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -6153,7 +6153,7 @@ exports[`Regions View renders correctly with unsupportedRegion 2`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], @@ -7351,7 +7351,7 @@ exports[`Regions View renders regions modal when pressing select button 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], diff --git a/app/components/UI/Ramp/Views/SendTransaction/SendTransaction.test.tsx b/app/components/UI/Ramp/Views/SendTransaction/SendTransaction.test.tsx index 2ffd1b51011..7f8d13ca8cf 100644 --- a/app/components/UI/Ramp/Views/SendTransaction/SendTransaction.test.tsx +++ b/app/components/UI/Ramp/Views/SendTransaction/SendTransaction.test.tsx @@ -6,8 +6,9 @@ import Routes from '../../../../../constants/navigation/Routes'; import { renderScreen } from '../../../../../util/test/renderWithProvider'; import initialBackgroundState from '../../../../../util/test/initial-background-state.json'; +import { addTransaction } from '../../../../../util/transaction-controller'; + import SendTransaction from './SendTransaction'; -import Engine from '../../../../../core/Engine'; type DeepPartial = { [key in keyof BaseType]?: DeepPartial; @@ -290,6 +291,7 @@ const mockGoBack = jest.fn(); const mockReset = jest.fn(); const mockPop = jest.fn(); const mockTrackEvent = jest.fn(); +const mockAddTransaction = addTransaction as jest.Mock; jest.mock('@react-navigation/native', () => { const actualReactNavigation = jest.requireActual('@react-navigation/native'); @@ -320,12 +322,10 @@ jest.mock('../../../../../util/navigation/navUtils', () => ({ useParams: jest.fn(() => mockUseParamsValues), })); -jest.mock('../../../../../core/Engine', () => ({ - context: { - TransactionController: { - addTransaction: jest.fn(), - }, - }, +jest.mock('../../../../../util/transaction-controller', () => ({ + __esModule: true, + ...jest.requireActual('../../../../../util/transaction-controller'), + addTransaction: jest.fn(), })); const mockDispatch = jest.fn(); @@ -346,7 +346,7 @@ describe('SendTransaction View', () => { mockPop.mockClear(); mockDispatch.mockClear(); mockTrackEvent.mockClear(); - Engine.context.TransactionController.addTransaction.mockClear(); + mockAddTransaction.mockClear(); }); beforeEach(() => { @@ -395,19 +395,16 @@ describe('SendTransaction View', () => { expect(screen.toJSON()).toMatchSnapshot(); }); - it('calls TransactionController.addTransaction for native coin when clicking on send button', async () => { + it('calls addTransaction for native coin when clicking on send button', async () => { render(SendTransaction); const nextButton = screen.getByRole('button', { name: 'Next' }); fireEvent.press(nextButton); - expect(Engine.context.TransactionController.addTransaction).toBeCalledTimes( - 1, - ); - expect(Engine.context.TransactionController.addTransaction.mock.calls) - .toMatchInlineSnapshot(` + expect(mockAddTransaction).toBeCalledTimes(1); + expect(mockAddTransaction.mock.calls).toMatchInlineSnapshot(` Array [ Array [ Object { - "chainId": 1, + "chainId": "0x1", "from": "0x1234", "to": "0x34256", "value": "0x2bea80d2171600", @@ -440,16 +437,13 @@ describe('SendTransaction View', () => { `); }); - it('calls TransactionController.addTransaction for erc20 when clicking on send button', async () => { + it('calls addTransaction for erc20 when clicking on send button', async () => { mockUseParamsValues = { orderId: 'test-id-2' }; render(SendTransaction); const nextButton = screen.getByRole('button', { name: 'Next' }); fireEvent.press(nextButton); - expect(Engine.context.TransactionController.addTransaction).toBeCalledTimes( - 1, - ); - expect(Engine.context.TransactionController.addTransaction.mock.calls) - .toMatchInlineSnapshot(` + expect(mockAddTransaction).toBeCalledTimes(1); + expect(mockAddTransaction.mock.calls).toMatchInlineSnapshot(` Array [ Array [ Object { @@ -469,9 +463,7 @@ describe('SendTransaction View', () => { it('calls analytics and redirects when the transaction is confirmed ', async () => { render(SendTransaction); const nextButton = screen.getByRole('button', { name: 'Next' }); - ( - Engine.context.TransactionController.addTransaction as jest.Mock - ).mockImplementationOnce(() => ({ + mockAddTransaction.mockImplementationOnce(() => ({ result: Promise.resolve('0x987654321'), })); @@ -494,12 +486,10 @@ describe('SendTransaction View', () => { expect(mockGoBack).toHaveBeenCalled(); }); - it('dispatches setFiatSellTxHash after getting hash from TransactionController.addTransaction', async () => { + it('dispatches setFiatSellTxHash after getting hash from addTransaction', async () => { render(SendTransaction); const nextButton = screen.getByRole('button', { name: 'Next' }); - ( - Engine.context.TransactionController.addTransaction as jest.Mock - ).mockImplementationOnce(() => ({ + mockAddTransaction.mockImplementationOnce(() => ({ result: Promise.resolve('0x987654321'), })); @@ -523,9 +513,7 @@ describe('SendTransaction View', () => { it('calls analytics when the transaction is rejected', async () => { render(SendTransaction); const nextButton = screen.getByRole('button', { name: 'Next' }); - ( - Engine.context.TransactionController.addTransaction as jest.Mock - ).mockImplementationOnce(() => ({ + mockAddTransaction.mockImplementationOnce(() => ({ result: Promise.reject(new Error('Transaction rejected')), })); diff --git a/app/components/UI/Ramp/Views/SendTransaction/SendTransaction.tsx b/app/components/UI/Ramp/Views/SendTransaction/SendTransaction.tsx index 75a0be4451d..7db84663a0e 100644 --- a/app/components/UI/Ramp/Views/SendTransaction/SendTransaction.tsx +++ b/app/components/UI/Ramp/Views/SendTransaction/SendTransaction.tsx @@ -5,7 +5,6 @@ import { useNavigation } from '@react-navigation/native'; import { BN } from 'ethereumjs-util'; import { SellOrder } from '@consensys/on-ramp-sdk/dist/API'; import { Transaction, WalletDevice } from '@metamask/transaction-controller'; -import Engine from '../../../../../core/Engine'; import Row from '../../components/Row'; import ScreenLayout from '../../components/ScreenLayout'; @@ -48,11 +47,13 @@ import { } from '../../../../../util/number'; import { strings } from '../../../../../../locales/i18n'; import { useStyles } from '../../../../../component-library/hooks'; +import { addTransaction } from '../../../../../util/transaction-controller'; import { NATIVE_ADDRESS } from '../../../../../constants/on-ramp'; import { safeToChecksumAddress } from '../../../../../util/address'; import { generateTransferData } from '../../../../../util/transactions'; import useAnalytics from '../../hooks/useAnalytics'; +import { toHex } from '@metamask/controller-utils'; interface SendTransactionParams { orderId?: string; @@ -110,8 +111,6 @@ function SendTransaction() { }, [trackEvent, transactionAnalyticsPayload]); const handleSend = useCallback(async () => { - const { TransactionController: TxController } = Engine.context; - let transactionParams: Transaction; const amount = addHexPrefix( new BN( @@ -126,7 +125,7 @@ function SendTransaction() { from: safeToChecksumAddress(orderData.walletAddress) as string, to: safeToChecksumAddress(orderData.depositWallet), value: amount, - chainId: orderData.cryptoCurrency.network.chainId, + chainId: toHex(orderData.cryptoCurrency.network.chainId), }; } else { transactionParams = { @@ -145,7 +144,7 @@ function SendTransaction() { 'OFFRAMP_SEND_TRANSACTION_INVOKED', transactionAnalyticsPayload, ); - const response = await TxController.addTransaction(transactionParams, { + const response = await addTransaction(transactionParams, { deviceConfirmedOn: WalletDevice.MM_MOBILE, }); const hash = await response.result; diff --git a/app/components/UI/Ramp/Views/SendTransaction/__snapshots__/SendTransaction.test.tsx.snap b/app/components/UI/Ramp/Views/SendTransaction/__snapshots__/SendTransaction.test.tsx.snap index 3ac07384d87..0b439fe7ec4 100644 --- a/app/components/UI/Ramp/Views/SendTransaction/__snapshots__/SendTransaction.test.tsx.snap +++ b/app/components/UI/Ramp/Views/SendTransaction/__snapshots__/SendTransaction.test.tsx.snap @@ -782,7 +782,7 @@ exports[`SendTransaction View renders correctly 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 16, "fontWeight": "500", @@ -1509,7 +1509,7 @@ exports[`SendTransaction View renders correctly for custom action payment method accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 16, "fontWeight": "500", @@ -2320,7 +2320,7 @@ exports[`SendTransaction View renders correctly for token 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 16, "fontWeight": "500", diff --git a/app/components/UI/Ramp/Views/Settings/__snapshots__/AddActivationKey.test.tsx.snap b/app/components/UI/Ramp/Views/Settings/__snapshots__/AddActivationKey.test.tsx.snap index 195d08469bc..709604fac45 100644 --- a/app/components/UI/Ramp/Views/Settings/__snapshots__/AddActivationKey.test.tsx.snap +++ b/app/components/UI/Ramp/Views/Settings/__snapshots__/AddActivationKey.test.tsx.snap @@ -548,7 +548,7 @@ exports[`AddActivationKey renders correctly 1`] = ` "textAlign": "center", }, Object { - "color": "#FCFCFC", + "color": "#FFFFFF", }, undefined, ], diff --git a/app/components/UI/Ramp/components/InfoAlert.tsx b/app/components/UI/Ramp/components/InfoAlert.tsx index 22e6e139758..fa3a7626f6b 100644 --- a/app/components/UI/Ramp/components/InfoAlert.tsx +++ b/app/components/UI/Ramp/components/InfoAlert.tsx @@ -51,6 +51,7 @@ interface Props { dismissButtonText?: string; providerWebsite?: string; providerPrivacyPolicy?: string; + providerTermsOfService?: string; providerSupport?: string; dismiss?: () => any; } @@ -64,6 +65,7 @@ const InfoAlert: React.FC = ({ dismiss, providerWebsite, providerPrivacyPolicy, + providerTermsOfService, providerSupport, }: Props) => { const { colors, themeAppearance } = useTheme(); @@ -94,6 +96,18 @@ const InfoAlert: React.FC = ({ [trackEvent], ); + const handleTermsOfServiceLinkPress = useCallback( + (url: string) => { + Linking.openURL(url); + trackEvent('ONRAMP_EXTERNAL_LINK_CLICKED', { + location: 'Quotes Screen', + text: 'Provider Terms of Service', + url_domain: url, + }); + }, + [trackEvent], + ); + const handleProviderSupportLinkPress = useCallback( (url: string) => { Linking.openURL(url); @@ -169,6 +183,17 @@ const InfoAlert: React.FC = ({ )} + {Boolean(providerTermsOfService) && ( + + handleTermsOfServiceLinkPress(providerTermsOfService as string) + } + > + + {strings('fiat_on_ramp_aggregator.terms_of_service')} + + + )} {Boolean(providerSupport) && ( diff --git a/app/components/UI/Ramp/components/OrderListItem/__snapshots__/OrderListItem.test.tsx.snap b/app/components/UI/Ramp/components/OrderListItem/__snapshots__/OrderListItem.test.tsx.snap index 23213dca0f2..4bb3eafbe12 100644 --- a/app/components/UI/Ramp/components/OrderListItem/__snapshots__/OrderListItem.test.tsx.snap +++ b/app/components/UI/Ramp/components/OrderListItem/__snapshots__/OrderListItem.test.tsx.snap @@ -161,7 +161,7 @@ exports[`OrderListItem should render correctly 1`] = ` accessibilityRole="text" style={ Object { - "color": "#28A745", + "color": "#1C8234", "fontFamily": "Euclid Circular B", "fontSize": 12, "fontWeight": "700", diff --git a/app/components/UI/Ramp/hooks/useAnalytics.ts b/app/components/UI/Ramp/hooks/useAnalytics.ts index 7f808483a4b..14d9791a1a0 100644 --- a/app/components/UI/Ramp/hooks/useAnalytics.ts +++ b/app/components/UI/Ramp/hooks/useAnalytics.ts @@ -1,8 +1,7 @@ import { useCallback } from 'react'; import { InteractionManager } from 'react-native'; -import Analytics from '../../../../core/Analytics/Analytics'; -import { MetaMetricsEvents } from '../../../../core/Analytics'; import { AnalyticsEvents } from '../types'; +import { MetaMetrics, MetaMetricsEvents } from '../../../../core/Analytics'; const AnonymousEvents: (keyof AnalyticsEvents)[] = [ 'RAMP_REGION_SELECTED', @@ -37,21 +36,24 @@ export function trackEvent( eventType: T, params: AnalyticsEvents[T], ) { + const metrics = MetaMetrics.getInstance(); const event = MetaMetricsEvents[eventType]; const anonymous = AnonymousEvents.includes(eventType); - InteractionManager.runAfterInteractions(() => { if (anonymous) { - Analytics.trackEventWithParameters(event, {}); - Analytics.trackEventWithParameters(event, params, true); + metrics.trackAnonymousEvent(event, { + ...params, + }); } else { - Analytics.trackEventWithParameters(event, params); + metrics.trackEvent(event, { + ...params, + }); } }); } function useAnalytics() { - const trackEventHook = useCallback( + return useCallback( ( eventType: T, params: AnalyticsEvents[T], @@ -60,8 +62,6 @@ function useAnalytics() { }, [], ); - - return trackEventHook; } export default useAnalytics; diff --git a/app/components/UI/Ramp/hooks/useBalance.ts b/app/components/UI/Ramp/hooks/useBalance.ts index e06c2f6d8cc..66a341c9f08 100644 --- a/app/components/UI/Ramp/hooks/useBalance.ts +++ b/app/components/UI/Ramp/hooks/useBalance.ts @@ -1,7 +1,7 @@ import { hexToBN } from '@metamask/controller-utils'; import { useSelector } from 'react-redux'; import { NATIVE_ADDRESS } from '../../../../constants/on-ramp'; -import { selectAccounts } from '../../../../selectors/accountTrackerController'; +import { selectAccountsByChainId } from '../../../../selectors/accountTrackerController'; import { selectConversionRate, selectCurrentCurrency, @@ -9,11 +9,13 @@ import { import { selectSelectedAddress } from '../../../../selectors/preferencesController'; import { selectContractBalances } from '../../../../selectors/tokenBalancesController'; import { selectContractExchangeRates } from '../../../../selectors/tokenRatesController'; +import { selectChainId } from '../../../../selectors/networkController'; import { safeToChecksumAddress } from '../../../../util/address'; import { balanceToFiat, renderFromTokenMinimalUnit, renderFromWei, + toHexadecimal, weiToFiat, } from '../../../../util/number'; @@ -24,7 +26,8 @@ interface Asset { export default function useBalance(asset?: Asset) { const assetAddress = safeToChecksumAddress(asset?.address); - const accounts = useSelector(selectAccounts); + const accountsByChainId = useSelector(selectAccountsByChainId); + const chainId = useSelector(selectChainId); const selectedAddress = useSelector(selectSelectedAddress); const conversionRate = useSelector(selectConversionRate); const currentCurrency = useSelector(selectCurrentCurrency); @@ -37,8 +40,13 @@ export default function useBalance(asset?: Asset) { let balance, balanceFiat, balanceBN; if (assetAddress === NATIVE_ADDRESS) { - balance = renderFromWei(accounts[selectedAddress]?.balance); - balanceBN = hexToBN(accounts[selectedAddress].balance); + balance = renderFromWei( + accountsByChainId[toHexadecimal(chainId)][selectedAddress]?.balance, + ); + + balanceBN = hexToBN( + accountsByChainId[toHexadecimal(chainId)][selectedAddress]?.balance, + ); balanceFiat = weiToFiat(balanceBN, conversionRate, currentCurrency); } else { const exchangeRate = diff --git a/app/components/UI/Ramp/hooks/useHandleSuccessfulOrder.ts b/app/components/UI/Ramp/hooks/useHandleSuccessfulOrder.ts index 9bc36ef7222..07fe2fa35da 100644 --- a/app/components/UI/Ramp/hooks/useHandleSuccessfulOrder.ts +++ b/app/components/UI/Ramp/hooks/useHandleSuccessfulOrder.ts @@ -14,9 +14,10 @@ import useThunkDispatch from '../../../hooks/useThunkDispatch'; import { useRampSDK } from '../sdk'; import { getNotificationDetails, stateHasOrder } from '../utils'; import useAnalytics from './useAnalytics'; -import { hexToBN } from '../../../../util/number'; -import { selectAccounts } from '../../../../selectors/accountTrackerController'; +import { hexToBN, toHexadecimal } from '../../../../util/number'; +import { selectAccountsByChainId } from '../../../../selectors/accountTrackerController'; import Routes from '../../../../constants/navigation/Routes'; +import { selectChainId } from '../../../../selectors/networkController'; function useHandleSuccessfulOrder() { const { selectedChainId, selectedAddress } = useRampSDK(); @@ -24,7 +25,8 @@ function useHandleSuccessfulOrder() { const dispatch = useDispatch(); const dispatchThunk = useThunkDispatch(); const trackEvent = useAnalytics(); - const accounts = useSelector(selectAccounts); + const accountsByChainId = useSelector(selectAccountsByChainId); + const chainIdFromProvider = useSelector(selectChainId); const addTokenToTokensController = useCallback( async (token: CryptoCurrency) => { @@ -108,8 +110,16 @@ function useHandleSuccessfulOrder() { provider_onramp: (order?.data as Order)?.provider?.name, chain_id_destination: selectedChainId, has_zero_currency_destination_balance: false, - has_zero_native_balance: accounts[selectedAddress]?.balance - ? (hexToBN(accounts[selectedAddress].balance) as any)?.isZero?.() + has_zero_native_balance: accountsByChainId[ + toHexadecimal(chainIdFromProvider) + ][selectedAddress]?.balance + ? ( + hexToBN( + accountsByChainId[toHexadecimal(chainIdFromProvider)][ + selectedAddress + ].balance, + ) as any + )?.isZero?.() : undefined, currency_source: (order?.data as Order)?.fiatCurrency.symbol, currency_destination: (order?.data as Order)?.cryptoCurrency.symbol, @@ -118,7 +128,8 @@ function useHandleSuccessfulOrder() { }); }, [ - accounts, + chainIdFromProvider, + accountsByChainId, addTokenToTokensController, dispatchThunk, handleDispatchUserWalletProtection, diff --git a/app/components/UI/Ramp/routes/index.tsx b/app/components/UI/Ramp/routes/index.tsx new file mode 100644 index 00000000000..911a0d44164 --- /dev/null +++ b/app/components/UI/Ramp/routes/index.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack'; +import Regions from '../Views/Regions'; +import Quotes from '../Views/Quotes'; +import PaymentMethods from '../Views/PaymentMethods'; +import NetworkSwitcher from '../Views/NetworkSwitcher'; +import GetStarted from '../Views/GetStarted'; +import CheckoutWebView from '../Views/Checkout'; +import BuildQuote from '../Views/BuildQuote'; +import { RampType } from '../types'; +import { RampSDKProvider } from '../sdk'; +import Routes from '../../../../constants/navigation/Routes'; + +const Stack = createStackNavigator(); + +const RampRoutes = ({ rampType }: { rampType: RampType }) => ( + + + + + + + + + + + + + + +); + +export default RampRoutes; diff --git a/app/components/UI/Ramp/types/index.ts b/app/components/UI/Ramp/types/index.ts index 45c0c4902f0..d98a7a6d817 100644 --- a/app/components/UI/Ramp/types/index.ts +++ b/app/components/UI/Ramp/types/index.ts @@ -7,6 +7,7 @@ export enum PROVIDER_LINKS { HOMEPAGE = 'Homepage', PRIVACY_POLICY = 'Privacy Policy', SUPPORT = 'Support', + TOS = 'Terms of Service', } export interface QuickAmount { diff --git a/app/components/UI/Ramp/utils/index.test.ts b/app/components/UI/Ramp/utils/index.test.ts index a77268f1eec..6fac8ed177e 100644 --- a/app/components/UI/Ramp/utils/index.test.ts +++ b/app/components/UI/Ramp/utils/index.test.ts @@ -137,6 +137,48 @@ describe('isNetworkBuySupported', () => { ]), ).toBe(false); }); + + it('should return true if network is supported when chainId is on hexadecimal format', () => { + expect( + isNetworkRampSupported('0x1', [ + { + active: true, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: true, + shortName: 'Ethereum', + }, + ]), + ).toBe(true); + }); + + it('should return false if network is not supported when chainId is on hexadecimal format', () => { + expect( + isNetworkRampSupported('0x1', [ + { + active: false, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: true, + shortName: 'Ethereum', + }, + ]), + ).toBe(false); + }); + + it('should return false if network is not found when chainId is on hexadecimal format', () => { + expect( + isNetworkRampSupported('0x22', [ + { + active: true, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: true, + shortName: 'Ethereum', + }, + ]), + ).toBe(false); + }); }); describe('isNetworkBuyNativeTokenSupported', () => { @@ -195,6 +237,62 @@ describe('isNetworkBuyNativeTokenSupported', () => { ]), ).toBe(false); }); + + it('should return true if network is supported and native token is supported when chainId is on hexadecimal format', () => { + expect( + isNetworkRampNativeTokenSupported('0x1', [ + { + active: true, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: true, + shortName: 'Ethereum', + }, + ]), + ).toBe(true); + }); + + it('should return false if network is not supported when chainId is on hexadecimal format', () => { + expect( + isNetworkRampNativeTokenSupported('0x1', [ + { + active: false, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: true, + shortName: 'Ethereum', + }, + ]), + ).toBe(false); + }); + + it('should return false if network is not found when chainId is on hexadecimal format', () => { + expect( + isNetworkRampNativeTokenSupported('0x22', [ + { + active: true, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: true, + shortName: 'Ethereum', + }, + ]), + ).toBe(false); + }); + + it('should return false if network is supported but native token is not supported when chainId is on hexadecimal format', () => { + expect( + isNetworkRampNativeTokenSupported('0x1', [ + { + active: true, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: false, + shortName: 'Ethereum', + }, + ]), + ).toBe(false); + }); }); describe('getOrderAmount', () => { diff --git a/app/components/UI/Ramp/utils/index.ts b/app/components/UI/Ramp/utils/index.ts index ec45cd7730e..59faa946016 100644 --- a/app/components/UI/Ramp/utils/index.ts +++ b/app/components/UI/Ramp/utils/index.ts @@ -19,6 +19,7 @@ import { getOrders, FiatOrder } from '../../../../reducers/fiatOrders'; import { RootState } from '../../../../reducers'; import { FIAT_ORDER_STATES } from '../../../../constants/on-ramp'; import { strings } from '../../../../../locales/i18n'; +import { getDecimalChainId } from '../../../../util/networks'; const isOverAnHour = (minutes: number) => minutes > 59; @@ -128,8 +129,9 @@ export function isNetworkRampSupported( networks: AggregatorNetwork[], ) { return ( - networks.find((network) => String(network.chainId) === chainId)?.active ?? - false + networks.find( + (network) => String(network.chainId) === getDecimalChainId(chainId), + )?.active ?? false ); } @@ -138,7 +140,7 @@ export function isNetworkRampNativeTokenSupported( networks: AggregatorNetwork[], ) { const network = networks.find( - (_network) => String(_network.chainId) === chainId, + (_network) => String(_network.chainId) === getDecimalChainId(chainId), ); return (network?.active && network.nativeTokenSupported) ?? false; } diff --git a/app/components/UI/ReceiveRequest/index.js b/app/components/UI/ReceiveRequest/index.js index 50174adf03f..0bd3dfd3a11 100644 --- a/app/components/UI/ReceiveRequest/index.js +++ b/app/components/UI/ReceiveRequest/index.js @@ -1,7 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { - InteractionManager, TouchableOpacity, SafeAreaView, Dimensions, @@ -15,7 +14,6 @@ import QRCode from 'react-native-qrcode-svg'; import EvilIcons from 'react-native-vector-icons/EvilIcons'; import { connect } from 'react-redux'; -import Analytics from '../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../core/Analytics'; import Logger from '../../../util/Logger'; import Device from '../../../util/device'; @@ -45,6 +43,8 @@ import { isNetworkRampSupported } from '../Ramp/utils'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; import { getRampNetworks } from '../../../reducers/fiatOrders'; import { RequestPaymentModalSelectorsIDs } from '../../../../e2e/selectors/Modals/RequestPaymentModal.selectors'; +import { getDecimalChainId } from '../../../util/networks'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; const createStyles = (theme) => StyleSheet.create({ @@ -152,6 +152,10 @@ class ReceiveRequest extends PureComponent { * Boolean that indicates if the network supports buy */ isNetworkBuySupported: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -174,9 +178,10 @@ class ReceiveRequest extends PureComponent { .catch((err) => { Logger.log('Error while trying to share address', err); }); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_SHARE_ADDRESS); - }); + + this.props.metrics.trackEvent( + MetaMetricsEvents.RECEIVE_OPTIONS_SHARE_ADDRESS, + ); }; /** @@ -193,15 +198,11 @@ class ReceiveRequest extends PureComponent { } else { toggleReceiveModal(); navigation.navigate(Routes.RAMP.BUY); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters( - MetaMetricsEvents.BUY_BUTTON_CLICKED, - { - text: 'Buy Native Token', - location: 'Receive Modal', - chain_id_destination: this.props.chainId, - }, - ); + + this.props.metrics.trackEvent(MetaMetricsEvents.BUY_BUTTON_CLICKED, { + text: 'Buy Native Token', + location: 'Receive Modal', + chain_id_destination: getDecimalChainId(this.props.chainId), }); } }; @@ -234,9 +235,8 @@ class ReceiveRequest extends PureComponent { */ openQrModal = () => { this.setState({ qrModalVisible: true }); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_QR_CODE); - }); + + this.props.metrics.trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_QR_CODE); }; onReceive = () => { @@ -245,9 +245,10 @@ class ReceiveRequest extends PureComponent { screen: 'PaymentRequest', params: { receiveAsset: this.props.receiveAsset }, }); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST); - }); + + this.props.metrics.trackEvent( + MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST, + ); }; render() { @@ -275,11 +276,10 @@ class ReceiveRequest extends PureComponent { // eslint-disable-next-line react/jsx-no-bind onPress={() => { toggleModal(); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent( - MetaMetricsEvents.RECEIVE_OPTIONS_QR_CODE, - ); - }); + + this.props.metrics.trackEvent( + MetaMetricsEvents.RECEIVE_OPTIONS_QR_CODE, + ); }} > ({ protectWalletModalVisible: () => dispatch(protectWalletModalVisible()), }); -export default connect(mapStateToProps, mapDispatchToProps)(ReceiveRequest); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(ReceiveRequest)); diff --git a/app/components/UI/SDKFeedback/index.tsx b/app/components/UI/SDKFeedback/index.tsx index 12b2f1ae862..eec9e5c2118 100644 --- a/app/components/UI/SDKFeedback/index.tsx +++ b/app/components/UI/SDKFeedback/index.tsx @@ -1,5 +1,5 @@ -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; -import { ThemeTypography } from '@metamask/design-tokens/dist/js/typography'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; +import type { ThemeTypography } from '@metamask/design-tokens/dist/types/js/typography'; import Icon, { IconName, IconSize, diff --git a/app/components/UI/SDKLoading/index.tsx b/app/components/UI/SDKLoading/index.tsx index cb965e460a2..0d8f92019c1 100644 --- a/app/components/UI/SDKLoading/index.tsx +++ b/app/components/UI/SDKLoading/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports, import/no-commonjs */ -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; import LottieView from 'lottie-react-native'; import React from 'react'; import { StyleSheet, View } from 'react-native'; diff --git a/app/components/UI/ScreenshotDeterrent/ScreenshotDeterrent.tsx b/app/components/UI/ScreenshotDeterrent/ScreenshotDeterrent.tsx index bdc0c31a2ae..c8a23aea45c 100644 --- a/app/components/UI/ScreenshotDeterrent/ScreenshotDeterrent.tsx +++ b/app/components/UI/ScreenshotDeterrent/ScreenshotDeterrent.tsx @@ -3,11 +3,11 @@ import { View, Linking, InteractionManager } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import PreventScreenshot from '../../../core/PreventScreenshot'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import useScreenshotDeterrent from '../../hooks/useScreenshotDeterrent'; import { SRP_GUIDE_URL } from '../../../constants/urls'; import Routes from '../../../constants/navigation/Routes'; import { strings } from '../../../../locales/i18n'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const ScreenshotDeterrentWithoutNavigation = ({ enabled, @@ -36,17 +36,18 @@ const ScreenshotDeterrentWithNavigation = ({ enabled: boolean; isSRP: boolean; }) => { + const { trackEvent } = useMetrics(); const [alertPresent, setAlertPresent] = useState(false); const navigation = useNavigation(); - const openSRPGuide = () => { + const openSRPGuide = useCallback(() => { setAlertPresent(false); - AnalyticsV2.trackEvent(MetaMetricsEvents.SCREENSHOT_LEARN_MORE, {}); + trackEvent(MetaMetricsEvents.SCREENSHOT_LEARN_MORE, {}); Linking.openURL(SRP_GUIDE_URL); - }; + }, [trackEvent]); const showScreenshotAlert = useCallback(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.SCREENSHOT_WARNING, {}); + trackEvent(MetaMetricsEvents.SCREENSHOT_WARNING, {}); setAlertPresent(true); navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { @@ -60,14 +61,14 @@ const ScreenshotDeterrentWithNavigation = ({ }), onCancel: () => { setAlertPresent(false); - AnalyticsV2.trackEvent(MetaMetricsEvents.SCREENSHOT_OK, {}); + trackEvent(MetaMetricsEvents.SCREENSHOT_OK, {}); }, onConfirm: openSRPGuide, confirmLabel: strings('reveal_credential.learn_more'), cancelLabel: strings('reveal_credential.got_it'), }, }); - }, [isSRP, navigation]); + }, [isSRP, navigation, trackEvent, openSRPGuide]); const [enableScreenshotWarning] = useScreenshotDeterrent(showScreenshotAlert); diff --git a/app/components/UI/SearchTokenAutocomplete/index.tsx b/app/components/UI/SearchTokenAutocomplete/index.tsx index 782f63b0f2b..45073899d59 100644 --- a/app/components/UI/SearchTokenAutocomplete/index.tsx +++ b/app/components/UI/SearchTokenAutocomplete/index.tsx @@ -12,7 +12,6 @@ import AssetSearch from '../AssetSearch'; import AssetList from '../AssetList'; import Engine from '../../../core/Engine'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import Alert, { AlertType } from '../../Base/Alert'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; @@ -22,6 +21,8 @@ import NotificationManager from '../../../core/NotificationManager'; import { useTheme } from '../../../util/theme'; import { selectChainId } from '../../../selectors/networkController'; import { selectUseTokenDetection } from '../../../selectors/preferencesController'; +import { getDecimalChainId } from '../../../util/networks'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors: any) => StyleSheet.create({ @@ -53,6 +54,7 @@ interface Props { * Component that provides ability to add searched assets with metadata. */ const SearchTokenAutocomplete = ({ navigation }: Props) => { + const { trackEvent } = useMetrics(); const [searchResults, setSearchResults] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [selectedAsset, setSelectedAsset] = useState({}); @@ -77,7 +79,7 @@ const SearchTokenAutocomplete = ({ navigation }: Props) => { return { token_address: address, token_symbol: symbol, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), source: 'Add token dropdown', }; } catch (error) { @@ -111,7 +113,7 @@ const SearchTokenAutocomplete = ({ navigation }: Props) => { name, }); - AnalyticsV2.trackEvent(MetaMetricsEvents.TOKEN_ADDED, getAnalyticsParams()); + trackEvent(MetaMetricsEvents.TOKEN_ADDED, getAnalyticsParams()); // Clear state before closing setSearchResults([]); @@ -140,6 +142,7 @@ const SearchTokenAutocomplete = ({ navigation }: Props) => { navigation, getAnalyticsParams, name, + trackEvent, ]); const renderTokenDetectionBanner = useCallback(() => { diff --git a/app/components/UI/SettingsDrawer/index.js b/app/components/UI/SettingsDrawer/index.js index ccd5d1c45fc..d1d2c241ebe 100644 --- a/app/components/UI/SettingsDrawer/index.js +++ b/app/components/UI/SettingsDrawer/index.js @@ -76,7 +76,7 @@ const propTypes = { /** * Title color */ - titleColor: TextColor, + titleColor: PropTypes.string, }; const defaultProps = { diff --git a/app/components/UI/SliderButton/__snapshots__/index.test.tsx.snap b/app/components/UI/SliderButton/__snapshots__/index.test.tsx.snap index 91d696769be..53a8784e594 100644 --- a/app/components/UI/SliderButton/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/SliderButton/__snapshots__/index.test.tsx.snap @@ -91,7 +91,7 @@ exports[`SliderButton should render correctly 1`] = ` { - const parameters = { - speed_set: changedGasEstimate?.selected, - gas_mode: changedGasEstimate?.selected ? 'Basic' : 'Advanced', - // TODO: how should we track EIP1559 values? - gas_fees: [ - GAS_ESTIMATE_TYPES.LEGACY, - GAS_ESTIMATE_TYPES.ETH_GASPRICE, - ].includes(gasEstimateType) - ? weiToFiat( - toWei( - swapsUtils.calcTokenAmount( - new BigNumber(changedGasLimit, 10).times( - decGWEIToHexWEI(changedGasEstimate.gasPrice), - 16, - ), - 18, + + const parameters = { + speed_set: changedGasEstimate?.selected, + gas_mode: changedGasEstimate?.selected ? 'Basic' : 'Advanced', + // TODO: how should we track EIP1559 values? + gas_fees: [ + GAS_ESTIMATE_TYPES.LEGACY, + GAS_ESTIMATE_TYPES.ETH_GASPRICE, + ].includes(gasEstimateType) + ? weiToFiat( + toWei( + swapsUtils.calcTokenAmount( + new BigNumber(changedGasLimit, 10).times( + decGWEIToHexWEI(changedGasEstimate.gasPrice), + 16, ), + 18, ), - conversionRate, - currentCurrency, - // eslint-disable-next-line no-mixed-spaces-and-tabs - ) - : '', - chain_id: chainId, - }; - Analytics.trackEventWithParameters( - MetaMetricsEvents.GAS_FEES_CHANGED, - {}, - ); - Analytics.trackEventWithParameters( - MetaMetricsEvents.GAS_FEES_CHANGED, - parameters, - true, - ); - }); + ), + conversionRate, + currentCurrency, + // eslint-disable-next-line no-mixed-spaces-and-tabs + ) + : '', + chain_id: getDecimalChainId(chainId), + }; + + trackAnonymousEvent(MetaMetricsEvents.GAS_FEES_CHANGED, parameters); }, - [chainId, conversionRate, currentCurrency, gasEstimateType, gasLimit], + [ + chainId, + conversionRate, + currentCurrency, + gasEstimateType, + gasLimit, + trackAnonymousEvent, + ], ); /* Handlers */ @@ -829,7 +831,7 @@ function SwapsQuotesView({ ), network_fees_ETH: renderFromWei(toWei(selectedQuoteValue?.ethFee)), other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), }, paramsForAnalytics: { sentAt: currentBlock.timestamp, @@ -860,40 +862,33 @@ function SwapsQuotesView({ const startSwapAnalytics = useCallback( (selectedQuote, selectedAddress) => { - InteractionManager.runAfterInteractions(() => { - const parameters = { - account_type: getAddressAccountType(selectedAddress), - token_from: sourceToken.symbol, - token_from_amount: fromTokenMinimalUnitString( - sourceAmount, - sourceToken.decimals, - ), - token_to: destinationToken.symbol, - token_to_amount: fromTokenMinimalUnitString( - selectedQuote.destinationAmount, - destinationToken.decimals, - ), - request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', - slippage, - custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, - best_quote_source: selectedQuote.aggregator, - available_quotes: allQuotes, - other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote, - network_fees_USD: weiToFiat( - toWei(selectedQuoteValue?.ethFee), - conversionRate, - 'usd', - ), - network_fees_ETH: renderFromWei(toWei(selectedQuoteValue?.ethFee)), - chain_id: chainId, - }; - Analytics.trackEventWithParameters(MetaMetricsEvents.SWAP_STARTED, {}); - Analytics.trackEventWithParameters( - MetaMetricsEvents.SWAP_STARTED, - parameters, - true, - ); - }); + const parameters = { + account_type: getAddressAccountType(selectedAddress), + token_from: sourceToken.symbol, + token_from_amount: fromTokenMinimalUnitString( + sourceAmount, + sourceToken.decimals, + ), + token_to: destinationToken.symbol, + token_to_amount: fromTokenMinimalUnitString( + selectedQuote.destinationAmount, + destinationToken.decimals, + ), + request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', + slippage, + custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, + best_quote_source: selectedQuote.aggregator, + available_quotes: allQuotes, + other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote, + network_fees_USD: weiToFiat( + toWei(selectedQuoteValue?.ethFee), + conversionRate, + 'usd', + ), + network_fees_ETH: renderFromWei(toWei(selectedQuoteValue?.ethFee)), + chain_id: getDecimalChainId(chainId), + }; + trackAnonymousEvent(MetaMetricsEvents.SWAP_STARTED, parameters); }, // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -910,18 +905,14 @@ function SwapsQuotesView({ ); const handleSwapTransaction = useCallback( - async ( - TransactionController, - newSwapsTransactions, - approvalTransactionMetaId, - ) => { + async (newSwapsTransactions, approvalTransactionMetaId) => { if (!selectedQuote) { return; } try { resetTransaction(); - const { transactionMeta } = await TransactionController.addTransaction( + const { transactionMeta } = await addTransaction( { ...selectedQuote.trade, ...getTransactionPropertiesFromGasEstimates( @@ -970,7 +961,7 @@ function SwapsQuotesView({ ) => { try { resetTransaction(); - const { transactionMeta } = await TransactionController.addTransaction( + const { transactionMeta } = await addTransaction( { ...approvalTransaction, ...getTransactionPropertiesFromGasEstimates( @@ -1102,43 +1093,33 @@ function SwapsQuotesView({ setEditQuoteTransactionsVisible(true); - InteractionManager.runAfterInteractions(() => { - const parameters = { - token_from: sourceToken.symbol, - token_from_amount: fromTokenMinimalUnitString( - sourceAmount, - sourceToken.decimals, - ), - token_to: destinationToken.symbol, - token_to_amount: fromTokenMinimalUnitString( - selectedQuote.destinationAmount, - destinationToken.decimals, - ), - request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', - slippage, - custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, - available_quotes: allQuotes.length, - best_quote_source: selectedQuote.aggregator, - other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote, - gas_fees: weiToFiat( - toWei(selectedQuoteValue?.ethFee), - conversionRate, - currentCurrency, - ), - custom_spend_limit_set: originalAmount !== currentAmount, - custom_spend_limit_amount: currentAmount, - chain_id: chainId, - }; - Analytics.trackEventWithParameters( - MetaMetricsEvents.EDIT_SPEND_LIMIT_OPENED, - {}, - ); - Analytics.trackEventWithParameters( - MetaMetricsEvents.EDIT_SPEND_LIMIT_OPENED, - parameters, - true, - ); - }); + const parameters = { + token_from: sourceToken.symbol, + token_from_amount: fromTokenMinimalUnitString( + sourceAmount, + sourceToken.decimals, + ), + token_to: destinationToken.symbol, + token_to_amount: fromTokenMinimalUnitString( + selectedQuote.destinationAmount, + destinationToken.decimals, + ), + request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', + slippage, + custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, + available_quotes: allQuotes.length, + best_quote_source: selectedQuote.aggregator, + other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote, + gas_fees: weiToFiat( + toWei(selectedQuoteValue?.ethFee), + conversionRate, + currentCurrency, + ), + custom_spend_limit_set: originalAmount !== currentAmount, + custom_spend_limit_amount: currentAmount, + chain_id: getDecimalChainId(chainId), + }; + trackAnonymousEvent(MetaMetricsEvents.EDIT_SPEND_LIMIT_OPENED, parameters); }, [ chainId, allQuotes, @@ -1154,43 +1135,37 @@ function SwapsQuotesView({ slippage, sourceAmount, sourceToken, + trackAnonymousEvent, ]); const handleQuotesReceivedMetric = useCallback(() => { if (!selectedQuote || !selectedQuoteValue) return; - InteractionManager.runAfterInteractions(() => { - const parameters = { - token_from: sourceToken.symbol, - token_from_amount: fromTokenMinimalUnitString( - sourceAmount, - sourceToken.decimals, - ), - token_to: destinationToken.symbol, - token_to_amount: fromTokenMinimalUnitString( - selectedQuote.destinationAmount, - destinationToken.decimals, - ), - request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', - slippage, - custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, - response_time: allQuotesFetchTime, - best_quote_source: selectedQuote.aggregator, - network_fees_USD: weiToFiat( - toWei(selectedQuoteValue.ethFee), - conversionRate, - 'usd', - ), - network_fees_ETH: renderFromWei(toWei(selectedQuoteValue.ethFee)), - available_quotes: allQuotes.length, - chain_id: chainId, - }; - Analytics.trackEventWithParameters(MetaMetricsEvents.QUOTES_RECEIVED, {}); - Analytics.trackEventWithParameters( - MetaMetricsEvents.QUOTES_RECEIVED, - parameters, - true, - ); - }); + const parameters = { + token_from: sourceToken.symbol, + token_from_amount: fromTokenMinimalUnitString( + sourceAmount, + sourceToken.decimals, + ), + token_to: destinationToken.symbol, + token_to_amount: fromTokenMinimalUnitString( + selectedQuote.destinationAmount, + destinationToken.decimals, + ), + request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', + slippage, + custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, + response_time: allQuotesFetchTime, + best_quote_source: selectedQuote.aggregator, + network_fees_USD: weiToFiat( + toWei(selectedQuoteValue.ethFee), + conversionRate, + 'usd', + ), + network_fees_ETH: renderFromWei(toWei(selectedQuoteValue.ethFee)), + available_quotes: allQuotes.length, + chain_id: getDecimalChainId(chainId), + }; + trackAnonymousEvent(MetaMetricsEvents.QUOTES_RECEIVED, parameters); }, [ chainId, sourceToken, @@ -1203,47 +1178,42 @@ function SwapsQuotesView({ selectedQuoteValue, allQuotes, conversionRate, + trackAnonymousEvent, ]); const handleOpenQuotesModal = useCallback(() => { if (!selectedQuote || !selectedQuoteValue) return; toggleQuotesModal(); - InteractionManager.runAfterInteractions(() => { - const parameters = { - token_from: sourceToken.symbol, - token_from_amount: fromTokenMinimalUnitString( - sourceAmount, - sourceToken.decimals, - ), - token_to: destinationToken.symbol, - token_to_amount: fromTokenMinimalUnitString( - selectedQuote.destinationAmount, - destinationToken.decimals, - ), - request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', - slippage, - custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, - response_time: allQuotesFetchTime, - best_quote_source: selectedQuote.aggregator, - network_fees_USD: weiToFiat( - toWei(selectedQuoteValue.ethFee), - conversionRate, - 'usd', - ), - network_fees_ETH: renderFromWei(toWei(selectedQuoteValue.ethFee)), - available_quotes: allQuotes.length, - chain_id: chainId, - }; - Analytics.trackEventWithParameters( - MetaMetricsEvents.ALL_AVAILABLE_QUOTES_OPENED, - {}, - ); - Analytics.trackEventWithParameters( - MetaMetricsEvents.ALL_AVAILABLE_QUOTES_OPENED, - parameters, - true, - ); - }); + const parameters = { + token_from: sourceToken.symbol, + token_from_amount: fromTokenMinimalUnitString( + sourceAmount, + sourceToken.decimals, + ), + token_to: destinationToken.symbol, + token_to_amount: fromTokenMinimalUnitString( + selectedQuote.destinationAmount, + destinationToken.decimals, + ), + request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', + slippage, + custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, + response_time: allQuotesFetchTime, + best_quote_source: selectedQuote.aggregator, + network_fees_USD: weiToFiat( + toWei(selectedQuoteValue.ethFee), + conversionRate, + 'usd', + ), + network_fees_ETH: renderFromWei(toWei(selectedQuoteValue.ethFee)), + available_quotes: allQuotes.length, + chain_id: getDecimalChainId(chainId), + }; + + trackAnonymousEvent( + MetaMetricsEvents.ALL_AVAILABLE_QUOTES_OPENED, + parameters, + ); }, [ chainId, selectedQuote, @@ -1257,6 +1227,7 @@ function SwapsQuotesView({ allQuotesFetchTime, conversionRate, allQuotes.length, + trackAnonymousEvent, ]); const handleQuotesErrorMetric = useCallback( @@ -1271,39 +1242,20 @@ function SwapsQuotesView({ request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', slippage, custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), }; if (error?.key === swapsUtils.SwapsError.QUOTES_EXPIRED_ERROR) { - InteractionManager.runAfterInteractions(() => { - const parameters = { - ...data, - gas_fees: '', - }; - Analytics.trackEventWithParameters( - MetaMetricsEvents.QUOTES_TIMED_OUT, - {}, - ); - Analytics.trackEventWithParameters( - MetaMetricsEvents.QUOTES_TIMED_OUT, - parameters, - true, - ); - }); + const parameters = { + ...data, + gas_fees: '', + }; + + trackAnonymousEvent(MetaMetricsEvents.QUOTES_TIMED_OUT, parameters); } else if ( error?.key === swapsUtils.SwapsError.QUOTES_NOT_AVAILABLE_ERROR ) { - InteractionManager.runAfterInteractions(() => { - const parameters = { ...data }; - Analytics.trackEventWithParameters( - MetaMetricsEvents.NO_QUOTES_AVAILABLE, - {}, - ); - Analytics.trackEventWithParameters( - MetaMetricsEvents.NO_QUOTES_AVAILABLE, - parameters, - true, - ); - }); + const parameters = { ...data }; + trackAnonymousEvent(MetaMetricsEvents.NO_QUOTES_AVAILABLE, parameters); } else { trackErrorAsAnalytics(`Swaps: ${error?.key}`, error?.description); } @@ -1315,6 +1267,7 @@ function SwapsQuotesView({ destinationToken, hasEnoughTokenBalance, slippage, + trackAnonymousEvent, ], ); @@ -1331,10 +1284,9 @@ function SwapsQuotesView({ } catch (error) { Logger.error(error, 'Navigation: Error when navigating to buy ETH.'); } - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST); - }); - }, [navigation]); + + trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST); + }, [navigation, trackEvent]); const handleTermsPress = useCallback( () => @@ -1574,22 +1526,13 @@ function SwapsQuotesView({ token_to: destinationToken.symbol, request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), }; navigation.setParams({ requestedTrade: data }); navigation.setParams({ selectedQuote: undefined }); navigation.setParams({ quoteBegin: Date.now() }); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters( - MetaMetricsEvents.QUOTES_REQUESTED, - {}, - ); - Analytics.trackEventWithParameters( - MetaMetricsEvents.QUOTES_REQUESTED, - data, - true, - ); - }); + + trackAnonymousEvent(MetaMetricsEvents.QUOTES_REQUESTED, data); }, [ chainId, destinationToken, @@ -1600,6 +1543,7 @@ function SwapsQuotesView({ sourceAmount, sourceToken, trackedRequestedQuotes, + trackAnonymousEvent, ]); /* Metrics: Quotes received */ @@ -2382,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/Swaps/components/ApprovalTransactionEditionModal.js b/app/components/UI/Swaps/components/ApprovalTransactionEditionModal.js index 0fb7ffd6912..afcd29cb0f0 100644 --- a/app/components/UI/Swaps/components/ApprovalTransactionEditionModal.js +++ b/app/components/UI/Swaps/components/ApprovalTransactionEditionModal.js @@ -6,7 +6,7 @@ import Modal from 'react-native-modal'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { swapsUtils } from '@metamask/swaps-controller'; -import EditPermission from '../../ApproveTransactionReview/EditPermission'; +import EditPermission from '../../../Views/confirmations/components/ApproveTransactionReview/EditPermission'; import { fromTokenMinimalUnitString, hexToBN } from '../../../../util/number'; import { decodeApproveData, diff --git a/app/components/UI/Swaps/components/TokenSelectModal.js b/app/components/UI/Swaps/components/TokenSelectModal.js index 4857b049b47..33f13768258 100644 --- a/app/components/UI/Swaps/components/TokenSelectModal.js +++ b/app/components/UI/Swaps/components/TokenSelectModal.js @@ -8,7 +8,6 @@ import { View, TouchableWithoutFeedback, ActivityIndicator, - InteractionManager, } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { useNavigation } from '@react-navigation/native'; @@ -55,11 +54,12 @@ import { selectContractExchangeRates } from '../../../../selectors/tokenRatesCon import { selectAccounts } from '../../../../selectors/accountTrackerController'; import { selectContractBalances } from '../../../../selectors/tokenBalancesController'; import { selectSelectedAddress } from '../../../../selectors/preferencesController'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; -import Analytics from '../../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import { useTheme } from '../../../../util/theme'; import { SWAP_SEARCH_TOKEN } from '../../../../../wdio/screen-objects/testIDs/Screens/QuoteView.js'; +import { getDecimalChainId } from '../../../../util/networks'; const createStyles = (colors) => StyleSheet.create({ @@ -157,6 +157,8 @@ function TokenSelectModal({ balances, }) { const navigation = useNavigation(); + const { trackEvent } = useMetrics(); + const searchInput = useRef(null); const list = useRef(); const [searchString, setSearchString] = useState(''); @@ -301,17 +303,15 @@ function TokenSelectModal({ const handlePressImportToken = useCallback( (item) => { const { address, symbol } = item; - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters( - MetaMetricsEvents.CUSTOM_TOKEN_IMPORTED, - { address, symbol, chain_id: chainId }, - true, - ); + trackEvent(MetaMetricsEvents.CUSTOM_TOKEN_IMPORTED, { + address, + symbol, + chain_id: getDecimalChainId(chainId), }); hideTokenImportModal(); onItemPress(item); }, - [chainId, hideTokenImportModal, onItemPress], + [chainId, hideTokenImportModal, onItemPress, trackEvent], ); const handleBlockExplorerPress = useCallback(() => { diff --git a/app/components/UI/Swaps/index.js b/app/components/UI/Swaps/index.js index 9aac6887de1..465a0ab44ea 100644 --- a/app/components/UI/Swaps/index.js +++ b/app/components/UI/Swaps/index.js @@ -87,6 +87,7 @@ import { SWAP_DEST_TOKEN, SWAP_MAX_SLIPPAGE, } from '../../../../wdio/screen-objects/testIDs/Screens/QuoteView.js'; +import { getDecimalChainId } from '../../../util/networks'; const createStyles = (colors) => StyleSheet.create({ @@ -283,7 +284,7 @@ function SwapsAmountView({ activeCurrency: swapsTokens?.find((token) => toLowerCaseEquals(token.address, initialSource), )?.symbol, - chain_id: chainId, + chain_id: getDecimalChainId(chainId), }; Analytics.trackEventWithParameters( MetaMetricsEvents.SWAPS_OPENED, diff --git a/app/components/UI/Swaps/utils/useBlockExplorer.js b/app/components/UI/Swaps/utils/useBlockExplorer.js index 246cee18285..190750b7b7c 100644 --- a/app/components/UI/Swaps/utils/useBlockExplorer.js +++ b/app/components/UI/Swaps/utils/useBlockExplorer.js @@ -21,7 +21,7 @@ function useBlockExplorer(providerConfig, networkConfigurations) { if (providerConfig.type === RPC) { try { const blockExplorer = findBlockExplorerForRpc( - providerConfig.rpcTarget, + providerConfig.rpcUrl, networkConfigurations, ); if (!blockExplorer) { diff --git a/app/components/UI/Tabs/__snapshots__/index.test.tsx.snap b/app/components/UI/Tabs/__snapshots__/index.test.tsx.snap index 98b2e74640b..7a6de5b2525 100644 --- a/app/components/UI/Tabs/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/Tabs/__snapshots__/index.test.tsx.snap @@ -1,176 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Tabs should render correctly 1`] = ` - - - - - - - - Close All - - - - - - - - - - Done - - - - + "id": 1, + "image": "", + "url": "about:blank", + }, + ] + } +/> `; diff --git a/app/components/UI/Tabs/index.js b/app/components/UI/Tabs/index.js index 7c337d5cc94..28a3b094bc2 100644 --- a/app/components/UI/Tabs/index.js +++ b/app/components/UI/Tabs/index.js @@ -7,7 +7,6 @@ import { ScrollView, TouchableOpacity, StyleSheet, - Platform, } from 'react-native'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; import PropTypes from 'prop-types'; @@ -16,10 +15,8 @@ import TabThumbnail from './TabThumbnail'; import { colors as importedColors, fontStyles } from '../../../styles/common'; import Device from '../../../util/device'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; - +import withMetricsAwareness from '../../hooks/useMetrics/withMetricsAwareness'; import { ThemeContext, mockTheme } from '../../../util/theme'; -import generateTestId from '../../../../wdio/utils/generateTestId'; import { MULTI_TAB_ADD_BUTTON, MULTI_TAB_CLOSE_ALL_BUTTON, @@ -131,7 +128,7 @@ const createStyles = (colors, shadows) => * PureComponent that wraps all the thumbnails * representing all the open tabs */ -export default class Tabs extends PureComponent { +class Tabs extends PureComponent { static propTypes = { /** * Array of tabs @@ -165,6 +162,10 @@ export default class Tabs extends PureComponent { * Sets the current tab used for the animation */ animateCurrentTab: PropTypes.func, // eslint-disable-line react/no-unused-prop-types + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; thumbnails = {}; @@ -231,10 +232,7 @@ export default class Tabs extends PureComponent { return ( - + {strings('browser.no_tabs_title')} {strings('browser.no_tabs_desc')} @@ -272,7 +270,7 @@ export default class Tabs extends PureComponent { }; trackNewTabEvent = (tabsNumber) => { - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_NEW_TAB, { + this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_NEW_TAB, { option_chosen: 'Browser Bottom Bar Menu', number_of_tabs: tabsNumber, }); @@ -287,7 +285,7 @@ export default class Tabs extends PureComponent { = ({ tokens }) => { const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const navigation = useNavigation>(); const [tokenToRemove, setTokenToRemove] = useState(); const [isAddTokenEnabled, setIsAddTokenEnabled] = useState(true); const [refreshing, setRefreshing] = useState(false); + const [isNetworkRampSupported, isNativeTokenRampSupported] = useRampNetwork(); const actionSheet = useRef(); @@ -138,13 +138,12 @@ const Tokens: React.FC = ({ tokens }) => { const goToAddToken = () => { setIsAddTokenEnabled(false); navigation.push('AddAsset', { assetType: 'token' }); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CLICKED, { - source: 'manual', - chain_id: getDecimalChainId(chainId), - }); - setIsAddTokenEnabled(true); + + trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CLICKED, { + source: 'manual', + chain_id: getDecimalChainId(chainId), }); + setIsAddTokenEnabled(true); }; const renderFooter = () => ( @@ -229,6 +228,8 @@ const Tokens: React.FC = ({ tokens }) => { // render balances according to primary currency let mainBalance, secondaryBalance; + mainBalance = TOKEN_BALANCE_LOADING; + if (primaryCurrency === 'ETH') { mainBalance = balanceValueFormatted; secondaryBalance = balanceFiat; @@ -315,28 +316,24 @@ const Tokens: React.FC = ({ tokens }) => { const goToBuy = () => { navigation.navigate(Routes.RAMP.BUY); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(MetaMetricsEvents.BUY_BUTTON_CLICKED, { - text: 'Buy Native Token', - location: 'Home Screen', - chain_id_destination: chainId, - }); + trackEvent(MetaMetricsEvents.BUY_BUTTON_CLICKED, { + text: 'Buy Native Token', + location: 'Home Screen', + chain_id_destination: getDecimalChainId(chainId), }); }; const showDetectedTokens = () => { navigation.navigate(...createDetectedTokensNavDetails()); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CLICKED, { - source: 'detected', - chain_id: getDecimalChainId(chainId), - tokens: detectedTokens.map( - (token) => `${token.symbol} - ${token.address}`, - ), - }); - - setIsAddTokenEnabled(true); + trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CLICKED, { + source: 'detected', + chain_id: getDecimalChainId(chainId), + tokens: detectedTokens.map( + (token) => `${token.symbol} - ${token.address}`, + ), }); + + setIsAddTokenEnabled(true); }; const renderTokensDetectedSection = () => { @@ -436,7 +433,7 @@ const Tokens: React.FC = ({ tokens }) => { screen: Routes.BROWSER.VIEW, params, }); - Analytics.trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, { + trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, { portfolioUrl: AppConstants.PORTFOLIO_URL, }); }; @@ -510,15 +507,13 @@ const Tokens: React.FC = ({ tokens }) => { tokenSymbol: symbol, }), }); - InteractionManager.runAfterInteractions(() => - AnalyticsV2.trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { - location: 'assets_list', - token_standard: 'ERC20', - asset_type: 'token', - tokens: [`${symbol} - ${tokenAddress}`], - chain_id: getDecimalChainId(chainId), - }), - ); + trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { + location: 'assets_list', + token_standard: 'ERC20', + asset_type: 'token', + tokens: [`${symbol} - ${tokenAddress}`], + chain_id: getDecimalChainId(chainId), + }); } catch (err) { Logger.log(err, 'Wallet: Failed to hide token!'); } diff --git a/app/components/UI/TransactionElement/TransactionDetails/index.js b/app/components/UI/TransactionElement/TransactionDetails/index.js index 47b183615dd..80042fc7942 100644 --- a/app/components/UI/TransactionElement/TransactionDetails/index.js +++ b/app/components/UI/TransactionElement/TransactionDetails/index.js @@ -201,13 +201,13 @@ class TransactionDetails extends PureComponent { componentDidMount = () => { const { - providerConfig: { rpcTarget, type }, + providerConfig: { rpcUrl, type }, networkConfigurations, } = this.props; let blockExplorer; if (type === RPC) { blockExplorer = - findBlockExplorerForRpc(rpcTarget, networkConfigurations) || + findBlockExplorerForRpc(rpcUrl, networkConfigurations) || NO_RPC_BLOCK_EXPLORER; } this.setState({ rpcBlockExplorer: blockExplorer }); diff --git a/app/components/UI/TransactionElement/index.js b/app/components/UI/TransactionElement/index.js index 6d0132d548b..2a2e6433a8f 100644 --- a/app/components/UI/TransactionElement/index.js +++ b/app/components/UI/TransactionElement/index.js @@ -24,9 +24,10 @@ import StatusText from '../../Base/StatusText'; import DetailsModal from '../../Base/DetailsModal'; import { isMainNet } from '../../../util/networks'; import { weiHexToGweiDec } from '@metamask/controller-utils'; -import { WalletDevice } from '@metamask/transaction-controller'; -// TODO: Update after this function has been exported from the package -import { isEIP1559Transaction } from '@metamask/transaction-controller/dist/utils'; +import { + WalletDevice, + isEIP1559Transaction, +} from '@metamask/transaction-controller'; import { ThemeContext, mockTheme } from '../../../util/theme'; import { selectChainId, diff --git a/app/components/UI/Transactions/index.js b/app/components/UI/Transactions/index.js index da8412ae08c..19d25f14e24 100644 --- a/app/components/UI/Transactions/index.js +++ b/app/components/UI/Transactions/index.js @@ -47,7 +47,7 @@ import { validateTransactionActionBalance } from '../../../util/transactions'; import withQRHardwareAwareness from '../QRHardware/withQRHardwareAwareness'; import TransactionActionModal from '../TransactionActionModal'; import TransactionElement from '../TransactionElement'; -import UpdateEIP1559Tx from '../UpdateEIP1559Tx'; +import UpdateEIP1559Tx from '../../Views/confirmations/components/UpdateEIP1559Tx'; import RetryModal from './RetryModal'; import PriceChartContext, { PriceChartProvider, @@ -69,6 +69,11 @@ import { import { getLedgerKeyring } from '../../../core/Ledger/Ledger'; import ExtendedKeyringTypes from '../../../constants/keyringTypes'; import { TOKEN_OVERVIEW_TXN_SCREEN } from '../../../../wdio/screen-objects/testIDs/Screens/TokenOverviewScreen.testIds'; +import { + speedUpTransaction, + updateIncomingTransactions, +} from '../../../util/transaction-controller'; +import { selectGasFeeEstimates } from '../../../selectors/confirmTransaction'; const createStyles = (colors, typography) => StyleSheet.create({ @@ -248,13 +253,13 @@ class Transactions extends PureComponent { updateBlockExplorer = () => { const { - providerConfig: { type, rpcTarget }, + providerConfig: { type, rpcUrl }, networkConfigurations, } = this.props; let blockExplorer; if (type === RPC) { blockExplorer = - findBlockExplorerForRpc(rpcTarget, networkConfigurations) || + findBlockExplorerForRpc(rpcUrl, networkConfigurations) || NO_RPC_BLOCK_EXPLORER; } @@ -326,11 +331,9 @@ class Transactions extends PureComponent { }; onRefresh = async () => { - const { TransactionController } = Engine.context; - this.setState({ refreshing: true }); - await TransactionController.updateIncomingTransactions(); + await updateIncomingTransactions(); this.setState({ refreshing: false }); }; @@ -529,7 +532,7 @@ class Transactions extends PureComponent { }, }); } else { - await Engine.context.TransactionController.speedUpTransaction( + await speedUpTransaction( this.speedUpTxId, transactionObject?.suggestedMaxFeePerGasHex && { maxFeePerGas: `0x${transactionObject?.suggestedMaxFeePerGasHex}`, @@ -865,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/UI/UpdateNeeded/UpdateNeeded.tsx b/app/components/UI/UpdateNeeded/UpdateNeeded.tsx index b644e01e3e2..05c3382dfed 100644 --- a/app/components/UI/UpdateNeeded/UpdateNeeded.tsx +++ b/app/components/UI/UpdateNeeded/UpdateNeeded.tsx @@ -17,10 +17,10 @@ import Button, { } from '../../../component-library/components/Buttons/Button'; import { MM_APP_STORE_LINK, MM_PLAY_STORE_LINK } from '../../../constants/urls'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { ScrollView } from 'react-native-gesture-handler'; import generateDeviceAnalyticsMetaData from '../../../util/metrics'; +import { useMetrics } from '../../../components/hooks/useMetrics'; /* eslint-disable import/no-commonjs, @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ const onboardingDeviceImage = require('../../../images/swaps_onboard_device.png'); @@ -32,40 +32,40 @@ export const createUpdateNeededNavDetails = createNavigationDetails( const UpdateNeeded = () => { const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const modalRef = useRef(null); useEffect(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.FORCE_UPGRADE_UPDATE_NEEDED_PROMPT_VIEWED, - generateDeviceAnalyticsMetaData(), - ); - }, []); + trackEvent(MetaMetricsEvents.FORCE_UPGRADE_UPDATE_NEEDED_PROMPT_VIEWED, { + ...generateDeviceAnalyticsMetaData(), + }); + }, [trackEvent]); const dismissModal = (cb?: () => void): void => modalRef?.current?.dismissModal(cb); const triggerClose = () => dismissModal(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.FORCE_UPGRADE_REMIND_ME_LATER_CLICKED, - generateDeviceAnalyticsMetaData(), - ); + trackEvent(MetaMetricsEvents.FORCE_UPGRADE_REMIND_ME_LATER_CLICKED, { + ...generateDeviceAnalyticsMetaData(), + }); }); const openAppStore = useCallback(() => { const link = Platform.OS === 'ios' ? MM_APP_STORE_LINK : MM_PLAY_STORE_LINK; - AnalyticsV2.trackEvent( + trackEvent( MetaMetricsEvents.FORCE_UPGRADE_UPDATE_TO_THE_LATEST_VERSION_CLICKED, { ...generateDeviceAnalyticsMetaData(), link }, ); + Linking.canOpenURL(link).then( (supported) => { supported && Linking.openURL(link); }, (err) => Logger.error(err, 'Unable to perform update'), ); - }, []); + }, [trackEvent]); const onUpdatePressed = useCallback(() => { dismissModal(openAppStore); diff --git a/app/components/UI/WarningAlert/__snapshots__/WarningAlert.test.tsx.snap b/app/components/UI/WarningAlert/__snapshots__/WarningAlert.test.tsx.snap index 0f5c933bc17..6b18246ea52 100644 --- a/app/components/UI/WarningAlert/__snapshots__/WarningAlert.test.tsx.snap +++ b/app/components/UI/WarningAlert/__snapshots__/WarningAlert.test.tsx.snap @@ -6,7 +6,7 @@ exports[`ButtonBase should render correctly 1`] = ` Array [ Object { "backgroundColor": "#FFFFFF", - "borderColor": "#F66A0A", + "borderColor": "#BF5208", "borderRadius": 8, "bottom": 20, "left": 16, diff --git a/app/components/UI/WhatsNewModal/types.ts b/app/components/UI/WhatsNewModal/types.ts index cf41881ee6a..c10a16ac2e6 100644 --- a/app/components/UI/WhatsNewModal/types.ts +++ b/app/components/UI/WhatsNewModal/types.ts @@ -41,7 +41,7 @@ type SlideContentType = type WhatsNewSlides = SlideContentType[][]; -type VersionString = `${number}.${number}.${number}`; +type VersionString = `${number}.${number}.${number}` | `${number}.${number}`; export interface WhatsNew { onlyUpdates: boolean; diff --git a/app/components/UI/WhatsNewModal/whatsNewList.ts b/app/components/UI/WhatsNewModal/whatsNewList.ts index 553d371f612..d7fc56a2c5a 100644 --- a/app/components/UI/WhatsNewModal/whatsNewList.ts +++ b/app/components/UI/WhatsNewModal/whatsNewList.ts @@ -7,8 +7,8 @@ import { WhatsNew } from './types'; export const whatsNew: WhatsNew = { // All users that have <6.4.0 and are updating to >=6.4.0 should see onlyUpdates: false, // false: Users who updated the app and new installs will see this. true: only users who update will see it - maxLastAppVersion: '7.15.0', // Only users who had a previous version <7.15.0 version will see this - minAppVersion: '7.15.0', // Only users who updated to a version >= 7.15.0 will see this + maxLastAppVersion: '7.20', // When updating, only users who had a previous version < '7.20' may see the modal + minAppVersion: '7.16.0', // Only users whose current version is >= 7.16.0 may see the modal. This should match the version with the latest copy changes in the modal. /** * Slides utilizes a templating system in the form of a 2D array, which is eventually rendered within app/components/UI/WhatsNewModal/index.js. * The root layer determines the number of slides. Ex. To display 3 slides, the root layer should contain 3 arrays. @@ -36,9 +36,13 @@ export const whatsNew: WhatsNew = { type: 'description', description: strings('whats_new.blockaid.description_2'), }, + { + type: 'description', + description: strings('whats_new.blockaid.description_3'), + }, { type: 'button', - buttonText: strings('whats_new.blockaid.action_text'), + buttonText: strings('whats_new.blockaid.got_it'), buttonType: 'blue', onPress: (props) => props.navigation.navigate(Routes.SETTINGS_VIEW, { diff --git a/app/components/Views/AccountActions/AccountActions.test.tsx b/app/components/Views/AccountActions/AccountActions.test.tsx index 9ed00606399..a25e5ccd3ec 100644 --- a/app/components/Views/AccountActions/AccountActions.test.tsx +++ b/app/components/Views/AccountActions/AccountActions.test.tsx @@ -19,7 +19,7 @@ import initialBackgroundState from '../../../util/test/initial-background-state. const mockEngine = Engine; const initialState = { - swaps: { '1': { isLive: true }, hasOnboarded: false, isLive: true }, + swaps: { '0x1': { isLive: true }, hasOnboarded: false, isLive: true }, engine: { backgroundState: { ...initialBackgroundState, diff --git a/app/components/Views/AccountActions/AccountActions.tsx b/app/components/Views/AccountActions/AccountActions.tsx index 806caec9d64..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); @@ -55,14 +56,14 @@ const AccountActions = () => { const networkConfigurations = useSelector(selectNetworkConfigurations); const blockExplorer = useMemo(() => { - if (providerConfig?.rpcTarget && providerConfig.type === RPC) { + if (providerConfig?.rpcUrl && providerConfig.type === RPC) { return findBlockExplorerForRpc( - providerConfig.rpcTarget, + providerConfig.rpcUrl, networkConfigurations, ); } return null; - }, [networkConfigurations, providerConfig.rpcTarget, providerConfig.type]); + }, [networkConfigurations, providerConfig.rpcUrl, providerConfig.type]); const blockExplorerName = getBlockExplorerName(blockExplorer); @@ -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 83f3e4c0a4f..0c492426f48 100644 --- a/app/components/Views/AccountBackupStep1/index.js +++ b/app/components/Views/AccountBackupStep1/index.js @@ -7,7 +7,6 @@ import { SafeAreaView, StyleSheet, BackHandler, - InteractionManager, } from 'react-native'; import PropTypes from 'prop-types'; import { fontStyles } from '../../../styles/common'; @@ -27,12 +26,11 @@ import SeedPhraseVideo from '../../UI/SeedPhraseVideo'; import { connect } from 'react-redux'; import setOnboardingWizardStep from '../../../actions/wizard'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import DefaultPreference from 'react-native-default-preference'; import { useTheme } from '../../../util/theme'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; - +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const createStyles = (colors) => StyleSheet.create({ mainWrapper: { @@ -128,6 +126,10 @@ const AccountBackupStep1 = (props) => { const { colors } = useTheme(); const styles = createStyles(colors); + const track = (event, properties) => { + trackOnboarding(event, properties); + }; + useEffect(() => { navigation.setOptions({ ...getOnboardingNavbarOptions( @@ -161,18 +163,14 @@ const AccountBackupStep1 = (props) => { const goNext = () => { props.navigation.navigate('AccountBackupStep1B', { ...props.route.params }); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SECURITY_STARTED); - }); + track(MetaMetricsEvents.WALLET_SECURITY_STARTED); }; const showRemindLater = () => { if (hasFunds) return; setRemindLaterModal(true); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SECURITY_SKIP_INITIATED); - }); + track(MetaMetricsEvents.WALLET_SECURITY_SKIP_INITIATED); }; const toggleSkipCheckbox = () => @@ -190,9 +188,7 @@ const AccountBackupStep1 = (props) => { const skip = async () => { hideRemindLaterModal(); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SECURITY_SKIP_CONFIRMED); - }); + track(MetaMetricsEvents.WALLET_SECURITY_SKIP_CONFIRMED); // Get onboarding wizard state const onboardingWizard = await DefaultPreference.get(ONBOARDING_WIZARD); if (onboardingWizard) { diff --git a/app/components/Views/AccountBackupStep1B/index.js b/app/components/Views/AccountBackupStep1B/index.js index 2b1c5378cc1..7693a2881d4 100644 --- a/app/components/Views/AccountBackupStep1B/index.js +++ b/app/components/Views/AccountBackupStep1B/index.js @@ -8,7 +8,6 @@ import { StyleSheet, Image, Dimensions, - InteractionManager, } from 'react-native'; import PropTypes from 'prop-types'; import Icon from 'react-native-vector-icons/FontAwesome'; @@ -23,10 +22,10 @@ import SeedphraseModal from '../../UI/SeedphraseModal'; import { getOnboardingNavbarOptions } from '../../UI/Navbar'; import { CHOOSE_PASSWORD_STEPS } from '../../../constants/onboarding'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { useTheme } from '../../../util/theme'; 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 @@ -206,17 +205,17 @@ const AccountBackupStep1B = (props) => { const { colors } = useTheme(); const styles = createStyles(colors); + const track = (event, properties) => { + trackOnboarding(event, properties); + }; + useEffect(() => { navigation.setOptions(getOnboardingNavbarOptions(route, {}, colors)); }, [navigation, route, colors]); const goNext = () => { props.navigation.navigate('ManualBackupStep1', { ...props.route.params }); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_MANUAL_BACKUP_INITIATED, - ); - }); + track(MetaMetricsEvents.WALLET_SECURITY_MANUAL_BACKUP_INITIATED); }; const learnMore = () => { diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index e511cd5f65f..4990423e22a 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -18,7 +18,6 @@ import BottomSheet, { import UntypedEngine from '../../../core/Engine'; import { isDefaultAccountName } from '../../../util/ENSUtils'; import Logger from '../../../util/Logger'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { SelectedAccount } from '../../../components/UI/AccountSelectorList/AccountSelectorList.types'; import { @@ -54,12 +53,15 @@ import AccountConnectSingleSelector from './AccountConnectSingleSelector'; 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, @@ -116,12 +118,19 @@ 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( + (numberOfConnectedAccounts: number) => + // Track dapp visited event + trackDappVisitedEvent({ hostname, numberOfConnectedAccounts }), + [hostname], ); const handleConnect = useCallback(async () => { @@ -149,7 +158,10 @@ const AccountConnect = (props: AccountConnectProps) => { await Engine.context.PermissionController.acceptPermissionsRequest( request, ); - AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_REQUEST_COMPLETED, { + + triggerDappVisitedEvent(connectedAccountLength); + + trackEvent(MetaMetricsEvents.CONNECT_REQUEST_COMPLETED, { number_of_accounts: accountsLength, number_of_accounts_connected: connectedAccountLength, account_type: getAddressAccountType(activeAddress), @@ -192,6 +204,8 @@ const AccountConnect = (props: AccountConnectProps) => { Engine.context.PermissionController, toastRef, accountsLength, + triggerDappVisitedEvent, + trackEvent, ]); const handleCreateAccount = useCallback( @@ -204,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) => @@ -254,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; } @@ -281,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 c2f3cac7837..ca2112a893c 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -14,6 +14,7 @@ import PickerNetwork from '../../../../component-library/components/Pickers/Pick import { getNetworkNameFromProviderConfig, getNetworkImageSource, + getDecimalChainId, } from '../../../../util/networks'; import AccountSelectorList from '../../../../components/UI/AccountSelectorList'; import { AccountPermissionsScreens } from '../AccountPermissions.types'; @@ -23,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'; @@ -32,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, @@ -48,6 +49,7 @@ const AccountPermissionsConnected = ({ urlWithProtocol, }: AccountPermissionsConnectedProps) => { const { navigate } = useNavigation(); + const { trackEvent } = useMetrics(); const providerConfig: ProviderConfig = useSelector(selectProviderConfig); @@ -111,10 +113,10 @@ const AccountPermissionsConnected = ({ screen: Routes.SHEET.NETWORK_SELECTOR, }); - AnalyticsV2.trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { - chain_id: providerConfig.chainId, + trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { + chain_id: getDecimalChainId(providerConfig.chainId), }); - }, [providerConfig.chainId, navigate]); + }, [providerConfig.chainId, navigate, trackEvent]); const renderSheetAction = useCallback( () => ( @@ -139,10 +141,7 @@ const AccountPermissionsConnected = ({ return ( <> - + { 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 ff74e68fdb7..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 { selectAccounts } from '../../../selectors/accountTrackerController'; +import { selectAccountsByChainId } from '../../../selectors/accountTrackerController'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const styles = StyleSheet.create({ wrapper: { @@ -25,20 +25,22 @@ 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); - const accounts = useSelector(selectAccounts); + const accountsByChainId = useSelector(selectAccountsByChainId); const openAccountSelector = useCallback(() => { navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.ACCOUNT_SELECTOR, }); // Track Event: "Opened Acount Switcher" - AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, { - number_of_accounts: Object.keys(accounts ?? {}).length, + trackEvent(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, { + number_of_accounts: Object.keys(accountsByChainId[selectedAddress] ?? {}) + .length, }); - }, [navigation, accounts]); + }, [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/AddAsset/__snapshots__/AddAsset.test.tsx.snap b/app/components/Views/AddAsset/__snapshots__/AddAsset.test.tsx.snap index 09e0f6e7c5c..e4fc039bcb2 100644 --- a/app/components/Views/AddAsset/__snapshots__/AddAsset.test.tsx.snap +++ b/app/components/Views/AddAsset/__snapshots__/AddAsset.test.tsx.snap @@ -22,8 +22,8 @@ exports[`AddAsset component renders correctly 1`] = ` { diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js index a698b9999b0..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, @@ -32,7 +31,7 @@ import { selectChainId, selectNetworkId, selectNetworkConfigurations, - selectRpcTarget, + selectRpcUrl, } from '../../../selectors/networkController'; import { selectTokens } from '../../../selectors/tokensController'; import { sortTransactions } from '../../../util/activity'; @@ -60,11 +59,12 @@ import { selectIdentities, selectSelectedAddress, } from '../../../selectors/preferencesController'; -import Engine from '../../../core/Engine'; import { TOKEN_OVERVIEW_BUY_BUTTON, 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({ @@ -170,12 +170,16 @@ class Asset extends PureComponent { * Object that represents the current route info like params passed to it */ route: PropTypes.object, - rpcTarget: PropTypes.string, + rpcUrl: PropTypes.string, networkConfigurations: PropTypes.object, /** * Boolean that indicates if native token is supported to buy */ isNetworkBuyNativeTokenSupported: PropTypes.bool, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -196,13 +200,13 @@ class Asset extends PureComponent { navAddress = undefined; updateNavBar = (contentOffset = 0) => { - const { navigation, route, chainId, rpcTarget, networkConfigurations } = + const { navigation, route, chainId, rpcUrl, networkConfigurations } = this.props; const colors = this.context.colors || mockTheme.colors; const isNativeToken = route.params.isETH; const isMainnet = isMainnetByChainId(chainId); const blockExplorer = findBlockExplorerForRpc( - rpcTarget, + rpcUrl, networkConfigurations, ); @@ -441,11 +445,9 @@ class Asset extends PureComponent { }; onRefresh = async () => { - const { TransactionController } = Engine.context; - this.setState({ refreshing: true }); - await TransactionController.updateIncomingTransactions(); + await updateIncomingTransactions(); this.setState({ refreshing: false }); }; @@ -476,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, }); }; @@ -589,7 +587,7 @@ const mapStateToProps = (state) => ({ tokens: selectTokens(state), networkId: selectNetworkId(state), transactions: state.engine.backgroundState.TransactionController.transactions, - rpcTarget: selectRpcTarget(state), + rpcUrl: selectRpcUrl(state), networkConfigurations: selectNetworkConfigurations(state), isNetworkBuyNativeTokenSupported: isNetworkRampNativeTokenSupported( selectChainId(state), @@ -597,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 9ee39d6a8a9..5af5ba85310 100644 --- a/app/components/Views/AssetDetails/index.tsx +++ b/app/components/Views/AssetDetails/index.tsx @@ -27,10 +27,9 @@ import { balanceToFiat, renderFromTokenMinimalUnit, } from '../../../util/number'; -import WarningMessage from '../SendFlow/WarningMessage'; +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/AssetOptions/AssetOptions.styles.ts b/app/components/Views/AssetOptions/AssetOptions.styles.ts index 31445ae5014..4ca2e5004ac 100644 --- a/app/components/Views/AssetOptions/AssetOptions.styles.ts +++ b/app/components/Views/AssetOptions/AssetOptions.styles.ts @@ -1,4 +1,4 @@ -import { Theme } from '@metamask/design-tokens'; +import type { Theme } from '@metamask/design-tokens'; import { StyleSheet, TextStyle } from 'react-native'; const styleSheet = (params: { theme: Theme }) => { 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 a40a5eee363..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,6 +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 { 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; @@ -258,7 +258,7 @@ export const BrowserTab = (props) => { const [progress, setProgress] = useState(0); const [initialUrl, setInitialUrl] = useState(''); const [firstUrlLoaded, setFirstUrlLoaded] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(false); const [showOptions, setShowOptions] = useState(false); const [entryScriptWeb3, setEntryScriptWeb3] = useState(null); const [showPhishingModal, setShowPhishingModal] = useState(false); @@ -289,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 */ @@ -385,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 @@ -532,6 +531,27 @@ export const BrowserTab = (props) => { [goBack, props.ipfsGateway, setIpfsBannerVisible, props.chainId], ); + const triggerDappVisitedEvent = (url) => { + const permissionsControllerState = + Engine.context.PermissionController.state; + const hostname = new URL(url).hostname; + const connectedAccounts = getPermittedAccountsByHostname( + permissionsControllerState, + hostname, + ); + + // Check if there are any connected accounts + if (!connectedAccounts.length) { + return; + } + + // Track dapp visited event + trackDappVisitedEvent({ + hostname, + numberOfConnectedAccounts: connectedAccounts.length, + }); + }; + /** * Go to a url */ @@ -574,6 +594,11 @@ export const BrowserTab = (props) => { ); } + // Skip tracking on initial open + if (!initialCall) { + triggerDappVisitedEvent(urlToGo); + } + setProgress(0); return prefixedUrl; } @@ -602,6 +627,7 @@ export const BrowserTab = (props) => { const { current } = webviewRef; current && current.reload(); + triggerDappVisitedEvent(url.current); }, []); /** @@ -685,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}"; @@ -814,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. @@ -883,10 +909,15 @@ export const BrowserTab = (props) => { setProgress(progress); }; + // We need to be sure we can remove this property https://github.com/react-native-webview/react-native-webview/issues/2970 + // We should check if this is fixed on the newest versions of react-native-webview const onLoad = ({ nativeEvent }) => { //For iOS url on the navigation bar should only update upon load. if (Device.isIos()) { - changeUrl(nativeEvent); + const { origin, pathname = '', query = '' } = new URL(nativeEvent.url); + const realUrl = `${origin}${pathname}${query}`; + changeUrl({ ...nativeEvent, url: realUrl, icon: favicon }); + changeAddressBar({ ...nativeEvent, url: realUrl, icon: favicon }); } }; @@ -898,13 +929,6 @@ export const BrowserTab = (props) => { if (nativeEvent.loading) { return; } - // Use URL to produce real url. This should be the actual website that the user is viewing. - const urlObj = new URL(nativeEvent.url); - const { origin, pathname = '', query = '' } = urlObj; - const realUrl = `${origin}${pathname}${query}`; - // Update navigation bar address with title of loaded url. - changeUrl({ ...nativeEvent, url: realUrl, icon: favicon }); - changeAddressBar({ ...nativeEvent, url: realUrl, icon: favicon }); }; /** @@ -937,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); }; /** @@ -1031,25 +1055,13 @@ export const BrowserTab = (props) => { * Website started to load */ const onLoadStart = async ({ nativeEvent }) => { - const { hostname } = new URL(nativeEvent.url); - - if ( - nativeEvent.url !== url.current && - nativeEvent.loading && - nativeEvent.navigationType === 'backforward' - ) { - changeAddressBar({ ...nativeEvent }); - } - - setError(false); - - changeUrl(nativeEvent); - sendActiveAccount(); - - icon.current = null; - if (isHomepage(nativeEvent.url)) { - injectHomePageScripts(); - } + // Use URL to produce real url. This should be the actual website that the user is viewing. + const { + origin, + pathname = '', + query = '', + hostname, + } = new URL(nativeEvent.url); // Reset the previous bridges backgroundBridges.current.length && @@ -1061,8 +1073,21 @@ export const BrowserTab = (props) => { return false; } + const realUrl = `${origin}${pathname}${query}`; + if (nativeEvent.url !== url.current) { + // Update navigation bar address with title of loaded url. + changeUrl({ ...nativeEvent, url: realUrl, icon: favicon }); + changeAddressBar({ ...nativeEvent, url: realUrl, icon: favicon }); + } + + sendActiveAccount(); + + icon.current = null; + if (isHomepage(nativeEvent.url)) { + injectHomePageScripts(); + } + backgroundBridges.current = []; - const origin = new URL(nativeEvent.url).origin; initializeBackgroundBridge(origin, true); }; @@ -1081,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: { @@ -1158,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, }); @@ -1168,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 || '', }); }; @@ -1177,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); }; /** @@ -1219,7 +1241,7 @@ export const BrowserTab = (props) => { }, }); trackAddToFavoritesEvent(); - Analytics.trackEvent(MetaMetricsEvents.DAPP_ADD_TO_FAVORITE); + trackEvent(MetaMetricsEvents.DAPP_ADD_TO_FAVORITE); }; /** @@ -1246,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 cb3e8fc8ca8..fc9a6cf9197 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -8,7 +8,6 @@ import { SafeAreaView, StyleSheet, Image, - InteractionManager, } from 'react-native'; import CheckBox from '@react-native-community/checkbox'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; @@ -55,7 +54,6 @@ import { import { CHOOSE_PASSWORD_STEPS } from '../../../constants/onboarding'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { Authentication } from '../../../core'; import AUTHENTICATION_TYPE from '../../../constants/userProperties'; import { ThemeContext, mockTheme } from '../../../util/theme'; @@ -64,6 +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 trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const createStyles = (colors) => StyleSheet.create({ @@ -260,6 +259,10 @@ class ChoosePassword extends PureComponent { // Flag to know if password in keyring was set or not keyringControllerPasswordSet = false; + track = (event, properties) => { + trackOnboarding(event, properties); + }; + updateNavBar = () => { const { route, navigation } = this.props; const colors = this.context.colors || mockTheme.colors; @@ -338,9 +341,7 @@ class ChoosePassword extends PureComponent { Alert.alert('Error', strings('choose_password.password_dont_match')); return; } - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_CREATION_ATTEMPTED); - }); + this.track(MetaMetricsEvents.WALLET_CREATION_ATTEMPTED); try { this.setState({ loading: true }); @@ -367,14 +368,12 @@ class ChoosePassword extends PureComponent { this.props.setLockTime(AppConstants.DEFAULT_LOCK_TIMEOUT); this.setState({ loading: false }); this.props.navigation.replace('AccountBackupStep1'); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_CREATED, { - biometrics_enabled: Boolean(this.state.biometryType), - }); - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SETUP_COMPLETED, { - wallet_setup_type: 'new', - new_wallet: true, - }); + this.track(MetaMetricsEvents.WALLET_CREATED, { + biometrics_enabled: Boolean(this.state.biometryType), + }); + this.track(MetaMetricsEvents.WALLET_SETUP_COMPLETED, { + wallet_setup_type: 'new', + new_wallet: true, }); } catch (error) { try { @@ -397,11 +396,9 @@ class ChoosePassword extends PureComponent { } else { this.setState({ loading: false, error: error.toString() }); } - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SETUP_FAILURE, { - wallet_setup_type: 'new', - error_type: error.toString(), - }); + this.track(MetaMetricsEvents.WALLET_SETUP_FAILURE, { + wallet_setup_type: 'new', + error_type: error.toString(), }); } }; diff --git a/app/components/Views/ConnectHardware/SelectHardware/index.tsx b/app/components/Views/ConnectHardware/SelectHardware/index.tsx index 5181d895613..86152efcb32 100644 --- a/app/components/Views/ConnectHardware/SelectHardware/index.tsx +++ b/app/components/Views/ConnectHardware/SelectHardware/index.tsx @@ -1,27 +1,30 @@ /* eslint @typescript-eslint/no-var-requires: "off" */ /* eslint @typescript-eslint/no-require-imports: "off" */ +import { useNavigation } from '@react-navigation/native'; import React, { useEffect } from 'react'; import { - View, + Image, SafeAreaView, StyleSheet, TouchableOpacity, - Image, + View, } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; +import { strings } from '../../../../../locales/i18n'; +import Text, { + TextVariant, +} from '../../../../component-library/components/Texts/Text'; +import Routes from '../../../../constants/navigation/Routes'; +import { MetaMetricsEvents } from '../../../../core/Analytics'; +import { getLedgerKeyring } from '../../../../core/Ledger/Ledger'; +import { fontStyles } from '../../../../styles/common'; import { mockTheme, useAppThemeFromContext, useAssetFromTheme, } from '../../../../util/theme'; import { getNavigationOptionsTitle } from '../../../UI/Navbar'; -import { fontStyles } from '../../../../styles/common'; -import { strings } from '../../../../../locales/i18n'; -import Routes from '../../../../constants/navigation/Routes'; -import Text, { - TextVariant, -} from '../../../../component-library/components/Texts/Text'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const createStyle = (colors: any) => StyleSheet.create({ @@ -66,6 +69,13 @@ const createStyle = (colors: any) => }, }); +// Ledger Logo +const ledgerLogoLightImgPath = 'images/ledger-light.png'; +const ledgerLogoLight = require(ledgerLogoLightImgPath); + +const ledgerLogoDarkImgPath = 'images/ledger-dark.png'; +const ledgerLogoDark = require(ledgerLogoDarkImgPath); + // QR Hardware Logo const qrHardwareLogoLightImgPath = 'images/qrhardware-light.png'; const qrHardwareLogoLight = require(qrHardwareLogoLightImgPath); @@ -75,6 +85,7 @@ const qrHardwareLogoDark = require(qrHardwareLogoDarkImgPath); const SelectHardwareWallet = () => { const navigation = useNavigation(); + const { trackEvent } = useMetrics(); const { colors } = useAppThemeFromContext() || mockTheme; const styles = createStyle(colors); @@ -93,12 +104,37 @@ const SelectHardwareWallet = () => { navigation.navigate(Routes.HW.CONNECT_QR_DEVICE); }; + const navigateToConnectLedger = async () => { + const ledgerKeyring = await getLedgerKeyring(); + const accounts = await ledgerKeyring.getAccounts(); + + trackEvent(MetaMetricsEvents.CONNECT_LEDGER, { + device_type: 'Ledger', + }); + + if (accounts.length === 0) { + navigation.navigate(Routes.HW.CONNECT_LEDGER); + } else { + navigation.navigate(Routes.HW.LEDGER_ACCOUNT, { + screen: Routes.HW.LEDGER_ACCOUNT, + params: { + accounts, + }, + }); + } + }; + const renderHardwareButton = (image: any, onPress: any) => ( ); + const LedgerButton = () => { + const ledgerLogo = useAssetFromTheme(ledgerLogoLight, ledgerLogoDark); + return renderHardwareButton(ledgerLogo, navigateToConnectLedger); + }; + const QRButton = () => { const qrHardwareLogo = useAssetFromTheme( qrHardwareLogoLight, @@ -115,6 +151,7 @@ const SelectHardwareWallet = () => { + diff --git a/app/components/Views/ConnectQRHardware/index.tsx b/app/components/Views/ConnectQRHardware/index.tsx index 10aa4b39ff8..565022faa84 100644 --- a/app/components/Views/ConnectQRHardware/index.tsx +++ b/app/components/Views/ConnectQRHardware/index.tsx @@ -16,7 +16,6 @@ import { strings } from '../../../../locales/i18n'; import { UR } from '@ngraveio/bc-ur'; import Alert, { AlertType } from '../../Base/Alert'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import Device from '../../../util/device'; @@ -26,6 +25,7 @@ import { fontStyles } from '../../../styles/common'; import Logger from '../../../util/Logger'; import { removeAccountsFromPermissions } from '../../../core/Permissions'; import { safeToChecksumAddress } from '../../../util/address'; +import { useMetrics } from '../../../components/hooks/useMetrics'; interface IConnectQRHardwareProps { navigation: any; @@ -74,6 +74,7 @@ const createStyles = (colors: any) => const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => { const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const KeyringController = useMemo(() => { @@ -138,23 +139,20 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => { }, [QRState.sync, hideScanner, showScanner]); const onConnectHardware = useCallback(async () => { - AnalyticsV2.trackEvent(MetaMetricsEvents.CONTINUE_QR_HARDWARE_WALLET, { + trackEvent(MetaMetricsEvents.CONTINUE_QR_HARDWARE_WALLET, { device_type: 'QR Hardware', }); resetError(); const _accounts = await KeyringController.connectQRHardware(0); setAccounts(_accounts); - }, [KeyringController, resetError]); + }, [KeyringController, resetError, trackEvent]); const onScanSuccess = useCallback( (ur: UR) => { hideScanner(); - AnalyticsV2.trackEvent( - MetaMetricsEvents.CONNECT_HARDWARE_WALLET_SUCCESS, - { - device_type: 'QR Hardware', - }, - ); + trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET_SUCCESS, { + device_type: 'QR Hardware', + }); if (ur.type === SUPPORTED_UR_TYPE.CRYPTO_HDKEY) { KeyringController.submitQRCryptoHDKey(ur.cbor.toString('hex')); } else { @@ -162,7 +160,7 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => { } resetError(); }, - [KeyringController, hideScanner, resetError], + [KeyringController, hideScanner, resetError, trackEvent], ); const onScanError = useCallback( diff --git a/app/components/Views/DetectedTokens/index.tsx b/app/components/Views/DetectedTokens/index.tsx index dbc794ac460..3cf5c335fb6 100644 --- a/app/components/Views/DetectedTokens/index.tsx +++ b/app/components/Views/DetectedTokens/index.tsx @@ -16,7 +16,6 @@ import NotificationManager from '../../../core/NotificationManager'; import { strings } from '../../../../locales/i18n'; import Logger from '../../../util/Logger'; import { useTheme } from '../../../util/theme'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { getDecimalChainId } from '../../../util/networks'; import { createNavigationDetails } from '../../../util/navigation/navUtils'; import Routes from '../../../constants/navigation/Routes'; @@ -25,6 +24,7 @@ import { selectChainId } from '../../../selectors/networkController'; import BottomSheet, { BottomSheetRef, } from '../../../component-library/components/BottomSheets/BottomSheet'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (colors: any) => StyleSheet.create({ @@ -68,6 +68,7 @@ interface IgnoredTokensByAddress { const DetectedTokens = () => { const navigation = useNavigation(); + const { trackEvent } = useMetrics(); const sheetRef = useRef(null); const detectedTokens = useSelector(selectDetectedTokens); const chainId = useSelector(selectChainId); @@ -125,7 +126,7 @@ const DetectedTokens = () => { await TokensController.addTokens(tokensToImport); InteractionManager.runAfterInteractions(() => tokensToImport.forEach(({ address, symbol }) => - AnalyticsV2.trackEvent(MetaMetricsEvents.TOKEN_ADDED, { + trackEvent(MetaMetricsEvents.TOKEN_ADDED, { token_address: address, token_symbol: symbol, chain_id: getDecimalChainId(chainId), @@ -145,7 +146,7 @@ const DetectedTokens = () => { } }); }, - [chainId, detectedTokens, ignoredTokens], + [chainId, detectedTokens, ignoredTokens, trackEvent], ); const triggerIgnoreAllTokens = () => { @@ -153,15 +154,14 @@ const DetectedTokens = () => { onConfirm: () => dismissModalAndTriggerAction(true), isHidingAll: true, }); - InteractionManager.runAfterInteractions(() => - AnalyticsV2.trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { - location: 'token_detection', - token_standard: 'ERC20', - asset_type: 'token', - tokens: detectedTokensForAnalytics, - chain_id: getDecimalChainId(chainId), - }), - ); + + trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { + location: 'token_detection', + token_standard: 'ERC20', + asset_type: 'token', + tokens: detectedTokensForAnalytics, + chain_id: getDecimalChainId(chainId), + }); }; const triggerImportTokens = async () => { @@ -251,7 +251,7 @@ const DetectedTokens = () => { if (hasPendingAction) { return; } - AnalyticsV2.trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CANCELED, { + trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CANCELED, { source: 'detected', tokens: detectedTokensForAnalytics, chain_id: getDecimalChainId(chainId), diff --git a/app/components/Views/EditAccountName/EditAccountName.test.tsx b/app/components/Views/EditAccountName/EditAccountName.test.tsx index cc7fe1c8158..ac1810c6066 100644 --- a/app/components/Views/EditAccountName/EditAccountName.test.tsx +++ b/app/components/Views/EditAccountName/EditAccountName.test.tsx @@ -18,7 +18,7 @@ jest.mock('../../../core/Engine', () => ({ })); const mockInitialState = { - swaps: { '1': { isLive: true }, hasOnboarded: false, isLive: true }, + swaps: { '0x1': { isLive: true }, hasOnboarded: false, isLive: true }, wizard: { step: 0, }, diff --git a/app/components/Views/EditAccountName/EditAccountName.tsx b/app/components/Views/EditAccountName/EditAccountName.tsx index 3f204b25396..a5ad060777c 100644 --- a/app/components/Views/EditAccountName/EditAccountName.tsx +++ b/app/components/Views/EditAccountName/EditAccountName.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useNavigation } from '@react-navigation/native'; import { useSelector } from 'react-redux'; -import { InteractionManager, Platform, SafeAreaView } from 'react-native'; +import { Platform, SafeAreaView } from 'react-native'; // External dependencies import Text from '../../../component-library/components/Texts/Text/Text'; @@ -22,7 +22,6 @@ import { useStyles } from '../../../component-library/hooks'; import { getEditAccountNameNavBarOptions } from '../../../components/UI/Navbar'; import Engine from '../../../core/Engine'; import generateTestId from '../../../../wdio/utils/generateTestId'; -import Analytics from '../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { selectChainId } from '../../../selectors/networkController'; import { @@ -37,9 +36,12 @@ import { useTheme } from '../../../util/theme'; // Internal dependencies import styleSheet from './EditAccountName.styles'; +import { getDecimalChainId } from '../../../util/networks'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const EditAccountName = () => { const { colors } = useTheme(); + const { trackEvent } = useMetrics(); const { styles } = useStyles(styleSheet, {}); const { setOptions, goBack, navigate } = useNavigation(); const [accountName, setAccountName] = useState(); @@ -80,26 +82,22 @@ const EditAccountName = () => { setAccountName(name); }; - const saveAccountName = () => { + const saveAccountName = async () => { const { PreferencesController } = Engine.context; PreferencesController.setAccountLabel(selectedAddress, accountName); navigate('WalletView'); - InteractionManager.runAfterInteractions(() => { - try { - const analyticsProperties = async () => { - const accountType = getAddressAccountType(selectedAddress); - const account_type = accountType === 'QR' ? 'hardware' : accountType; - return { account_type, chain_id: chainId }; - }; - Analytics.trackEventWithParameters( - MetaMetricsEvents.ACCOUNT_RENAMED, - analyticsProperties(), - ); - } catch { - return {}; - } - }); + try { + const analyticsProperties = async () => { + const accountType = getAddressAccountType(selectedAddress); + const account_type = accountType === 'QR' ? 'hardware' : accountType; + return { account_type, chain_id: getDecimalChainId(chainId) }; + }; + const analyticsProps = await analyticsProperties(); + trackEvent(MetaMetricsEvents.ACCOUNT_RENAMED, analyticsProps); + } catch { + return {}; + } }; return ( diff --git a/app/components/Views/EditAccountName/__snapshots__/EditAccountName.test.tsx.snap b/app/components/Views/EditAccountName/__snapshots__/EditAccountName.test.tsx.snap index dd78f8e24e8..1e786368a12 100644 --- a/app/components/Views/EditAccountName/__snapshots__/EditAccountName.test.tsx.snap +++ b/app/components/Views/EditAccountName/__snapshots__/EditAccountName.test.tsx.snap @@ -244,7 +244,7 @@ exports[`EditAccountName should render correctly 1`] = ` accessibilityRole="text" style={ Object { - "color": "#FCFCFC", + "color": "#FFFFFF", "fontFamily": "Euclid Circular B", "fontSize": 14, "fontWeight": "400", diff --git a/app/components/Views/ImportFromSecretRecoveryPhrase/index.js b/app/components/Views/ImportFromSecretRecoveryPhrase/index.js index 80bf58caca2..fe4a4992d7d 100644 --- a/app/components/Views/ImportFromSecretRecoveryPhrase/index.js +++ b/app/components/Views/ImportFromSecretRecoveryPhrase/index.js @@ -8,7 +8,6 @@ import { View, TextInput, SafeAreaView, - InteractionManager, Platform, } from 'react-native'; import { connect } from 'react-redux'; @@ -35,7 +34,6 @@ import { } from '../../../util/password'; import importAdditionalAccounts from '../../../util/importAdditionalAccounts'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { useTheme } from '../../../util/theme'; import { passwordSet, seedphraseBackedUp } from '../../../actions/user'; @@ -65,6 +63,7 @@ import { import navigateTermsOfUse from '../../../util/termsOfUse/termsOfUse'; import { ImportFromSeedSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ImportFromSeed.selectors'; import { ChoosePasswordSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ChoosePassword.selectors'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const MINIMUM_SUPPORTED_CLIPBOARD_VERSION = 9; @@ -105,6 +104,10 @@ const ImportFromSecretRecoveryPhrase = ({ const passwordInput = React.createRef(); const confirmPasswordInput = React.createRef(); + const track = (event, properties) => { + trackOnboarding(event, properties); + }; + const updateNavBar = () => { navigation.setOptions(getOnboardingNavbarOptions(route, {}, colors)); }; @@ -184,9 +187,7 @@ const ImportFromSecretRecoveryPhrase = ({ setSeed(parsedSeed); if (loading) return; - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_IMPORT_ATTEMPTED); - }); + track(MetaMetricsEvents.WALLET_IMPORT_ATTEMPTED); let error = null; if (!passwordRequirementsMet(password)) { error = strings('import_from_seed.password_length_error'); @@ -202,11 +203,9 @@ const ImportFromSecretRecoveryPhrase = ({ if (error) { Alert.alert(strings('import_from_seed.error'), error); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SETUP_FAILURE, { - wallet_setup_type: 'import', - error_type: error, - }); + track(MetaMetricsEvents.WALLET_SETUP_FAILURE, { + wallet_setup_type: 'import', + error_type: error, }); } else { try { @@ -234,14 +233,12 @@ const ImportFromSecretRecoveryPhrase = ({ passwordSet(); setLockTime(AppConstants.DEFAULT_LOCK_TIMEOUT); seedphraseBackedUp(); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_IMPORTED, { - biometrics_enabled: Boolean(biometryType), - }); - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SETUP_COMPLETED, { - wallet_setup_type: 'import', - new_wallet: false, - }); + track(MetaMetricsEvents.WALLET_IMPORTED, { + biometrics_enabled: Boolean(biometryType), + }); + track(MetaMetricsEvents.WALLET_SETUP_COMPLETED, { + wallet_setup_type: 'import', + new_wallet: false, }); if (onboardingWizard) { navigation.replace(Routes.ONBOARDING.MANUAL_BACKUP.STEP_3); @@ -265,11 +262,9 @@ const ImportFromSecretRecoveryPhrase = ({ setError(error.message); Logger.log('Error with seed phrase import', error.message); } - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SETUP_FAILURE, { - wallet_setup_type: 'import', - error_type: error.toString(), - }); + track(MetaMetricsEvents.WALLET_SETUP_FAILURE, { + wallet_setup_type: 'import', + error_type: error.toString(), }); } } diff --git a/app/components/Views/ImportPrivateKeySuccess/__snapshots__/index.test.tsx.snap b/app/components/Views/ImportPrivateKeySuccess/__snapshots__/index.test.tsx.snap index 12ad2e4786e..9ba37139c0b 100644 --- a/app/components/Views/ImportPrivateKeySuccess/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/ImportPrivateKeySuccess/__snapshots__/index.test.tsx.snap @@ -65,7 +65,7 @@ exports[`ImportPrivateKeySuccess should render correctly 1`] = ` > StyleSheet.create({ @@ -79,6 +79,7 @@ const createStyles = (colors: any) => const LedgerAccountInfo = () => { const dispatch = useDispatch(); const navigation = useNavigation(); + const { trackEvent } = useMetrics(); const [account, setAccount] = useState(''); const [accountBalance, setAccountBalance] = useState('0'); const { colors } = useAppThemeFromContext() ?? mockTheme; @@ -118,7 +119,7 @@ const LedgerAccountInfo = () => { const onForgetDevice = async () => { await forgetLedger(); dispatch(setReloadAccounts(true)); - AnalyticsV2.trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_FORGOTTEN, { + trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_FORGOTTEN, { device_type: 'Ledger', }); navigation.dispatch(StackActions.pop(2)); @@ -143,12 +144,12 @@ const LedgerAccountInfo = () => { const toBlockExplorer = useCallback( (address: string) => { - const { type, rpcTarget } = provider; + const { type, rpcUrl } = provider; let accountLink: string; if (type === RPC) { const blockExplorer = - findBlockExplorerForRpc(rpcTarget, frequentRpcList) || + findBlockExplorerForRpc(rpcUrl, frequentRpcList) || NO_RPC_BLOCK_EXPLORER; accountLink = `${blockExplorer}/address/${address}`; } else { diff --git a/app/components/Views/LedgerConnect/Scan.test.tsx b/app/components/Views/LedgerConnect/Scan.test.tsx index 3962ce78e64..5fc2d17f286 100644 --- a/app/components/Views/LedgerConnect/Scan.test.tsx +++ b/app/components/Views/LedgerConnect/Scan.test.tsx @@ -7,6 +7,14 @@ import configureMockStore from 'redux-mock-store'; const mockStore = configureMockStore(); const store = mockStore({}); +jest.mock('react-native-ble-plx', () => ({ + BleManager: () => ({ + onStateChange: () => ({ + remove: jest.fn(), + }), + }), +})); + describe('Scan', () => { it('should render correctly', () => { const wrapper = shallow( diff --git a/app/components/Views/LedgerConnect/index.test.tsx b/app/components/Views/LedgerConnect/index.test.tsx index ce4f6d50226..2cc9011d82b 100644 --- a/app/components/Views/LedgerConnect/index.test.tsx +++ b/app/components/Views/LedgerConnect/index.test.tsx @@ -8,6 +8,8 @@ import configureMockStore from 'redux-mock-store'; const mockStore = configureMockStore(); const store = mockStore({}); +jest.mock('@ledgerhq/react-native-hw-transport-ble', () => null); + jest.mock('../../../core/Engine', () => ({ context: { KeyringController: { diff --git a/app/components/Views/LedgerConnect/index.tsx b/app/components/Views/LedgerConnect/index.tsx index efda5df9beb..1db5cc6b9d4 100644 --- a/app/components/Views/LedgerConnect/index.tsx +++ b/app/components/Views/LedgerConnect/index.tsx @@ -8,6 +8,7 @@ import { ActivityIndicator, } from 'react-native'; import { StackActions, useNavigation } from '@react-navigation/native'; +import { Device as NanoDevice } from '@ledgerhq/react-native-hw-transport-ble/lib/types'; import { useDispatch } from 'react-redux'; import { strings } from '../../../../locales/i18n'; import Engine from '../../../core/Engine'; @@ -31,13 +32,13 @@ import LedgerConnectionError, { import { getNavigationOptionsTitle } from '../../UI/Navbar'; import { unlockLedgerDefaultAccount } from '../../../core/Ledger/Ledger'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { LEDGER_SUPPORT_LINK } from '../../../constants/urls'; import ledgerDeviceDarkImage from '../../../images/ledger-device-dark.png'; import ledgerDeviceLightImage from '../../../images/ledger-device-light.png'; import ledgerConnectLightImage from '../../../images/ledger-connect-light.png'; import ledgerConnectDarkImage from '../../../images/ledger-connect-dark.png'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const createStyles = (theme: any) => StyleSheet.create({ @@ -113,9 +114,10 @@ const createStyles = (theme: any) => const LedgerConnect = () => { const { AccountTrackerController } = Engine.context as any; const theme = useAppThemeFromContext() ?? mockTheme; + const { trackEvent } = useMetrics(); const navigation = useNavigation(); const styles = useMemo(() => createStyles(theme), [theme]); - const [selectedDevice, setSelectedDevice] = useState(null); + const [selectedDevice, setSelectedDevice] = useState(null); const [errorDetail, setErrorDetails] = useState(); const [loading, setLoading] = useState(false); const [retryTimes, setRetryTimes] = useState(0); @@ -136,13 +138,13 @@ const LedgerConnect = () => { const connectLedger = () => { setLoading(true); - AnalyticsV2.trackEvent(MetaMetricsEvents.CONTINUE_LEDGER_HARDWARE_WALLET, { + trackEvent(MetaMetricsEvents.CONTINUE_LEDGER_HARDWARE_WALLET, { device_type: 'Ledger', }); ledgerLogicToRun(async () => { - const account = await unlockLedgerDefaultAccount(); + const account = await unlockLedgerDefaultAccount(true); await AccountTrackerController.syncBalanceWithAddresses([account]); - AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_LEDGER_SUCCESS, { + trackEvent(MetaMetricsEvents.CONNECT_LEDGER_SUCCESS, { device_type: 'Ledger', }); navigation.dispatch(StackActions.pop(2)); @@ -164,7 +166,7 @@ const LedgerConnect = () => { }; const openHowToInstallEthApp = () => { - navigation.push('Webview', { + navigation.navigate('Webview', { screen: 'SimpleWebview', params: { url: LEDGER_SUPPORT_LINK, @@ -322,7 +324,7 @@ const LedgerConnect = () => { type="confirm" onPress={connectLedger} testID={'add-network-button'} - disabled={isSendingLedgerCommands} + disabled={loading || isSendingLedgerCommands} > {loading || isSendingLedgerCommands ? ( diff --git a/app/components/Views/LockScreen/index.js b/app/components/Views/LockScreen/index.js index f8f0284a741..e04ccfdd933 100644 --- a/app/components/Views/LockScreen/index.js +++ b/app/components/Views/LockScreen/index.js @@ -13,7 +13,6 @@ import { connect } from 'react-redux'; import LottieView from 'lottie-react-native'; import { baseStyles } from '../../../styles/common'; import Logger from '../../../util/Logger'; -import { trackErrorAsAnalytics } from '../../../util/analyticsV2'; import { Authentication } from '../../../core'; import { getAssetFromTheme, @@ -23,6 +22,7 @@ import { import Routes from '../../../constants/navigation/Routes'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; import { CommonActions } from '@react-navigation/native'; +import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics'; const LOGO_SIZE = 175; const createStyles = (colors) => @@ -133,8 +133,15 @@ class LockScreen extends PureComponent { async unlockKeychain() { const { bioStateMachineId } = this.props; try { - // Retreive the credentials + // Retrieve the credentials Logger.log('Lockscreen::unlockKeychain - getting credentials'); + + // Log to provide insights into bug research. + // Check https://github.com/MetaMask/mobile-planning/issues/1507 + const { selectedAddress } = this.props; + if (typeof selectedAddress !== 'string') { + Logger.error('unlockKeychain error', 'selectedAddress is not a string'); + } await Authentication.appTriggeredAuth({ selectedAddress: this.props.selectedAddress, bioStateMachineId, diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js index 9e109aa3bc8..50282f4b630 100644 --- a/app/components/Views/Login/index.js +++ b/app/components/Views/Login/index.js @@ -42,10 +42,6 @@ import { import Routes from '../../../constants/navigation/Routes'; import { passwordRequirementsMet } from '../../../util/password'; import ErrorBoundary from '../ErrorBoundary'; -import { - trackErrorAsAnalytics, - trackEventV2 as trackEvent, -} from '../../../util/analyticsV2'; import { toLowerCaseEquals } from '../../../util/general'; import DefaultPreference from 'react-native-default-preference'; import { Authentication } from '../../../core'; @@ -61,6 +57,8 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; import { RevealSeedViewSelectorsIDs } from '../../../../e2e/selectors/Settings/SecurityAndPrivacy/RevealSeedView.selectors'; import { LoginViewSelectors } from '../../../../e2e/selectors/LoginView.selectors'; +import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; +import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics'; const deviceHeight = Device.getDeviceHeight(); const breakPoint = deviceHeight < 700; @@ -222,6 +220,10 @@ class Login extends PureComponent { * Action to set if the user is using remember me */ setAllowLoginWithRememberMe: PropTypes.func, + /** + * Metrics injected by withMetricsAwareness HOC + */ + metrics: PropTypes.object, }; state = { @@ -243,7 +245,7 @@ class Login extends PureComponent { fieldRef = React.createRef(); async componentDidMount() { - trackEvent(MetaMetricsEvents.LOGIN_SCREEN_VIEWED); + this.props.metrics.trackEvent(MetaMetricsEvents.LOGIN_SCREEN_VIEWED); BackHandler.addEventListener('hardwareBackPress', this.handleBackPress); const authData = await Authentication.getType(); @@ -366,6 +368,13 @@ class Login extends PureComponent { ); try { + // Log to provide insights into bug research. + // Check https://github.com/MetaMask/mobile-planning/issues/1507 + const { selectedAddress } = this.props; + if (typeof selectedAddress !== 'string') { + Logger.error('Login error', 'selectedAddress is not a string'); + } + await Authentication.userEntryAuth( password, authType, @@ -435,6 +444,12 @@ class Login extends PureComponent { const { current: field } = this.fieldRef; field?.blur(); try { + // Log to provide insights into bug research. + // Check https://github.com/MetaMask/mobile-planning/issues/1507 + const { selectedAddress } = this.props; + if (typeof selectedAddress !== 'string') { + Logger.error('unlockKeychain error', 'selectedAddress is not a string'); + } await Authentication.appTriggeredAuth({ selectedAddress: this.props.selectedAddress, }); @@ -627,4 +642,7 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setAllowLoginWithRememberMe(enabled)), }); -export default connect(mapStateToProps, mapDispatchToProps)(Login); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(Login)); diff --git a/app/components/Views/ManualBackupStep1/index.js b/app/components/Views/ManualBackupStep1/index.js index 952de38fb1d..378d08d6f60 100644 --- a/app/components/Views/ManualBackupStep1/index.js +++ b/app/components/Views/ManualBackupStep1/index.js @@ -4,7 +4,6 @@ import { View, SafeAreaView, ActivityIndicator, - InteractionManager, TextInput, KeyboardAvoidingView, Appearance, @@ -15,6 +14,7 @@ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view import FeatherIcons from 'react-native-vector-icons/Feather'; import { BlurView } from '@react-native-community/blur'; import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english'; +import Logger from '../../../util/Logger'; import { baseStyles } from '../../../styles/common'; import StyledButton from '../../UI/StyledButton'; import OnboardingProgress from '../../UI/OnboardingProgress'; @@ -34,9 +34,9 @@ import { uint8ArrayToMnemonic } from '../../../util/mnemonic'; import { createStyles } from './styles'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { Authentication } from '../../../core'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; /** * View that's shown during the second step of @@ -70,6 +70,10 @@ const ManualBackupStep1 = ({ route, navigation, appTheme }) => { return uint8ArrayToMnemonic(uint8ArrayMnemonic, wordlist).split(' '); }; + const track = (event, properties) => { + trackOnboarding(event, properties); + }; + useEffect(() => { const getSeedphrase = async () => { if (!words.length) { @@ -81,6 +85,7 @@ const ManualBackupStep1 = ({ route, navigation, appTheme }) => { setView(CONFIRM_PASSWORD); } } catch (e) { + Logger.error('Error trying to recover SRP from keyring-controller'); setView(CONFIRM_PASSWORD); } } @@ -109,9 +114,7 @@ const ManualBackupStep1 = ({ route, navigation, appTheme }) => { const revealSeedPhrase = () => { setSeedPhraseHidden(false); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SECURITY_PHRASE_REVEALED); - }); + track(MetaMetricsEvents.WALLET_SECURITY_PHRASE_REVEALED); }; const tryUnlockWithPassword = async (password) => { diff --git a/app/components/Views/ManualBackupStep2/index.js b/app/components/Views/ManualBackupStep2/index.js index d26f50b0e06..123341804c9 100644 --- a/app/components/Views/ManualBackupStep2/index.js +++ b/app/components/Views/ManualBackupStep2/index.js @@ -18,10 +18,10 @@ import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons'; import { getOnboardingNavbarOptions } from '../../UI/Navbar'; import { shuffle, compareMnemonics } from '../../../util/mnemonic'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { useTheme } from '../../../util/theme'; import createStyles from './styles'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const ManualBackupStep2 = ({ navigation, seedphraseBackedUp, route }) => { const { colors } = useTheme(); @@ -46,6 +46,10 @@ const ManualBackupStep2 = ({ navigation, seedphraseBackedUp, route }) => { setWordsDict(dict); }; + const track = (event, properties) => { + trackOnboarding(event, properties); + }; + const updateNavBar = useCallback(() => { navigation.setOptions(getOnboardingNavbarOptions(route, {}, colors)); }, [colors, navigation, route]); @@ -123,15 +127,13 @@ const ManualBackupStep2 = ({ navigation, seedphraseBackedUp, route }) => { const goNext = () => { if (validateWords()) { seedphraseBackedUp(); - InteractionManager.runAfterInteractions(() => { + InteractionManager.runAfterInteractions(async () => { const words = route.params?.words; navigation.navigate('ManualBackupStep3', { steps: route.params?.steps, words, }); - AnalyticsV2.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PHRASE_CONFIRMED, - ); + track(MetaMetricsEvents.WALLET_SECURITY_PHRASE_CONFIRMED); }); } else { Alert.alert( diff --git a/app/components/Views/ManualBackupStep3/index.js b/app/components/Views/ManualBackupStep3/index.js index e767cbb02d2..a5e380f60ae 100644 --- a/app/components/Views/ManualBackupStep3/index.js +++ b/app/components/Views/ManualBackupStep3/index.js @@ -7,7 +7,6 @@ import { StyleSheet, Keyboard, TouchableOpacity, - InteractionManager, } from 'react-native'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; @@ -29,10 +28,10 @@ import { SEED_PHRASE_HINTS, } from '../../../constants/storage'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import DefaultPreference from 'react-native-default-preference'; import { ThemeContext, mockTheme } from '../../../util/theme'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const createStyles = (colors) => StyleSheet.create({ @@ -118,6 +117,10 @@ class ManualBackupStep3 extends PureComponent { setOnboardingWizardStep: PropTypes.func, }; + track = (event, properties) => { + trackOnboarding(event, properties); + }; + updateNavBar = () => { const { navigation } = this.props; const colors = this.context.colors || mockTheme.colors; @@ -139,9 +142,7 @@ class ManualBackupStep3 extends PureComponent { this.setState({ hintText: manualBackup, }); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.WALLET_SECURITY_COMPLETED); - }); + this.track(MetaMetricsEvents.WALLET_SECURITY_COMPLETED); BackHandler.addEventListener(HARDWARE_BACK_PRESS, hardwareBackPress); }; @@ -187,11 +188,7 @@ class ManualBackupStep3 extends PureComponent { SEED_PHRASE_HINTS, JSON.stringify({ ...parsedHints, manualBackup: hintText }), ); - InteractionManager.runAfterInteractions(() => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_RECOVERY_HINT_SAVED, - ); - }); + this.track(MetaMetricsEvents.WALLET_SECURITY_RECOVERY_HINT_SAVED); }; done = async () => { diff --git a/app/components/Views/NetworkSelector/NetworkSelector.test.tsx b/app/components/Views/NetworkSelector/NetworkSelector.test.tsx index ab737d93190..6c8f9df227f 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.test.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.test.tsx @@ -55,18 +55,18 @@ const initialState = { type: 'mainnet', nickname: 'Ethereum mainnet', ticket: 'eth', - chainId: '1', + chainId: '0x1', }, networkConfigurations: { networkId1: { - chainId: '43114', + chainId: '0xa86a', nickname: 'Avalanche Mainnet C-Chain', rpcPrefs: { blockExplorerUrl: 'https://snowtrace.io' }, rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', ticker: 'AVAX', }, networkId2: { - chainId: '137', + chainId: '0x89', nickname: 'Polygon Mainnet', rpcPrefs: { blockExplorerUrl: 'https://polygonscan.com' }, rpcUrl: @@ -74,7 +74,7 @@ const initialState = { ticker: 'MATIC', }, networkId3: { - chainId: '10', + chainId: '0xa', nickname: 'Optimism', rpcPrefs: { blockExplorerUrl: 'https://optimistic.etherscan.io' }, rpcUrl: @@ -82,7 +82,7 @@ const initialState = { ticker: 'ETH', }, networkId4: { - chainId: '100', + chainId: '0x64', nickname: 'Gnosis Chain', rpcPrefs: { blockExplorerUrl: 'https://blockscout.com/xdai/mainnet/', @@ -104,8 +104,8 @@ const initialState = { }, }, NftController: { - allNfts: { '0x': { '1': [] } }, - allNftContracts: { '0x': { '1': [] } }, + allNfts: { '0x': { '0x1': [] } }, + allNftContracts: { '0x': { '0x1': [] } }, }, }, }, @@ -160,7 +160,7 @@ describe('Network Selector', () => { type: 'mainnet', nickname: 'Goerli mainnet', ticket: 'eth', - chainId: '5', + chainId: '0x5', }, }, }, diff --git a/app/components/Views/NetworkSelector/NetworkSelector.tsx b/app/components/Views/NetworkSelector/NetworkSelector.tsx index 511472e8660..500c1488b4e 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.tsx @@ -25,6 +25,7 @@ import { selectShowTestNetworks } from '../../../selectors/preferencesController import Networks, { compareRpcUrls, getAllNetworks, + getDecimalChainId, getNetworkImageSource, isTestNet, } from '../../../util/networks'; @@ -36,7 +37,6 @@ import { ButtonWidthTypes, } from '../../../component-library/components/Buttons/Button'; import Engine from '../../../core/Engine'; -import analyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; import Routes from '../../../constants/navigation/Routes'; import generateTestId from '../../../../wdio/utils/generateTestId'; @@ -51,6 +51,8 @@ import { TextColor, TextVariant, } from '../../../component-library/components/Texts/Text'; +import { updateIncomingTransactions } from '../../../util/transaction-controller'; +import { useMetrics } from '../../../components/hooks/useMetrics'; // Internal dependencies import styles from './NetworkSelector.styles'; @@ -58,6 +60,7 @@ import styles from './NetworkSelector.styles'; const NetworkSelector = () => { const { navigate } = useNavigation(); const theme = useTheme(); + const { trackEvent } = useMetrics(); const { colors } = theme; const sheetRef = useRef(null); const showTestNetworks = useSelector(selectShowTestNetworks); @@ -66,20 +69,24 @@ const NetworkSelector = () => { const networkConfigurations = useSelector(selectNetworkConfigurations); const onNetworkChange = (type: string) => { - const { NetworkController, CurrencyRateController, TransactionController } = - Engine.context; + const { + NetworkController, + CurrencyRateController, + AccountTrackerController, + } = Engine.context; CurrencyRateController.setNativeCurrency('ETH'); NetworkController.setProviderType(type); + AccountTrackerController.refresh(); setTimeout(async () => { - await TransactionController.updateIncomingTransactions(); + await updateIncomingTransactions(); }, 1000); sheetRef.current?.onCloseBottomSheet(); - analyticsV2.trackEvent(MetaMetricsEvents.NETWORK_SWITCHED, { - chain_id: providerConfig.chainId, + trackEvent(MetaMetricsEvents.NETWORK_SWITCHED, { + chain_id: getDecimalChainId(providerConfig.chainId), from_network: providerConfig.type === 'rpc' ? providerConfig.nickname @@ -104,8 +111,8 @@ const NetworkSelector = () => { NetworkController.setActiveNetwork(networkConfigurationId); sheetRef.current?.onCloseBottomSheet(); - analyticsV2.trackEvent(MetaMetricsEvents.NETWORK_SWITCHED, { - chain_id: providerConfig.chainId, + trackEvent(MetaMetricsEvents.NETWORK_SWITCHED, { + chain_id: getDecimalChainId(providerConfig.chainId), from_network: providerConfig.type, to_network: nickname, }); @@ -124,8 +131,7 @@ const NetworkSelector = () => { imageSource: images.ETHEREUM, }} isSelected={ - chainId.toString() === providerConfig.chainId && - !providerConfig.rpcTarget + chainId === providerConfig.chainId && !providerConfig.rpcUrl } onPress={() => onNetworkChange(MAINNET)} style={styles.networkCell} @@ -144,7 +150,7 @@ const NetworkSelector = () => { name: lineaMainnetName, imageSource: images['LINEA-MAINNET'], }} - isSelected={chainId.toString() === providerConfig.chainId} + isSelected={chainId === providerConfig.chainId} onPress={() => onNetworkChange(LINEA_MAINNET)} /> ); @@ -169,8 +175,7 @@ const NetworkSelector = () => { imageSource: image, }} isSelected={Boolean( - chainId.toString() === providerConfig.chainId && - providerConfig.rpcTarget, + chainId === providerConfig.chainId && providerConfig.rpcUrl, )} onPress={() => onSetRpcTarget(rpcUrl)} style={styles.networkCell} @@ -195,7 +200,7 @@ const NetworkSelector = () => { name, imageSource, }} - isSelected={chainId.toString() === providerConfig.chainId} + isSelected={chainId === providerConfig.chainId} onPress={() => onNetworkChange(networkType)} style={styles.networkCell} /> diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js index f54be07c796..bcbadbc9c3b 100644 --- a/app/components/Views/Onboarding/index.js +++ b/app/components/Views/Onboarding/index.js @@ -26,8 +26,6 @@ import { strings } from '../../../../locales/i18n'; import Button from '@metamask/react-native-button'; import { connect } from 'react-redux'; import FadeOutOverlay from '../../UI/FadeOutOverlay'; -import Analytics from '../../../core/Analytics/Analytics'; -import { saveOnboardingEvent } from '../../../actions/onboarding'; import { getTransparentBackOnboardingNavbarOptions, getTransparentOnboardingNavbarOptions, @@ -39,11 +37,9 @@ import { loadingSet, loadingUnset } from '../../../actions/user'; import PreventScreenshot from '../../../core/PreventScreenshot'; import WarningExistingUserModal from '../../UI/WarningExistingUserModal'; import { PREVIOUS_SCREEN, ONBOARDING } from '../../../constants/navigation'; -import { EXISTING_USER, METRICS_OPT_IN } from '../../../constants/storage'; +import { EXISTING_USER } from '../../../constants/storage'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; - -import DefaultPreference from 'react-native-default-preference'; +import { withMetricsAwareness } from '../../hooks/useMetrics'; import { Authentication } from '../../../core'; import { ThemeContext, mockTheme } from '../../../util/theme'; import AnimatedFox from 'react-native-animated-fox'; @@ -51,6 +47,7 @@ import { OnboardingSelectorIDs } from '../../../../e2e/selectors/Onboarding/Onbo import Routes from '../../../constants/navigation/Routes'; import { selectAccounts } from '../../../selectors/accountTrackerController'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; const createStyles = (colors) => StyleSheet.create({ @@ -140,10 +137,6 @@ class Onboarding extends PureComponent { * redux flag that indicates if the user set a password */ passwordSet: PropTypes.bool, - /** - * Save onboarding event to state - */ - saveOnboardingEvent: PropTypes.func, /** * loading status */ @@ -164,6 +157,10 @@ class Onboarding 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, }; notificationAnimated = new Animated.Value(100); @@ -276,8 +273,8 @@ class Onboarding extends PureComponent { onPressCreate = () => { const action = async () => { - const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN); - if (metricsOptIn) { + const { metrics } = this.props; + if (metrics.isEnabled()) { this.props.navigation.navigate('ChoosePassword', { [PREVIOUS_SCREEN]: ONBOARDING, }); @@ -298,8 +295,8 @@ class Onboarding extends PureComponent { onPressImport = () => { const action = async () => { - const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN); - if (metricsOptIn) { + const { metrics } = this.props; + if (metrics.isEnabled()) { this.props.navigation.push( Routes.ONBOARDING.IMPORT_FROM_SECRET_RECOVERY_PHRASE, ); @@ -318,17 +315,8 @@ class Onboarding extends PureComponent { this.handleExistingUser(action); }; - track = (...eventArgs) => { - InteractionManager.runAfterInteractions(async () => { - if (Analytics.checkEnabled()) { - AnalyticsV2.trackEvent(...eventArgs); - return; - } - const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN); - if (!metricsOptIn) { - this.props.saveOnboardingEvent(eventArgs); - } - }); + track = (event) => { + trackOnboarding(event); }; alertExistingUser = (callback) => { @@ -497,7 +485,9 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ setLoading: (msg) => dispatch(loadingSet(msg)), unsetLoading: () => dispatch(loadingUnset()), - saveOnboardingEvent: (event) => dispatch(saveOnboardingEvent(event)), }); -export default connect(mapStateToProps, mapDispatchToProps)(Onboarding); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(Onboarding)); diff --git a/app/components/Views/OnboardingCarousel/index.js b/app/components/Views/OnboardingCarousel/index.js index 0a9932838a8..a2c4b29d44b 100644 --- a/app/components/Views/OnboardingCarousel/index.js +++ b/app/components/Views/OnboardingCarousel/index.js @@ -7,7 +7,6 @@ import { StyleSheet, Image, Dimensions, - InteractionManager, Platform, } from 'react-native'; import { MetaMetricsEvents } from '../../../core/Analytics'; @@ -21,13 +20,12 @@ import OnboardingScreenWithBg from '../../UI/OnboardingScreenWithBg'; import Device from '../../../util/device'; import { saveOnboardingEvent } from '../../../actions/onboarding'; import { connect } from 'react-redux'; -import AnalyticsV2 from '../../../util/analyticsV2'; -import DefaultPreference from 'react-native-default-preference'; -import { METRICS_OPT_IN } from '../../../constants/storage'; import { ThemeContext, mockTheme } from '../../../util/theme'; import { WELCOME_SCREEN_CAROUSEL_TITLE_ID } from '../../../../wdio/screen-objects/testIDs/Screens/WelcomeScreen.testIds'; import { OnboardingCarouselSelectorIDs } from '../../../../e2e/selectors/Onboarding/OnboardingCarousel.selectors'; import generateTestId from '../../../../wdio/utils/generateTestId'; +import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; + const IMAGE_3_RATIO = 215 / 315; const IMAGE_2_RATIO = 222 / 239; const IMAGE_1_RATIO = 285 / 203; @@ -143,27 +141,20 @@ class OnboardingCarousel extends PureComponent { currentTab: 1, }; - trackEvent = (eventArgs) => { - InteractionManager.runAfterInteractions(async () => { - const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN); - if (metricsOptIn) { - AnalyticsV2.trackEvent(eventArgs); - } else { - this.props.saveOnboardingEvent(eventArgs); - } - }); + track = (event, properties) => { + trackOnboarding(event, properties, this.props.saveOnboardingEvent); }; onPressGetStarted = () => { this.props.navigation.navigate('Onboarding'); - this.trackEvent(MetaMetricsEvents.ONBOARDING_STARTED); + this.track(MetaMetricsEvents.ONBOARDING_STARTED); }; renderTabBar = () => ; onChangeTab = (obj) => { this.setState({ currentTab: obj.i + 1 }); - this.trackEvent(MetaMetricsEvents.ONBOARDING_WELCOME_SCREEN_ENGAGEMENT, { + this.track(MetaMetricsEvents.ONBOARDING_WELCOME_SCREEN_ENGAGEMENT, { message_title: strings(`onboarding_carousel.title${[obj.i + 1]}`, { locale: 'en', }), @@ -179,7 +170,7 @@ class OnboardingCarousel extends PureComponent { componentDidMount = () => { this.updateNavBar(); - this.trackEvent(MetaMetricsEvents.ONBOARDING_WELCOME_MESSAGE_VIEWED); + this.track(MetaMetricsEvents.ONBOARDING_WELCOME_MESSAGE_VIEWED); }; componentDidUpdate = () => { diff --git a/app/components/Views/Quiz/SRPQuiz/SRPQuiz.tsx b/app/components/Views/Quiz/SRPQuiz/SRPQuiz.tsx index f57d562cd09..d1a0816abdf 100644 --- a/app/components/Views/Quiz/SRPQuiz/SRPQuiz.tsx +++ b/app/components/Views/Quiz/SRPQuiz/SRPQuiz.tsx @@ -10,7 +10,6 @@ import Icon, { } from '../../../../component-library/components/Icons/Icon'; import { useStyles } from '../../../hooks/useStyles'; import { strings } from '../../../../../locales/i18n'; -import AnalyticsV2 from '../../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import Routes from '../../../../constants/navigation/Routes'; import { SRP_GUIDE_URL } from '../../../../constants/urls'; @@ -18,6 +17,7 @@ import { SRP_GUIDE_URL } from '../../../../constants/urls'; import { QuizStage } from '../types'; import { QuizContent } from '../QuizContent'; import stylesheet from './styles'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; const introductionImg = require('../../../../images/reveal-srp.png'); @@ -27,6 +27,7 @@ const SRPQuiz = () => { const { styles, theme } = useStyles(stylesheet, {}); const { colors } = theme; const navigation = useNavigation(); + const { trackEvent } = useMetrics(); const dismissModal = (): void => { modalRef.current?.dismissModal(); @@ -66,16 +67,16 @@ const SRPQuiz = () => { ); const goToRevealPrivateCredential = useCallback((): void => { - AnalyticsV2.trackEvent(MetaMetricsEvents.REVEAL_SRP_INITIATED, {}); - AnalyticsV2.trackEvent(MetaMetricsEvents.REVEAL_SRP_CTA, {}); + trackEvent(MetaMetricsEvents.REVEAL_SRP_INITIATED, {}); + trackEvent(MetaMetricsEvents.REVEAL_SRP_CTA, {}); navigation.navigate(Routes.SETTINGS.REVEAL_PRIVATE_CREDENTIAL, { credentialName: 'seed_phrase', shouldUpdateNav: true, }); - }, [navigation]); + }, [navigation, trackEvent]); const introduction = useCallback(() => { - AnalyticsV2.trackEvent(MetaMetricsEvents.SRP_REVEAL_QUIZ_PROMPT_SEEN, {}); + trackEvent(MetaMetricsEvents.SRP_REVEAL_QUIZ_PROMPT_SEEN, {}); return ( { { label: strings('srp_security_quiz.get_started'), onPress: () => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.SRP_REVEAL_START_CTA_SELECTED, - {}, - ); + trackEvent(MetaMetricsEvents.SRP_REVEAL_START_CTA_SELECTED, {}); setStage(QuizStage.questionOne); }, variant: ButtonVariants.Primary, @@ -104,13 +102,10 @@ const SRPQuiz = () => { dismiss={dismissModal} /> ); - }, []); + }, [trackEvent]); const questionOne = useCallback((): Element => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_SEEN, - {}, - ); + trackEvent(MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_SEEN, {}); return ( { dismiss={dismissModal} /> ); - }, []); + }, [trackEvent]); const rightAnswerQuestionOne = useCallback((): Element => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_RIGHT_ASNWER, - {}, - ); + trackEvent(MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_RIGHT_ASNWER, {}); return ( { dismiss={dismissModal} /> ); - }, [rightAnswerIcon, styles.rightText]); + }, [rightAnswerIcon, styles.rightText, trackEvent]); const wrongAnswerQuestionOne = useCallback((): Element => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_WRONG_ANSWER, - {}, - ); + trackEvent(MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_WRONG_ANSWER, {}); return ( { dismiss={dismissModal} /> ); - }, [styles.wrongText, wrongAnswerIcon]); + }, [styles.wrongText, wrongAnswerIcon, trackEvent]); const questionTwo = useCallback((): Element => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_SEEN, - {}, - ); + trackEvent(MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_SEEN, {}); return ( { dismiss={dismissModal} /> ); - }, []); + }, [trackEvent]); const rightAnswerQuestionTwo = useCallback((): Element => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_RIGHT_ASNWER, - {}, - ); + trackEvent(MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_RIGHT_ASNWER, {}); return ( { dismiss={dismissModal} /> ); - }, [goToRevealPrivateCredential, rightAnswerIcon, styles.rightText]); + }, [ + goToRevealPrivateCredential, + rightAnswerIcon, + styles.rightText, + trackEvent, + ]); const wrongAnswerQuestionTwo = useCallback((): Element => { - AnalyticsV2.trackEvent( - MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_WRONG_ANSWER, - {}, - ); + trackEvent(MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_WRONG_ANSWER, {}); return ( { dismiss={dismissModal} /> ); - }, [styles.wrongText, wrongAnswerIcon]); + }, [styles.wrongText, wrongAnswerIcon, trackEvent]); const quizPage = useCallback(() => { switch (stage) { diff --git a/app/components/Views/RestoreWallet/RestoreWallet.tsx b/app/components/Views/RestoreWallet/RestoreWallet.tsx index 2b6c786632f..2333630949a 100644 --- a/app/components/Views/RestoreWallet/RestoreWallet.tsx +++ b/app/components/Views/RestoreWallet/RestoreWallet.tsx @@ -19,9 +19,10 @@ import { useAppThemeFromContext } from '../../../util/theme'; import { createWalletResetNeededNavDetails } from './WalletResetNeeded'; import { createWalletRestoredNavDetails } from './WalletRestored'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import { trackEventV2 as trackEvent } from '../../../util/analyticsV2'; + import generateDeviceAnalyticsMetaData from '../../../util/metrics'; import { StackNavigationProp } from '@react-navigation/stack'; +import { useMetrics } from '../../../components/hooks/useMetrics'; /* eslint-disable import/no-commonjs, @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ const onboardingDeviceImage = require('../../../images/swaps_onboard_device.png'); @@ -43,6 +44,7 @@ export const createRestoreWalletNavDetailsNested = ); const RestoreWallet = () => { + const { trackEvent } = useMetrics(); const { colors } = useAppThemeFromContext(); const styles = createStyles(colors); @@ -58,10 +60,11 @@ const RestoreWallet = () => { MetaMetricsEvents.VAULT_CORRUPTION_RESTORE_WALLET_SCREEN_VIEWED, { ...deviceMetaData, previousScreen }, ); - }, [deviceMetaData, previousScreen]); + }, [deviceMetaData, previousScreen, trackEvent]); const handleOnNext = useCallback(async (): Promise => { setLoading(true); + trackEvent( MetaMetricsEvents.VAULT_CORRUPTION_RESTORE_WALLET_BUTTON_PRESSED, deviceMetaData, @@ -74,7 +77,7 @@ const RestoreWallet = () => { replace(...createWalletResetNeededNavDetails()); setLoading(false); } - }, [deviceMetaData, replace]); + }, [deviceMetaData, replace, trackEvent]); return ( diff --git a/app/components/Views/RestoreWallet/WalletResetNeeded.tsx b/app/components/Views/RestoreWallet/WalletResetNeeded.tsx index 321e892a6e1..8fb3ecfbeeb 100644 --- a/app/components/Views/RestoreWallet/WalletResetNeeded.tsx +++ b/app/components/Views/RestoreWallet/WalletResetNeeded.tsx @@ -18,8 +18,8 @@ import { useNavigation } from '@react-navigation/native'; import { StackNavigationProp } from '@react-navigation/stack'; import { createRestoreWalletNavDetails } from './RestoreWallet'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import { trackEventV2 as trackEvent } from '../../../util/analyticsV2'; import generateDeviceAnalyticsMetaData from '../../../util/metrics'; +import { useMetrics } from '../../../components/hooks/useMetrics'; export const createWalletResetNeededNavDetails = createNavigationDetails( Routes.VAULT_RECOVERY.WALLET_RESET_NEEDED, @@ -27,6 +27,7 @@ export const createWalletResetNeededNavDetails = createNavigationDetails( const WalletResetNeeded = () => { const { colors } = useAppThemeFromContext(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const navigation = useNavigation>(); @@ -38,7 +39,7 @@ const WalletResetNeeded = () => { MetaMetricsEvents.VAULT_CORRUPTION_WALLET_RESET_NEEDED_SCREEN_VIEWED, deviceMetaData, ); - }, [deviceMetaData]); + }, [trackEvent, deviceMetaData]); const handleCreateNewWallet = useCallback(async () => { trackEvent( @@ -48,7 +49,7 @@ const WalletResetNeeded = () => { navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.MODAL.DELETE_WALLET, }); - }, [deviceMetaData, navigation]); + }, [deviceMetaData, navigation, trackEvent]); const handleTryAgain = useCallback(async () => { trackEvent( @@ -60,7 +61,7 @@ const WalletResetNeeded = () => { previousScreen: Routes.VAULT_RECOVERY.WALLET_RESET_NEEDED, }), ); - }, [deviceMetaData, navigation]); + }, [deviceMetaData, navigation, trackEvent]); return ( diff --git a/app/components/Views/RestoreWallet/WalletRestored.tsx b/app/components/Views/RestoreWallet/WalletRestored.tsx index 78c90d211de..d481bc883e7 100644 --- a/app/components/Views/RestoreWallet/WalletRestored.tsx +++ b/app/components/Views/RestoreWallet/WalletRestored.tsx @@ -13,6 +13,7 @@ import Text, { TextVariant, } from '../../../component-library/components/Texts/Text'; import StyledButton from '../../UI/StyledButton'; +import Logger from '../../../util/Logger'; import { createNavigationDetails } from '../../../util/navigation/navUtils'; import Routes from '../../../constants/navigation/Routes'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -21,11 +22,11 @@ import { useNavigation } from '@react-navigation/native'; import { Authentication } from '../../../core'; import { useAppThemeFromContext } from '../../../util/theme'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import { trackEventV2 as trackEvent } from '../../../util/analyticsV2'; import generateDeviceAnalyticsMetaData from '../../../util/metrics'; import { SRP_GUIDE_URL } from '../../../constants/urls'; import { StackNavigationProp } from '@react-navigation/stack'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; +import { useMetrics } from '../../../components/hooks/useMetrics'; export const createWalletRestoredNavDetails = createNavigationDetails( Routes.VAULT_RECOVERY.WALLET_RESTORED, @@ -34,6 +35,7 @@ export const createWalletRestoredNavDetails = createNavigationDetails( const WalletRestored = () => { const [loading, setLoading] = useState(false); const { colors } = useAppThemeFromContext(); + const { trackEvent } = useMetrics(); const styles = createStyles(colors); const navigation = useNavigation>(); const selectedAddress = useSelector(selectSelectedAddress); @@ -45,10 +47,15 @@ const WalletRestored = () => { MetaMetricsEvents.VAULT_CORRUPTION_WALLET_SUCCESSFULLY_RESTORED_SCREEN_VIEWED, deviceMetaData, ); - }, [deviceMetaData]); + }, [deviceMetaData, trackEvent]); const finishWalletRestore = useCallback(async (): Promise => { try { + // Log to provide insights into bug research. + // Check https://github.com/MetaMask/mobile-planning/issues/1507 + if (typeof selectedAddress !== 'string') { + Logger.error('Wallet restore error', 'selectedAddress is not a string'); + } await Authentication.appTriggeredAuth({ selectedAddress }); navigation.replace(Routes.ONBOARDING.HOME_NAV); } catch (e) { @@ -68,7 +75,7 @@ const WalletRestored = () => { deviceMetaData, ); await finishWalletRestore(); - }, [deviceMetaData, finishWalletRestore]); + }, [deviceMetaData, finishWalletRestore, trackEvent]); return ( diff --git a/app/components/Views/RestoreWallet/styles.ts b/app/components/Views/RestoreWallet/styles.ts index e57f1e5e6e6..16070c45870 100644 --- a/app/components/Views/RestoreWallet/styles.ts +++ b/app/components/Views/RestoreWallet/styles.ts @@ -1,5 +1,5 @@ /* eslint-disable import/prefer-default-export */ -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; import { StyleSheet } from 'react-native'; export const createStyles = (colors: ThemeColors) => diff --git a/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx b/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx index 1c00b147e8c..a9dbab7fe4a 100644 --- a/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx +++ b/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx @@ -39,7 +39,6 @@ import { useTheme } from '../../../util/theme'; import Engine from '../../../core/Engine'; import { BIOMETRY_CHOICE } from '../../../constants/storage'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; import { uint8ArrayToMnemonic } from '../../../util/mnemonic'; import { passwordRequirementsMet } from '../../../util/password'; import { Authentication } from '../../../core/'; @@ -54,6 +53,7 @@ import generateTestId from '../../../../wdio/utils/generateTestId'; import { RevealSeedViewSelectorsIDs } from '../../../../e2e/selectors/Settings/SecurityAndPrivacy/RevealSeedView.selectors'; import { selectSelectedAddress } from '../../../selectors/preferencesController'; +import { useMetrics } from '../../../components/hooks/useMetrics'; const PRIVATE_KEY = 'private_key'; @@ -89,6 +89,7 @@ const RevealPrivateCredential = ({ const dispatch = useDispatch(); const theme = useTheme(); + const { trackEvent } = useMetrics(); const { colors, themeAppearance } = theme; const styles = createStyles(theme); @@ -153,7 +154,7 @@ const RevealPrivateCredential = ({ updateNavBar(); // Track SRP Reveal screen rendered if (!isPrivateKey) { - AnalyticsV2.trackEvent(MetaMetricsEvents.REVEAL_SRP_SCREEN, {}); + trackEvent(MetaMetricsEvents.REVEAL_SRP_SCREEN); } const unlockWithBiometrics = async () => { @@ -186,15 +187,14 @@ const RevealPrivateCredential = ({ const cancelReveal = () => { if (!unlocked) - AnalyticsV2.trackEvent( + trackEvent( isPrivateKey ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_CANCELLED : MetaMetricsEvents.REVEAL_SRP_CANCELLED, { view: 'Enter password' }, ); - if (!isPrivateKey) - AnalyticsV2.trackEvent(MetaMetricsEvents.CANCEL_REVEAL_SRP_CTA, {}); + if (!isPrivateKey) trackEvent(MetaMetricsEvents.CANCEL_REVEAL_SRP_CTA); if (cancel) return cancel(); navigateBack(); }; @@ -212,7 +212,7 @@ const RevealPrivateCredential = ({ if (!isPrivateKey) { const currentDate = new Date(); dispatch(recordSRPRevealTimestamp(currentDate.toString())); - AnalyticsV2.trackEvent(MetaMetricsEvents.NEXT_REVEAL_SRP_CTA, {}); + trackEvent(MetaMetricsEvents.NEXT_REVEAL_SRP_CTA); } setIsModalVisible(true); setWarningIncorrectPassword(''); @@ -223,22 +223,21 @@ const RevealPrivateCredential = ({ }; const done = () => { - if (!isPrivateKey) - AnalyticsV2.trackEvent(MetaMetricsEvents.SRP_DONE_CTA, {}); + if (!isPrivateKey) trackEvent(MetaMetricsEvents.SRP_DONE_CTA); navigateBack(); }; const copyPrivateCredentialToClipboard = async ( privCredentialName: string, ) => { - AnalyticsV2.trackEvent( + trackEvent( privCredentialName === PRIVATE_KEY ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED : MetaMetricsEvents.REVEAL_SRP_COMPLETED, { action: 'copied to clipboard' }, ); - if (!isPrivateKey) AnalyticsV2.trackEvent(MetaMetricsEvents.COPY_SRP, {}); + if (!isPrivateKey) trackEvent(MetaMetricsEvents.COPY_SRP); await ClipboardManager.setStringExpire(clipboardPrivateCredential); @@ -283,24 +282,23 @@ const RevealPrivateCredential = ({ const onTabBarChange = (event: { i: number }) => { if (event.i === 0) { - AnalyticsV2.trackEvent( + trackEvent( isPrivateKey ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED : MetaMetricsEvents.REVEAL_SRP_COMPLETED, { action: 'viewed SRP' }, ); - if (!isPrivateKey) AnalyticsV2.trackEvent(MetaMetricsEvents.VIEW_SRP, {}); + if (!isPrivateKey) trackEvent(MetaMetricsEvents.VIEW_SRP); } else if (event.i === 1) { - AnalyticsV2.trackEvent( + trackEvent( isPrivateKey ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED : MetaMetricsEvents.REVEAL_SRP_COMPLETED, { action: 'viewed QR code' }, ); - if (!isPrivateKey) - AnalyticsV2.trackEvent(MetaMetricsEvents.VIEW_SRP_QR, {}); + if (!isPrivateKey) trackEvent(MetaMetricsEvents.VIEW_SRP_QR); } }; @@ -395,17 +393,14 @@ const RevealPrivateCredential = ({ ); const closeModal = () => { - AnalyticsV2.trackEvent( + trackEvent( isPrivateKey ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_CANCELLED : MetaMetricsEvents.REVEAL_SRP_CANCELLED, { view: 'Hold to reveal' }, ); - AnalyticsV2.trackEvent( - MetaMetricsEvents.SRP_DISMISS_HOLD_TO_REVEAL_DIALOG, - {}, - ); + trackEvent(MetaMetricsEvents.SRP_DISMISS_HOLD_TO_REVEAL_DIALOG); setIsModalVisible(false); }; diff --git a/app/components/Views/SDKSessionsManager/SDKSessionItem.tsx b/app/components/Views/SDKSessionsManager/SDKSessionItem.tsx index f70d0b920e7..fe09a15ae15 100644 --- a/app/components/Views/SDKSessionsManager/SDKSessionItem.tsx +++ b/app/components/Views/SDKSessionsManager/SDKSessionItem.tsx @@ -1,5 +1,5 @@ -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; -import { ThemeTypography } from '@metamask/design-tokens/dist/js/typography'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; +import type { ThemeTypography } from '@metamask/design-tokens/dist/types/js/typography'; import React, { useEffect, useState } from 'react'; import { StyleSheet, TextStyle, View } from 'react-native'; import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context'; diff --git a/app/components/Views/SDKSessionsManager/SDKSessionsManager.tsx b/app/components/Views/SDKSessionsManager/SDKSessionsManager.tsx index def3e13913a..7dc4a90dea1 100644 --- a/app/components/Views/SDKSessionsManager/SDKSessionsManager.tsx +++ b/app/components/Views/SDKSessionsManager/SDKSessionsManager.tsx @@ -5,8 +5,8 @@ import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context'; import { strings } from '../../../../locales/i18n'; import { useTheme } from '../../../util/theme'; -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; -import { ThemeTypography } from '@metamask/design-tokens/dist/js/typography'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; +import type { ThemeTypography } from '@metamask/design-tokens/dist/types/js/typography'; import { SDKSelectorsIDs } from '../../../../e2e/selectors/Settings/SDK.selectors'; import ActionModal from '../../../components/UI/ActionModal'; import { getNavigationOptionsTitle } from '../../../components/UI/Navbar'; diff --git a/app/components/Views/Settings/AdvancedSettings/EthSignFriction/EthSignFriction.styles.ts b/app/components/Views/Settings/AdvancedSettings/EthSignFriction/EthSignFriction.styles.ts index d76209d01d5..068b14f1008 100644 --- a/app/components/Views/Settings/AdvancedSettings/EthSignFriction/EthSignFriction.styles.ts +++ b/app/components/Views/Settings/AdvancedSettings/EthSignFriction/EthSignFriction.styles.ts @@ -1,7 +1,7 @@ // Third party dependencies. import { StyleSheet, TextStyle } from 'react-native'; import { typography } from '@metamask/design-tokens'; -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; /** * Style sheet function for EthSignFriction component. * diff --git a/app/components/Views/Settings/AdvancedSettings/index.js b/app/components/Views/Settings/AdvancedSettings/index.js index aa6e08e275e..ab5691a2bd5 100644 --- a/app/components/Views/Settings/AdvancedSettings/index.js +++ b/app/components/Views/Settings/AdvancedSettings/index.js @@ -36,7 +36,7 @@ import { selectUseTokenDetection, } from '../../../../selectors/preferencesController'; import Routes from '../../../../constants/navigation/Routes'; -import { trackEventV2 as trackEvent } from '../../../../util/analyticsV2'; + import { MetaMetricsEvents } from '../../../../core/Analytics'; import { AdvancedViewSelectorsIDs } from '../../../../../e2e/selectors/Settings/AdvancedView.selectors'; import Text, { @@ -52,6 +52,8 @@ import Banner, { BannerAlertSeverity, BannerVariant, } from '../../../../component-library/components/Banners/Banner'; +import { withMetricsAwareness } from '../../../../components/hooks/useMetrics'; +import { wipeTransactions } from '../../../../util/transaction-controller'; const createStyles = (colors) => StyleSheet.create({ @@ -185,6 +187,10 @@ class AdvancedSettings 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, }; scrollView = React.createRef(); @@ -240,9 +246,8 @@ class AdvancedSettings extends PureComponent { }; resetAccount = () => { - const { TransactionController } = Engine.context; const { navigation } = this.props; - TransactionController.wipeTransactions(true); + wipeTransactions(true); navigation.navigate('WalletView'); }; @@ -291,7 +296,9 @@ class AdvancedSettings extends PureComponent { // Disable eth_sign directly without friction const { PreferencesController } = Engine.context; PreferencesController.setDisabledRpcMethodPreference('eth_sign', false); - trackEvent(MetaMetricsEvents.SETTINGS_ADVANCED_ETH_SIGN_DISABLED, {}); + this.props.metrics.trackEvent( + MetaMetricsEvents.SETTINGS_ADVANCED_ETH_SIGN_DISABLED, + ); } }; @@ -552,4 +559,7 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setShowCustomNonce(showCustomNonce)), }); -export default connect(mapStateToProps, mapDispatchToProps)(AdvancedSettings); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(AdvancedSettings)); diff --git a/app/components/Views/Settings/Contacts/AmbiguousAddressSheet/AmbiguousAddressSheet.styles.ts b/app/components/Views/Settings/Contacts/AmbiguousAddressSheet/AmbiguousAddressSheet.styles.ts index 5cf4b710c6d..f03505a6ba7 100644 --- a/app/components/Views/Settings/Contacts/AmbiguousAddressSheet/AmbiguousAddressSheet.styles.ts +++ b/app/components/Views/Settings/Contacts/AmbiguousAddressSheet/AmbiguousAddressSheet.styles.ts @@ -1,7 +1,7 @@ // Third party dependencies. import { StyleSheet, TextStyle } from 'react-native'; import { typography } from '@metamask/design-tokens'; -import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types'; +import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; /** * Style sheet function for AmbiguousAddressSheet component. * diff --git a/app/components/Views/Settings/Contacts/ContactForm/index.js b/app/components/Views/Settings/Contacts/ContactForm/index.js index 4242c3ee0cc..cbdc91e0d01 100644 --- a/app/components/Views/Settings/Contacts/ContactForm/index.js +++ b/app/components/Views/Settings/Contacts/ContactForm/index.js @@ -21,7 +21,7 @@ import { renderShortAddress, validateAddressOrENS, } from '../../../../../util/address'; -import ErrorMessage from '../../../SendFlow/ErrorMessage'; +import ErrorMessage from '../../../confirmations/SendFlow/ErrorMessage'; import AntIcon from 'react-native-vector-icons/AntDesign'; import ActionSheet from 'react-native-actionsheet'; import { mockTheme, ThemeContext } from '../../../../../util/theme'; diff --git a/app/components/Views/Settings/Contacts/index.js b/app/components/Views/Settings/Contacts/index.js index 48809f72878..e1c1e68d4ac 100644 --- a/app/components/Views/Settings/Contacts/index.js +++ b/app/components/Views/Settings/Contacts/index.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { strings } from '../../../../../locales/i18n'; import { getNavigationOptionsTitle } from '../../../UI/Navbar'; import { connect } from 'react-redux'; -import AddressList from '../../SendFlow/AddressList'; +import AddressList from '../../confirmations/SendFlow/AddressList'; import StyledButton from '../../../UI/StyledButton'; import Engine from '../../../../core/Engine'; import ActionSheet from 'react-native-actionsheet'; diff --git a/app/components/Views/Settings/ExperimentalSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/Settings/ExperimentalSettings/__snapshots__/index.test.tsx.snap index ce70653185c..3c174754ac1 100644 --- a/app/components/Views/Settings/ExperimentalSettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/Settings/ExperimentalSettings/__snapshots__/index.test.tsx.snap @@ -1,325 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ExperimentalSettings should render blockaid toggle button 1`] = ` -Array [ - - - - WalletConnect Sessions - - - View the list of active WalletConnect sessions - - - - View sessions - - - - Security - - - - - Code Lockdown - - - - - This is a test feature that blocks any changes to the app's JavaScript code without permission. - - - - Learn more - - - . - - - - - Security alerts - - - This feature alerts you to malicious activity by locally reviewing your transaction and signature requests. Always do your own due diligence before approving any requests. There's no guarantee that this feature will detect all malicious activity. By enabling this feature you agree to the provider's terms of use. - - - - - Blockaid - - - - - Privacy preserving - no data is shared with third parties. Available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Optimism and Polygon. - - - , - ",", -] -`; - exports[`ExperimentalSettings should render correctly 1`] = ` Array [ - - - Security alerts - - - This feature alerts you to malicious activity by locally reviewing your transaction and signature requests. Always do your own due diligence before approving any requests. There's no guarantee that this feature will detect all malicious activity. By enabling this feature you agree to the provider's terms of use. - - - - - Blockaid - - - - - Privacy preserving - no data is shared with third parties. Available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Optimism and Polygon. - , ",", diff --git a/app/components/Views/Settings/ExperimentalSettings/index.test.tsx b/app/components/Views/Settings/ExperimentalSettings/index.test.tsx index 5d1b2607cc9..6a8c5e95e3f 100644 --- a/app/components/Views/Settings/ExperimentalSettings/index.test.tsx +++ b/app/components/Views/Settings/ExperimentalSettings/index.test.tsx @@ -7,49 +7,18 @@ import { render } from '@testing-library/react-native'; import initialBackgroundState from '../../../../util/test/initial-background-state.json'; import { mockTheme, ThemeContext } from '../../../../util/theme'; import ExperimentalSettings from './'; -import SECURITY_ALERTS_TOGGLE_TEST_ID from './constants'; const mockStore = configureMockStore(); const initialState = { experimentalSettings: { - securityAlertsEnabled: false, + securityAlertsEnabled: true, }, engine: { backgroundState: initialBackgroundState, }, }; -jest.mock('@react-navigation/native', () => ({ - useNavigation: () => ({ - navigation: {}, - }), - createNavigatorFactory: () => ({}), -})); - -jest.mock('../../../../util/blockaid', () => ({ - isBlockaidFeatureEnabled: jest.fn().mockReturnValue(true), - isBlockaidSupportedOnCurrentChain: jest.fn().mockReturnValue(true), -})); - -jest.mock('../../../../core/Engine', () => ({ - context: { - PreferencesController: { - state: { - securityAlertsEnabled: false, - }, - setSecurityAlertsEnabled: () => undefined, - }, - NetworkController: { - state: { - providerConfig: { - chainId: 1, - }, - }, - }, - }, -})); - const store = mockStore(initialState); const setOptions = jest.fn(); @@ -71,27 +40,4 @@ describe('ExperimentalSettings', () => { ); expect(wrapper).toMatchSnapshot(); }); - - it('should render blockaid toggle button', async () => { - const wrapper = render( - - - - , - - , - ); - expect(wrapper).toMatchSnapshot(); - expect(await wrapper.findByText('Security alerts')).toBeDefined(); - expect(await wrapper.findByText('Blockaid')).toBeDefined(); - - const toggle = wrapper.getByTestId(SECURITY_ALERTS_TOGGLE_TEST_ID); - expect(toggle).toBeDefined(); - expect(toggle.props.value).toBe(false); - }); }); diff --git a/app/components/Views/Settings/ExperimentalSettings/index.tsx b/app/components/Views/Settings/ExperimentalSettings/index.tsx index 0a360041535..12fa788ebd6 100644 --- a/app/components/Views/Settings/ExperimentalSettings/index.tsx +++ b/app/components/Views/Settings/ExperimentalSettings/index.tsx @@ -3,7 +3,6 @@ import { Linking, ScrollView, Switch, View } from 'react-native'; import { MMKV } from 'react-native-mmkv'; import { strings } from '../../../../../locales/i18n'; -import Engine from '../../../../core/Engine'; import { colors as importedColors } from '../../../../styles/common'; import { useTheme } from '../../../../util/theme'; import Text, { @@ -11,16 +10,10 @@ import Text, { TextColor, } from '../../../../component-library/components/Texts/Text'; import { UpdatePPOMInitializationStatus } from '../../../../actions/experimental'; -import AnalyticsV2 from '../../../../util/analyticsV2'; -import { MetaMetricsEvents } from '../../../../core/Analytics'; import { getNavigationOptionsTitle } from '../../../UI/Navbar'; -import SECURITY_ALERTS_TOGGLE_TEST_ID from './constants'; -import { isBlockaidFeatureEnabled } from '../../../../util/blockaid'; -import Routes from '../../../../constants/navigation/Routes'; -import { useSelector, useDispatch } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { Props } from './ExperimentalSettings.types'; import createStyles from './ExperimentalSettings.styles'; -import { selectIsSecurityAlertsEnabled } from '../../../../selectors/preferencesController'; import Button, { ButtonVariants, ButtonSize, @@ -35,11 +28,16 @@ const storage = new MMKV(); // id: mmkv.default * Main view for app Experimental Settings */ const ExperimentalSettings = ({ navigation, route }: Props) => { - const { PreferencesController } = Engine.context; - const dispatch = useDispatch(); - const securityAlertsEnabled = useSelector(selectIsSecurityAlertsEnabled); + const [sesEnabled, setSesEnabled] = useState( + storage.getBoolean('is-ses-enabled'), + ); + + const toggleSesEnabled = () => { + storage.set('is-ses-enabled', !sesEnabled); + setSesEnabled(!sesEnabled); + }; const [sesEnabled, setSesEnabled] = useState( storage.getBoolean('is-ses-enabled'), @@ -56,22 +54,6 @@ const ExperimentalSettings = ({ navigation, route }: Props) => { const { colors } = theme; const styles = createStyles(colors); - const toggleSecurityAlertsEnabled = () => { - if (securityAlertsEnabled) { - PreferencesController?.setSecurityAlertsEnabled(!securityAlertsEnabled); - AnalyticsV2.trackEvent( - MetaMetricsEvents.SETTINGS_EXPERIMENTAL_SECURITY_ALERTS_ENABLED, - { - security_alerts_enabled: false, - }, - ); - } else { - navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.BLOCKAID_INDICATOR, - }); - } - }; - useEffect(() => { dispatch(UpdatePPOMInitializationStatus()); }, [dispatch]); @@ -121,7 +103,7 @@ const ExperimentalSettings = ({ navigation, route }: Props) => { ); - const BlockaidSettings: FC = () => ( + const SesSettings: FC = () => ( <> {Device.isAndroid() && ( { )} - - {strings('experimental_settings.security_alerts')} - + + + {strings('app_settings.ses_heading')} + + + - {strings('experimental_settings.security_alerts_desc')} - - - - - {strings('experimental_settings.blockaid')} + {strings('app_settings.ses_description')}{' '} +