diff --git a/.circleci/config.yml b/.circleci/config.yml index 8884b4efb241..c5132bbb5c88 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -119,6 +119,9 @@ workflows: - prep-build-test: requires: - prep-deps + - prep-build-multichain-test: + requires: + - prep-deps - prep-build-test-mv3: requires: - prep-deps @@ -147,22 +150,19 @@ workflows: - test-e2e-chrome: requires: - prep-build-test + - test-e2e-chrome-multichain: + requires: + - prep-build-multichain-test - test-e2e-firefox: requires: - prep-build-test - test-e2e-chrome-rpc: requires: - prep-build-test - - test-e2e-chrome-snaps: - requires: - - prep-build-test - - test-e2e-firefox-snaps: - requires: - - prep-build-test - - test-e2e-chrome-snaps-flask: + - test-e2e-chrome-flask: requires: - prep-build-test-flask - - test-e2e-firefox-snaps-flask: + - test-e2e-firefox-flask: requires: - prep-build-test-flask - test-e2e-chrome-mmi: @@ -252,11 +252,10 @@ workflows: - test-mozilla-lint-desktop - test-mozilla-lint-flask - test-e2e-chrome + - test-e2e-chrome-multichain - test-e2e-firefox - - test-e2e-chrome-snaps - - test-e2e-firefox-snaps - - test-e2e-chrome-snaps-flask - - test-e2e-firefox-snaps-flask + - test-e2e-chrome-flask + - test-e2e-firefox-flask - test-e2e-chrome-mmi - test-e2e-chrome-rpc-mmi - test-storybook @@ -658,6 +657,27 @@ jobs: - dist-test - builds-test + prep-build-multichain-test: + executor: node-browsers-medium-plus + steps: + - run: *shallow-git-clone + - attach_workspace: + at: . + - run: + name: Build extension for testing + command: MULTICHAIN=1 yarn build:test + - run: + name: Move test build to 'dist-test' to avoid conflict with production build + command: mv ./dist ./dist-test-multichain + - run: + name: Move test zips to 'builds-test' to avoid conflict with production build + command: mv ./builds ./builds-test-multichain + - persist_to_workspace: + root: . + paths: + - dist-test-multichain + - builds-test-multichain + prep-build-storybook: executor: node-browsers-large steps: @@ -792,7 +812,7 @@ jobs: test-e2e-chrome: executor: node-browsers - parallelism: 8 + parallelism: 12 steps: - run: *shallow-git-clone - run: @@ -814,22 +834,15 @@ jobs: yarn test:e2e:chrome --retries 2 --debug fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e - test-e2e-chrome-mv3: + test-e2e-chrome-multichain: executor: node-browsers - parallelism: 8 + parallelism: 12 steps: - run: *shallow-git-clone - run: @@ -839,27 +852,28 @@ jobs: at: . - run: name: Move test build to dist - command: mv ./dist-test-mv3 ./dist + command: mv ./dist-test-multichain ./dist - run: name: Move test zips to builds - command: mv ./builds-test-mv3 ./builds + command: mv ./builds-test-multichain ./builds - run: - name: test:e2e:chrome + name: test:e2e:chrome-multichain command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:chrome --retries 2 --debug --mv3 || echo "Temporarily suppressing MV3 e2e test failures" + MULTICHAIN=1 yarn test:e2e:chrome --retries 2 --debug fi no_output_timeout: 20m - store_artifacts: path: test-artifacts destination: test-artifacts - - test-e2e-chrome-rpc: + - store_test_results: + path: test/test-results/e2e + test-e2e-chrome-mv3: executor: node-browsers - parallelism: 1 + parallelism: 12 steps: - - checkout + - run: *shallow-git-clone - run: name: Re-Install Chrome command: ./.circleci/scripts/chrome-install.sh @@ -867,33 +881,25 @@ jobs: at: . - run: name: Move test build to dist - command: mv ./dist-test ./dist + command: mv ./dist-test-mv3 ./dist - run: name: Move test zips to builds - command: mv ./builds-test ./builds + command: mv ./builds-test-mv3 ./builds - run: - name: test:e2e:chrome:rpc + name: test:e2e:chrome command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:chrome:rpc --retries 2 + yarn test:e2e:chrome --retries 2 --debug || echo "Temporarily suppressing MV3 e2e test failures" fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - - store_test_results: - path: test/test-results/e2e.xml - test-e2e-chrome-rpc-mmi: + test-e2e-chrome-rpc: executor: node-browsers + parallelism: 1 steps: - checkout - run: @@ -901,43 +907,6 @@ jobs: command: ./.circleci/scripts/chrome-install.sh - attach_workspace: at: . - - run: - name: Move test build to dist - command: mv ./dist-test-mmi ./dist - - run: - name: Move test zips to builds - command: mv ./builds-test-mmi ./builds - - run: - name: test:e2e:chrome:rpc - command: | - if .circleci/scripts/test-run-e2e.sh - then - yarn test:e2e:chrome:rpc --retries 2 --debug --build-type=mmi - fi - no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - - store_artifacts: - path: test-artifacts - destination: test-artifacts - - store_test_results: - path: test/test-results/e2e.xml - - test-e2e-firefox-snaps: - executor: node-browsers - parallelism: 4 - steps: - - run: *shallow-git-clone - - run: - name: Install Firefox - command: ./.circleci/scripts/firefox-install.sh - - attach_workspace: - at: . - run: name: Move test build to dist command: mv ./dist-test ./dist @@ -945,31 +914,23 @@ jobs: name: Move test zips to builds command: mv ./builds-test ./builds - run: - name: test:e2e:firefox:snaps + name: test:e2e:chrome:rpc command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:firefox:snaps --retries 2 --debug --build-type=main + yarn test:e2e:chrome:rpc --retries 2 fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e - test-e2e-chrome-snaps: + test-e2e-chrome-rpc-mmi: executor: node-browsers - parallelism: 4 steps: - - run: *shallow-git-clone + - checkout - run: name: Re-Install Chrome command: ./.circleci/scripts/chrome-install.sh @@ -977,34 +938,27 @@ jobs: at: . - run: name: Move test build to dist - command: mv ./dist-test ./dist + command: mv ./dist-test-mmi ./dist - run: name: Move test zips to builds - command: mv ./builds-test ./builds + command: mv ./builds-test-mmi ./builds - run: - name: test:e2e:chrome:snaps + name: test:e2e:chrome:rpc command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:chrome:snaps --retries 2 --debug --build-type=main + yarn test:e2e:chrome:rpc --retries 2 --debug --build-type=mmi fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e - test-e2e-firefox-snaps-flask: + test-e2e-firefox-flask: executor: node-browsers - parallelism: 4 + parallelism: 8 steps: - run: *shallow-git-clone - run: @@ -1019,29 +973,22 @@ jobs: name: Move test zips to builds command: mv ./builds-test-flask ./builds - run: - name: test:e2e:firefox:snaps + name: test:e2e:firefox:flask command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:firefox:snaps --retries 2 --debug --build-type=flask + yarn test:e2e:firefox:flask --retries 2 --debug fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e - test-e2e-chrome-snaps-flask: + test-e2e-chrome-flask: executor: node-browsers - parallelism: 4 + parallelism: 8 steps: - run: *shallow-git-clone - run: @@ -1056,29 +1003,22 @@ jobs: name: Move test zips to builds command: mv ./builds-test-flask ./builds - run: - name: test:e2e:chrome:snaps + name: test:e2e:chrome:flask command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:chrome:snaps --retries 2 --debug --build-type=flask + yarn test:e2e:chrome:flask --retries 2 --debug fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-chrome-mmi: executor: node-browsers - parallelism: 4 + parallelism: 8 steps: - run: *shallow-git-clone - run: @@ -1100,22 +1040,15 @@ jobs: yarn test:e2e:chrome:mmi --retries 2 --debug --build-type=mmi fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-firefox: executor: node-browsers-medium-plus - parallelism: 8 + parallelism: 12 steps: - run: *shallow-git-clone - run: @@ -1137,18 +1070,11 @@ jobs: yarn test:e2e:firefox --retries 2 --debug fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e benchmark: executor: node-browsers-medium-plus diff --git a/.circleci/scripts/chrome-install.sh b/.circleci/scripts/chrome-install.sh index 2cf0bdb0e393..559907193c12 100755 --- a/.circleci/scripts/chrome-install.sh +++ b/.circleci/scripts/chrome-install.sh @@ -7,12 +7,12 @@ set -o pipefail sudo apt-get update # To get the latest version, see -CHROME_VERSION='116.0.5845.179-1' +CHROME_VERSION='118.0.5993.88-1' CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb" CHROME_BINARY_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/${CHROME_BINARY}" # To retrieve this checksum, run the `wget` and `shasum` commands below -CHROME_BINARY_SHA512SUM='cbdad3f5c928ef79a46a3619054b3c4a73a99f942f9bf4ea75d37d6434912da5c01f6ee30718a58e869ff6b57b10bb7fea1cf91885a25aac290a50a2ee3c03c4' +CHROME_BINARY_SHA512SUM='cae6a5cd8632ad350b41f4dfaf80449e6cf19d0b02816b9a1600f54b15df2adf5c4ded3792bfbe3855fa11a79ea256622f50180aa3c6779cedd75a55e7a6da9d' wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}" diff --git a/.depcheckrc.yml b/.depcheckrc.yml index 972b13b06e8a..f205629debef 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -32,6 +32,7 @@ ignores: - 'geckodriver' - 'jest' - 'lavamoat-viz' + - 'mocha-junit-reporter' - 'prettier-plugin-sort-json' # automatically imported by prettier - 'source-map-explorer' - 'playwright' @@ -41,6 +42,7 @@ ignores: # storybook - '@storybook/cli' - '@storybook/core' + - '@storybook/addon-designs' - '@storybook/addon-essentials' - '@storybook/addon-a11y' - '@storybook/addon-mdx-gfm' diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index df752860bfb1..f8a3d35d16bc 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -26,10 +26,10 @@ body: label: Expected behavior description: What did you expect to happen? - type: textarea - id: screenshot + id: screenshot-recording attributes: - label: Screenshots - description: Please include screenshots if applicable! + label: Screenshots/Recordings + description: Please include screenshots/recordings if applicable! (https://recordit.co/ is recommended) - type: textarea id: reproduce attributes: diff --git a/.github/ISSUE_TEMPLATE/general-issue.yml b/.github/ISSUE_TEMPLATE/general-issue.yml index f23b47082e5a..5bd95322ec50 100644 --- a/.github/ISSUE_TEMPLATE/general-issue.yml +++ b/.github/ISSUE_TEMPLATE/general-issue.yml @@ -13,7 +13,7 @@ body: id: description attributes: label: What is this about? - placeholder: Describe the issue here. + placeholder: As a user, describe the issue here. validations: required: true diff --git a/.github/guidelines/LABELING_GUIDELINES.md b/.github/guidelines/LABELING_GUIDELINES.md index ec7ad636c729..4018414714c9 100644 --- a/.github/guidelines/LABELING_GUIDELINES.md +++ b/.github/guidelines/LABELING_GUIDELINES.md @@ -4,7 +4,7 @@ To maintain a consistent and efficient development workflow, we have set specifi ### Mandatory team labels: - **Internal Developers**: Every PR raised by an internal developer must include a label prefixed with `team-` (e.g., `team-extension-ux`, `team-extension-platform`, etc.). This indicates the respective internal team responsible for the PR. -- **External Contributors**: PRs from contributors outside the organization must have the `external-contributor` label. +- **External Contributors**: PRs submitted by contributors who are not part of the organization will be automatically labeled with `external-contributor`. It's essential to ensure that PRs have the appropriate labels before they are considered for merging. diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index fcdebbcb5215..0ec8a16ca715 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -1,41 +1,43 @@ ## **Description** -_Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions:_ -_1. What is the reason for the change?_ -_2. What is the improvement/solution?_ + + + +## **Related issues** + +Fixes: # ## **Manual testing steps** -_1. Step1:_ -_2. Step2:_ -_3. ..._ +1. Go to this page... +2. +3. ## **Screenshots/Recordings** -_If applicable, add screenshots and/or recordings to visualize the before and after of your change._ + ### **Before** -_[screenshot]_ + ### **After** -_[screenshot]_ - -## **Related issues** - -_Fixes #???_ + ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). -- [ ] I've clearly explained: - - [ ] What problem this PR is solving. - - [ ] How this problem was solved. - - [ ] How reviewers can test my changes. -- [ ] I’ve indicated what issue this PR is linked to: Fixes #??? -- [ ] I’ve included tests if applicable. -- [ ] I’ve documented any added code. -- [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). +- [ ] I've clearly explained what problem this PR is solving and how it is solved. +- [ ] I've linked related issues +- [ ] I've included manual testing steps +- [ ] I've included screenshots/recordings if applicable +- [ ] I’ve included tests if applicable +- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable +- [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". diff --git a/.github/scripts/add-release-label-to-pr-and-linked-issues.ts b/.github/scripts/add-release-label-to-pr-and-linked-issues.ts index 0bf34182e012..a9b84532c8e6 100644 --- a/.github/scripts/add-release-label-to-pr-and-linked-issues.ts +++ b/.github/scripts/add-release-label-to-pr-and-linked-issues.ts @@ -2,14 +2,10 @@ import * as core from '@actions/core'; import { context, getOctokit } from '@actions/github'; import { GitHub } from '@actions/github/lib/utils'; -// A labelable object can be a pull request or an issue -interface Labelable { - id: string; - number: number; - repoOwner: string; - repoName: string; - createdAt: string; -} +import { retrieveLinkedIssues } from './shared/issue'; +import { Label } from './shared/label'; +import { Labelable, addLabelToLabelable } from './shared/labelable'; +import { retrievePullRequest } from './shared/pull-request'; main().catch((error: Error): void => { console.error(error); @@ -42,45 +38,56 @@ async function main(): Promise { } if (!isValidVersionFormat(nextReleaseVersionNumber)) { - core.setFailed(`NEXT_SEMVER_VERSION (${nextReleaseVersionNumber}) is not a valid version format. The expected format is "x.y.z", where "x", "y" and "z" are numbers.`); + core.setFailed( + `NEXT_SEMVER_VERSION (${nextReleaseVersionNumber}) is not a valid version format. The expected format is "x.y.z", where "x", "y" and "z" are numbers.`, + ); process.exit(1); } // Release label indicates the next release version number // Example release label: "release-6.5.0" - const releaseLabelName = `release-${nextReleaseVersionNumber}`; - const releaseLabelColor = "ededed"; - const releaseLabelDescription = `Issue or pull request that will be included in release ${nextReleaseVersionNumber}`; + const releaseLabel: Label = { + name: `release-${nextReleaseVersionNumber}`, + color: 'EDEDED', + description: `Issue or pull request that will be included in release ${nextReleaseVersionNumber}`, + }; // Initialise octokit, required to call Github GraphQL API - const octokit: InstanceType = getOctokit( - personalAccessToken, - { - previews: ["bane"], // The "bane" preview is required for adding, updating, creating and deleting labels. - }, - ); + const octokit: InstanceType = getOctokit(personalAccessToken, { + previews: ['bane'], // The "bane" preview is required for adding, updating, creating and deleting labels. + }); // Retrieve pull request info from context - const prRepoOwner = context.repo.owner; - const prRepoName = context.repo.repo; - const prNumber = context.payload.pull_request?.number; - if (!prNumber) { + const pullRequestRepoOwner = context.repo.owner; + const pullRequestRepoName = context.repo.repo; + const pullRequestNumber = context.payload.pull_request?.number; + if (!pullRequestNumber) { core.setFailed('Pull request number not found'); process.exit(1); } // Retrieve pull request - const pullRequest: Labelable = await retrievePullRequest(octokit, prRepoOwner, prRepoName, prNumber); + const pullRequest: Labelable = await retrievePullRequest( + octokit, + pullRequestRepoOwner, + pullRequestRepoName, + pullRequestNumber, + ); // Add the release label to the pull request - await addLabelToLabelable(octokit, pullRequest, releaseLabelName, releaseLabelColor, releaseLabelDescription); + await addLabelToLabelable(octokit, pullRequest, releaseLabel); // Retrieve linked issues for the pull request - const linkedIssues: Labelable[] = await retrieveLinkedIssues(octokit, prRepoOwner, prRepoName, prNumber); + const linkedIssues: Labelable[] = await retrieveLinkedIssues( + octokit, + pullRequestRepoOwner, + pullRequestRepoName, + pullRequestNumber, + ); // Add the release label to the linked issues for (const linkedIssue of linkedIssues) { - await addLabelToLabelable(octokit, linkedIssue, releaseLabelName, releaseLabelColor, releaseLabelDescription); + await addLabelToLabelable(octokit, linkedIssue, releaseLabel); } } @@ -89,240 +96,3 @@ function isValidVersionFormat(str: string): boolean { const regex = /^\d+\.\d+\.\d+$/; return regex.test(str); } - -// This function retrieves the repo -async function retrieveRepo(octokit: InstanceType, repoOwner: string, repoName: string): Promise { - - const retrieveRepoQuery = ` - query RetrieveRepo($repoOwner: String!, $repoName: String!) { - repository(owner: $repoOwner, name: $repoName) { - id - } - } -`; - - const retrieveRepoResult: { - repository: { - id: string; - }; - } = await octokit.graphql(retrieveRepoQuery, { - repoOwner, - repoName, - }); - - const repoId = retrieveRepoResult?.repository?.id; - - return repoId; -} - -// This function retrieves the label on a specific repo -async function retrieveLabel(octokit: InstanceType, repoOwner: string, repoName: string, labelName: string): Promise { - - const retrieveLabelQuery = ` - query RetrieveLabel($repoOwner: String!, $repoName: String!, $labelName: String!) { - repository(owner: $repoOwner, name: $repoName) { - label(name: $labelName) { - id - } - } - } - `; - - const retrieveLabelResult: { - repository: { - label: { - id: string; - }; - }; - } = await octokit.graphql(retrieveLabelQuery, { - repoOwner, - repoName, - labelName, - }); - - const labelId = retrieveLabelResult?.repository?.label?.id; - - return labelId; -} - -// This function creates the label on a specific repo -async function createLabel(octokit: InstanceType, repoId: string, labelName: string, labelColor: string, labelDescription: string): Promise { - - const createLabelMutation = ` - mutation CreateLabel($repoId: ID!, $labelName: String!, $labelColor: String!, $labelDescription: String) { - createLabel(input: {repositoryId: $repoId, name: $labelName, color: $labelColor, description: $labelDescription}) { - label { - id - } - } - } - `; - - const createLabelResult: { - createLabel: { - label: { - id: string; - }; - }; - } = await octokit.graphql(createLabelMutation, { - repoId, - labelName, - labelColor, - labelDescription, - }); - - const labelId = createLabelResult?.createLabel?.label?.id; - - return labelId; -} - -// This function creates or retrieves the label on a specific repo -async function createOrRetrieveLabel(octokit: InstanceType, repoOwner: string, repoName: string, labelName: string, labelColor: string, labelDescription: string): Promise { - - // Check if label already exists on the repo - let labelId = await retrieveLabel(octokit, repoOwner, repoName, labelName); - - // If label doesn't exist on the repo, create it - if (!labelId) { - // Retrieve PR's repo - const repoId = await retrieveRepo(octokit, repoOwner, repoName); - - // Create label on repo - labelId = await createLabel(octokit, repoId, labelName, labelColor, labelDescription); - } - - return labelId; -} - -// This function retrieves the pull request on a specific repo -async function retrievePullRequest(octokit: InstanceType, repoOwner: string, repoName: string, prNumber: number): Promise { - - const retrievePullRequestQuery = ` - query GetPullRequest($repoOwner: String!, $repoName: String!, $prNumber: Int!) { - repository(owner: $repoOwner, name: $repoName) { - pullRequest(number: $prNumber) { - id - createdAt - } - } - } - `; - - const retrievePullRequestResult: { - repository: { - pullRequest: { - id: string; - createdAt: string; - }; - }; - } = await octokit.graphql(retrievePullRequestQuery, { - repoOwner, - repoName, - prNumber, - }); - - const pullRequest: Labelable = { - id: retrievePullRequestResult?.repository?.pullRequest?.id, - number: prNumber, - repoOwner: repoOwner, - repoName: repoName, - createdAt: retrievePullRequestResult?.repository?.pullRequest?.createdAt, - } - - return pullRequest; -} - - -// This function retrieves the list of linked issues for a pull request -async function retrieveLinkedIssues(octokit: InstanceType, repoOwner: string, repoName: string, prNumber: number): Promise { - - // We assume there won't be more than 100 linked issues - const retrieveLinkedIssuesQuery = ` - query ($repoOwner: String!, $repoName: String!, $prNumber: Int!) { - repository(owner: $repoOwner, name: $repoName) { - pullRequest(number: $prNumber) { - closingIssuesReferences(first: 100) { - nodes { - id - number - createdAt - repository { - name - owner { - login - } - } - } - } - } - } - } - `; - - const retrieveLinkedIssuesResult: { - repository: { - pullRequest: { - closingIssuesReferences: { - nodes: Array<{ - id: string; - number: number; - createdAt: string; - repository: { - name: string; - owner: { - login: string; - }; - }; - }>; - }; - }; - }; - } = await octokit.graphql(retrieveLinkedIssuesQuery, { - repoOwner, - repoName, - prNumber - }); - - const linkedIssues = retrieveLinkedIssuesResult?.repository?.pullRequest?.closingIssuesReferences?.nodes?.map((issue: { - id: string; - number: number; - createdAt: string; - repository: { - name: string; - owner: { - login: string; - }; - }; - }) => { - return { - id: issue?.id, - number: issue?.number, - repoOwner: issue?.repository?.owner?.login, - repoName: issue?.repository?.name, - createdAt: issue?.createdAt - }; - }) || []; - - return linkedIssues; -} - -// This function adds label to a labelable object (i.e. a pull request or an issue) -async function addLabelToLabelable(octokit: InstanceType, labelable: Labelable, labelName: string, labelColor: string, labelDescription: string): Promise { - - // Retrieve label from the labelable's repo, or create label if required - const labelId = await createOrRetrieveLabel(octokit, labelable?.repoOwner, labelable?.repoName, labelName, labelColor, labelDescription); - - const addLabelsToLabelableMutation = ` - mutation AddLabelsToLabelable($labelableId: ID!, $labelIds: [ID!]!) { - addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) { - clientMutationId - } - } - `; - - await octokit.graphql(addLabelsToLabelableMutation, { - labelableId: labelable?.id, - labelIds: [labelId], - }); - -} diff --git a/.github/scripts/check-issue-template-and-add-labels.ts b/.github/scripts/check-issue-template-and-add-labels.ts deleted file mode 100644 index 298b94f79071..000000000000 --- a/.github/scripts/check-issue-template-and-add-labels.ts +++ /dev/null @@ -1,551 +0,0 @@ -import * as core from '@actions/core'; -import { context, getOctokit } from '@actions/github'; -import { GitHub } from '@actions/github/lib/utils'; - -// A labelable object can be a pull request or an issue -interface Labelable { - id: string; - number: number; - repoOwner: string; - repoName: string; - body: string; - author: string; - labels: { - id: string; - name: string; - }[]; -} - -// An enum, to categorise issues, based on template it matches -enum IssueType { - GeneralIssue, - BugReport, - None, -} - -// Titles of our two issues templates ('general-issue.yml' and 'bug-report.yml' issue) -const generalIssueTemplateTitles = [ - '### What is this about?', - '### Scenario', - '### Design', - '### Technical Details', - '### Threat Modeling Framework', - '### Acceptance Criteria', - '### References', -]; -const bugReportTemplateTitles = [ - '### Describe the bug', - '### Expected behavior', - '### Screenshots', - '### Steps to reproduce', - '### Error messages or log output', - '### Version', - '### Build type', - '### Browser', - '### Operating system', - '### Hardware wallet', - '### Additional context', - '### Severity' -]; - -// External contributor label -const externalContributorLabelName = `external-contributor`; -const externalContributorLabelColor = 'B60205'; // red -const externalContributorLabelDescription = `Issue or PR created by user outside MetaMask organisation`; - -// Craft invalid issue template label -const invalidIssueTemplateLabelName = `INVALID-ISSUE-TEMPLATE`; -const invalidIssueTemplateLabelColor = 'B60205'; // red -const invalidIssueTemplateLabelDescription = `Issue's body doesn't match any issue template.`; - -main().catch((error: Error): void => { - console.error(error); - process.exit(1); -}); - -async function main(): Promise { - // "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions. - // We can't use "GITHUB_TOKEN" here, as its permissions don't allow neither to create new labels - // nor to retrieve the list of organisations a user belongs to. - // In our case, we may want to create "regression-prod-x.y.z" label when it doesn't already exist. - // We may also want to retrieve the list of organisations a user belongs to. - // As a consequence, we need to create our own "LABEL_TOKEN" with "repo" and "read:org" permissions. - // Such a token allows both to create new labels and fetch user's list of organisations. - const personalAccessToken = process.env.LABEL_TOKEN; - if (!personalAccessToken) { - core.setFailed('LABEL_TOKEN not found'); - process.exit(1); - } - - // Retrieve pull request info from context - const issueRepoOwner = context.repo.owner; - const issueRepoName = context.repo.repo; - const issueNumber = context.payload.issue?.number; - if (!issueNumber) { - core.setFailed('Issue number not found'); - process.exit(1); - } - - // Initialise octokit, required to call Github GraphQL API - const octokit: InstanceType = getOctokit(personalAccessToken, { - previews: ['bane'], // The "bane" preview is required for adding, updating, creating and deleting labels. - }); - - // Retrieve issue - const issue: Labelable = await retrieveIssue( - octokit, - issueRepoOwner, - issueRepoName, - issueNumber, - ); - - // Add external contributor label to the issue, in case author is not part of the MetaMask organisation - await addExternalContributorLabel(octokit, issue); - - // Check if issue's body matches one of the two issues templates ('general-issue.yml' or 'bug-report.yml') - const issueType: IssueType = extractIssueTypeFromIssueBody(issue.body); - - if (issueType === IssueType.GeneralIssue) { - console.log("Issue matches 'general-issue.yml' template."); - await removeInvalidIssueTemplateLabelIfPresent(octokit, issue); - } else if (issueType === IssueType.BugReport) { - console.log("Issue matches 'bug-report.yml' template."); - await removeInvalidIssueTemplateLabelIfPresent(octokit, issue); - - // Extract release version from issue body (is existing) - const releaseVersion = extractReleaseVersionFromIssueBody(issue.body); - - // Add regression prod label to the issue if release version was found is issue body - if (releaseVersion) { - await addRegressionProdLabel(octokit, releaseVersion, issue); - } else { - console.log( - `No release version was found in body of issue ${issue?.number}.`, - ); - } - } else { - const errorMessage = - "Issue body does not match any of expected templates ('general-issue.yml' or 'bug-report.yml')."; - console.log(errorMessage); - - // Add invalid issue template label to the issue, in case issue doesn't match any template - await addInvalidIssueTemplateLabel(octokit, issue); - - // Github action shall fail in case issue doesn't match any template - throw new Error(errorMessage); - } -} - -// This helper function checks if issue's body matches one of the two issues templates ('general-issue.yml' or 'bug-report.yml'). -function extractIssueTypeFromIssueBody(issueBody: string): IssueType { - let missingGeneralIssueTitle: boolean = false; - for (const title of generalIssueTemplateTitles) { - if (!issueBody.includes(title)) { - missingGeneralIssueTitle = true; - } - } - - let missingBugReportTitle: boolean = false; - for (const title of bugReportTemplateTitles) { - if (!issueBody.includes(title)) { - missingBugReportTitle = true; - } - } - - if (!missingGeneralIssueTitle) { - return IssueType.GeneralIssue; - } else if (!missingBugReportTitle) { - return IssueType.BugReport; - } else { - return IssueType.None; - } -} - -// This helper function checks if issue's body has a bug report format. -function extractReleaseVersionFromIssueBody( - issueBody: string, -): string | undefined { - // Remove newline characters - const cleanedIssueBody = issueBody.replace(/\r?\n/g, ' '); - - // Extract version from the cleaned issue body - const regex = /### Version\s+((.*?)(?= |$))/; - const versionMatch = cleanedIssueBody.match(regex); - const version = versionMatch?.[1]; - - // Check if version is in the format x.y.z - if (version && !/^(\d+\.)?(\d+\.)?(\*|\d+)$/.test(version)) { - throw new Error('Version is not in the format x.y.z'); - } - - return version; -} - -// This function adds the "external-contributor" label to the issue, in case author is not part of the MetaMask organisation -async function addExternalContributorLabel( - octokit: InstanceType, - issue: Labelable, -): Promise { - // If author is not part of the MetaMask organisation - if (!(await userBelongsToMetaMaskOrg(octokit, issue?.author))) { - // Add external contributor label to the issue - await addLabelToLabelable( - octokit, - issue, - externalContributorLabelName, - externalContributorLabelColor, - externalContributorLabelDescription, - ); - } -} - -// This function adds the correct "regression-prod-x.y.z" label to the issue, and removes other ones -async function addRegressionProdLabel( - octokit: InstanceType, - releaseVersion: string, - issue: Labelable, -): Promise { - // Craft regression prod label to add - const regressionProdLabelName = `regression-prod-${releaseVersion}`; - const regressionProdLabelColor = '5319E7'; // violet - const regressionProdLabelDescription = `Regression bug that was found in production in release ${releaseVersion}`; - - let regressionProdLabelFound: boolean = false; - const regressionProdLabelsToBeRemoved: { - id: string; - name: string; - }[] = []; - - // Loop over issue's labels, to see if regression labels are either missing, or to be removed - issue?.labels?.forEach((label) => { - if (label?.name === regressionProdLabelName) { - regressionProdLabelFound = true; - } else if (label?.name?.startsWith('regression-prod-')) { - regressionProdLabelsToBeRemoved.push(label); - } - }); - - // Add regression prod label to the issue if missing - if (regressionProdLabelFound) { - console.log( - `Issue ${issue?.number} already has ${regressionProdLabelName} label.`, - ); - } else { - console.log( - `Add ${regressionProdLabelName} label to issue ${issue?.number}.`, - ); - await addLabelToLabelable( - octokit, - issue, - regressionProdLabelName, - regressionProdLabelColor, - regressionProdLabelDescription, - ); - } - - // Remove other regression prod label from the issue - await Promise.all( - regressionProdLabelsToBeRemoved.map((label) => { - removeLabelFromLabelable(octokit, issue, label?.id); - }), - ); -} - -// This function adds the "INVALID-ISSUE-TEMPLATE" label to the issue -async function addInvalidIssueTemplateLabel( - octokit: InstanceType, - issue: Labelable, -): Promise { - // Add label to issue - await addLabelToLabelable( - octokit, - issue, - invalidIssueTemplateLabelName, - invalidIssueTemplateLabelColor, - invalidIssueTemplateLabelDescription, - ); -} - -// This function removes the "INVALID-ISSUE-TEMPLATE" label from the issue, in case it's present -async function removeInvalidIssueTemplateLabelIfPresent( - octokit: InstanceType, - issue: Labelable, -): Promise { - // Check if label is present on issue - const label = issue?.labels?.find( - (label) => label.name === invalidIssueTemplateLabelName, - ); - - if (label?.id) { - // Remove label from issue - await removeLabelFromLabelable(octokit, issue, label.id); - } -} - -// This function retrieves the repo -async function retrieveRepo( - octokit: InstanceType, - repoOwner: string, - repoName: string, -): Promise { - const retrieveRepoQuery = ` - query RetrieveRepo($repoOwner: String!, $repoName: String!) { - repository(owner: $repoOwner, name: $repoName) { - id - } - } -`; - - const retrieveRepoResult: { - repository: { - id: string; - }; - } = await octokit.graphql(retrieveRepoQuery, { - repoOwner, - repoName, - }); - - const repoId = retrieveRepoResult?.repository?.id; - - return repoId; -} - -// This function retrieves the label on a specific repo -async function retrieveLabel( - octokit: InstanceType, - repoOwner: string, - repoName: string, - labelName: string, -): Promise { - const retrieveLabelQuery = ` - query RetrieveLabel($repoOwner: String!, $repoName: String!, $labelName: String!) { - repository(owner: $repoOwner, name: $repoName) { - label(name: $labelName) { - id - } - } - } - `; - - const retrieveLabelResult: { - repository: { - label: { - id: string; - }; - }; - } = await octokit.graphql(retrieveLabelQuery, { - repoOwner, - repoName, - labelName, - }); - - const labelId = retrieveLabelResult?.repository?.label?.id; - - return labelId; -} - -// This function creates the label on a specific repo -async function createLabel( - octokit: InstanceType, - repoId: string, - labelName: string, - labelColor: string, - labelDescription: string, -): Promise { - const createLabelMutation = ` - mutation CreateLabel($repoId: ID!, $labelName: String!, $labelColor: String!, $labelDescription: String) { - createLabel(input: {repositoryId: $repoId, name: $labelName, color: $labelColor, description: $labelDescription}) { - label { - id - } - } - } - `; - - const createLabelResult: { - createLabel: { - label: { - id: string; - }; - }; - } = await octokit.graphql(createLabelMutation, { - repoId, - labelName, - labelColor, - labelDescription, - }); - - const labelId = createLabelResult?.createLabel?.label?.id; - - return labelId; -} - -// This function creates or retrieves the label on a specific repo -async function createOrRetrieveLabel( - octokit: InstanceType, - repoOwner: string, - repoName: string, - labelName: string, - labelColor: string, - labelDescription: string, -): Promise { - // Check if label already exists on the repo - let labelId = await retrieveLabel(octokit, repoOwner, repoName, labelName); - - // If label doesn't exist on the repo, create it - if (!labelId) { - // Retrieve PR's repo - const repoId = await retrieveRepo(octokit, repoOwner, repoName); - - // Create label on repo - labelId = await createLabel( - octokit, - repoId, - labelName, - labelColor, - labelDescription, - ); - } - - return labelId; -} - -// This function retrieves the issue on a specific repo -async function retrieveIssue( - octokit: InstanceType, - repoOwner: string, - repoName: string, - issueNumber: number, -): Promise { - const retrieveIssueQuery = ` - query GetIssue($repoOwner: String!, $repoName: String!, $issueNumber: Int!) { - repository(owner: $repoOwner, name: $repoName) { - issue(number: $issueNumber) { - id - body - author { - login - } - labels(first: 100) { - nodes { - id - name - } - } - } - } - } - `; - - const retrieveIssueResult: { - repository: { - issue: { - id: string; - body: string; - author: { - login: string; - }; - labels: { - nodes: { - id: string; - name: string; - }[]; - }; - }; - }; - } = await octokit.graphql(retrieveIssueQuery, { - repoOwner, - repoName, - issueNumber, - }); - - const issue: Labelable = { - id: retrieveIssueResult?.repository?.issue?.id, - number: issueNumber, - repoOwner: repoOwner, - repoName: repoName, - body: retrieveIssueResult?.repository?.issue?.body, - author: retrieveIssueResult?.repository?.issue?.author?.login, - labels: retrieveIssueResult?.repository?.issue?.labels?.nodes, - }; - - return issue; -} - -// This function adds label to a labelable object (i.e. a pull request or an issue) -async function addLabelToLabelable( - octokit: InstanceType, - labelable: Labelable, - labelName: string, - labelColor: string, - labelDescription: string, -): Promise { - // Retrieve label from the labelable's repo, or create label if required - const labelId = await createOrRetrieveLabel( - octokit, - labelable?.repoOwner, - labelable?.repoName, - labelName, - labelColor, - labelDescription, - ); - - const addLabelsToLabelableMutation = ` - mutation AddLabelsToLabelable($labelableId: ID!, $labelIds: [ID!]!) { - addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) { - clientMutationId - } - } - `; - - await octokit.graphql(addLabelsToLabelableMutation, { - labelableId: labelable?.id, - labelIds: [labelId], - }); -} - -// This function removes a label from a labelable object (i.e. a pull request or an issue) -async function removeLabelFromLabelable( - octokit: InstanceType, - labelable: Labelable, - labelId: string, -): Promise { - const removeLabelsFromLabelableMutation = ` - mutation RemoveLabelsFromLabelable($labelableId: ID!, $labelIds: [ID!]!) { - removeLabelsFromLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) { - clientMutationId - } - } - `; - - await octokit.graphql(removeLabelsFromLabelableMutation, { - labelableId: labelable?.id, - labelIds: [labelId], - }); -} - -// This function checks if user belongs to MetaMask organization on Github -async function userBelongsToMetaMaskOrg( - octokit: InstanceType, - username: string, -): Promise { - const userBelongsToMetaMaskOrgQuery = ` - query UserBelongsToMetaMaskOrg($login: String!) { - user(login: $login) { - organization(login: "MetaMask") { - id - } - } - } - `; - - const userBelongsToMetaMaskOrgResult: { - user: { - organization: { - id: string; - }; - }; - } = await octokit.graphql(userBelongsToMetaMaskOrgQuery, { login: username }); - - return Boolean(userBelongsToMetaMaskOrgResult?.user?.organization?.id); -} diff --git a/.github/scripts/check-pr-has-required-labels.ts b/.github/scripts/check-pr-has-required-labels.ts index f5c81cc9b172..15ef77022ceb 100644 --- a/.github/scripts/check-pr-has-required-labels.ts +++ b/.github/scripts/check-pr-has-required-labels.ts @@ -1,6 +1,9 @@ import * as core from '@actions/core'; import { context, getOctokit } from '@actions/github'; import { GitHub } from '@actions/github/lib/utils'; +import { externalContributorLabel } from './shared/label'; +import { Labelable } from './shared/labelable'; +import { retrievePullRequest } from './shared/pull-request'; main().catch((error: Error): void => { console.error(error); @@ -19,29 +22,46 @@ async function main(): Promise { const octokit: InstanceType = getOctokit(githubToken); // Retrieve pull request info from context - const prRepoOwner = context.repo.owner; - const prRepoName = context.repo.repo; - const prNumber = context.payload.pull_request?.number; - if (!prNumber) { + const pullRequestRepoOwner = context.repo.owner; + const pullRequestRepoName = context.repo.repo; + const pullRequestNumber = context.payload.pull_request?.number; + if (!pullRequestNumber) { core.setFailed('Pull request number not found'); process.exit(1); } // Retrieve pull request labels - const prLabels = await retrievePullRequestLabels(octokit, prRepoOwner, prRepoName, prNumber); - - const preventMergeLabels = ["needs-qa", "QA'd but questions", "issues-found", "need-ux-ds-review", "blocked", "stale", "DO-NOT-MERGE"]; + const pullRequest: Labelable = await retrievePullRequest( + octokit, + pullRequestRepoOwner, + pullRequestRepoName, + pullRequestNumber, + ); + const pullRequestLabels = + pullRequest.labels?.map((labelObject) => labelObject?.name) || []; + const preventMergeLabels = [ + 'needs-qa', + "QA'd but questions", + 'issues-found', + 'need-ux-ds-review', + 'blocked', + 'stale', + 'DO-NOT-MERGE', + ]; let hasTeamLabel = false; // Check pull request has at least required QA label and team label - for (const label of prLabels) { - if (label.startsWith("team-") || label === "external-contributor") { + for (const label of pullRequestLabels) { + if (label.startsWith('team-') || label === externalContributorLabel.name) { console.log(`PR contains a team label as expected: ${label}`); hasTeamLabel = true; } if (preventMergeLabels.includes(label)) { - throw new Error(`PR cannot be merged because it still contains this label: ${label}`); + core.setFailed( + `PR cannot be merged because it still contains this label: ${label}`, + ); + process.exit(1); } if (hasTeamLabel) { return; @@ -54,44 +74,6 @@ async function main(): Promise { errorMessage += 'No team labels found on the PR. '; } errorMessage += `Please make sure the PR is appropriately labeled before merging it.\n\nSee labeling guidelines for more detail: https://github.com/MetaMask/metamask-extension/blob/develop/.github/LABELING_GUIDELINES.md`; - throw new Error(errorMessage); - -} - -// This function retrieves the pull request on a specific repo -async function retrievePullRequestLabels(octokit: InstanceType, repoOwner: string, repoName: string, prNumber: number): Promise { - - const retrievePullRequestLabelsQuery = ` - query RetrievePullRequestLabels($repoOwner: String!, $repoName: String!, $prNumber: Int!) { - repository(owner: $repoOwner, name: $repoName) { - pullRequest(number: $prNumber) { - labels(first: 100) { - nodes { - name - } - } - } - } - } - `; - - const retrievePullRequestLabelsResult: { - repository: { - pullRequest: { - labels: { - nodes: { - name: string; - }[]; - } - }; - }; - } = await octokit.graphql(retrievePullRequestLabelsQuery, { - repoOwner, - repoName, - prNumber, - }); - - const pullRequestLabels = retrievePullRequestLabelsResult?.repository?.pullRequest?.labels?.nodes?.map(labelObject => labelObject?.name); - - return pullRequestLabels || []; + core.setFailed(errorMessage); + process.exit(1); } diff --git a/.github/scripts/check-template-and-add-labels.ts b/.github/scripts/check-template-and-add-labels.ts new file mode 100644 index 000000000000..e5f51fe14476 --- /dev/null +++ b/.github/scripts/check-template-and-add-labels.ts @@ -0,0 +1,271 @@ +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import { GitHub } from '@actions/github/lib/utils'; + +import { retrieveIssue } from './shared/issue'; +import { + Labelable, + LabelableType, + addLabelToLabelable, + removeLabelFromLabelable, + removeLabelFromLabelableIfPresent, +} from './shared/labelable'; +import { + Label, + externalContributorLabel, + invalidIssueTemplateLabel, + invalidPullRequestTemplateLabel, +} from './shared/label'; +import { TemplateType, templates } from './shared/template'; +import { retrievePullRequest } from './shared/pull-request'; + +main().catch((error: Error): void => { + console.error(error); + process.exit(1); +}); + +async function main(): Promise { + // "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions. + // We can't use "GITHUB_TOKEN" here, as its permissions don't allow neither to create new labels + // nor to retrieve the list of organisations a user belongs to. + // In our case, we may want to create "regression-prod-x.y.z" label when it doesn't already exist. + // We may also want to retrieve the list of organisations a user belongs to. + // As a consequence, we need to create our own "LABEL_TOKEN" with "repo" and "read:org" permissions. + // Such a token allows both to create new labels and fetch user's list of organisations. + const personalAccessToken = process.env.LABEL_TOKEN; + if (!personalAccessToken) { + core.setFailed('LABEL_TOKEN not found'); + process.exit(1); + } + + // Initialise octokit, required to call Github GraphQL API + const octokit: InstanceType = getOctokit(personalAccessToken, { + previews: ['bane'], // The "bane" preview is required for adding, updating, creating and deleting labels. + }); + + // Retrieve labelable object (i.e. a pull request or an issue) info from context + const labelableRepoOwner = context.repo.owner; + const labelableRepoName = context.repo.repo; + let labelable: Labelable; + if (context.payload.issue?.number) { + // Retrieve issue + labelable = await retrieveIssue( + octokit, + labelableRepoOwner, + labelableRepoName, + context.payload.issue?.number, + ); + } else if (context.payload.pull_request?.number) { + // Retrieve PR + labelable = await retrievePullRequest( + octokit, + labelableRepoOwner, + labelableRepoName, + context.payload.pull_request?.number, + ); + } else { + core.setFailed( + 'Labelable object (i.e. a pull request or an issue) number not found', + ); + process.exit(1); + } + + // If author is not part of the MetaMask organisation + if (!(await userBelongsToMetaMaskOrg(octokit, labelable?.author))) { + // Add external contributor label to the issue + await addLabelToLabelable(octokit, labelable, externalContributorLabel); + } + + // Check if labelable's body matches one of the issue or PR templates ('general-issue.yml' or 'bug-report.yml' or 'pull-request-template.md'). + const templateType: TemplateType = extractTemplateTypeFromBody( + labelable.body, + ); + + if (labelable.type === LabelableType.Issue) { + if (templateType === TemplateType.GeneralIssue) { + console.log("Issue matches 'general-issue.yml' template."); + await removeLabelFromLabelableIfPresent( + octokit, + labelable, + invalidIssueTemplateLabel, + ); + } else if (templateType === TemplateType.BugReportIssue) { + console.log("Issue matches 'bug-report.yml' template."); + await removeLabelFromLabelableIfPresent( + octokit, + labelable, + invalidIssueTemplateLabel, + ); + + // Extract release version from bug report issue body (if existing) + const releaseVersion = extractReleaseVersionFromBugReportIssueBody( + labelable.body, + ); + + // Add regression prod label to the bug report issue if release version was found in issue body + if (releaseVersion) { + await addRegressionProdLabelToIssue(octokit, releaseVersion, labelable); + } else { + console.log( + `No release version was found in body of bug report issue ${labelable?.number}.`, + ); + } + } else { + const errorMessage = + "Issue body does not match any of expected templates ('general-issue.yml' or 'bug-report.yml')."; + console.log(errorMessage); + + // Add label to indicate issue doesn't match any template + await addLabelToLabelable(octokit, labelable, invalidIssueTemplateLabel); + + // Github action shall fail in case issue body doesn't match any template + core.setFailed(errorMessage); + process.exit(1); + } + } else if (labelable.type === LabelableType.PullRequest) { + if (templateType === TemplateType.PullRequest) { + console.log("PR matches 'pull-request-template.md' template."); + await removeLabelFromLabelableIfPresent( + octokit, + labelable, + invalidPullRequestTemplateLabel, + ); + } else { + const errorMessage = + "PR body does not match template ('pull-request-template.md')."; + console.log(errorMessage); + + // Add label to indicate PR body doesn't match template + await addLabelToLabelable( + octokit, + labelable, + invalidPullRequestTemplateLabel, + ); + + // Github action shall fail in case PR doesn't match template + core.setFailed(errorMessage); + process.exit(1); + } + } else { + core.setFailed( + `Shall never happen: Labelable is neither an issue nor a PR (${JSON.stringify( + labelable, + )}).`, + ); + process.exit(1); + } +} + +// This helper function checks if body matches one of the issue or PR templates ('general-issue.yml' or 'bug-report.yml' or 'pull-request-template.md') +function extractTemplateTypeFromBody(body: string): TemplateType { + for (const [templateType, template] of templates) { + let matches = true; + + for (const title of template.titles) { + if (!body.includes(title)) { + matches = false; + break; + } + } + + if (matches) { + return templateType; + } + } + + return TemplateType.None; +} + +// This helper function extracts release version from bug report issue's body. +function extractReleaseVersionFromBugReportIssueBody( + body: string, +): string | undefined { + // Remove newline characters + const cleanedBody = body.replace(/\r?\n/g, ' '); + + // Extract version from the cleaned body + const regex = /### Version\s+((.*?)(?= |$))/; + const versionMatch = cleanedBody.match(regex); + const version = versionMatch?.[1]; + + // Check if version is in the format x.y.z + if (version && !/^(\d+\.)?(\d+\.)?(\*|\d+)$/.test(version)) { + throw new Error('Version is not in the format x.y.z'); + } + + return version; +} + +// This function adds the correct "regression-prod-x.y.z" label to the issue, and removes other ones +async function addRegressionProdLabelToIssue( + octokit: InstanceType, + releaseVersion: string, + issue: Labelable, +): Promise { + // Craft regression prod label to add + const regressionProdLabel: Label = { + name: `regression-prod-${releaseVersion}`, + color: '5319E7', // violet + description: `Regression bug that was found in production in release ${releaseVersion}`, + }; + + let regressionProdLabelFound: boolean = false; + const regressionProdLabelsToBeRemoved: { + id: string; + name: string; + }[] = []; + + // Loop over issue's labels, to see if regression labels are either missing, or to be removed + issue?.labels?.forEach((label) => { + if (label?.name === regressionProdLabel.name) { + regressionProdLabelFound = true; + } else if (label?.name?.startsWith('regression-prod-')) { + regressionProdLabelsToBeRemoved.push(label); + } + }); + + // Add regression prod label to the issue if missing + if (regressionProdLabelFound) { + console.log( + `Issue ${issue?.number} already has ${regressionProdLabel.name} label.`, + ); + } else { + console.log( + `Add ${regressionProdLabel.name} label to issue ${issue?.number}.`, + ); + await addLabelToLabelable(octokit, issue, regressionProdLabel); + } + + // Remove other regression prod label from the issue + await Promise.all( + regressionProdLabelsToBeRemoved.map((label) => { + removeLabelFromLabelable(octokit, issue, label?.id); + }), + ); +} + +// This function checks if user belongs to MetaMask organization on Github +async function userBelongsToMetaMaskOrg( + octokit: InstanceType, + username: string, +): Promise { + const userBelongsToMetaMaskOrgQuery = ` + query UserBelongsToMetaMaskOrg($login: String!) { + user(login: $login) { + organization(login: "MetaMask") { + id + } + } + } + `; + + const userBelongsToMetaMaskOrgResult: { + user: { + organization: { + id: string; + }; + }; + } = await octokit.graphql(userBelongsToMetaMaskOrgQuery, { login: username }); + + return Boolean(userBelongsToMetaMaskOrgResult?.user?.organization?.id); +} diff --git a/.github/scripts/close-release-bug-report-issue.ts b/.github/scripts/close-release-bug-report-issue.ts index 8f1cf8ed91d1..625d865aced1 100644 --- a/.github/scripts/close-release-bug-report-issue.ts +++ b/.github/scripts/close-release-bug-report-issue.ts @@ -29,13 +29,17 @@ async function main(): Promise { } // Extract branch name from the context - const branchName: string = context.payload.pull_request?.head.ref || ""; + const branchName: string = context.payload.pull_request?.head.ref || ''; // Extract semver version number from the branch name - const releaseVersionNumberMatch = branchName.match(/^Version-v(\d+\.\d+\.\d+)$/); + const releaseVersionNumberMatch = branchName.match( + /^release\/(\d+\.\d+\.\d+)$/, + ); if (!releaseVersionNumberMatch) { - core.setFailed(`Failed to extract version number from branch name: ${branchName}`); + core.setFailed( + `Failed to extract version number from branch name: ${branchName}`, + ); process.exit(1); } @@ -44,17 +48,31 @@ async function main(): Promise { // Initialise octokit, required to call Github GraphQL API const octokit: InstanceType = getOctokit(personalAccessToken); - const bugReportIssue = await retrieveOpenBugReportIssue(octokit, repoOwner, bugReportRepo, releaseVersionNumber); + const bugReportIssue = await retrieveOpenBugReportIssue( + octokit, + repoOwner, + bugReportRepo, + releaseVersionNumber, + ); if (!bugReportIssue) { - throw new Error(`No open bug report issue was found for release ${releaseVersionNumber} on ${repoOwner}/${bugReportRepo} repo`); + throw new Error( + `No open bug report issue was found for release ${releaseVersionNumber} on ${repoOwner}/${bugReportRepo} repo`, + ); } - if (bugReportIssue.title?.toLocaleLowerCase() !== `v${releaseVersionNumber} Bug Report`.toLocaleLowerCase()) { - throw new Error(`Unexpected bug report title: "${bugReportIssue.title}" instead of "v${releaseVersionNumber} Bug Report"`); + if ( + bugReportIssue.title?.toLocaleLowerCase() !== + `v${releaseVersionNumber} Bug Report`.toLocaleLowerCase() + ) { + throw new Error( + `Unexpected bug report title: "${bugReportIssue.title}" instead of "v${releaseVersionNumber} Bug Report"`, + ); } - console.log(`Closing bug report issue with title "${bugReportIssue.title}" and id: ${bugReportIssue.id}`); + console.log( + `Closing bug report issue with title "${bugReportIssue.title}" and id: ${bugReportIssue.id}`, + ); await closeIssue(octokit, bugReportIssue.id); @@ -62,11 +80,18 @@ async function main(): Promise { } // This function retrieves the issue titled "vx.y.z Bug Report" on a specific repo -async function retrieveOpenBugReportIssue(octokit: InstanceType, repoOwner: string, repoName: string, releaseVersionNumber: string): Promise<{ - id: string; - title: string; -} | undefined> { - +async function retrieveOpenBugReportIssue( + octokit: InstanceType, + repoOwner: string, + repoName: string, + releaseVersionNumber: string, +): Promise< + | { + id: string; + title: string; + } + | undefined +> { const retrieveOpenBugReportIssueQuery = ` query RetrieveOpenBugReportIssue { search(query: "repo:${repoOwner}/${repoName} type:issue is:open in:title v${releaseVersionNumber} Bug Report", type: ISSUE, first: 1) { @@ -94,10 +119,11 @@ async function retrieveOpenBugReportIssue(octokit: InstanceType, return bugReportIssues?.length > 0 ? bugReportIssues[0] : undefined; } - // This function closes a Github issue, based on its ID -async function closeIssue(octokit: InstanceType, issueId: string): Promise { - +async function closeIssue( + octokit: InstanceType, + issueId: string, +): Promise { const closeIssueMutation = ` mutation CloseIssue($issueId: ID!) { updateIssue(input: {id: $issueId, state: CLOSED}) { @@ -114,7 +140,8 @@ async function closeIssue(octokit: InstanceType, issueId: string) issueId, }); - const clientMutationId = closeIssueMutationResult?.updateIssue?.clientMutationId; + const clientMutationId = + closeIssueMutationResult?.updateIssue?.clientMutationId; return clientMutationId; } diff --git a/.github/scripts/shared/issue.ts b/.github/scripts/shared/issue.ts new file mode 100644 index 000000000000..e5c6804630ed --- /dev/null +++ b/.github/scripts/shared/issue.ts @@ -0,0 +1,183 @@ +import { GitHub } from '@actions/github/lib/utils'; + +import { LabelableType, Labelable } from './labelable'; + +// This function retrieves an issue on a specific repo +export async function retrieveIssue( + octokit: InstanceType, + repoOwner: string, + repoName: string, + issueNumber: number, +): Promise { + const retrieveIssueQuery = ` + query GetIssue($repoOwner: String!, $repoName: String!, $issueNumber: Int!) { + repository(owner: $repoOwner, name: $repoName) { + issue(number: $issueNumber) { + id + createdAt + body + author { + login + } + labels(first: 100) { + nodes { + id + name + } + } + } + } + } + `; + + const retrieveIssueResult: { + repository: { + issue: { + id: string; + createdAt: string; + body: string; + author: { + login: string; + }; + labels: { + nodes: { + id: string; + name: string; + }[]; + }; + }; + }; + } = await octokit.graphql(retrieveIssueQuery, { + repoOwner, + repoName, + issueNumber, + }); + + const issue: Labelable = { + id: retrieveIssueResult?.repository?.issue?.id, + type: LabelableType.Issue, + number: issueNumber, + repoOwner: repoOwner, + repoName: repoName, + createdAt: retrieveIssueResult?.repository?.issue?.createdAt, + body: retrieveIssueResult?.repository?.issue?.body, + author: retrieveIssueResult?.repository?.issue?.author?.login, + labels: retrieveIssueResult?.repository?.issue?.labels?.nodes, + }; + + return issue; +} + +// This function retrieves the list of linked issues for a pull request +export async function retrieveLinkedIssues( + octokit: InstanceType, + repoOwner: string, + repoName: string, + prNumber: number, +): Promise { + // We assume there won't be more than 100 linked issues + const retrieveLinkedIssuesQuery = ` + query ($repoOwner: String!, $repoName: String!, $prNumber: Int!) { + repository(owner: $repoOwner, name: $repoName) { + pullRequest(number: $prNumber) { + closingIssuesReferences(first: 100) { + nodes { + id + number + createdAt + body + author { + login + } + labels(first: 100) { + nodes { + id + name + } + } + repository { + name + owner { + login + } + } + } + } + } + } + } + `; + + const retrieveLinkedIssuesResult: { + repository: { + pullRequest: { + closingIssuesReferences: { + nodes: Array<{ + id: string; + number: number; + createdAt: string; + body: string; + author: { + login: string; + }; + labels: { + nodes: { + id: string; + name: string; + }[]; + }; + repository: { + name: string; + owner: { + login: string; + }; + }; + }>; + }; + }; + }; + } = await octokit.graphql(retrieveLinkedIssuesQuery, { + repoOwner, + repoName, + prNumber, + }); + + const linkedIssues: Labelable[] = + retrieveLinkedIssuesResult?.repository?.pullRequest?.closingIssuesReferences?.nodes?.map( + (issue: { + id: string; + number: number; + createdAt: string; + body: string; + author: { + login: string; + }; + labels: { + nodes: { + id: string; + name: string; + }[]; + }; + repository: { + name: string; + owner: { + login: string; + }; + }; + }) => { + return { + id: issue?.id, + type: LabelableType.Issue, + number: issue?.number, + repoOwner: issue?.repository?.owner?.login, + repoName: issue?.repository?.name, + createdAt: issue?.createdAt, + body: issue?.body, + author: issue?.author?.login, + labels: issue?.labels?.nodes, + }; + }, + ) || []; + + return linkedIssues; +} diff --git a/.github/scripts/shared/label.ts b/.github/scripts/shared/label.ts new file mode 100644 index 000000000000..32097fda66ee --- /dev/null +++ b/.github/scripts/shared/label.ts @@ -0,0 +1,121 @@ +import { GitHub } from '@actions/github/lib/utils'; + +import { retrieveRepo } from './repo'; + +export interface Label { + name: string; + color: string; + description: string; +} + +export const externalContributorLabel: Label = { + name: 'external-contributor', + color: '7057FF', + description: 'Issue or PR created by user outside org', +}; + +export const invalidIssueTemplateLabel: Label = { + name: 'INVALID-ISSUE-TEMPLATE', + color: 'EDEDED', + description: "Issue's body doesn't match template", +}; + +export const invalidPullRequestTemplateLabel: Label = { + name: 'INVALID-PR-TEMPLATE', + color: 'EDEDED', + description: "PR's body doesn't match template", +}; + +// This function creates or retrieves the label on a specific repo +export async function createOrRetrieveLabel( + octokit: InstanceType, + repoOwner: string, + repoName: string, + label: Label, +): Promise { + // Check if label already exists on the repo + let labelId = await retrieveLabel(octokit, repoOwner, repoName, label.name); + + // If label doesn't exist on the repo, create it + if (!labelId) { + console.log( + `${label.name} label doesn't exist on ${repoName} repo. It needs to be created.`, + ); + + // Retrieve PR's repo + const repoId = await retrieveRepo(octokit, repoOwner, repoName); + + // Create label on repo + labelId = await createLabel(octokit, repoId, label); + } + + return labelId; +} + +// This function creates the label on a specific repo +async function createLabel( + octokit: InstanceType, + repoId: string, + label: Label, +): Promise { + const createLabelMutation = ` + mutation CreateLabel($repoId: ID!, $labelName: String!, $labelColor: String!, $labelDescription: String) { + createLabel(input: {repositoryId: $repoId, name: $labelName, color: $labelColor, description: $labelDescription}) { + label { + id + } + } + } + `; + + const createLabelResult: { + createLabel: { + label: { + id: string; + }; + }; + } = await octokit.graphql(createLabelMutation, { + repoId, + labelName: label.name, + labelColor: label.color, + labelDescription: label.description, + }); + + const labelId = createLabelResult?.createLabel?.label?.id; + + return labelId; +} + +// This function retrieves the label on a specific repo +async function retrieveLabel( + octokit: InstanceType, + repoOwner: string, + repoName: string, + labelName: string, +): Promise { + const retrieveLabelQuery = ` + query RetrieveLabel($repoOwner: String!, $repoName: String!, $labelName: String!) { + repository(owner: $repoOwner, name: $repoName) { + label(name: $labelName) { + id + } + } + } + `; + + const retrieveLabelResult: { + repository: { + label: { + id: string; + }; + }; + } = await octokit.graphql(retrieveLabelQuery, { + repoOwner, + repoName, + labelName, + }); + + const labelId = retrieveLabelResult?.repository?.label?.id; + + return labelId; +} diff --git a/.github/scripts/shared/labelable.ts b/.github/scripts/shared/labelable.ts new file mode 100644 index 000000000000..56a8675190c0 --- /dev/null +++ b/.github/scripts/shared/labelable.ts @@ -0,0 +1,89 @@ +import { GitHub } from '@actions/github/lib/utils'; + +import { Label, createOrRetrieveLabel } from './label'; + +export enum LabelableType { + Issue, + PullRequest, +} + +// A labelable object can be a pull request or an issue +export interface Labelable { + id: string; + type: LabelableType; + number: number; + repoOwner: string; + repoName: string; + createdAt: string; + body: string; + author: string; + labels: { + id: string; + name: string; + }[]; +} + +// This function adds label to a labelable object (i.e. a pull request or an issue) +export async function addLabelToLabelable( + octokit: InstanceType, + labelable: Labelable, + label: Label, +): Promise { + // Retrieve label from the labelable's repo, or create label if required + const labelId = await createOrRetrieveLabel( + octokit, + labelable?.repoOwner, + labelable?.repoName, + label, + ); + + const addLabelsToLabelableMutation = ` + mutation AddLabelsToLabelable($labelableId: ID!, $labelIds: [ID!]!) { + addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) { + clientMutationId + } + } + `; + + await octokit.graphql(addLabelsToLabelableMutation, { + labelableId: labelable?.id, + labelIds: [labelId], + }); +} + +// This function removes a label from a labelable object (i.e. a pull request or an issue) +export async function removeLabelFromLabelable( + octokit: InstanceType, + labelable: Labelable, + labelId: string, +): Promise { + const removeLabelsFromLabelableMutation = ` + mutation RemoveLabelsFromLabelable($labelableId: ID!, $labelIds: [ID!]!) { + removeLabelsFromLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) { + clientMutationId + } + } + `; + + await octokit.graphql(removeLabelsFromLabelableMutation, { + labelableId: labelable?.id, + labelIds: [labelId], + }); +} + +// This function removes a label from a labelable object (i.e. a pull request or an issue) if present +export async function removeLabelFromLabelableIfPresent( + octokit: InstanceType, + labelable: Labelable, + labelToRemove: Label, +): Promise { + // Check if label is present on issue + const labelFound = labelable?.labels?.find( + (label) => label.name === labelToRemove?.name, + ); + + if (labelFound?.id) { + // Remove label from labelable + await removeLabelFromLabelable(octokit, labelable, labelFound?.id); + } +} diff --git a/.github/scripts/shared/pull-request.ts b/.github/scripts/shared/pull-request.ts new file mode 100644 index 000000000000..8cf6805f57cf --- /dev/null +++ b/.github/scripts/shared/pull-request.ts @@ -0,0 +1,69 @@ +import { GitHub } from '@actions/github/lib/utils'; + +import { LabelableType, Labelable } from './labelable'; + +// This function retrieves a pull request on a specific repo +export async function retrievePullRequest( + octokit: InstanceType, + repoOwner: string, + repoName: string, + prNumber: number, +): Promise { + const retrievePullRequestQuery = ` + query RetrievePullRequestLabels($repoOwner: String!, $repoName: String!, $prNumber: Int!) { + repository(owner: $repoOwner, name: $repoName) { + pullRequest(number: $prNumber) { + id + createdAt + body + author { + login + } + labels(first: 100) { + nodes { + id + name + } + } + } + } + } + `; + + const retrievePullRequestResult: { + repository: { + pullRequest: { + id: string; + createdAt: string; + body: string; + author: { + login: string; + }; + labels: { + nodes: { + id: string; + name: string; + }[]; + }; + }; + }; + } = await octokit.graphql(retrievePullRequestQuery, { + repoOwner, + repoName, + prNumber, + }); + + const pullRequest: Labelable = { + id: retrievePullRequestResult?.repository?.pullRequest?.id, + type: LabelableType.PullRequest, + number: prNumber, + repoOwner: repoOwner, + repoName: repoName, + createdAt: retrievePullRequestResult?.repository?.pullRequest?.createdAt, + body: retrievePullRequestResult?.repository?.pullRequest?.body, + author: retrievePullRequestResult?.repository?.pullRequest?.author?.login, + labels: retrievePullRequestResult?.repository?.pullRequest?.labels?.nodes, + }; + + return pullRequest; +} diff --git a/.github/scripts/shared/repo.ts b/.github/scripts/shared/repo.ts new file mode 100644 index 000000000000..09e475350016 --- /dev/null +++ b/.github/scripts/shared/repo.ts @@ -0,0 +1,29 @@ +import { GitHub } from '@actions/github/lib/utils'; + +// This function retrieves the repo +export async function retrieveRepo( + octokit: InstanceType, + repoOwner: string, + repoName: string, +): Promise { + const retrieveRepoQuery = ` + query RetrieveRepo($repoOwner: String!, $repoName: String!) { + repository(owner: $repoOwner, name: $repoName) { + id + } + } + `; + + const retrieveRepoResult: { + repository: { + id: string; + }; + } = await octokit.graphql(retrieveRepoQuery, { + repoOwner, + repoName, + }); + + const repoId = retrieveRepoResult?.repository?.id; + + return repoId; +} diff --git a/.github/scripts/shared/template.ts b/.github/scripts/shared/template.ts new file mode 100644 index 000000000000..43a705f073d0 --- /dev/null +++ b/.github/scripts/shared/template.ts @@ -0,0 +1,71 @@ +interface Template { + titles: string[]; +} + +// An enum for different templates and issue/PR can match +export enum TemplateType { + GeneralIssue, + BugReportIssue, + PullRequest, + None, +} + +// Titles of general issue template +const generalIssueTemplateTitles = [ + '### What is this about?', + '### Scenario', + '### Design', + '### Technical Details', + '### Threat Modeling Framework', + '### Acceptance Criteria', + '### References', +]; + +// Titles of bug report template +const bugReportIssueTemplateTitles = [ + '### Describe the bug', + '### Expected behavior', + '### Screenshots', // TODO: replace '### Screenshots' by '### Screenshots/Recordings' in January 2024 (as most issues will meet this criteria by then) + '### Steps to reproduce', + '### Error messages or log output', + '### Version', + '### Build type', + '### Browser', + '### Operating system', + '### Hardware wallet', + '### Additional context', + '### Severity', +]; + +// Titles of PR template +const prTemplateTitles = [ + '## **Description**', + '## **Related issues**', + '## **Manual testing steps**', + '## **Screenshots/Recordings**', + '### **Before**', + '### **After**', + '## **Pre-merge author checklist**', + '## **Pre-merge reviewer checklist**', +]; + +export const templates = new Map([ + [ + TemplateType.GeneralIssue, + { + titles: generalIssueTemplateTitles, + }, + ], + [ + TemplateType.BugReportIssue, + { + titles: bugReportIssueTemplateTitles, + }, + ], + [ + TemplateType.PullRequest, + { + titles: prTemplateTitles, + }, + ], +]); diff --git a/.github/workflows/check-issue-template-and-add-labels.yml b/.github/workflows/check-template-and-add-labels.yml similarity index 53% rename from .github/workflows/check-issue-template-and-add-labels.yml rename to .github/workflows/check-template-and-add-labels.yml index 11baaeafbc60..e5311b70d22e 100644 --- a/.github/workflows/check-issue-template-and-add-labels.yml +++ b/.github/workflows/check-template-and-add-labels.yml @@ -1,19 +1,19 @@ -name: Check issue template and add labels +name: Check template and add labels on: issues: - types: - - opened - - edited + types: [opened, edited] + pull_request_target: + types: [opened, edited] jobs: - add-regression-prod-label: + check-template-and-add-labels: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 with: - fetch-depth: 1 # This retrieves only the latest commit. + fetch-depth: 1 # This retrieves only the latest commit. - name: Set up Node.js uses: actions/setup-node@v3 @@ -24,8 +24,8 @@ jobs: - name: Install dependencies run: yarn --immutable - - name: Check issue template and add labels - id: check-issue-template-and-add-labels + - name: Check template and add labels + id: check-template-and-add-labels env: LABEL_TOKEN: ${{ secrets.LABEL_TOKEN }} - run: npm run check-issue-template-and-add-labels + run: npm run check-template-and-add-labels diff --git a/.storybook/initial-states/approval-screens/token-approval.js b/.storybook/initial-states/approval-screens/token-approval.js index 08aa375d7c0f..66ebe7e45d8f 100644 --- a/.storybook/initial-states/approval-screens/token-approval.js +++ b/.storybook/initial-states/approval-screens/token-approval.js @@ -2,7 +2,6 @@ export const currentNetworkTxListSample = { "id": 7900715443136469, "time": 1621395091737, "status": "unapproved", - "metamaskNetworkId": "1337", "chainId": "0x539", "loadingDefaults": false, "txParams": { @@ -20,7 +19,6 @@ export const currentNetworkTxListSample = { "id": 7900715443136469, "time": 1621395091737, "status": "unapproved", - "metamaskNetworkId": "1337", "chainId": "0x539", "loadingDefaults": true, "txParams": { @@ -53,4 +51,4 @@ export const subjectMetadata = { "iconUrl": "https://metamask.github.io/test-dapp/metamask-fox.svg", "subjectType": "website" } -} \ No newline at end of file +} diff --git a/.storybook/initial-states/transactions.js b/.storybook/initial-states/transactions.js index cc0f0e1be3c6..6dd76b2be7e8 100644 --- a/.storybook/initial-states/transactions.js +++ b/.storybook/initial-states/transactions.js @@ -24,7 +24,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 643368596521636, time: 1653527035634, status: 'submitted', - metamaskNetworkId: '5', originalGasEstimate: '5208', userEditedGasLimit: false, chainId: '0x5', @@ -50,7 +49,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 643368596521636, time: 1653527035634, status: 'approved', - metamaskNetworkId: '5', originalGasEstimate: '5208', userEditedGasLimit: false, chainId: '0x5', @@ -158,7 +156,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { }, id: 7694052085150913, loadingDefaults: true, - metamaskNetworkId: '5', origin: 'https://remix.ethereum.org', originalGasEstimate: '0x118f4', sendFlowHistory: [], @@ -181,7 +178,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.DEPLOY_CONTRACT]: { blockNumber: '6195527', id: 4243712234858468, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1585088013000, txParams: { @@ -238,7 +235,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.INCOMING]: { blockNumber: '6477257', id: 4243712234858505, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1589314295000, txParams: { @@ -292,7 +289,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 3938342322880462, time: 1653459456297, status: 'failed', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '14609', userEditedGasLimit: false, chainId: '0x5', @@ -322,7 +319,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 3938342322880462, time: 1653459456297, status: 'approved', - metamaskNetworkId: '5', originalGasEstimate: '14609', userEditedGasLimit: false, chainId: '0x5', @@ -481,7 +477,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 4243712234858512, time: 1589314601567, status: 'confirmed', - metamaskNetworkId: '5', + chainId: '0x5', loadingDefaults: false, txParams: { from: '0xabca64466f257793eaa52fcfff5066894b76a149', @@ -497,7 +493,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.SMART]: { blockNumber: '6195527', id: 4243712234858468, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1585088013000, txParams: { @@ -517,7 +513,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.SWAP]: { blockNumber: '6195527', id: 4243712234858467, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1585088013000, txParams: { @@ -536,7 +532,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.SWAP_APPROVAL]: { blockNumber: '6195527', id: 4243712234858467, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1585088013000, txParams: { @@ -556,7 +552,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058729, time: 1653457101080, status: 'submitted', - metamaskNetworkId: '5', originalGasEstimate: '0xb427', userEditedGasLimit: false, chainId: '0x5', @@ -584,7 +579,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058729, time: 1653457101080, status: 'unapproved', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0xb427', userEditedGasLimit: false, chainId: '0x5', @@ -760,7 +755,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 1441203963845330, time: 1652206763566, status: 'confirmed', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0x118e0', userEditedGasLimit: false, chainId: '0x5', @@ -861,7 +856,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058725, time: 1653457077370, status: 'confirmed', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x5', @@ -889,7 +884,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058725, time: 1653457077370, status: 'unapproved', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x5', @@ -1215,7 +1210,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058754, time: 1653457323504, status: 'confirmed', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0x10896', userEditedGasLimit: false, chainId: '0x5', @@ -1265,7 +1260,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058754, time: 1653457323504, status: 'unapproved', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0x10896', userEditedGasLimit: false, chainId: '0x5', diff --git a/.storybook/main.js b/.storybook/main.js index 9a7c342c7de0..b2060367f6e7 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -23,6 +23,7 @@ module.exports = { 'storybook-dark-mode', '@whitespace/storybook-addon-html', '@storybook/addon-mdx-gfm', + '@storybook/addon-designs', ], staticDirs: ['../app', './images'], // Uses babel.config.js settings and prevents "Missing class properties transform" error diff --git a/.storybook/preview.js b/.storybook/preview.js index fa8e49cf588a..d3a060481250 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -15,7 +15,7 @@ import MetaMetricsProviderStorybook from './metametrics'; import testData from './test-data.js'; import { Router } from 'react-router-dom'; import { createBrowserHistory } from 'history'; -import { _setBackgroundConnection } from '../ui/store/action-queue'; +import { setBackgroundConnection } from '../ui/store/background-connection'; import MetaMaskStorybookTheme from './metamask-storybook-theme'; import { addons } from '@storybook/addons'; @@ -78,7 +78,7 @@ const proxiedBackground = new Proxy( }, }, ); -_setBackgroundConnection(proxiedBackground); +setBackgroundConnection(proxiedBackground); const metamaskDecorator = (story, context) => { const [isDark, setDark] = useState(false); diff --git a/.storybook/test-data.js b/.storybook/test-data.js index b78ae04c0620..85ed71007646 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -319,7 +319,6 @@ const state = { id: 3111025347726181, time: 1620710815484, status: 'unapproved', - metamaskNetworkId: '5', msgParams: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4', chainId: '0x5', loadingDefaults: false, @@ -339,7 +338,6 @@ const state = { id: 7786962153682822, time: 1620710815484, status: 'unapproved', - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: true, txParams: { @@ -586,7 +584,6 @@ const state = { dappSuggestedGasFees: null, id: 2360388496987298, loadingDefaults: true, - metamaskNetworkId: '56', origin: 'metamask', status: 'unapproved', time: 1629582710520, @@ -873,7 +870,6 @@ const state = { ], id: 7900715443136469, loadingDefaults: false, - metamaskNetworkId: '56', origin: 'metamask', r: '0x90a4dfb0646eef9815454d0ab543b5844acb8772101084565155c93ecce8ed69', rawTx: @@ -1153,7 +1149,7 @@ const state = { '0x2de9256a7c604586f7ecfd87ae9509851e217f588f9f85feed793c54ed2ce0aa': { blockNumber: '8888976', id: 4678200543090532, - metamaskNetworkId: '1', + chainId: '0x1', status: 'confirmed', time: 1573114896000, txParams: { @@ -1170,7 +1166,7 @@ const state = { '0x320a1fd769373578f78570e5d8f56e89bc7bce9657bb5f4c12d8fe790d471bfd': { blockNumber: '9453174', id: 4678200543090535, - metamaskNetworkId: '1', + chainId: '0x1', status: 'confirmed', time: 1581312411000, txParams: { @@ -1187,7 +1183,7 @@ const state = { '0x8add6c1ea089a8de9b15fa2056b1875360f17916755c88ace9e5092b7a4b1239': { blockNumber: '10892417', id: 4678200543090542, - metamaskNetworkId: '1', + chainId: '0x1', status: 'confirmed', time: 1600515224000, txParams: { @@ -1204,7 +1200,7 @@ const state = { '0x50be62ab1cabd03ff104c602c11fdef7a50f3d73c55006d5583ba97950ab1144': { blockNumber: '10902987', id: 4678200543090545, - metamaskNetworkId: '1', + chainId: '0x1', status: 'confirmed', time: 1600654021000, txParams: { @@ -1385,7 +1381,6 @@ const state = { isLoading: false, warning: null, buyView: {}, - isMouseUser: true, gasIsLoading: false, defaultHdPaths: { trezor: "m/44'/60'/0'/0", @@ -1425,7 +1420,6 @@ const state = { id: 3111025347726181, time: 1620723786838, status: 'unapproved', - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: false, txParams: { @@ -1444,7 +1438,6 @@ const state = { id: 3111025347726181, time: 1620723786838, status: 'unapproved', - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: true, txParams: { diff --git a/.yarn/patches/@metamask-assets-controllers-npm-13.0.0-91c45127c5.patch b/.yarn/patches/@metamask-assets-controllers-npm-13.0.0-91c45127c5.patch deleted file mode 100644 index f08c11b7e114..000000000000 --- a/.yarn/patches/@metamask-assets-controllers-npm-13.0.0-91c45127c5.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/dist/AssetsContractController.js b/dist/AssetsContractController.js -index 8b34ffd2bcf5642967cdfdbd979084bd20a46fc9..18ece5b68db58de0702d569b41df1ccde55b05bf 100644 ---- a/dist/AssetsContractController.js -+++ b/dist/AssetsContractController.js -@@ -34,6 +34,8 @@ exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = { - [assetsUtil_1.SupportedTokenDetectionNetworks.polygon]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4', - [assetsUtil_1.SupportedTokenDetectionNetworks.avax]: '0xD023D153a0DFa485130ECFdE2FAA7e612EF94818', - [assetsUtil_1.SupportedTokenDetectionNetworks.aurora]: '0x1286415D333855237f89Df27D388127181448538', -+ [assetsUtil_1.SupportedTokenDetectionNetworks.linea_goerli]: '0x10dAd7Ca3921471f616db788D9300DC97Db01783', -+ [assetsUtil_1.SupportedTokenDetectionNetworks.linea_mainnet]: '0xF62e6a41561b3650a69Bb03199C735e3E3328c0D', - }; - exports.MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; - /** -diff --git a/dist/assetsUtil.js b/dist/assetsUtil.js -index 521fd94d1cebd828fe13b1e4b2a0970b0337cbc7..4b1164705297a2ca39619e48f9ec9433c26bd728 100644 ---- a/dist/assetsUtil.js -+++ b/dist/assetsUtil.js -@@ -121,6 +121,8 @@ var SupportedTokenDetectionNetworks; - SupportedTokenDetectionNetworks["polygon"] = "0x89"; - SupportedTokenDetectionNetworks["avax"] = "0xa86a"; - SupportedTokenDetectionNetworks["aurora"] = "0x4e454152"; -+ SupportedTokenDetectionNetworks["linea_goerli"] = "0xe704"; -+ SupportedTokenDetectionNetworks["linea_mainnet"] = "0xe708"; - })(SupportedTokenDetectionNetworks = exports.SupportedTokenDetectionNetworks || (exports.SupportedTokenDetectionNetworks = {})); - /** - * Check if token detection is enabled for certain networks. diff --git a/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch b/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch deleted file mode 100644 index 128ad3f80189..000000000000 --- a/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch +++ /dev/null @@ -1,17 +0,0 @@ -diff --git a/dist/SignatureController.js b/dist/SignatureController.js -index 46d4b4d0553f86d368d30b7e90a9dc2e03d26ef9..e7063a3753bc3821e661c11132e33304b4fce416 100644 ---- a/dist/SignatureController.js -+++ b/dist/SignatureController.js -@@ -280,8 +280,11 @@ _SignatureController_isEthSignEnabled = new WeakMap(), _SignatureController_getA - resultCallbacks = acceptResult.resultCallbacks; - } - catch (_a) { -+ signaturePromise.catch(() => { -+ // Expecting reject error but throwing manually rather than waiting -+ }); - __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_cancelAbstractMessage).call(this, messageManager, messageId); -- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the request.'); -+ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(`MetaMask ${messageName} Signature: User denied message signature.`); - } - yield signMessage(messageParamsWithId, signingOpts); - const signatureResult = yield signaturePromise; diff --git a/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch b/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch new file mode 100644 index 000000000000..692db45490f5 --- /dev/null +++ b/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch @@ -0,0 +1,23 @@ +diff --git a/dist/SignatureController.js b/dist/SignatureController.js +index 8ac1b2158ff4564fe2f942ca955bd337d78a94ef..c6552d874d830e610fcff791eb0f87f51fae1770 100644 +--- a/dist/SignatureController.js ++++ b/dist/SignatureController.js +@@ -278,6 +278,9 @@ _SignatureController_isEthSignEnabled = new WeakMap(), _SignatureController_getA + const messageParamsWithId = Object.assign(Object.assign(Object.assign({}, messageParams), { metamaskId: messageId }), (version && { version })); + const signaturePromise = messageManager.waitForFinishStatus(messageParamsWithId, messageName); + try { ++ signaturePromise.catch(() => { ++ // Expecting reject error but throwing manually rather than waiting ++ }); + // Signature request is proposed to the user + __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_addLog).call(this, signTypeForLogger, logging_controller_1.SigningStage.Proposed, messageParamsWithId); + const acceptResult = yield __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_requestApproval).call(this, messageParamsWithId, approvalType); +@@ -287,7 +290,7 @@ _SignatureController_isEthSignEnabled = new WeakMap(), _SignatureController_getA + // User rejected the signature request + __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_addLog).call(this, signTypeForLogger, logging_controller_1.SigningStage.Rejected, messageParamsWithId); + __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_cancelAbstractMessage).call(this, messageManager, messageId); +- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the request.'); ++ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(`MetaMask ${messageName} Signature: User denied message signature.`); + } + yield signMessage(messageParamsWithId, signingOpts); + const signatureResult = yield signaturePromise; diff --git a/.yarnrc.yml b/.yarnrc.yml index 5881cfe128a8..ccf8881d6796 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -106,6 +106,11 @@ npmAuditIgnoreAdvisories: # upon old versions of ethereumjs-utils. - 'ethereum-cryptography (deprecation)' + # Currently only dependent on deprecated @metamask/types as it is brought in + # by @metamask/keyring-api. Updating the dependency in keyring-api will + # remove this. + - '@metamask/types (deprecation)' + npmRegistries: 'https://npm.pkg.github.com': npmAlwaysAuth: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 519b56d5db27..a79036fd96f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.5.0] +### Added +- Updated logging so that signature requests are included in the MetaMask state logs, which can be downloaded from Settings. ([#21207)](https://github.com/MetaMask/metamask-extension/pull/21207)) + +### Changed +- Updated the token import button to display the number of tokens to be imported ([#21521)](https://github.com/MetaMask/metamask-extension/pull/21521)) +- Improved the visibility of the 'Buy & Sell' button label in full-screen mode ([#21568)](https://github.com/MetaMask/metamask-extension/pull/21568)) +- Updated the account picker to prevent its background from changing when it is disabled ([#21451)](https://github.com/MetaMask/metamask-extension/pull/21451)) and ([#21450)](https://github.com/MetaMask/metamask-extension/pull/21450)) +- Updated 'Copy to Clipboard' functionality to ensure copied content is in plain text format ([#21387 ](https://github.com/MetaMask/metamask-extension/pull/21387)) + +### Fixed +- Fixed an issue where the correct icon was not displayed for some custom ERC20 tokens ([#21508)](https://github.com/MetaMask/metamask-extension/pull/21508)) +- Prevent errors when accessing a token details page for a token not already imported by the user ([#21400)](https://github.com/MetaMask/metamask-extension/pull/21400)) +- Ensure ERC20 Token shows correct name ([#21401)](https://github.com/MetaMask/metamask-extension/pull/21401)) +- EFixed the send screen so that it clears Hex Data when changing the asset from an ERC20 token to ETH ([#21336)](https://github.com/MetaMask/metamask-extension/pull/21336)) +- Fixed an issue where the conversion rate was incorrectly displayed as 'ETH' when sending tokens on Polygon and BNB chains ([#21185)](https://github.com/MetaMask/metamask-extension/pull/21185)) +- Fixed the incorrect display of insufficient balance errors when the account issuing the transaction is different from the currently selected account ([#21174)](https://github.com/MetaMask/metamask-extension/pull/21174)) +- Fixing truncation and alignment in the network toggle component ([#21370)](https://github.com/MetaMask/metamask-extension/pull/21370)) +- Fixed an issue in the transaction history where token amounts sent without decimals were incorrectly displayed as 0([#21338)](https://github.com/MetaMask/metamask-extension/pull/21338)) +- Fixed overflow issues in the Blockaid Security Alert ([#21317)](https://github.com/MetaMask/metamask-extension/pull/21317)) +- Fix alignment of legacy connect text ([#21552)](https://github.com/MetaMask/metamask-extension/pull/21552)) +- Remove network name from the network picker in the Popup view ([#21374)](https://github.com/MetaMask/metamask-extension/pull/21374)) + ## [11.4.1] ### Changed - Fixes the snaps website link pointing to the wrong URL. ([#21619](https://github.com/MetaMask/metamask-extension/pull/21619)) @@ -127,7 +150,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added a toggle to allow users to turn off IPFS image resolution ([#20172](https://github.com/MetaMask/metamask-extension/pull/20172)) - Added toggles to allow users a per-network opt-out of incoming transactions functionality ([#20363](https://github.com/MetaMask/metamask-extension/pull/20363)) - Added a toggle to allow users to opt-out of 4byte contract method names resolution ([#20098](https://github.com/MetaMask/metamask-extension/pull/20098)) -- MetaMask Institutional releases will now be available from the releases page ([#20788](https://github.com/MetaMask/metamask-extension/pull/20788)) +- MetaMask Institutional releases will now be available from the releases page ([#20788](https://github.com/MetaMask/metamask-extension/pull/20788)) ### Changed - Display a "Buy more" link in swaps if the user has insufficient funds for a proposed swap ([#20241](https://github.com/MetaMask/metamask-extension/pull/20241)) @@ -160,7 +183,7 @@ Update styles and spacing on the critical error page ([#20350](https://github.c - Remove some spacing in the NFT collection UI ([#20442](https://github.com/MetaMask/metamask-extension/pull/20442)) - Modify the visual alignment of the "MetaMask Support" links ([#20354](https://github.com/MetaMask/metamask-extension/pull/20354)) - Remove the "Source" and "Link" URLs from NFT details ([#20248](https://github.com/MetaMask/metamask-extension/pull/20248)) -- Modify full screen settings styles and spacing ([#20676](https://github.com/MetaMask/metamask-extension/pull/20676)) ([#20674](https://github.com/MetaMask/metamask-extension/pull/20674)) +- Modify full screen settings styles and spacing ([#20676](https://github.com/MetaMask/metamask-extension/pull/20676)) ([#20674](https://github.com/MetaMask/metamask-extension/pull/20674)) - Switch display order of fiat and eth values in Account List Menu ([#20334](https://github.com/MetaMask/metamask-extension/pull/20334)) - Update OpenSea alert placement on Token Allowance, Confirm Pages, SIWE, and Signature V3/V4 pages ([#20530](https://github.com/MetaMask/metamask-extension/pull/20530)) - Update scroll behaviour on Snaps install screens, so users can scroll with a button, and proceed to the next screen after scrolling to the bottom once ([#20889](https://github.com/MetaMask/metamask-extension/pull/20889)) @@ -4148,7 +4171,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c ### Uncategorized - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.4.1...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.5.0...HEAD +[11.5.0]: https://github.com/MetaMask/metamask-extension/compare/v11.4.1...v11.5.0 [11.4.1]: https://github.com/MetaMask/metamask-extension/compare/v11.4.0...v11.4.1 [11.4.0]: https://github.com/MetaMask/metamask-extension/compare/v11.3.0...v11.4.0 [11.3.0]: https://github.com/MetaMask/metamask-extension/compare/v11.2.0...v11.3.0 diff --git a/README.md b/README.md index adad35727fbc..a616d89e0ca2 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Differnt build types have different e2e tests sets. In order to run them look in ```console "test:e2e:chrome:mmi": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mmi", "test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps", - "test:e2e:chrome:mv3": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mv3", + "test:e2e:chrome:mv3": "ENABLE_MV3=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js", ``` Note: MMI runs a subset of MetaMask's e2e tests. To facilitate this, we have appended the `@no-mmi` tags to the names of those tests that are not applicable to this build type. @@ -164,8 +164,8 @@ Whenever you change dependencies (adding, removing, or updating, either in `pack ## Dapp Developer Resources -- [Extend MetaMask's features w/ MetaMask Snaps.](https://docs.metamask.io/guide/snaps.html) -- [Prompt your users to add and switch to a new network.](https://medium.com/metamask/connect-users-to-layer-2-networks-with-the-metamask-custom-networks-api-d0873fac51e5) -- [Change the logo that appears when your dapp connects to MetaMask.](https://docs.metamask.io/guide/defining-your-icon.html) +- [Extend MetaMask's features w/ MetaMask Snaps.](https://docs.metamask.io/snaps/) +- [Prompt your users to add and switch to a new network.](https://docs.metamask.io/wallet/how-to/add-network/) +- [Change the logo that appears when your dapp connects to MetaMask.](https://docs.metamask.io/wallet/how-to/display/icon/) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BbackgroundConnection%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index c82fc509d096..860129bc4336 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 ist einsatzbereit" }, - "snapUpdate": { - "message": "Snap aktualisieren" - }, "snapUpdateAvailable": { "message": "Update verfügbar" }, @@ -4035,9 +4032,6 @@ "message": "Alle Daten, die Sie mit Drittanbieterdiensten teilen, werden direkt von diesen Drittanbieterdiensten im Einklang mit deren Datenschutzerklärung erfasst. Für weitere Informationen lesen Sie bitte die jeweiligen Datenschutzerklärungen.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Verwalten Sie Ihre Snaps." - }, "snapsTermsOfUse": { "message": "Nutzungsbedingungen" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 65e49c18d128..c4a097a48cde 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "Το $1 είναι έτοιμο για χρήση" }, - "snapUpdate": { - "message": "Ενημέρωση του snap" - }, "snapUpdateAvailable": { "message": "Διαθέσιμη ενημέρωση" }, @@ -4035,9 +4032,6 @@ "message": "Οποιεσδήποτε πληροφορίες μοιράζεστε με τις Υπηρεσίες Τρίτων θα συλλέγονται απευθείας από τις εν λόγω Υπηρεσίες Τρίτων σύμφωνα με τις πολιτικές απορρήτου τους. Ανατρέξτε στις πολιτικές απορρήτου τους για περισσότερες πληροφορίες.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Διαχειριστείτε τα Snaps σας" - }, "snapsTermsOfUse": { "message": "Όροι Χρήσης" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index ecde90430fed..77e3304cadf5 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -609,8 +609,7 @@ "message": "If you approve this request, a third party known for scams will take all your assets." }, "blockaidMessage": { - "message": "Privacy preserving - no data is shared with third parties. Available on Ethereum Mainnet. $1", - "description": "$1 is a link to a Blockaid's terms of use" + "message": "Privacy preserving - no data is shared with third parties. Available on Ethereum Mainnet." }, "blockaidTitleDeceptive": { "message": "This is a deceptive request" @@ -2064,6 +2063,9 @@ "installOrigin": { "message": "Install origin" }, + "installRequest": { + "message": "Installation request" + }, "installedOn": { "message": "Installed on $1", "description": "$1 is the date when the snap has been installed" @@ -4028,6 +4030,9 @@ "send": { "message": "Send" }, + "sendAToken": { + "message": "Send a token" + }, "sendBugReport": { "message": "Send us a bug report." }, @@ -4324,9 +4329,6 @@ "snapResultSuccessDescription": { "message": "$1 is ready to use" }, - "snapUpdate": { - "message": "Update snap" - }, "snapUpdateAvailable": { "message": "Update available" }, @@ -4378,9 +4380,6 @@ "snapsSettings": { "message": "Snap settings" }, - "snapsSettingsDescription": { - "message": "Manage your Snaps" - }, "snapsTermsOfUse": { "message": "Terms of Use" }, @@ -5505,6 +5504,9 @@ "update": { "message": "Update" }, + "updateRequest": { + "message": "Update request" + }, "updatedWithDate": { "message": "Updated $1" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index c83605862e23..e5d3379ebca0 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 está listo para usar" }, - "snapUpdate": { - "message": "Actualizar snap" - }, "snapUpdateAvailable": { "message": "Actualización disponible" }, @@ -4035,9 +4032,6 @@ "message": "Cualquier información que comparta con Servicios de terceros será recopilada directamente por dichos Servicios de terceros de acuerdo con sus políticas de privacidad. Consulte sus políticas de privacidad para obtener más información.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Administre sus snaps" - }, "snapsTermsOfUse": { "message": "Términos de uso" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 455a116d0a08..efa8fbb53383 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 est prêt à être utilisé" }, - "snapUpdate": { - "message": "Mettre à jour Snap" - }, "snapUpdateAvailable": { "message": "Une mise à jour est disponible" }, @@ -4035,9 +4032,6 @@ "message": "Toute information que vous partagez avec des services tiers sera collectée directement par ces services tiers conformément à leur politique de confidentialité. Pour plus d’informations, veuillez consulter leur politique de confidentialité.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Gérez vos Snaps" - }, "snapsTermsOfUse": { "message": "Conditions d’utilisation" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index fb8c1fec8908..e7fc14629206 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 इस्तेमाल के लिए तैयार है" }, - "snapUpdate": { - "message": "Snap अपडेट करें" - }, "snapUpdateAvailable": { "message": "अपडेट उपलब्ध है" }, @@ -4035,9 +4032,6 @@ "message": "थर्ड पार्टी सेवाओं के साथ आप जो भी सूचना शेयर करते हैं, उसे उन थर्ड पार्टी सेवाओं द्वारा उनकी अपनी गोपनीयता नीतियों के अनुसार सीधे एकत्र की जाएगी। अधिक जानकारी के लिए कृपया उनकी गोपनीयता नीतियां देखें।", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "अपने Snaps प्रबंधित करें" - }, "snapsTermsOfUse": { "message": "इस्तेमाल की शर्तें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 9361a0d18725..1807c227c7a4 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 siap digunakan" }, - "snapUpdate": { - "message": "Perbarui Snap" - }, "snapUpdateAvailable": { "message": "Pembaruan tersedia" }, @@ -4035,9 +4032,6 @@ "message": "Setiap informasi yang Anda bagikan kepada Layanan Pihak Ketiga akan dikumpulkan langsung oleh Layanan Pihak Ketiga tersebut sesuai dengan kebijakan privasinya. Baca kebijakan privasinya untuk informasi lebih lanjut.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Kelola Snap Anda" - }, "snapsTermsOfUse": { "message": "Ketentuan Penggunaan" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index b025ee475416..d674d210d094 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1を使用する準備が整いました" }, - "snapUpdate": { - "message": "snapを更新" - }, "snapUpdateAvailable": { "message": "アップデートが利用できます" }, @@ -4035,9 +4032,6 @@ "message": "サードパーティサービスと共有する情報は、当該サードパーティサービスにより、それぞれのプライバシーポリシーに従い直接収集されます。詳細は、各サードパーティサービスのプライバシーポリシーをご覧ください。", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Snapsの管理" - }, "snapsTermsOfUse": { "message": "利用規約" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 59ecbc154bc7..866677ef726d 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 사용 가능" }, - "snapUpdate": { - "message": "스냅 업데이트" - }, "snapUpdateAvailable": { "message": "업데이트 가능" }, @@ -4035,9 +4032,6 @@ "message": "타사와 공유하는 모든 정보는 해당 타사의 개인정보 처리방침에 따라 직접 수집됩니다. 더 자세한 내용은 해당 회사의 개인정보 처리방침을 참고하시기 바랍니다.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "스냅 관리" - }, "snapsTermsOfUse": { "message": "이용약관" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 48ace43cb738..6f12221a4123 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 está pronto para ser usado" }, - "snapUpdate": { - "message": "Atualizar snap" - }, "snapUpdateAvailable": { "message": "Atualização disponível" }, @@ -4035,9 +4032,6 @@ "message": "Informações compartilhadas com Serviços de Terceiros serão coletadas diretamente por eles, de acordo com políticas de privacidade próprias. Por favor, consulte-as para obter mais informações.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Gerencie seus snaps" - }, "snapsTermsOfUse": { "message": "Termos de Uso" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 1b242bfc3a2c..5c966b1ec65f 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 готово к использованию" }, - "snapUpdate": { - "message": "Обновить snap" - }, "snapUpdateAvailable": { "message": "Доступно обновление" }, @@ -4035,9 +4032,6 @@ "message": "Любая информация, которую вы передаете Сторонним службам, будет собираться непосредственно этими Сторонними службами в соответствии с их политикой конфиденциальности. Пожалуйста, обратитесь к их политике конфиденциальности для получения дополнительной информации.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Управление вашим Snaps" - }, "snapsTermsOfUse": { "message": "Условия использования" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index e9b90c2a6a35..8bb427f47343 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "Handa nang gamitin ang $1" }, - "snapUpdate": { - "message": "I-update ang snap" - }, "snapUpdateAvailable": { "message": "Available ang update" }, @@ -4035,9 +4032,6 @@ "message": "Ang anumang impormasyong ibinabahagi mo sa mga Serbisyo ng Third Party ay direktang kokolektahin ng mga Serbisyo ng Third Party na iyon alinsunod sa kanilang mga patakaran sa pagkapribado. Pakitingnan ang kanilang mga patakaran sa pagkapribado para sa higit pang impormasyon.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Pamahalaan ang iyong mga Snap" - }, "snapsTermsOfUse": { "message": "Mga Tuntunin ng Paggamit" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 288ebc27d591..06181b9ea347 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 kullanıma hazır" }, - "snapUpdate": { - "message": "Snapi güncelle" - }, "snapUpdateAvailable": { "message": "Güncelleme mevcut" }, @@ -4035,9 +4032,6 @@ "message": "Üçüncü Taraf Hizmetleri ile paylaştığınız tüm bilgiler söz konusu Üçüncü Taraf Hizmetlerinin gizlilik politikalarına göre doğrudan üçüncü taraflar tarafından toplanacaktır. Daha fazla bilgi için lütfen üçüncü tarafların gizlilik politikalarına bakın.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Snaplerini yönet" - }, "snapsTermsOfUse": { "message": "Kullanım Şartları" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index c2947dc6f564..40e87c48e8e5 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1 đã sẵn sàng để sử dụng" }, - "snapUpdate": { - "message": "Cập nhật Snap" - }, "snapUpdateAvailable": { "message": "Có cập nhật mới" }, @@ -4035,9 +4032,6 @@ "message": "Mọi thông tin mà bạn chia sẻ với Dịch vụ bên thứ ba sẽ được Dịch vụ bên thứ ba đó thu thập trực tiếp theo chính sách quyền riêng tư của họ. Vui lòng tham khảo chính sách quyền riêng tư của họ để biết thêm thông tin.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Quản lý Snap" - }, "snapsTermsOfUse": { "message": "Điều khoản sử dụng" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 02bd5041a838..0416921c96e5 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -3998,9 +3998,6 @@ "snapResultSuccessDescription": { "message": "$1已可以使用" }, - "snapUpdate": { - "message": "更新Snap" - }, "snapUpdateAvailable": { "message": "有更新" }, @@ -4035,9 +4032,6 @@ "message": "您与第三方服务分享的任何信息,将由这些第三方服务根据其隐私政策直接收集。请参阅其隐私政策以了解更多信息。", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "管理您的 Snaps" - }, "snapsTermsOfUse": { "message": "使用条款" }, diff --git a/app/scripts/background.js b/app/scripts/background.js index 9e86cf58ef09..469d1cc7ba0b 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -114,9 +114,6 @@ const ONE_SECOND_IN_MILLISECONDS = 1_000; // Timeout for initializing phishing warning page. const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS; -const ACK_KEEP_ALIVE_MESSAGE = 'ACK_KEEP_ALIVE_MESSAGE'; -const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE'; - ///: BEGIN:ONLY_INCLUDE_IN(desktop) const OVERRIDE_ORIGIN = { EXTENSION: 'EXTENSION', @@ -208,6 +205,12 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { connectExternal(...args); }); +function saveTimestamp() { + const timestamp = new Date().toISOString(); + + browser.storage.session.set({ timestamp }); +} + /** * @typedef {import('../../shared/constants/transaction').TransactionMeta} TransactionMeta */ @@ -233,7 +236,6 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { * @property {object} providerConfig - The current selected network provider. * @property {string} providerConfig.rpcUrl - The address for the RPC API, if using an RPC API. * @property {string} providerConfig.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks. - * @property {string} networkId - The stringified number of the current network ID. * @property {string} networkStatus - Either "unknown", "available", "unavailable", or "blocked", depending on the status of the currently selected network. * @property {object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values. * @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string. @@ -279,6 +281,13 @@ async function initialize() { let isFirstMetaMaskControllerSetup; if (isManifestV3) { + // Save the timestamp immediately and then every `SAVE_TIMESTAMP_INTERVAL` + // miliseconds. This keeps the service worker alive. + const SAVE_TIMESTAMP_INTERVAL_MS = 2 * 1000; + + saveTimestamp(); + setInterval(saveTimestamp, SAVE_TIMESTAMP_INTERVAL_MS); + const sessionData = await browser.storage.session.get([ 'isFirstMetaMaskControllerSetup', ]); @@ -300,6 +309,7 @@ async function initialize() { } await sendReadyMessageToTabs(); log.info('MetaMask initialization complete.'); + resolveInitialization(); } catch (error) { rejectInitialization(error); @@ -601,20 +611,6 @@ export function setupController( controller.isClientOpen = true; controller.setupTrustedCommunication(portStream, remotePort.sender); - if (isManifestV3) { - // If we get a WORKER_KEEP_ALIVE message, we respond with an ACK - remotePort.onMessage.addListener((message) => { - if (message.name === WORKER_KEEP_ALIVE_MESSAGE) { - // To test un-comment this line and wait for 1 minute. An error should be shown on MetaMask UI. - remotePort.postMessage({ name: ACK_KEEP_ALIVE_MESSAGE }); - - controller.appStateController.setServiceWorkerLastActiveTime( - Date.now(), - ); - } - }); - } - if (processName === ENVIRONMENT_TYPE_POPUP) { popupIsOpen = true; endOfStream(portStream, () => { diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 1ed6d60a68e5..360c6cd80249 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,12 +1,10 @@ -import pump from 'pump'; import { WindowPostMessageStream } from '@metamask/post-message-stream'; -import ObjectMultiplex from 'obj-multiplex'; -import browser from 'webextension-polyfill'; import PortStream from 'extension-port-stream'; +import ObjectMultiplex from 'obj-multiplex'; +import pump from 'pump'; import { obj as createThoughStream } from 'through2'; -import log from 'loglevel'; - -import { EXTENSION_MESSAGES, MESSAGE_TYPE } from '../../shared/constants/app'; +import browser from 'webextension-polyfill'; +import { EXTENSION_MESSAGES } from '../../shared/constants/app'; import { checkForLastError } from '../../shared/modules/browser-runtime.utils'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; import shouldInjectProvider from '../../shared/modules/provider-injection'; @@ -82,72 +80,6 @@ function injectScript(content) { } } -/** - * SERVICE WORKER LOGIC - */ - -const EXTENSION_CONTEXT_INVALIDATED_CHROMIUM_ERROR = - 'Extension context invalidated.'; - -const WORKER_KEEP_ALIVE_INTERVAL = 1000; -const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE'; -const TIME_45_MIN_IN_MS = 45 * 60 * 1000; - -/** - * Don't run the keep-worker-alive logic for JSON-RPC methods called on initial load. - * This is to prevent the service worker from being kept alive when accounts are not - * connected to the dapp or when the user is not interacting with the extension. - * The keep-alive logic should not work for non-dapp pages. - */ -const IGNORE_INIT_METHODS_FOR_KEEP_ALIVE = [ - MESSAGE_TYPE.GET_PROVIDER_STATE, - MESSAGE_TYPE.SEND_METADATA, -]; - -let keepAliveInterval; -let keepAliveTimer; - -/** - * Sending a message to the extension to receive will keep the service worker alive. - * - * If the extension is unloaded or reloaded during a session and the user attempts to send a - * message to the extension, an "Extension context invalidated." error will be thrown from - * chromium browsers. When this happens, prompt the user to reload the extension. Note: Handling - * this error is not supported in Firefox here. - */ -const sendMessageWorkerKeepAlive = () => { - browser.runtime - .sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }) - .catch((e) => { - e.message === EXTENSION_CONTEXT_INVALIDATED_CHROMIUM_ERROR - ? log.error(`Please refresh the page. MetaMask: ${e}`) - : log.error(`MetaMask: ${e}`); - }); -}; - -/** - * Running this method will ensure the service worker is kept alive for 45 minutes. - * The first message is sent immediately and subsequent messages are sent at an - * interval of WORKER_KEEP_ALIVE_INTERVAL. - */ -const runWorkerKeepAliveInterval = () => { - clearTimeout(keepAliveTimer); - - keepAliveTimer = setTimeout(() => { - clearInterval(keepAliveInterval); - }, TIME_45_MIN_IN_MS); - - clearInterval(keepAliveInterval); - - sendMessageWorkerKeepAlive(); - - keepAliveInterval = setInterval(() => { - if (browser.runtime.id) { - sendMessageWorkerKeepAlive(); - } - }, WORKER_KEEP_ALIVE_INTERVAL); -}; - /** * PHISHING STREAM LOGIC */ @@ -159,10 +91,6 @@ function setupPhishingPageStreams() { target: PHISHING_WARNING_PAGE, }); - if (isManifestV3) { - runWorkerKeepAliveInterval(); - } - // create and connect channel muxers // so we can handle the channels individually phishingPageMux = new ObjectMultiplex(); @@ -299,14 +227,6 @@ const setupPageStreams = () => { target: INPAGE, }); - if (isManifestV3) { - pageStream.on('data', ({ data: { method } }) => { - if (!IGNORE_INIT_METHODS_FOR_KEEP_ALIVE.includes(method)) { - runWorkerKeepAliveInterval(); - } - }); - } - // create and connect channel muxers // so we can handle the channels individually pageMux = new ObjectMultiplex(); @@ -381,14 +301,6 @@ const setupLegacyPageStreams = () => { target: LEGACY_INPAGE, }); - if (isManifestV3) { - legacyPageStream.on('data', ({ data: { method } }) => { - if (!IGNORE_INIT_METHODS_FOR_KEEP_ALIVE.includes(method)) { - runWorkerKeepAliveInterval(); - } - }); - } - legacyPageMux = new ObjectMultiplex(); legacyPageMux.setMaxListeners(25); diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 0d219d751e5b..853a02aa4171 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -62,7 +62,6 @@ export default class AppStateController extends EventEmitter { '0x5': true, '0x539': true, }, - serviceWorkerLastActiveTime: 0, }); this.timer = null; @@ -435,12 +434,6 @@ export default class AppStateController extends EventEmitter { return this.store.getState().currentPopupId; } - setServiceWorkerLastActiveTime(serviceWorkerLastActiveTime) { - this.store.updateState({ - serviceWorkerLastActiveTime, - }); - } - _requestApproval() { this._approvalRequestId = uuid(); diff --git a/app/scripts/controllers/ens/ens.js b/app/scripts/controllers/ens/ens.js index 4f8de4a12a1f..62779bccaf8d 100644 --- a/app/scripts/controllers/ens/ens.js +++ b/app/scripts/controllers/ens/ens.js @@ -1,17 +1,18 @@ import { Web3Provider } from '@ethersproject/providers'; import ensNetworkMap from 'ethereum-ens-network-map'; -import { NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP } from '../../../../shared/constants/network'; +import { CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP } from '../../../../shared/constants/network'; export default class Ens { - static getNetworkEnsSupport(network) { - return Boolean(ensNetworkMap[network]); + static getChainEnsSupport(chainId) { + return Boolean(ensNetworkMap[parseInt(chainId, 16).toString()]); } - constructor({ network, provider } = {}) { - const networkName = NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP[network]; - const ensAddress = ensNetworkMap[network]; + constructor({ chainId, provider } = {}) { + const networkName = CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP[chainId]; + const chainIdInt = parseInt(chainId, 16); + const ensAddress = ensNetworkMap[chainIdInt.toString()]; const ethProvider = new Web3Provider(provider, { - chainId: parseInt(network, 10), + chainId: chainIdInt, name: networkName, ensAddress, }); diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js index 47363a1562e6..efea49e85567 100644 --- a/app/scripts/controllers/ens/index.js +++ b/app/scripts/controllers/ens/index.js @@ -1,7 +1,6 @@ import punycode from 'punycode/punycode'; import { ObservableStore } from '@metamask/obs-store'; import log from 'loglevel'; -import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../../../shared/constants/network'; import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; import Ens from './ens'; @@ -17,10 +16,9 @@ export default class EnsController { this._ens = ens; if (!this._ens) { const chainId = getCurrentChainId(); - const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId]; - if (Ens.getNetworkEnsSupport(network)) { + if (Ens.getChainEnsSupport(chainId)) { this._ens = new Ens({ - network, + chainId, provider, }); } @@ -35,10 +33,9 @@ export default class EnsController { onNetworkDidChange(() => { this.store.putState(initState); const chainId = getCurrentChainId(); - const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId]; - if (Ens.getNetworkEnsSupport(network)) { + if (Ens.getChainEnsSupport(chainId)) { this._ens = new Ens({ - network, + chainId, provider, }); } else { diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index a3e5593f8791..1fc0255cd8a7 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -144,6 +144,10 @@ export default class MetaMetricsController { this.extension = extension; this.environment = environment; + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + this.selectedAddress = prefState.selectedAddress; + ///: END:ONLY_INCLUDE_IN + const abandonedFragments = omitBy(initState?.fragments, 'persist'); const segmentApiCalls = initState?.segmentApiCalls || {}; @@ -194,7 +198,7 @@ export default class MetaMetricsController { // tracked if the event isn't progressed within that amount of time. if (isManifestV3) { /* eslint-disable no-undef */ - this.extension.alarms.getAll((alarms) => { + this.extension.alarms.getAll().then((alarms) => { const hasAlarm = checkAlarmExists( alarms, METAMETRICS_FINALIZE_EVENT_FRAGMENT_ALARM, @@ -712,6 +716,10 @@ export default class MetaMetricsController { if (this.extension?.runtime?.id) { mmiProps.extensionId = this.extension.runtime.id; } + + if (this.selectedAddres) { + mmiProps.accountAddress = this.selectedAddres; + } ///: END:ONLY_INCLUDE_IN return { diff --git a/app/scripts/controllers/mmi-controller.test.js b/app/scripts/controllers/mmi-controller.test.js index f8c06d62981d..7adc37044a34 100644 --- a/app/scripts/controllers/mmi-controller.test.js +++ b/app/scripts/controllers/mmi-controller.test.js @@ -41,7 +41,6 @@ describe('MMIController', function () { type: 'rinkeby', }, getCurrentChainId: jest.fn(), - getNetworkId: jest.fn(), onNetworkStateChange: jest.fn(), }), signatureController: new SignatureController({ diff --git a/app/scripts/controllers/permissions/snaps/snap-permissions.js b/app/scripts/controllers/permissions/snaps/snap-permissions.js index 4788608d010d..ca6467eb0e31 100644 --- a/app/scripts/controllers/permissions/snaps/snap-permissions.js +++ b/app/scripts/controllers/permissions/snaps/snap-permissions.js @@ -1,7 +1,7 @@ import { restrictedMethodPermissionBuilders, selectHooks, -} from '@metamask/rpc-methods'; +} from '@metamask/snaps-rpc-methods'; import { endowmentPermissionBuilders } from '@metamask/snaps-controllers'; import { ExcludedSnapEndowments, diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js index 42f685bfbf2e..af2c8631c04f 100644 --- a/app/scripts/controllers/permissions/specifications.js +++ b/app/scripts/controllers/permissions/specifications.js @@ -4,7 +4,7 @@ import { } from '@metamask/permission-controller'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications } from '@metamask/snaps-controllers'; -import { caveatSpecifications as snapsCaveatsSpecifications } from '@metamask/rpc-methods'; +import { caveatSpecifications as snapsCaveatsSpecifications } from '@metamask/snaps-rpc-methods'; ///: END:ONLY_INCLUDE_IN import { CaveatTypes, diff --git a/app/scripts/controllers/permissions/specifications.test.js b/app/scripts/controllers/permissions/specifications.test.js index eb64a6a4c7cf..0c672efe68a7 100644 --- a/app/scripts/controllers/permissions/specifications.test.js +++ b/app/scripts/controllers/permissions/specifications.test.js @@ -1,4 +1,4 @@ -import { SnapCaveatType } from '@metamask/rpc-methods'; +import { SnapCaveatType } from '@metamask/snaps-rpc-methods'; import { CaveatTypes, RestrictedMethods, diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 1e9fe2c44eb6..775a02506517 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -155,17 +155,20 @@ export default class SwapsController { this.indexOfNewestCallInFlight = 0; this.ethersProvider = new Web3Provider(provider); - this._currentNetworkId = networkController.state.networkId; + this._currentChainId = networkController.state.providerConfig.chainId; onNetworkStateChange(() => { - const { networkId, networksMetadata, selectedNetworkClientId } = - networkController.state; + const { + networksMetadata, + selectedNetworkClientId, + providerConfig: { chainId }, + } = networkController.state; const selectedNetworkStatus = networksMetadata[selectedNetworkClientId]?.status; if ( selectedNetworkStatus === NetworkStatus.Available && - networkId !== this._currentNetworkId + chainId !== this._currentChainId ) { - this._currentNetworkId = networkId; + this._currentChainId = chainId; this.ethersProvider = new Web3Provider(provider); } }); diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index f4765f1b3096..44245fcb28db 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -5,11 +5,7 @@ import { BigNumber } from '@ethersproject/bignumber'; import { mapValues } from 'lodash'; import BigNumberjs from 'bignumber.js'; import { NetworkType } from '@metamask/controller-utils'; -import { - CHAIN_IDS, - NETWORK_IDS, - NetworkStatus, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, NetworkStatus } from '../../../shared/constants/network'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import { createTestProviderTools } from '../../../test/stub/provider'; import { SECOND } from '../../../shared/constants/time'; @@ -102,7 +98,6 @@ const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({ function getMockNetworkController() { return { state: { - networkId: NETWORK_IDS.GOERLI, selectedNetworkClientId: NetworkType.goerli, networksMetadata: { [NetworkType.goerli]: { @@ -110,6 +105,9 @@ function getMockNetworkController() { status: NetworkStatus.Available, }, }, + providerConfig: { + chainId: CHAIN_IDS.GOERLI, + }, }, }; } @@ -230,7 +228,6 @@ describe('SwapsController', function () { const currentEthersInstance = swapsController.ethersProvider; networkController.state = { - networkId: NETWORK_IDS.MAINNET, selectedNetworkClientId: NetworkType.mainnet, networksMetadata: { [NetworkType.mainnet]: { @@ -238,6 +235,9 @@ describe('SwapsController', function () { status: NetworkStatus.Available, }, }, + providerConfig: { + chainId: CHAIN_IDS.MAINNET, + }, }; networkStateChangeListener(); @@ -268,7 +268,6 @@ describe('SwapsController', function () { const currentEthersInstance = swapsController.ethersProvider; networkController.state = { - networkId: null, selectedNetworkClientId: NetworkType.goerli, networksMetadata: { [NetworkType.goerli]: { @@ -276,6 +275,9 @@ describe('SwapsController', function () { status: NetworkStatus.Unknown, }, }, + providerConfig: { + chainId: CHAIN_IDS.GOERLI, + }, }; networkStateChangeListener(); @@ -306,7 +308,6 @@ describe('SwapsController', function () { const currentEthersInstance = swapsController.ethersProvider; networkController.state = { - networkId: NETWORK_IDS.GOERLI, selectedNetworkClientId: NetworkType.goerli, networksMetadata: { [NetworkType.goerli]: { @@ -314,6 +315,9 @@ describe('SwapsController', function () { status: NetworkStatus.Available, }, }, + providerConfig: { + chainId: CHAIN_IDS.GOERLI, + }, }; networkStateChangeListener(); diff --git a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts index ee53f04d3741..d850616b2e06 100644 --- a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts +++ b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts @@ -98,7 +98,6 @@ const EXPECTED_NORMALISED_TRANSACTION_BASE = { chainId: undefined, hash: ETHERSCAN_TRANSACTION_SUCCESS_MOCK.hash, id: ID_MOCK, - metamaskNetworkId: undefined, status: TransactionStatus.confirmed, time: 1543596356000, txParams: { @@ -158,7 +157,6 @@ describe('EtherscanRemoteTransactionSource', () => { expect( new EtherscanRemoteTransactionSource().isSupportedNetwork( CHAIN_IDS.MAINNET, - '1', ), ).toBe(true); }); @@ -167,7 +165,6 @@ describe('EtherscanRemoteTransactionSource', () => { expect( new EtherscanRemoteTransactionSource().isSupportedNetwork( CHAIN_IDS.LOCALHOST, - '1', ), ).toBe(false); }); diff --git a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts index 0fe179d59b6c..f0ac784c060b 100644 --- a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts +++ b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts @@ -43,7 +43,7 @@ export class EtherscanRemoteTransactionSource this.#includeTokenTransfers = includeTokenTransfers ?? true; } - isSupportedNetwork(chainId: Hex, _networkId: string): boolean { + isSupportedNetwork(chainId: Hex): boolean { return Object.keys(ETHERSCAN_SUPPORTED_NETWORKS).includes(chainId); } @@ -68,19 +68,11 @@ export class EtherscanRemoteTransactionSource await Promise.all([transactionPromise, tokenTransactionPromise]); const transactions = etherscanTransactions.result.map((tx) => - this.#normalizeTransaction( - tx, - request.currentNetworkId, - request.currentChainId, - ), + this.#normalizeTransaction(tx, request.currentChainId), ); const tokenTransactions = etherscanTokenTransactions.result.map((tx) => - this.#normalizeTokenTransaction( - tx, - request.currentNetworkId, - request.currentChainId, - ), + this.#normalizeTokenTransaction(tx, request.currentChainId), ); return [...transactions, ...tokenTransactions]; @@ -88,14 +80,9 @@ export class EtherscanRemoteTransactionSource #normalizeTransaction( txMeta: EtherscanTransactionMeta, - currentNetworkId: string, currentChainId: Hex, ): TransactionMeta { - const base = this.#normalizeTransactionBase( - txMeta, - currentNetworkId, - currentChainId, - ); + const base = this.#normalizeTransactionBase(txMeta, currentChainId); return { ...base, @@ -113,14 +100,9 @@ export class EtherscanRemoteTransactionSource #normalizeTokenTransaction( txMeta: EtherscanTokenTransactionMeta, - currentNetworkId: string, currentChainId: Hex, ): TransactionMeta { - const base = this.#normalizeTransactionBase( - txMeta, - currentNetworkId, - currentChainId, - ); + const base = this.#normalizeTransactionBase(txMeta, currentChainId); return { ...base, @@ -129,7 +111,6 @@ export class EtherscanRemoteTransactionSource #normalizeTransactionBase( txMeta: EtherscanTransactionMetaBase, - currentNetworkId: string, currentChainId: Hex, ): TransactionMeta { const time = parseInt(txMeta.timeStamp, 10) * 1000; @@ -139,7 +120,6 @@ export class EtherscanRemoteTransactionSource chainId: currentChainId, hash: txMeta.hash, id: uuid(), - metamaskNetworkId: currentNetworkId, status: TransactionStatus.confirmed, time, txParams: { diff --git a/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts b/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts index f20675215885..b04977b74157 100644 --- a/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts +++ b/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts @@ -19,7 +19,6 @@ const NETWORK_STATE_MOCK: NetworkState = { chainId: '0x1', type: NetworkType.mainnet, }, - networkId: '1', } as unknown as NetworkState; const ADDERSS_MOCK = '0x1'; @@ -139,7 +138,6 @@ describe('IncomingTransactionHelper', () => { expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledWith({ address: ADDERSS_MOCK, currentChainId: NETWORK_STATE_MOCK.providerConfig.chainId, - currentNetworkId: NETWORK_STATE_MOCK.networkId, fromBlock: expect.any(Number), limit: CONTROLLER_ARGS_MOCK.transactionLimit, }); diff --git a/app/scripts/controllers/transactions/IncomingTransactionHelper.ts b/app/scripts/controllers/transactions/IncomingTransactionHelper.ts index 3acb6372637f..9cad8e4d0948 100644 --- a/app/scripts/controllers/transactions/IncomingTransactionHelper.ts +++ b/app/scripts/controllers/transactions/IncomingTransactionHelper.ts @@ -117,7 +117,6 @@ export class IncomingTransactionHelper { const fromBlock = this.#getFromBlock(latestBlockNumber); const address = this.#getCurrentAccount(); const currentChainId = this.#getCurrentChainId(); - const currentNetworkId = this.#getCurrentNetworkId(); let remoteTransactions = []; @@ -126,7 +125,6 @@ export class IncomingTransactionHelper { await this.#remoteTransactionSource.fetchTransactions({ address, currentChainId, - currentNetworkId, fromBlock, limit: this.#transactionLimit, }); @@ -262,12 +260,9 @@ export class IncomingTransactionHelper { #canStart(): boolean { const isEnabled = this.#isEnabled(); const currentChainId = this.#getCurrentChainId(); - const currentNetworkId = this.#getCurrentNetworkId(); - const isSupportedNetwork = this.#remoteTransactionSource.isSupportedNetwork( - currentChainId, - currentNetworkId, - ); + const isSupportedNetwork = + this.#remoteTransactionSource.isSupportedNetwork(currentChainId); return isEnabled && isSupportedNetwork; } @@ -275,8 +270,4 @@ export class IncomingTransactionHelper { #getCurrentChainId(): Hex { return this.#getNetworkState().providerConfig.chainId; } - - #getCurrentNetworkId(): string { - return this.#getNetworkState().networkId as string; - } } diff --git a/app/scripts/controllers/transactions/README.md b/app/scripts/controllers/transactions/README.md index 09ba59e806e3..c872bf951963 100644 --- a/app/scripts/controllers/transactions/README.md +++ b/app/scripts/controllers/transactions/README.md @@ -30,7 +30,8 @@ txMeta = { "id": 2828415030114568, // unique id for this txMeta used for look ups "time": 1524094064821, // time of creation "status": "confirmed", - "metamaskNetworkId": "1524091532133", //the network id for the transaction + "chainId": "0x1", // the chain id for the transaction + "metamaskNetworkId": "1524091532133", // Deprecated: the network id for the transaction. Use chainId instead "loadingDefaults": false, // used to tell the ui when we are done calculating gas defaults "txParams": { // the txParams object "from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", @@ -45,6 +46,7 @@ txMeta = { "id": 2828415030114568, "time": 1524094064821, "status": "unapproved", + "chainId": "0x1", "metamaskNetworkId": "1524091532133", "loadingDefaults": true, "txParams": { diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index fee818f99f82..81e16ec5e78a 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -20,9 +20,7 @@ import { import { TransactionStatus, TransactionType, - TokenStandard, TransactionEnvelopeType, - TransactionMetaMetricsEvent, TransactionApprovalAmountType, } from '../../../../shared/constants/transaction'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; @@ -50,25 +48,16 @@ import { NetworkStatus, } from '../../../../shared/constants/network'; import { - determineTransactionAssetType, determineTransactionContractCode, determineTransactionType, isEIP1559Transaction, } from '../../../../shared/modules/transaction.utils'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; -///: BEGIN:ONLY_INCLUDE_IN(blockaid) -import { - BlockaidReason, - BlockaidResultType, -} from '../../../../shared/constants/security-provider'; -///: END:ONLY_INCLUDE_IN import { calcGasTotal, getSwapsTokensReceivedFromTxMeta, - TRANSACTION_ENVELOPE_TYPE_NAMES, } from '../../../../shared/lib/transactions-controller-utils'; import { Numeric } from '../../../../shared/modules/Numeric'; -import getSnapAndHardwareInfoForMetrics from '../../lib/snap-keyring/metrics'; import TransactionStateManager from './tx-state-manager'; import TxGasUtil from './tx-gas-utils'; import PendingTransactionTracker from './pending-tx-tracker'; @@ -99,8 +88,6 @@ const VALID_UNAPPROVED_TRANSACTION_TYPES = [ * @typedef {import('../../../../shared/constants/gas').TxGasFees} TxGasFees */ -const METRICS_STATUS_FAILED = 'failed on-chain'; - /** * @typedef {object} CustomGasSettings * @property {string} [gas] - The gas limit to use for the transaction @@ -128,7 +115,6 @@ const METRICS_STATUS_FAILED = 'failed on-chain'; * * @param {object} opts * @param {object} opts.initState - initial transaction list default is an empty array - * @param {Function} opts.getNetworkId - Get the current network ID. * @param {Function} opts.getNetworkStatus - Get the current network status. * @param {Function} opts.getNetworkState - Get the network state. * @param {Function} opts.onNetworkStateChange - Subscribe to network state change events. @@ -145,7 +131,6 @@ const METRICS_STATUS_FAILED = 'failed on-chain'; export default class TransactionController extends EventEmitter { constructor(opts) { super(); - this.getNetworkId = opts.getNetworkId; this.getNetworkStatus = opts.getNetworkStatus; this._getNetworkState = opts.getNetworkState; this._getCurrentChainId = opts.getCurrentChainId; @@ -160,14 +145,8 @@ export default class TransactionController extends EventEmitter { this.blockTracker = opts.blockTracker; this.signEthTx = opts.signTransaction; this.inProcessOfSigning = new Set(); - this._trackMetaMetricsEvent = opts.trackMetaMetricsEvent; this._getParticipateInMetrics = opts.getParticipateInMetrics; this._getEIP1559GasFeeEstimates = opts.getEIP1559GasFeeEstimates; - this.createEventFragment = opts.createEventFragment; - this.updateEventFragment = opts.updateEventFragment; - this.finalizeEventFragment = opts.finalizeEventFragment; - this.getEventFragmentById = opts.getEventFragmentById; - this.getTokenStandardAndDetails = opts.getTokenStandardAndDetails; this.securityProviderRequest = opts.securityProviderRequest; this.getSelectedAddress = opts.getSelectedAddress; this.getAccountType = opts.getAccountType; @@ -189,7 +168,6 @@ export default class TransactionController extends EventEmitter { this.txStateManager = new TransactionStateManager({ initState: opts.initState, txHistoryLimit: opts.txHistoryLimit, - getNetworkId: this.getNetworkId, getNetworkStatus: this.getNetworkStatus, getCurrentChainId: opts.getCurrentChainId, }); @@ -727,25 +705,9 @@ export default class TransactionController extends EventEmitter { this.txStateManager.setTxStatusConfirmed(txId); this._markNonceDuplicatesDropped(txId); - const { submittedTime } = txMeta; - const metricsParams = { gas_used: gasUsed }; - - if (submittedTime) { - metricsParams.completion_time = - this._getTransactionCompletionTime(submittedTime); - } - - if (txReceipt.status === '0x0') { - metricsParams.status = METRICS_STATUS_FAILED; - // metricsParams.error = TODO: figure out a way to get the on-chain failure reason - } - - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - undefined, - metricsParams, - ); + this.emit('transaction-finalized', { + transactionMeta: txMeta, + }); this.txStateManager.updateTransaction( txMeta, @@ -776,28 +738,6 @@ export default class TransactionController extends EventEmitter { this.txStateManager.updateTransaction(txMeta, 'transactions#setTxHash'); } - /** - * Convenience method for the UI to easily create event fragments when the - * fragment does not exist in state. - * - * @param {number} transactionId - The transaction id to create the event - * fragment for - * @param {valueOf} event - event type to create - * @param {string} actionId - actionId passed from UI - */ - async createTransactionEventFragment(transactionId, event, actionId) { - const txMeta = this.txStateManager.getTransaction(transactionId); - const { properties, sensitiveProperties } = - await this._buildEventFragmentProperties(txMeta); - this._createTransactionEventFragment( - txMeta, - event, - properties, - sensitiveProperties, - actionId, - ); - } - startIncomingTransactionPolling() { this.incomingTransactionHelper.start(); } @@ -877,26 +817,13 @@ export default class TransactionController extends EventEmitter { // For 'rpc' we need to use the same basic configuration as mainnet, since // we only support EVM compatible chains, and then override the - // name, chainId and networkId properties. This is done using the + // name, and chainId properties. This is done using the // `forCustomChain` static method on the Common class. const chainId = parseInt(this._getCurrentChainId(), 16); - const networkStatus = this.getNetworkStatus(); - const networkId = this.getNetworkId(); return Common.custom({ name, chainId, - // It is improbable for a transaction to be signed while the network - // is loading for two reasons. - // 1. Pending, unconfirmed transactions are wiped on network change - // 2. The UI is unusable (loading indicator) when network is loading. - // setting the networkId to 0 is for type safety and to explicity lead - // the transaction to failing if a user is able to get to this branch - // on a custom network that requires valid network id. I have not ran - // into this limitation on any network I have attempted, even when - // hardcoding networkId to 'loading'. - networkId: - networkStatus === NetworkStatus.Available ? parseInt(networkId, 10) : 0, hardfork, }); } @@ -971,25 +898,9 @@ export default class TransactionController extends EventEmitter { this.txStateManager.setTxStatusConfirmed(txId); this._markNonceDuplicatesDropped(txId); - const { submittedTime } = txMeta; - const metricsParams = { gas_used: gasUsed }; - - if (submittedTime) { - metricsParams.completion_time = - this._getTransactionCompletionTime(submittedTime); - } - - if (txReceipt.status === '0x0') { - metricsParams.status = METRICS_STATUS_FAILED; - // metricsParams.error = TODO: figure out a way to get the on-chain failure reason - } - - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - undefined, - metricsParams, - ); + this.emit('transaction-finalized', { + transactionMeta: txMeta, + }); this.txStateManager.updateTransaction( txMeta, @@ -1316,8 +1227,8 @@ export default class TransactionController extends EventEmitter { * * @param {number} txId - the tx's Id * @param {string} rawTx - the hex string of the serialized signed transaction + * @param {string} actionId - actionId passed from UI * @returns {Promise} - * @param {number} actionId - actionId passed from UI */ async _publishTransaction(txId, rawTx, actionId) { const txMeta = this.txStateManager.getTransaction(txId); @@ -1345,11 +1256,10 @@ export default class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.submitted, + this.emit('transaction-submitted', { + transactionMeta: txMeta, actionId, - ); + }); } /** @@ -1797,7 +1707,9 @@ export default class TransactionController extends EventEmitter { ), ); case TransactionStatus.failed: - throw cleanErrorStack(ethErrors.rpc.internal(finalTxMeta.err.message)); + throw cleanErrorStack( + ethErrors.rpc.internal(finalTxMeta.error.message), + ); default: throw cleanErrorStack( ethErrors.rpc.internal( @@ -1871,11 +1783,10 @@ export default class TransactionController extends EventEmitter { // sign transaction const rawTx = await this._signTransaction(txId); await this._publishTransaction(txId, rawTx, actionId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, + this.emit('transaction-approved', { + transactionMeta: txMeta, actionId, - ); + }); // must set transaction to submitted/failed before releasing lock nonceLock.releaseLock(); } catch (err) { @@ -1906,11 +1817,10 @@ export default class TransactionController extends EventEmitter { async _cancelTransaction(txId, actionId) { const txMeta = this.txStateManager.getTransaction(txId); this.txStateManager.setTxStatusRejected(txId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.rejected, + this.emit('transaction-rejected', { + transactionMeta: txMeta, actionId, - ); + }); } /** maps methods for convenience*/ @@ -2174,7 +2084,7 @@ export default class TransactionController extends EventEmitter { _trackSwapsMetrics(txMeta, approvalTxMeta) { if (this._getParticipateInMetrics() && txMeta.swapMetaData) { if (txMeta.txReceipt.status === '0x0') { - this._trackMetaMetricsEvent({ + this.emit('transaction-swap-failed', { event: 'Swap Failed', sensitiveProperties: { ...txMeta.swapMetaData }, category: MetaMetricsEventCategory.Swaps, @@ -2210,7 +2120,7 @@ export default class TransactionController extends EventEmitter { approvalTxMeta, ); - this._trackMetaMetricsEvent({ + this.emit('transaction-swap-finalized', { event: MetaMetricsEventName.SwapCompleted, category: MetaMetricsEventCategory.Swaps, sensitiveProperties: { @@ -2260,491 +2170,6 @@ export default class TransactionController extends EventEmitter { return null; } - /** - * The allowance amount in relation to the balance for that specific token - * - * @param {string} transactionApprovalAmountType - The transaction approval amount type - * @param {string} dappProposedTokenAmount - The dapp proposed token amount - * @param {string} currentTokenBalance - The balance of the token that is being send - */ - _allowanceAmountInRelationToTokenBalance( - transactionApprovalAmountType, - dappProposedTokenAmount, - currentTokenBalance, - ) { - if ( - (transactionApprovalAmountType === TransactionApprovalAmountType.custom || - transactionApprovalAmountType === - TransactionApprovalAmountType.dappProposed) && - dappProposedTokenAmount && - currentTokenBalance - ) { - return `${new BigNumber(dappProposedTokenAmount, 16) - .div(currentTokenBalance, 10) - .times(100) - .round(2)}`; - } - return null; - } - - async _buildEventFragmentProperties(txMeta, extraParams) { - const { - type, - time, - status, - chainId, - origin: referrer, - txParams: { - gasPrice, - gas: gasLimit, - maxFeePerGas, - maxPriorityFeePerGas, - estimateSuggested, - estimateUsed, - }, - defaultGasEstimates, - originalType, - replacedById, - metamaskNetworkId: network, - customTokenAmount, - dappProposedTokenAmount, - currentTokenBalance, - originalApprovalAmount, - finalApprovalAmount, - contractMethodName, - securityProviderResponse, - ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - securityAlertResponse, - ///: END:ONLY_INCLUDE_IN - } = txMeta; - - const source = referrer === ORIGIN_METAMASK ? 'user' : 'dapp'; - - const { assetType, tokenStandard } = await determineTransactionAssetType( - txMeta, - this.query, - this.getTokenStandardAndDetails, - ); - - const gasParams = {}; - - if (isEIP1559Transaction(txMeta)) { - gasParams.max_fee_per_gas = maxFeePerGas; - gasParams.max_priority_fee_per_gas = maxPriorityFeePerGas; - } else { - gasParams.gas_price = gasPrice; - } - - if (defaultGasEstimates) { - const { estimateType } = defaultGasEstimates; - if (estimateType) { - gasParams.default_estimate = estimateType; - let defaultMaxFeePerGas = txMeta.defaultGasEstimates.maxFeePerGas; - let defaultMaxPriorityFeePerGas = - txMeta.defaultGasEstimates.maxPriorityFeePerGas; - - if ( - [ - GasRecommendations.low, - GasRecommendations.medium, - GasRecommendations.high, - ].includes(estimateType) - ) { - const { gasFeeEstimates } = await this._getEIP1559GasFeeEstimates(); - if (gasFeeEstimates?.[estimateType]?.suggestedMaxFeePerGas) { - defaultMaxFeePerGas = - gasFeeEstimates[estimateType]?.suggestedMaxFeePerGas; - gasParams.default_max_fee_per_gas = defaultMaxFeePerGas; - } - if (gasFeeEstimates?.[estimateType]?.suggestedMaxPriorityFeePerGas) { - defaultMaxPriorityFeePerGas = - gasFeeEstimates[estimateType]?.suggestedMaxPriorityFeePerGas; - gasParams.default_max_priority_fee_per_gas = - defaultMaxPriorityFeePerGas; - } - } - } - - if (txMeta.defaultGasEstimates.gas) { - gasParams.default_gas = txMeta.defaultGasEstimates.gas; - } - if (txMeta.defaultGasEstimates.gasPrice) { - gasParams.default_gas_price = txMeta.defaultGasEstimates.gasPrice; - } - } - - if (estimateSuggested) { - gasParams.estimate_suggested = estimateSuggested; - } - - if (estimateUsed) { - gasParams.estimate_used = estimateUsed; - } - - if (extraParams?.gas_used) { - gasParams.gas_used = extraParams.gas_used; - } - - const gasParamsInGwei = this._getGasValuesInGWEI(gasParams); - - let eip1559Version = '0'; - if (txMeta.txParams.maxFeePerGas) { - eip1559Version = '2'; - } - - const contractInteractionTypes = [ - TransactionType.contractInteraction, - TransactionType.tokenMethodApprove, - TransactionType.tokenMethodSafeTransferFrom, - TransactionType.tokenMethodSetApprovalForAll, - TransactionType.tokenMethodTransfer, - TransactionType.tokenMethodTransferFrom, - TransactionType.smart, - TransactionType.swap, - TransactionType.swapApproval, - ].includes(type); - - const contractMethodNames = { - APPROVE: 'Approve', - }; - - let transactionApprovalAmountType; - let transactionContractMethod; - let transactionApprovalAmountVsProposedRatio; - let transactionApprovalAmountVsBalanceRatio; - let transactionType = TransactionType.simpleSend; - if (type === TransactionType.cancel) { - transactionType = TransactionType.cancel; - } else if (type === TransactionType.retry) { - transactionType = originalType; - } else if (type === TransactionType.deployContract) { - transactionType = TransactionType.deployContract; - } else if (contractInteractionTypes) { - transactionType = TransactionType.contractInteraction; - transactionContractMethod = contractMethodName; - if ( - transactionContractMethod === contractMethodNames.APPROVE && - tokenStandard === TokenStandard.ERC20 - ) { - if (dappProposedTokenAmount === '0' || customTokenAmount === '0') { - transactionApprovalAmountType = TransactionApprovalAmountType.revoke; - } else if ( - customTokenAmount && - customTokenAmount !== dappProposedTokenAmount - ) { - transactionApprovalAmountType = TransactionApprovalAmountType.custom; - } else if (dappProposedTokenAmount) { - transactionApprovalAmountType = - TransactionApprovalAmountType.dappProposed; - } - transactionApprovalAmountVsProposedRatio = - this._allowanceAmountInRelationToDappProposedValue( - transactionApprovalAmountType, - originalApprovalAmount, - finalApprovalAmount, - ); - transactionApprovalAmountVsBalanceRatio = - this._allowanceAmountInRelationToTokenBalance( - transactionApprovalAmountType, - dappProposedTokenAmount, - currentTokenBalance, - ); - } - } - - const replacedTxMeta = this._getTransaction(replacedById); - - const TRANSACTION_REPLACEMENT_METHODS = { - RETRY: TransactionType.retry, - CANCEL: TransactionType.cancel, - SAME_NONCE: 'other', - }; - - let transactionReplaced; - if (extraParams?.dropped) { - transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.SAME_NONCE; - if (replacedTxMeta?.type === TransactionType.cancel) { - transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.CANCEL; - } else if (replacedTxMeta?.type === TransactionType.retry) { - transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.RETRY; - } - } - - let uiCustomizations; - - ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - if (securityAlertResponse?.result_type === BlockaidResultType.Failed) { - uiCustomizations = ['security_alert_failed']; - } else { - ///: END:ONLY_INCLUDE_IN - // eslint-disable-next-line no-lonely-if - if (securityProviderResponse?.flagAsDangerous === 1) { - uiCustomizations = ['flagged_as_malicious']; - } else if (securityProviderResponse?.flagAsDangerous === 2) { - uiCustomizations = ['flagged_as_safety_unknown']; - } else { - uiCustomizations = null; - } - ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - } - ///: END:ONLY_INCLUDE_IN - - /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ - let properties = { - chain_id: chainId, - referrer, - source, - status, - network, - eip_1559_version: eip1559Version, - gas_edit_type: 'none', - gas_edit_attempted: 'none', - asset_type: assetType, - token_standard: tokenStandard, - transaction_type: transactionType, - transaction_speed_up: type === TransactionType.retry, - ui_customizations: uiCustomizations, - ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - security_alert_response: - securityAlertResponse?.result_type ?? BlockaidResultType.NotApplicable, - security_alert_reason: - securityAlertResponse?.reason ?? BlockaidReason.notApplicable, - ///: END:ONLY_INCLUDE_IN - }; - - const snapAndHardwareInfo = await getSnapAndHardwareInfoForMetrics( - this.getSelectedAddress, - this.getAccountType, - this.getDeviceModel, - this.snapAndHardwareMessenger, - ); - - // merge the snapAndHardwareInfo into the properties - Object.assign(properties, snapAndHardwareInfo); - - if (transactionContractMethod === contractMethodNames.APPROVE) { - properties = { - ...properties, - transaction_approval_amount_type: transactionApprovalAmountType, - }; - } - - let sensitiveProperties = { - transaction_envelope_type: isEIP1559Transaction(txMeta) - ? TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET - : TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - first_seen: time, - gas_limit: gasLimit, - transaction_contract_method: transactionContractMethod, - transaction_replaced: transactionReplaced, - ...extraParams, - ...gasParamsInGwei, - }; - - if (transactionContractMethod === contractMethodNames.APPROVE) { - sensitiveProperties = { - ...sensitiveProperties, - transaction_approval_amount_vs_balance_ratio: - transactionApprovalAmountVsBalanceRatio, - transaction_approval_amount_vs_proposed_ratio: - transactionApprovalAmountVsProposedRatio, - }; - } - - return { properties, sensitiveProperties }; - } - - /** - * Helper method that checks for the presence of an existing fragment by id - * appropriate for the type of event that triggered fragment creation. If the - * appropriate fragment exists, then nothing is done. If it does not exist a - * new event fragment is created with the appropriate payload. - * - * @param {TransactionMeta} txMeta - Transaction meta object - * @param {TransactionMetaMetricsEvent} event - The event type that - * triggered fragment creation - * @param {object} properties - properties to include in the fragment - * @param {object} [sensitiveProperties] - sensitive properties to include in - * @param {object} [actionId] - actionId passed from UI - * the fragment - */ - _createTransactionEventFragment( - txMeta, - event, - properties, - sensitiveProperties, - actionId, - ) { - const isSubmitted = [ - TransactionMetaMetricsEvent.finalized, - TransactionMetaMetricsEvent.submitted, - ].includes(event); - const uniqueIdentifier = `transaction-${ - isSubmitted ? 'submitted' : 'added' - }-${txMeta.id}`; - - const fragment = this.getEventFragmentById(uniqueIdentifier); - if (typeof fragment !== 'undefined') { - return; - } - - switch (event) { - // When a transaction is added to the controller, we know that the user - // will be presented with a confirmation screen. The user will then - // either confirm or reject that transaction. Each has an associated - // event we want to track. While we don't necessarily need an event - // fragment to model this, having one allows us to record additional - // properties onto the event from the UI. For example, when the user - // edits the transactions gas params we can record that property and - // then get analytics on the number of transactions in which gas edits - // occur. - case TransactionMetaMetricsEvent.added: - this.createEventFragment({ - category: MetaMetricsEventCategory.Transactions, - initialEvent: TransactionMetaMetricsEvent.added, - successEvent: TransactionMetaMetricsEvent.approved, - failureEvent: TransactionMetaMetricsEvent.rejected, - properties, - sensitiveProperties, - persist: true, - uniqueIdentifier, - actionId, - }); - break; - // If for some reason an approval or rejection occurs without the added - // fragment existing in memory, we create the added fragment but without - // the initialEvent firing. This is to prevent possible duplication of - // events. A good example why this might occur is if the user had - // unapproved transactions in memory when updating to the version that - // includes this change. A migration would have also helped here but this - // implementation hardens against other possible bugs where a fragment - // does not exist. - case TransactionMetaMetricsEvent.approved: - case TransactionMetaMetricsEvent.rejected: - this.createEventFragment({ - category: MetaMetricsEventCategory.Transactions, - successEvent: TransactionMetaMetricsEvent.approved, - failureEvent: TransactionMetaMetricsEvent.rejected, - properties, - sensitiveProperties, - persist: true, - uniqueIdentifier, - actionId, - }); - break; - // When a transaction is submitted it will always result in updating - // to a finalized state (dropped, failed, confirmed) -- eventually. - // However having a fragment started at this stage allows augmenting - // analytics data with user interactions such as speeding up and - // canceling the transactions. From this controllers perspective a new - // transaction with a new id is generated for speed up and cancel - // transactions, but from the UI we could augment the previous ID with - // supplemental data to show user intent. Such as when they open the - // cancel UI but don't submit. We can record that this happened and add - // properties to the transaction event. - case TransactionMetaMetricsEvent.submitted: - this.createEventFragment({ - category: MetaMetricsEventCategory.Transactions, - initialEvent: TransactionMetaMetricsEvent.submitted, - successEvent: TransactionMetaMetricsEvent.finalized, - properties, - sensitiveProperties, - persist: true, - uniqueIdentifier, - actionId, - }); - break; - // If for some reason a transaction is finalized without the submitted - // fragment existing in memory, we create the submitted fragment but - // without the initialEvent firing. This is to prevent possible - // duplication of events. A good example why this might occur is if th - // user had pending transactions in memory when updating to the version - // that includes this change. A migration would have also helped here but - // this implementation hardens against other possible bugs where a - // fragment does not exist. - case TransactionMetaMetricsEvent.finalized: - this.createEventFragment({ - category: MetaMetricsEventCategory.Transactions, - successEvent: TransactionMetaMetricsEvent.finalized, - properties, - sensitiveProperties, - persist: true, - uniqueIdentifier, - actionId, - }); - break; - default: - break; - } - } - - /** - * Extracts relevant properties from a transaction meta - * object and uses them to create and send metrics for various transaction - * events. - * - * @param {object} txMeta - the txMeta object - * @param {TransactionMetaMetricsEvent} event - the name of the transaction event - * @param {string} actionId - actionId passed from UI - * @param {object} extraParams - optional props and values to include in sensitiveProperties - */ - async _trackTransactionMetricsEvent( - txMeta, - event, - actionId, - extraParams = {}, - ) { - if (!txMeta) { - return; - } - const { properties, sensitiveProperties } = - await this._buildEventFragmentProperties(txMeta, extraParams); - - // Create event fragments for event types that spawn fragments, and ensure - // existence of fragments for event types that act upon them. - this._createTransactionEventFragment( - txMeta, - event, - properties, - sensitiveProperties, - actionId, - ); - - let id; - - switch (event) { - // If the user approves a transaction, finalize the transaction added - // event fragment. - case TransactionMetaMetricsEvent.approved: - id = `transaction-added-${txMeta.id}`; - this.updateEventFragment(id, { properties, sensitiveProperties }); - this.finalizeEventFragment(id); - break; - // If the user rejects a transaction, finalize the transaction added - // event fragment. with the abandoned flag set. - case TransactionMetaMetricsEvent.rejected: - id = `transaction-added-${txMeta.id}`; - this.updateEventFragment(id, { properties, sensitiveProperties }); - this.finalizeEventFragment(id, { - abandoned: true, - }); - break; - // When a transaction is finalized, also finalize the transaction - // submitted event fragment. - case TransactionMetaMetricsEvent.finalized: - id = `transaction-submitted-${txMeta.id}`; - this.updateEventFragment(id, { properties, sensitiveProperties }); - this.finalizeEventFragment(`transaction-submitted-${txMeta.id}`); - break; - default: - break; - } - } - - _getTransactionCompletionTime(submittedTime) { - return Math.round((Date.now() - submittedTime) / 1000).toString(); - } - _getGasValuesInGWEI(gasParams) { const gasValuesInGwei = {}; for (const param in gasParams) { @@ -2760,27 +2185,19 @@ export default class TransactionController extends EventEmitter { _failTransaction(txId, error, actionId) { this.txStateManager.setTxStatusFailed(txId, error); const txMeta = this.txStateManager.getTransaction(txId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, + this.emit('transaction-finalized', { actionId, - { - error: error.message, - }, - ); + error: error.message, + transactionMeta: txMeta, + }); } _dropTransaction(txId) { this.txStateManager.setTxStatusDropped(txId); const txMeta = this.txStateManager.getTransaction(txId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - undefined, - { - dropped: true, - }, - ); + this.emit('transaction-dropped', { + transactionMeta: txMeta, + }); } /** @@ -2792,11 +2209,10 @@ export default class TransactionController extends EventEmitter { _addTransaction(txMeta) { this.txStateManager.addTransaction(txMeta); this.emit(`${txMeta.id}:unapproved`, txMeta); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - txMeta.actionId, - ); + this.emit('transaction-added', { + transactionMeta: txMeta, + actionId: txMeta.actionId, + }); } _onIncomingTransactions({ added: transactions }) { diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index 2c3b4fdfc03e..e90bf6a19a32 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -8,26 +8,14 @@ import { ApprovalType } from '@metamask/controller-utils'; import sinon from 'sinon'; import { errorCodes, ethErrors } from 'eth-rpc-errors'; -import { - BlockaidReason, - BlockaidResultType, -} from '../../../../shared/constants/security-provider'; import { createTestProviderTools, getTestAccounts, } from '../../../../test/stub/provider'; -import mockEstimates from '../../../../test/data/mock-estimates.json'; -import { - MetaMetricsEventCategory, - MetaMetricsTransactionEventSource, -} from '../../../../shared/constants/metametrics'; import { TransactionStatus, TransactionType, TransactionEnvelopeType, - TransactionMetaMetricsEvent, - AssetType, - TokenStandard, } from '../../../../shared/constants/transaction'; import { @@ -36,7 +24,6 @@ import { } from '../../../../shared/constants/gas'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { NetworkStatus } from '../../../../shared/constants/network'; -import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; import TxGasUtil from './tx-gas-utils'; import * as IncomingTransactionHelperClass from './IncomingTransactionHelper'; import TransactionController from '.'; @@ -48,7 +35,6 @@ const currentNetworkStatus = NetworkStatus.Available; const providerConfig = { type: 'goerli', }; -const actionId = 'DUMMY_ACTION_ID'; const VALID_ADDRESS = '0x0000000000000000000000000000000000000000'; const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001'; @@ -71,7 +57,6 @@ describe('Transaction Controller', function () { provider, providerResultStub, fromAccount, - fragmentExists, networkStatusStore, preferencesStore, getCurrentChainId, @@ -82,7 +67,6 @@ describe('Transaction Controller', function () { incomingTransactionHelperEventMock; beforeEach(function () { - fragmentExists = false; providerResultStub = { // 1 gwei eth_gasPrice: '0x0de0b6b3a7640000', @@ -131,7 +115,6 @@ describe('Transaction Controller', function () { getGasPrice() { return '0xee6b2800'; }, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => networkStatusStore.getState(), onNetworkStateChange: (listener) => networkStatusStore.subscribe(listener), @@ -147,12 +130,6 @@ describe('Transaction Controller', function () { getPermittedAccounts: () => undefined, getCurrentChainId, getParticipateInMetrics: () => false, - trackMetaMetricsEvent: () => undefined, - createEventFragment: () => undefined, - updateEventFragment: () => undefined, - finalizeEventFragment: () => undefined, - getEventFragmentById: () => - fragmentExists === false ? undefined : { id: 0 }, getEIP1559GasFeeEstimates: () => undefined, securityProviderRequest: () => undefined, preferencesStore, @@ -197,7 +174,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -207,7 +184,7 @@ describe('Transaction Controller', function () { { id: 2, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -217,7 +194,7 @@ describe('Transaction Controller', function () { { id: 3, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -236,7 +213,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -246,7 +223,7 @@ describe('Transaction Controller', function () { { id: 2, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -256,7 +233,7 @@ describe('Transaction Controller', function () { { id: 3, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -280,63 +257,63 @@ describe('Transaction Controller', function () { { id: 0, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 1, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 2, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 3, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 4, status: TransactionStatus.rejected, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 5, status: TransactionStatus.approved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 6, status: TransactionStatus.signed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 7, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 8, status: TransactionStatus.failed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, @@ -368,7 +345,7 @@ describe('Transaction Controller', function () { txMeta = { status: TransactionStatus.unapproved, id: 1, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }; @@ -403,10 +380,7 @@ describe('Transaction Controller', function () { })); assert.ok('id' in txMeta, 'should have a id'); assert.ok('time' in txMeta, 'should have a time stamp'); - assert.ok( - 'metamaskNetworkId' in txMeta, - 'should have a metamaskNetworkId', - ); + assert.ok('chainId' in txMeta, 'should have a chainId'); assert.ok('txParams' in txMeta, 'should have a txParams'); assert.ok('history' in txMeta, 'should have a history'); assert.equal( @@ -1235,7 +1209,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1285,7 +1259,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1335,7 +1309,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1385,7 +1359,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1439,7 +1413,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1556,7 +1530,7 @@ describe('Transaction Controller', function () { { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1635,7 +1609,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, @@ -1801,7 +1775,7 @@ describe('Transaction Controller', function () { { status: TransactionStatus.unapproved, id: 1, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { from: VALID_ADDRESS_TWO, @@ -1824,7 +1798,7 @@ describe('Transaction Controller', function () { { status: TransactionStatus.unapproved, id: 2, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { from: VALID_ADDRESS_TWO, @@ -1843,7 +1817,7 @@ describe('Transaction Controller', function () { }); describe('_publishTransaction', function () { - let hash, txMeta, trackTransactionMetricsEventSpy; + let hash, txMeta; beforeEach(function () { hash = @@ -1856,17 +1830,9 @@ describe('Transaction Controller', function () { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }; providerResultStub.eth_sendRawTransaction = hash; - trackTransactionMetricsEventSpy = sinon.spy( - txController, - '_trackTransactionMetricsEvent', - ); - }); - - afterEach(function () { - trackTransactionMetricsEventSpy.restore(); }); it('should publish a tx, updates the rawTx when provided a one', async function () { @@ -1894,22 +1860,6 @@ describe('Transaction Controller', function () { ); assert.equal(publishedTx.status, TransactionStatus.submitted); }); - - it('should call _trackTransactionMetricsEvent with the correct params', async function () { - const rawTx = - '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c'; - txController.txStateManager.addTransaction(txMeta); - await txController._publishTransaction(txMeta.id, rawTx); - assert.equal(trackTransactionMetricsEventSpy.callCount, 1); - assert.deepEqual( - trackTransactionMetricsEventSpy.getCall(0).args[0], - txMeta, - ); - assert.equal( - trackTransactionMetricsEventSpy.getCall(0).args[1], - TransactionMetaMetricsEvent.submitted, - ); - }); }); describe('#_markNonceDuplicatesDropped', function () { @@ -1918,7 +1868,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1929,7 +1879,7 @@ describe('Transaction Controller', function () { { id: 2, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1940,7 +1890,7 @@ describe('Transaction Controller', function () { { id: 3, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1951,7 +1901,7 @@ describe('Transaction Controller', function () { { id: 4, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1962,7 +1912,7 @@ describe('Transaction Controller', function () { { id: 5, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1973,7 +1923,7 @@ describe('Transaction Controller', function () { { id: 6, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1984,7 +1934,7 @@ describe('Transaction Controller', function () { { id: 7, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -2016,7 +1966,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2025,7 +1975,7 @@ describe('Transaction Controller', function () { { id: 2, status: TransactionStatus.rejected, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2035,7 +1985,7 @@ describe('Transaction Controller', function () { { id: 3, status: TransactionStatus.approved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2045,7 +1995,7 @@ describe('Transaction Controller', function () { { id: 4, status: TransactionStatus.signed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2055,7 +2005,7 @@ describe('Transaction Controller', function () { { id: 5, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2065,7 +2015,7 @@ describe('Transaction Controller', function () { { id: 6, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2075,7 +2025,7 @@ describe('Transaction Controller', function () { { id: 7, status: TransactionStatus.failed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2102,897 +2052,6 @@ describe('Transaction Controller', function () { }); }); - describe('#_trackTransactionMetricsEvent', function () { - let trackMetaMetricsEventSpy; - let createEventFragmentSpy; - let finalizeEventFragmentSpy; - - beforeEach(function () { - trackMetaMetricsEventSpy = sinon.spy( - txController, - '_trackMetaMetricsEvent', - ); - - createEventFragmentSpy = sinon.spy(txController, 'createEventFragment'); - - finalizeEventFragmentSpy = sinon.spy( - txController, - 'finalizeEventFragment', - ); - - sinon - .stub(txController, '_getEIP1559GasFeeEstimates') - .resolves(mockEstimates['fee-market']); - }); - - afterEach(function () { - trackMetaMetricsEventSpy.restore(); - createEventFragmentSpy.restore(); - finalizeEventFragmentSpy.restore(); - }); - - describe('On transaction created by the user', function () { - let txMeta; - - before(function () { - txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: ORIGIN_METAMASK, - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - defaultGasEstimates: { - gas: '0x7b0d', - gasPrice: '0x77359400', - }, - securityProviderResponse: { - flagAsDangerous: 0, - }, - }; - }); - - it('should create an event fragment when transaction added', async function () { - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: ORIGIN_METAMASK, - source: MetaMetricsTransactionEventSource.User, - transaction_type: TransactionType.simpleSend, - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('Should finalize the transaction added fragment as abandoned if user rejects transaction', async function () { - fragmentExists = true; - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.rejected, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], { - abandoned: true, - }); - }); - - it('Should finalize the transaction added fragment if user approves transaction', async function () { - fragmentExists = true; - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[1], - undefined, - ); - }); - - it('should create an event fragment when transaction is submitted', async function () { - const expectedPayload = { - actionId, - initialEvent: 'Transaction Submitted', - successEvent: 'Transaction Finalized', - uniqueIdentifier: 'transaction-submitted-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: ORIGIN_METAMASK, - source: MetaMetricsTransactionEventSource.User, - transaction_type: TransactionType.simpleSend, - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.submitted, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('Should finalize the transaction submitted fragment when transaction finalizes', async function () { - fragmentExists = true; - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-submitted-1', - ); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[1], - undefined, - ); - }); - }); - - describe('On transaction suggested by dapp', function () { - let txMeta; - before(function () { - txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - defaultGasEstimates: { - gas: '0x7b0d', - gasPrice: '0x77359400', - }, - securityProviderResponse: { - flagAsDangerous: 0, - }, - }; - }); - - it('should create an event fragment when transaction added', async function () { - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('Should finalize the transaction added fragment as abandoned if user rejects transaction', async function () { - fragmentExists = true; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.rejected, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], { - abandoned: true, - }); - }); - - it('Should finalize the transaction added fragment if user approves transaction', async function () { - fragmentExists = true; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[1], - undefined, - ); - }); - - it('should create an event fragment when transaction is submitted', async function () { - const expectedPayload = { - actionId, - initialEvent: 'Transaction Submitted', - successEvent: 'Transaction Finalized', - uniqueIdentifier: 'transaction-submitted-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.submitted, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('Should finalize the transaction submitted fragment when transaction finalizes', async function () { - fragmentExists = true; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-submitted-1', - ); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[1], - undefined, - ); - }); - }); - - it('should create missing fragments when events happen out of order or are missing', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityProviderResponse: { - flagAsDangerous: 0, - }, - securityAlertResponse: { - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - }, - }; - - const expectedPayload = { - actionId, - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], undefined); - }); - - it('should call _trackMetaMetricsEvent with the correct payload (extra params)', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityProviderResponse: { - flagAsDangerous: 0, - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('should call _trackMetaMetricsEvent with the correct payload when blockaid verification fails', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityAlertResponse: { - result_type: BlockaidResultType.Failed, - reason: 'some error', - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - status: 'unapproved', - transaction_type: TransactionType.simpleSend, - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: ['security_alert_failed'], - security_alert_reason: 'some error', - security_alert_response: BlockaidResultType.Failed, - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('should call _trackMetaMetricsEvent with the correct payload (extra params) when flagAsDangerous is malicious', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityProviderResponse: { - flagAsDangerous: 1, - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: ['flagged_as_malicious'], - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('should call _trackMetaMetricsEvent with the correct payload (extra params) when flagAsDangerous is unknown', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityProviderResponse: { - flagAsDangerous: 2, - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: ['flagged_as_safety_unknown'], - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('should call _trackMetaMetricsEvent with the correct payload (EIP-1559)', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - maxFeePerGas: '0x77359400', - maxPriorityFeePerGas: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - estimateSuggested: GasRecommendations.medium, - estimateUsed: GasRecommendations.high, - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - defaultGasEstimates: { - estimateType: 'medium', - maxFeePerGas: '0x77359400', - maxPriorityFeePerGas: '0x77359400', - }, - securityProviderResponse: { - flagAsDangerous: 0, - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - chain_id: '0x5', - eip_1559_version: '2', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - asset_type: AssetType.native, - token_standard: TokenStandard.none, - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - max_fee_per_gas: '2', - max_priority_fee_per_gas: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET, - estimate_suggested: GasRecommendations.medium, - estimate_used: GasRecommendations.high, - default_estimate: 'medium', - default_max_fee_per_gas: '70', - default_max_priority_fee_per_gas: '7', - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - }); - - describe('#_getTransactionCompletionTime', function () { - let nowStub; - - beforeEach(function () { - nowStub = sinon.stub(Date, 'now').returns(1625782016341); - }); - - afterEach(function () { - nowStub.restore(); - }); - - it('calculates completion time (one)', function () { - const submittedTime = 1625781997397; - const result = txController._getTransactionCompletionTime(submittedTime); - assert.equal(result, '19'); - }); - - it('calculates completion time (two)', function () { - const submittedTime = 1625781995397; - const result = txController._getTransactionCompletionTime(submittedTime); - assert.equal(result, '21'); - }); - }); - describe('#_getGasValuesInGWEI', function () { it('converts gas values in hex GWEi to dec GWEI (EIP-1559)', function () { const params = { @@ -3044,7 +2103,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { gasLimit: '0x001', gasPrice: '0x002', @@ -3084,7 +2143,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { maxPriorityFeePerGas: '0x003', to: VALID_ADDRESS, @@ -3102,7 +2161,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '3', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { maxPriorityFeePerGas: '0x003', maxFeePerGas: '0x004', @@ -3125,7 +2184,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '4', status: TransactionStatus.dropped, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { maxPriorityFeePerGas: '0x007', maxFeePerGas: '0x008', @@ -3171,7 +2230,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { gas: '0x001', gasPrice: '0x002', @@ -3209,7 +2268,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { gas: '0x001', gasPrice: '0x002', @@ -3240,7 +2299,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '3', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { gas: '0x001', gasPrice: '0x002', @@ -3290,7 +2349,7 @@ describe('Transaction Controller', function () { id: firstTxId, origin: ORIGIN_METAMASK, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -3302,7 +2361,7 @@ describe('Transaction Controller', function () { id: secondTxId, origin: ORIGIN_METAMASK, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -3347,7 +2406,7 @@ describe('Transaction Controller', function () { id: txId, origin: ORIGIN_METAMASK, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index c2a4dc56ea14..37f1eaf59d26 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -5,7 +5,6 @@ import { values, keyBy, mapValues, omitBy, pickBy, sortBy } from 'lodash'; import { v1 as uuid } from 'uuid'; import { TransactionStatus } from '../../../../shared/constants/transaction'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; -import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { NetworkStatus } from '../../../../shared/constants/network'; import { @@ -55,14 +54,12 @@ export const ERROR_SUBMITTING = * transactions list keyed by id * @param {number} [opts.txHistoryLimit] - limit for how many finished * transactions can hang around in state - * @param {Function} opts.getNetworkId - Get the current network Id. * @param {Function} opts.getNetworkStatus - Get the current network status. */ export default class TransactionStateManager extends EventEmitter { constructor({ initState, txHistoryLimit, - getNetworkId, getNetworkStatus, getCurrentChainId, }) { @@ -73,7 +70,6 @@ export default class TransactionStateManager extends EventEmitter { ...initState, }); this.txHistoryLimit = txHistoryLimit; - this.getNetworkId = getNetworkId; this.getNetworkStatus = getNetworkStatus; this.getCurrentChainId = getCurrentChainId; } @@ -90,7 +86,6 @@ export default class TransactionStateManager extends EventEmitter { * @returns {TransactionMeta} the default txMeta object */ generateTxMeta(opts = {}) { - const networkId = this.getNetworkId(); const networkStatus = this.getNetworkStatus(); const chainId = this.getCurrentChainId(); if (networkStatus !== NetworkStatus.Available) { @@ -133,7 +128,6 @@ export default class TransactionStateManager extends EventEmitter { id: uuid(), time: new Date().getTime(), status: TransactionStatus.unapproved, - metamaskNetworkId: networkId, originalGasEstimate: opts.txParams?.gas, userEditedGasLimit: false, chainId, @@ -155,12 +149,11 @@ export default class TransactionStateManager extends EventEmitter { */ getUnapprovedTxList() { const chainId = this.getCurrentChainId(); - const networkId = this.getNetworkId(); return pickBy( this.store.getState().transactions, (transaction) => transaction.status === TransactionStatus.unapproved && - transactionMatchesNetwork(transaction, chainId, networkId), + transaction.chainId === chainId, ); } @@ -279,9 +272,8 @@ export default class TransactionStateManager extends EventEmitter { const txsToDelete = transactions .reverse() .filter((tx) => { - const { nonce, from } = tx.txParams; - const { chainId, metamaskNetworkId, status } = tx; - const key = `${nonce}-${chainId ?? metamaskNetworkId}-${from}`; + const { chainId, status, txParams } = tx; + const key = `${txParams.nonce}-${chainId}-${txParams.from}`; if (nonceNetworkSet.has(key)) { return false; } else if ( @@ -419,7 +411,6 @@ export default class TransactionStateManager extends EventEmitter { limit, } = {}) { const chainId = this.getCurrentChainId(); - const networkId = this.getNetworkId(); // searchCriteria is an object that might have values that aren't predicate // methods. When providing any other value type (string, number, etc), we // consider this shorthand for "check the value at key for strict equality @@ -444,12 +435,7 @@ export default class TransactionStateManager extends EventEmitter { // matching transactions that are sorted by time. const filteredTransactions = sortBy( pickBy(transactionsToFilter, (transaction) => { - // default matchesCriteria to the value of transactionMatchesNetwork - // when filterToCurrentNetwork is true. - if ( - filterToCurrentNetwork && - transactionMatchesNetwork(transaction, chainId, networkId) === false - ) { + if (filterToCurrentNetwork && transaction.chainId !== chainId) { return false; } // iterate over the predicateMethods keys to check if the transaction @@ -582,16 +568,16 @@ export default class TransactionStateManager extends EventEmitter { * the error on the TransactionMeta object. * * @param {number} txId - the target TransactionMeta's Id - * @param {Error} err - error object + * @param {Error} error - error object */ - setTxStatusFailed(txId, err) { - const error = err || new Error('Internal metamask failure'); + setTxStatusFailed(txId, error) { + const err = error || new Error('Internal metamask failure'); const txMeta = this.getTransaction(txId); - txMeta.err = { - message: error.message?.toString() || error.toString(), - rpc: error.value, - stack: error.stack, + txMeta.error = { + message: err.message?.toString() || err.toString(), + rpc: err.value, + stack: err.stack, }; this._updateTransactionHistory( txMeta, @@ -611,7 +597,6 @@ export default class TransactionStateManager extends EventEmitter { // network only tx const { transactions } = this.store.getState(); const chainId = this.getCurrentChainId(); - const networkId = this.getNetworkId(); // Update state this.store.updateState({ @@ -619,7 +604,7 @@ export default class TransactionStateManager extends EventEmitter { transactions, (transaction) => transaction.txParams.from === address && - transactionMatchesNetwork(transaction, chainId, networkId), + transaction.chainId === chainId, ), }); } diff --git a/app/scripts/controllers/transactions/tx-state-manager.test.js b/app/scripts/controllers/transactions/tx-state-manager.test.js index a3b420e10b84..51b27dfd61f2 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.test.js +++ b/app/scripts/controllers/transactions/tx-state-manager.test.js @@ -4,11 +4,7 @@ import { TransactionStatus, TransactionType, } from '../../../../shared/constants/transaction'; -import { - CHAIN_IDS, - NETWORK_IDS, - NetworkStatus, -} from '../../../../shared/constants/network'; +import { CHAIN_IDS, NetworkStatus } from '../../../../shared/constants/network'; import { GAS_LIMITS } from '../../../../shared/constants/gas'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import TxStateManager, { ERROR_SUBMITTING } from './tx-state-manager'; @@ -48,10 +44,9 @@ function generateTransactions( } describe('TransactionStateManager', function () { let txStateManager; - const currentNetworkId = NETWORK_IDS.GOERLI; const currentNetworkStatus = NetworkStatus.Available; const currentChainId = CHAIN_IDS.MAINNET; - const otherNetworkId = '2'; + const otherChainId = '0x2'; beforeEach(function () { txStateManager = new TxStateManager({ @@ -59,7 +54,6 @@ describe('TransactionStateManager', function () { transactions: {}, }, txHistoryLimit: 10, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => currentNetworkStatus, getCurrentChainId: () => currentChainId, }); @@ -70,7 +64,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -88,7 +82,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -112,7 +106,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -129,7 +123,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -158,7 +152,7 @@ describe('TransactionStateManager', function () { it('should return a full list of transactions', function () { const submittedTx = { id: 0, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 0, txParams: { from: '0xAddress', @@ -170,7 +164,7 @@ describe('TransactionStateManager', function () { const confirmedTx = { id: 3, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 3, txParams: { from: '0xAddress', @@ -187,7 +181,6 @@ describe('TransactionStateManager', function () { [confirmedTx.id]: confirmedTx, }, }, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => currentNetworkStatus, getCurrentChainId: () => currentChainId, }); @@ -198,7 +191,7 @@ describe('TransactionStateManager', function () { it('should return a list of transactions, limited by N unique nonces when there are NO duplicates', function () { const submittedTx0 = { id: 0, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 0, txParams: { from: '0xAddress', @@ -210,7 +203,7 @@ describe('TransactionStateManager', function () { const unapprovedTx1 = { id: 1, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 1, txParams: { from: '0xAddress', @@ -222,7 +215,7 @@ describe('TransactionStateManager', function () { const approvedTx2 = { id: 2, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 2, txParams: { from: '0xAddress', @@ -234,7 +227,7 @@ describe('TransactionStateManager', function () { const confirmedTx3 = { id: 3, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 3, txParams: { from: '0xAddress', @@ -253,7 +246,6 @@ describe('TransactionStateManager', function () { [confirmedTx3.id]: confirmedTx3, }, }, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => currentNetworkStatus, getCurrentChainId: () => currentChainId, }); @@ -267,7 +259,7 @@ describe('TransactionStateManager', function () { it('should return a list of transactions, limited by N unique nonces when there ARE duplicates', function () { const submittedTx0 = { id: 0, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 0, txParams: { from: '0xAddress', @@ -278,7 +270,7 @@ describe('TransactionStateManager', function () { }; const submittedTx0Dupe = { id: 1, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 0, txParams: { from: '0xAddress', @@ -290,7 +282,6 @@ describe('TransactionStateManager', function () { const unapprovedTx1 = { id: 2, - metamaskNetworkId: currentNetworkId, chainId: currentChainId, time: 1, txParams: { @@ -303,7 +294,7 @@ describe('TransactionStateManager', function () { const approvedTx2 = { id: 3, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 2, txParams: { from: '0xAddress', @@ -314,7 +305,6 @@ describe('TransactionStateManager', function () { }; const approvedTx2Dupe = { id: 4, - metamaskNetworkId: currentNetworkId, chainId: currentChainId, time: 2, txParams: { @@ -327,7 +317,7 @@ describe('TransactionStateManager', function () { const failedTx3 = { id: 5, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 3, txParams: { from: '0xAddress', @@ -338,7 +328,6 @@ describe('TransactionStateManager', function () { }; const failedTx3Dupe = { id: 6, - metamaskNetworkId: currentNetworkId, chainId: currentChainId, time: 3, txParams: { @@ -363,7 +352,6 @@ describe('TransactionStateManager', function () { [failedTx3Dupe.id]: failedTx3Dupe, }, }, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => currentNetworkStatus, getCurrentChainId: () => currentChainId, }); @@ -382,61 +370,61 @@ describe('TransactionStateManager', function () { id: 0, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 1, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 2, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 3, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 4, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 5, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 6, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 7, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 8, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 9, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, ]; txMetas.forEach((txMeta) => txStateManager.addTransaction(txMeta)); @@ -503,7 +491,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -533,7 +521,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { ...validTxParams, [key]: value, @@ -554,7 +542,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -563,7 +551,7 @@ describe('TransactionStateManager', function () { const tx2 = { id: 2, status: TransactionStatus.confirmed, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -762,7 +750,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -771,7 +759,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -799,7 +787,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: validTxParams, }); @@ -830,7 +818,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS, @@ -906,7 +894,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS, @@ -925,7 +913,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS, @@ -967,7 +955,7 @@ describe('TransactionStateManager', function () { assert.equal(result.history[1][1].value.message, ERROR_SUBMITTING); assert.equal(result.history[1][2].op, 'add'); - assert.equal(result.history[1][2].path, '/err'); + assert.equal(result.history[1][2].path, '/error'); assert.equal( result.history[1][2].value.message, 'Invalid transaction envelope type: specified type "0x0" but including maxFeePerGas and maxPriorityFeePerGas requires type: "0x2"', @@ -987,7 +975,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS, @@ -1013,7 +1001,7 @@ describe('TransactionStateManager', function () { 'transactions:tx-state-manager#fail - add error', ); assert.equal(result.history[1][0].op, 'add'); - assert.equal(result.history[1][0].path, '/err'); + assert.equal(result.history[1][0].path, '/error'); assert.equal( result.history[1][0].value.message, 'Testing tx status failed with arbitrary error', @@ -1034,7 +1022,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -1043,7 +1031,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -1061,7 +1049,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -1070,7 +1058,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -1097,19 +1085,19 @@ describe('TransactionStateManager', function () { id: 0, status: TransactionStatus.unapproved, txParams: { from: specificAddress, to: otherAddress }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 1, status: TransactionStatus.confirmed, txParams: { from: otherAddress, to: specificAddress }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 2, status: TransactionStatus.confirmed, txParams: { from: otherAddress, to: specificAddress }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, ]; txMetas.forEach((txMeta) => txStateManager.addTransaction(txMeta)); @@ -1133,19 +1121,19 @@ describe('TransactionStateManager', function () { id: 0, status: TransactionStatus.unapproved, txParams: { from: specificAddress, to: otherAddress }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 1, status: TransactionStatus.confirmed, txParams: { from: specificAddress, to: otherAddress }, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, }, { id: 2, status: TransactionStatus.confirmed, txParams: { from: specificAddress, to: otherAddress }, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, }, ]; @@ -1158,7 +1146,7 @@ describe('TransactionStateManager', function () { .filter((txMeta) => txMeta.txParams.from === specificAddress); const txFromOtherNetworks = txStateManager .getTransactions({ filterToCurrentNetwork: false }) - .filter((txMeta) => txMeta.metamaskNetworkId === otherNetworkId); + .filter((txMeta) => txMeta.chainId === otherChainId); assert.equal(txsFromCurrentNetworkAndAddress.length, 0); assert.equal(txFromOtherNetworks.length, 2); @@ -1328,25 +1316,25 @@ describe('TransactionStateManager', function () { id: 0, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 1, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 2, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, }, { id: 3, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, }, ]; diff --git a/app/scripts/controllers/transactions/types.ts b/app/scripts/controllers/transactions/types.ts index e59205e1b654..9f956896f71f 100644 --- a/app/scripts/controllers/transactions/types.ts +++ b/app/scripts/controllers/transactions/types.ts @@ -20,11 +20,6 @@ export interface RemoteTransactionSourceRequest { */ currentChainId: Hex; - /** - * The networkId of the current network. - */ - currentNetworkId: string; - /** * Block number to start fetching transactions from. */ @@ -41,7 +36,7 @@ export interface RemoteTransactionSourceRequest { * Used by the IncomingTransactionHelper to retrieve remote transaction data. */ export interface RemoteTransactionSource { - isSupportedNetwork: (chainId: Hex, networkId: string) => boolean; + isSupportedNetwork: (chainId: Hex) => boolean; fetchTransactions: ( request: RemoteTransactionSourceRequest, diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index 610b5ede2e6e..825994c391ec 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -17,7 +17,7 @@ import { } from '../../../shared/constants/security-provider'; ///: END:ONLY_INCLUDE_IN -import getSnapAndHardwareInfoForMetrics from './snap-keyring/metrics'; +import { getSnapAndHardwareInfoForMetrics } from './snap-keyring/metrics'; /** * These types determine how the method tracking middleware handles incoming diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js index 842cee101f11..b8ab03378420 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js @@ -4,7 +4,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IN(snaps) permittedMethods as permittedSnapMethods, ///: END:ONLY_INCLUDE_IN -} from '@metamask/rpc-methods'; +} from '@metamask/snaps-rpc-methods'; import { ethErrors } from 'eth-rpc-errors'; import { flatten } from 'lodash'; import { UNSUPPORTED_RPC_METHODS } from '../../../../shared/constants/network'; diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 217f98b4237c..147783da7963 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -69,7 +69,6 @@ export const SENTRY_BACKGROUND_STATE = { qrHardware: true, recoveryPhraseReminderHasBeenShown: true, recoveryPhraseReminderLastShown: true, - serviceWorkerLastActiveTime: true, showBetaHeader: true, showProductTour: true, showTestnetMessageInDropdown: true, @@ -128,7 +127,6 @@ export const SENTRY_BACKGROUND_STATE = { }, NetworkController: { networkConfigurations: false, - networkId: true, networksMetadata: true, providerConfig: { chainId: true, @@ -226,7 +224,6 @@ export const SENTRY_BACKGROUND_STATE = { }, }, SnapController: { - snapErrors: false, snapStates: false, snaps: false, }, diff --git a/app/scripts/lib/snap-keyring/metrics.test.ts b/app/scripts/lib/snap-keyring/metrics.test.ts new file mode 100644 index 000000000000..af817abefff2 --- /dev/null +++ b/app/scripts/lib/snap-keyring/metrics.test.ts @@ -0,0 +1,103 @@ +import { getSnapAndHardwareInfoForMetrics } from './metrics'; + +describe('getSnapAndHardwareInfoForMetrics', () => { + let getSelectedAddress: jest.Mock; + let getAccountType: jest.Mock; + let getDeviceModel: jest.Mock; + let messenger; + + beforeEach(() => { + getSelectedAddress = jest.fn(); + getAccountType = jest.fn(); + getDeviceModel = jest.fn(); + messenger = { + call: jest.fn(), + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return an empty object if no messenger is provided', async () => { + const result = await getSnapAndHardwareInfoForMetrics( + getSelectedAddress, + getAccountType, + getDeviceModel, + // @ts-expect-error - We're testing the case where messenger is null + null, + ); + expect(result).toEqual({}); + }); + + it('should call the appropriate functions with the correct arguments when the keyring exposes the listAccounts method', async () => { + getSelectedAddress.mockReturnValue('0x123'); + getAccountType.mockResolvedValue('accountType'); + getDeviceModel.mockResolvedValue('deviceModel'); + messenger.call + .mockResolvedValueOnce({ + listAccounts: () => [ + { address: '0x123', metadata: { snap: { id: 'snapId' } } }, + ], + }) + .mockResolvedValueOnce({ id: 'snapId', version: 'snapVersion' }); + + const result = await getSnapAndHardwareInfoForMetrics( + getSelectedAddress, + getAccountType, + getDeviceModel, + messenger, + ); + + expect(getSelectedAddress).toHaveBeenCalled(); + expect(getAccountType).toHaveBeenCalledWith('0x123'); + expect(getDeviceModel).toHaveBeenCalledWith('0x123'); + expect(messenger.call).toHaveBeenCalledWith( + 'KeyringController:getKeyringForAccount', + '0x123', + ); + expect(messenger.call).toHaveBeenCalledWith('SnapController:get', 'snapId'); + expect(result).toEqual({ + account_type: 'accountType', + device_model: 'deviceModel', + account_hardware_type: undefined, + account_snap_type: 'snapId', + account_snap_version: 'snapVersion', + }); + }); + + it('should call the appropriate functions with the correct arguments when the keyring does not have the listAccounts method', async () => { + getSelectedAddress.mockReturnValue('0x123'); + getAccountType.mockResolvedValue('accountType'); + getDeviceModel.mockResolvedValue('deviceModel'); + messenger.call + .mockResolvedValueOnce({}) + .mockResolvedValueOnce({ id: 'snapId', version: 'snapVersion' }); + + const result = await getSnapAndHardwareInfoForMetrics( + getSelectedAddress, + getAccountType, + getDeviceModel, + messenger, + ); + + expect(getSelectedAddress).toHaveBeenCalled(); + expect(getAccountType).toHaveBeenCalledWith('0x123'); + expect(getDeviceModel).toHaveBeenCalledWith('0x123'); + expect(messenger.call).toHaveBeenCalledWith( + 'KeyringController:getKeyringForAccount', + '0x123', + ); + expect(messenger.call).toHaveBeenCalledWith( + 'SnapController:get', + undefined, + ); + expect(result).toEqual({ + account_type: 'accountType', + device_model: 'deviceModel', + account_hardware_type: undefined, + account_snap_type: 'snapId', + account_snap_version: 'snapVersion', + }); + }); +}); diff --git a/app/scripts/lib/snap-keyring/metrics.ts b/app/scripts/lib/snap-keyring/metrics.ts index b22036f89f17..9036b8529435 100644 --- a/app/scripts/lib/snap-keyring/metrics.ts +++ b/app/scripts/lib/snap-keyring/metrics.ts @@ -13,7 +13,7 @@ export type SnapAndHardwareMessenger = RestrictedControllerMessenger< never >; -export default async function getSnapAndHardwareInfoForMetrics( +export async function getSnapAndHardwareInfoForMetrics( getSelectedAddress: () => string, getAccountType: (address: string) => Promise, getDeviceModel: (address: string) => Promise, diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index a9c42df7a659..9a1b37f6a4ca 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -17,8 +17,6 @@ export function setupMultiplex(connectionStream) { * https://github.com/MetaMask/object-multiplex/blob/280385401de84f57ef57054d92cfeb8361ef2680/src/ObjectMultiplex.ts#L63 */ mux.ignoreStream(EXTENSION_MESSAGES.CONNECTION_READY); - mux.ignoreStream('ACK_KEEP_ALIVE_MESSAGE'); - mux.ignoreStream('WORKER_KEEP_ALIVE_MESSAGE'); pump(connectionStream, mux, connectionStream, (err) => { if (err) { console.error(err); diff --git a/app/scripts/lib/transaction-metrics.test.ts b/app/scripts/lib/transaction-metrics.test.ts new file mode 100644 index 000000000000..8634e3201465 --- /dev/null +++ b/app/scripts/lib/transaction-metrics.test.ts @@ -0,0 +1,673 @@ +import { Provider } from '@metamask/network-controller'; +import { + createTestProviderTools, + getTestAccounts, +} from '../../../test/stub/provider'; +import { ORIGIN_METAMASK } from '../../../shared/constants/app'; +import { + TransactionType, + TransactionStatus, + AssetType, + TokenStandard, + TransactionMetaMetricsEvent, +} from '../../../shared/constants/transaction'; +import { + MetaMetricsTransactionEventSource, + MetaMetricsEventCategory, +} from '../../../shared/constants/metametrics'; +import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../shared/lib/transactions-controller-utils'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import { BlockaidReason } from '../../../shared/constants/security-provider'; +///: END:ONLY_INCLUDE_IN(blockaid) +import { + handleTransactionAdded, + handleTransactionApproved, + handleTransactionDropped, + handleTransactionFinalized, + handleTransactionRejected, + handleTransactionSubmitted, + METRICS_STATUS_FAILED, +} from './transaction-metrics'; + +const providerResultStub = { + eth_getCode: '0x123', +}; +const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + networkId: '5', + chainId: '5', +}); + +jest.mock('./snap-keyring/metrics', () => { + return { + getSnapAndHardwareInfoForMetrics: jest.fn().mockResolvedValue({ + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + }), + }; +}); + +const mockTransactionMetricsRequest = { + createEventFragment: jest.fn(), + finalizeEventFragment: jest.fn(), + getEventFragmentById: jest.fn(), + updateEventFragment: jest.fn(), + getAccountType: jest.fn(), + getDeviceModel: jest.fn(), + getEIP1559GasFeeEstimates: jest.fn(), + getSelectedAddress: jest.fn(), + getTokenStandardAndDetails: jest.fn(), + getTransaction: jest.fn(), + snapAndHardwareMessenger: jest.fn() as any, + provider: provider as Provider, +}; + +describe('Transaction metrics', () => { + let fromAccount, + mockChainId, + mockNetworkId, + mockTransactionMeta, + mockActionId; + + beforeEach(() => { + fromAccount = getTestAccounts()[0]; + mockChainId = '5'; + mockNetworkId = '5'; + mockActionId = '2'; + mockTransactionMeta = { + id: '1', + status: TransactionStatus.unapproved, + txParams: { + from: fromAccount.address, + to: '0x1678a085c290ebd122dc42cba69373b5953b831d', + gasPrice: '0x77359400', + gas: '0x7b0d', + nonce: '0x4b', + }, + type: TransactionType.simpleSend, + origin: ORIGIN_METAMASK, + chainId: mockChainId, + time: 1624408066355, + metamaskNetworkId: mockNetworkId, + defaultGasEstimates: { + gas: '0x7b0d', + gasPrice: '0x77359400', + }, + securityProviderResponse: { + flagAsDangerous: 0, + }, + }; + + jest.clearAllMocks(); + }); + + describe('handleTransactionAdded', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionAdded(mockTransactionMetricsRequest, {} as any); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + }); + + it('should create event fragment', async () => { + await handleTransactionAdded(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta as any, + actionId: mockActionId, + }); + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + failureEvent: TransactionMetaMetricsEvent.rejected, + initialEvent: TransactionMetaMetricsEvent.added, + successEvent: TransactionMetaMetricsEvent.approved, + uniqueIdentifier: 'transaction-added-1', + persist: true, + properties: { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }, + sensitiveProperties: { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }, + }); + }); + }); + + describe('handleTransactionApproved', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionApproved(mockTransactionMetricsRequest, {} as any); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + + it('should create, update, finalize event fragment', async () => { + await handleTransactionApproved(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta as any, + actionId: mockActionId, + }); + + const expectedUniqueId = 'transaction-added-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + }); + + describe('handleTransactionFinalized', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionFinalized( + mockTransactionMetricsRequest, + {} as any, + ); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + + it('should create, update, finalize event fragment', async () => { + mockTransactionMeta.txReceipt = { + gasUsed: '0x123', + status: '0x0', + }; + mockTransactionMeta.submittedTime = 123; + + await handleTransactionFinalized(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + completion_time: expect.any(String), + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + gas_used: '0.000000291', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + status: METRICS_STATUS_FAILED, + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + + it('should append error to event properties', async () => { + const mockErrorMessage = 'Unexpected error'; + + await handleTransactionFinalized(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta, + actionId: mockActionId, + error: mockErrorMessage, + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + error: mockErrorMessage, + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + }); + + describe('handleTransactionDropped', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionDropped(mockTransactionMetricsRequest, {} as any); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + + it('should create, update, finalize event fragment', async () => { + await handleTransactionDropped(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + dropped: true, + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: 'other', + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + }); + + describe('handleTransactionRejected', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionRejected(mockTransactionMetricsRequest, {} as any); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + + it('should create, update, finalize event fragment', async () => { + await handleTransactionRejected(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-added-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId, { + abandoned: true, + }); + }); + }); + + describe('handleTransactionSubmitted', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionSubmitted( + mockTransactionMetricsRequest, + {} as any, + ); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + }); + + it('should only create event fragment', async () => { + await handleTransactionSubmitted(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta as any, + actionId: mockActionId, + }); + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + initialEvent: TransactionMetaMetricsEvent.submitted, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: 'transaction-submitted-1', + persist: true, + properties: { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }, + sensitiveProperties: { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }, + }); + + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + }); +}); diff --git a/app/scripts/lib/transaction-metrics.ts b/app/scripts/lib/transaction-metrics.ts new file mode 100644 index 000000000000..01e149bc45a6 --- /dev/null +++ b/app/scripts/lib/transaction-metrics.ts @@ -0,0 +1,912 @@ +import { isHexString } from 'ethereumjs-util'; +import EthQuery from 'eth-query'; +import { BigNumber } from 'bignumber.js'; +import type { Provider } from '@metamask/network-controller'; +import { FetchGasFeeEstimateOptions } from '@metamask/gas-fee-controller'; + +import { ORIGIN_METAMASK } from '../../../shared/constants/app'; +import { + determineTransactionAssetType, + isEIP1559Transaction, +} from '../../../shared/modules/transaction.utils'; +import { hexWEIToDecGWEI } from '../../../shared/modules/conversion.utils'; +import { + TransactionType, + TokenStandard, + TransactionApprovalAmountType, + TransactionMetaMetricsEvent, + TransactionMeta, +} from '../../../shared/constants/transaction'; +import { + MetaMetricsEventCategory, + MetaMetricsEventFragment, + MetaMetricsPageObject, + MetaMetricsReferrerObject, +} from '../../../shared/constants/metametrics'; +import { GasRecommendations } from '../../../shared/constants/gas'; +import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../shared/lib/transactions-controller-utils'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import { + BlockaidReason, + BlockaidResultType, +} from '../../../shared/constants/security-provider'; +///: END:ONLY_INCLUDE_IN +import { + getSnapAndHardwareInfoForMetrics, + type SnapAndHardwareMessenger, +} from './snap-keyring/metrics'; + +export const METRICS_STATUS_FAILED = 'failed on-chain'; + +export type TransactionMetricsRequest = { + createEventFragment: ( + options: MetaMetricsEventFragment, + ) => MetaMetricsEventFragment; + finalizeEventFragment: ( + fragmentId: string, + options?: { + abandoned?: boolean; + page?: MetaMetricsPageObject; + referrer?: MetaMetricsReferrerObject; + }, + ) => void; + getEventFragmentById: (fragmentId: string) => MetaMetricsEventFragment; + updateEventFragment: ( + fragmentId: string, + payload: Partial, + ) => void; + getAccountType: ( + address: string, + ) => Promise<'hardware' | 'imported' | 'MetaMask'>; + getDeviceModel: ( + address: string, + ) => Promise<'ledger' | 'lattice' | 'N/A' | string>; + // According to the type GasFeeState returned from getEIP1559GasFeeEstimates + // doesn't include some properties used in buildEventFragmentProperties, + // hence returning any here to avoid type errors. + getEIP1559GasFeeEstimates(options?: FetchGasFeeEstimateOptions): Promise; + getSelectedAddress: () => string; + getTokenStandardAndDetails: () => { + decimals?: string; + balance?: string; + symbol?: string; + standard?: TokenStandard; + }; + getTransaction: (transactionId: string) => TransactionMeta; + snapAndHardwareMessenger: SnapAndHardwareMessenger; + provider: Provider; +}; + +type TransactionEventPayload = { + transactionMeta: TransactionMeta; + actionId?: string; + error?: string; +}; + +/** + * This function is called when a transaction is added to the controller. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionAdded = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + const { properties, sensitiveProperties } = + await buildEventFragmentProperties({ + transactionEventPayload, + transactionMetricsRequest, + }); + + createTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.added, + transactionEventPayload, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); +}; + +/** + * This function is called when a transaction is approved by the user. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionApproved = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + + await createUpdateFinalizeTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.approved, + transactionEventPayload, + transactionMetricsRequest, + }); +}; + +/** + * This function is called when a transaction is finalized. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + * @param transactionEventPayload.error - The error message if the transaction failed + */ +export const handleTransactionFinalized = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + + const extraParams = {} as Record; + if (transactionEventPayload.error) { + // This is a failed transaction + extraParams.error = transactionEventPayload.error; + } else { + const { transactionMeta } = transactionEventPayload; + const { txReceipt } = transactionMeta; + + extraParams.gas_used = txReceipt.gasUsed; + + const { submittedTime } = transactionMeta; + + if (submittedTime) { + extraParams.completion_time = getTransactionCompletionTime(submittedTime); + } + + if (txReceipt.status === '0x0') { + extraParams.status = METRICS_STATUS_FAILED; + } + } + + await createUpdateFinalizeTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.finalized, + extraParams, + transactionEventPayload, + transactionMetricsRequest, + }); +}; + +/** + * This function is called when a transaction is dropped. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionDropped = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + + const extraParams = { + dropped: true, + }; + + await createUpdateFinalizeTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.finalized, + extraParams, + transactionEventPayload, + transactionMetricsRequest, + }); +}; + +/** + * This function is called when a transaction is rejected by the user. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionRejected = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + + await createUpdateFinalizeTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.rejected, + transactionEventPayload, + transactionMetricsRequest, + }); +}; + +/** + * This function is called when a transaction is submitted to the network. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionSubmitted = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + const { properties, sensitiveProperties } = + await buildEventFragmentProperties({ + transactionEventPayload, + transactionMetricsRequest, + }); + + createTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.submitted, + transactionEventPayload, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); +}; + +/** + * UI needs this specific create function in order to be sure that event fragment exists when updating transaction gas values. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param eventPayload - The event payload + * @param eventPayload.actionId - The action id of the transaction + * @param eventPayload.transactionId - The transaction id + */ +export const createTransactionEventFragmentWithTxId = async ( + transactionMetricsRequest: TransactionMetricsRequest, + { + transactionId, + actionId, + }: { + transactionId: string; + actionId: string; + }, +) => { + const transactionMeta = + transactionMetricsRequest.getTransaction(transactionId); + + transactionMeta.actionId = actionId; + + const { properties, sensitiveProperties } = + await buildEventFragmentProperties({ + transactionEventPayload: { + transactionMeta, + }, + transactionMetricsRequest, + }); + createTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.approved, + transactionEventPayload: { + actionId: transactionMeta.actionId, + transactionMeta, + }, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); +}; + +function createTransactionEventFragment({ + eventName, + transactionEventPayload: { transactionMeta, actionId }, + transactionMetricsRequest, + payload, +}: { + eventName: TransactionMetaMetricsEvent; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; + payload: any; +}) { + if ( + hasFragment( + transactionMetricsRequest.getEventFragmentById, + eventName, + transactionMeta, + ) + ) { + return; + } + + const uniqueIdentifier = getUniqueId(eventName, transactionMeta.id); + + switch (eventName) { + // When a transaction is added to the controller, we know that the user + // will be presented with a confirmation screen. The user will then + // either confirm or reject that transaction. Each has an associated + // event we want to track. While we don't necessarily need an event + // fragment to model this, having one allows us to record additional + // properties onto the event from the UI. For example, when the user + // edits the transactions gas params we can record that property and + // then get analytics on the number of transactions in which gas edits + // occur. + case TransactionMetaMetricsEvent.added: + transactionMetricsRequest.createEventFragment({ + category: MetaMetricsEventCategory.Transactions, + initialEvent: TransactionMetaMetricsEvent.added, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + actionId, + uniqueIdentifier, + persist: true, + }); + break; + // If for some reason an approval or rejection occurs without the added + // fragment existing in memory, we create the added fragment but without + // the initialEvent firing. This is to prevent possible duplication of + // events. A good example why this might occur is if the user had + // unapproved transactions in memory when updating to the version that + // includes this change. A migration would have also helped here but this + // implementation hardens against other possible bugs where a fragment + // does not exist. + case TransactionMetaMetricsEvent.approved: + case TransactionMetaMetricsEvent.rejected: + transactionMetricsRequest.createEventFragment({ + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + actionId, + uniqueIdentifier, + persist: true, + }); + break; + // When a transaction is submitted it will always result in updating + // to a finalized state (dropped, failed, confirmed) -- eventually. + // However having a fragment started at this stage allows augmenting + // analytics data with user interactions such as speeding up and + // canceling the transactions. From this controllers perspective a new + // transaction with a new id is generated for speed up and cancel + // transactions, but from the UI we could augment the previous ID with + // supplemental data to show user intent. Such as when they open the + // cancel UI but don't submit. We can record that this happened and add + // properties to the transaction event. + case TransactionMetaMetricsEvent.submitted: + transactionMetricsRequest.createEventFragment({ + category: MetaMetricsEventCategory.Transactions, + initialEvent: TransactionMetaMetricsEvent.submitted, + successEvent: TransactionMetaMetricsEvent.finalized, + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + actionId, + uniqueIdentifier, + persist: true, + }); + break; + // If for some reason a transaction is finalized without the submitted + // fragment existing in memory, we create the submitted fragment but + // without the initialEvent firing. This is to prevent possible + // duplication of events. A good example why this might occur is if th + // user had pending transactions in memory when updating to the version + // that includes this change. A migration would have also helped here but + // this implementation hardens against other possible bugs where a + // fragment does not exist. + case TransactionMetaMetricsEvent.finalized: + transactionMetricsRequest.createEventFragment({ + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + actionId, + uniqueIdentifier, + persist: true, + }); + break; + default: + break; + } +} + +function updateTransactionEventFragment({ + eventName, + transactionEventPayload: { transactionMeta }, + transactionMetricsRequest, + payload, +}: { + eventName: TransactionMetaMetricsEvent; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; + payload: any; +}) { + const uniqueId = getUniqueId(eventName, transactionMeta.id); + + switch (eventName) { + case TransactionMetaMetricsEvent.approved: + transactionMetricsRequest.updateEventFragment(uniqueId, { + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + }); + break; + + case TransactionMetaMetricsEvent.rejected: + transactionMetricsRequest.updateEventFragment(uniqueId, { + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + }); + break; + + case TransactionMetaMetricsEvent.finalized: + transactionMetricsRequest.updateEventFragment(uniqueId, { + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + }); + break; + default: + break; + } +} + +function finalizeTransactionEventFragment({ + eventName, + transactionMetricsRequest, + transactionEventPayload: { transactionMeta }, +}: { + eventName: TransactionMetaMetricsEvent; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; +}) { + const uniqueId = getUniqueId(eventName, transactionMeta.id); + + switch (eventName) { + case TransactionMetaMetricsEvent.approved: + transactionMetricsRequest.finalizeEventFragment(uniqueId); + break; + + case TransactionMetaMetricsEvent.rejected: + transactionMetricsRequest.finalizeEventFragment(uniqueId, { + abandoned: true, + }); + break; + + case TransactionMetaMetricsEvent.finalized: + transactionMetricsRequest.finalizeEventFragment(uniqueId); + break; + default: + break; + } +} + +async function createUpdateFinalizeTransactionEventFragment({ + eventName, + transactionEventPayload, + transactionMetricsRequest, + extraParams = {}, +}: { + eventName: TransactionMetaMetricsEvent; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; + extraParams?: Record; +}) { + const { properties, sensitiveProperties } = + await buildEventFragmentProperties({ + transactionEventPayload, + transactionMetricsRequest, + extraParams, + }); + + createTransactionEventFragment({ + eventName, + transactionEventPayload, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); + + updateTransactionEventFragment({ + eventName, + transactionEventPayload, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); + + finalizeTransactionEventFragment({ + eventName, + transactionEventPayload, + transactionMetricsRequest, + }); +} + +function hasFragment( + getEventFragmentById: (arg0: string) => any, + eventName: TransactionMetaMetricsEvent, + transactionMeta: TransactionMeta, +) { + const uniqueId = getUniqueId(eventName, transactionMeta.id); + const fragment = getEventFragmentById(uniqueId); + return typeof fragment !== 'undefined'; +} + +function getUniqueId( + eventName: TransactionMetaMetricsEvent, + transactionId: string, +) { + const isSubmitted = [ + TransactionMetaMetricsEvent.finalized, + TransactionMetaMetricsEvent.submitted, + ].includes(eventName); + const uniqueIdentifier = `transaction-${ + isSubmitted ? 'submitted' : 'added' + }-${transactionId}`; + + return uniqueIdentifier; +} + +async function buildEventFragmentProperties({ + transactionEventPayload: { transactionMeta }, + transactionMetricsRequest, + extraParams = {}, +}: { + extraParams?: Record; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; +}) { + const { + type, + time, + status, + chainId, + origin: referrer, + txParams: { + gasPrice, + gas: gasLimit, + maxFeePerGas, + maxPriorityFeePerGas, + estimateSuggested, + estimateUsed, + }, + defaultGasEstimates, + originalType, + replacedById, + metamaskNetworkId: network, + customTokenAmount, + dappProposedTokenAmount, + currentTokenBalance, + originalApprovalAmount, + finalApprovalAmount, + contractMethodName, + securityProviderResponse, + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + securityAlertResponse, + ///: END:ONLY_INCLUDE_IN + } = transactionMeta; + + const query = new EthQuery(transactionMetricsRequest.provider); + const source = referrer === ORIGIN_METAMASK ? 'user' : 'dapp'; + + const { assetType, tokenStandard } = await determineTransactionAssetType( + transactionMeta, + query, + transactionMetricsRequest.getTokenStandardAndDetails, + ); + + const gasParams = {} as Record; + + if (isEIP1559Transaction(transactionMeta)) { + gasParams.max_fee_per_gas = maxFeePerGas; + gasParams.max_priority_fee_per_gas = maxPriorityFeePerGas; + } else { + gasParams.gas_price = gasPrice; + } + + if (defaultGasEstimates) { + const { estimateType } = defaultGasEstimates; + if (estimateType) { + gasParams.default_estimate = estimateType; + let defaultMaxFeePerGas = + transactionMeta.defaultGasEstimates.maxFeePerGas; + let defaultMaxPriorityFeePerGas = + transactionMeta.defaultGasEstimates.maxPriorityFeePerGas; + + if ( + [ + GasRecommendations.low, + GasRecommendations.medium, + GasRecommendations.high, + ].includes(estimateType) + ) { + const { gasFeeEstimates } = + await transactionMetricsRequest.getEIP1559GasFeeEstimates(); + if (gasFeeEstimates?.[estimateType]?.suggestedMaxFeePerGas) { + defaultMaxFeePerGas = + gasFeeEstimates[estimateType]?.suggestedMaxFeePerGas; + gasParams.default_max_fee_per_gas = defaultMaxFeePerGas; + } + if (gasFeeEstimates?.[estimateType]?.suggestedMaxPriorityFeePerGas) { + defaultMaxPriorityFeePerGas = + gasFeeEstimates[estimateType]?.suggestedMaxPriorityFeePerGas; + gasParams.default_max_priority_fee_per_gas = + defaultMaxPriorityFeePerGas; + } + } + } + + if (transactionMeta.defaultGasEstimates.gas) { + gasParams.default_gas = transactionMeta.defaultGasEstimates.gas; + } + if (transactionMeta.defaultGasEstimates.gasPrice) { + gasParams.default_gas_price = + transactionMeta.defaultGasEstimates.gasPrice; + } + } + + if (estimateSuggested) { + gasParams.estimate_suggested = estimateSuggested; + } + + if (estimateUsed) { + gasParams.estimate_used = estimateUsed; + } + + if (extraParams?.gas_used) { + gasParams.gas_used = extraParams.gas_used; + } + + const gasParamsInGwei = getGasValuesInGWEI(gasParams); + + let eip1559Version = '0'; + if (transactionMeta.txParams.maxFeePerGas) { + eip1559Version = '2'; + } + + const contractInteractionTypes = [ + TransactionType.contractInteraction, + TransactionType.tokenMethodApprove, + TransactionType.tokenMethodSafeTransferFrom, + TransactionType.tokenMethodSetApprovalForAll, + TransactionType.tokenMethodTransfer, + TransactionType.tokenMethodTransferFrom, + TransactionType.smart, + TransactionType.swap, + TransactionType.swapApproval, + ].includes(type); + + const contractMethodNames = { + APPROVE: 'Approve', + }; + + let transactionApprovalAmountType; + let transactionContractMethod; + let transactionApprovalAmountVsProposedRatio; + let transactionApprovalAmountVsBalanceRatio; + let transactionType = TransactionType.simpleSend; + if (type === TransactionType.cancel) { + transactionType = TransactionType.cancel; + } else if (type === TransactionType.retry) { + transactionType = originalType; + } else if (type === TransactionType.deployContract) { + transactionType = TransactionType.deployContract; + } else if (contractInteractionTypes) { + transactionType = TransactionType.contractInteraction; + transactionContractMethod = contractMethodName; + if ( + transactionContractMethod === contractMethodNames.APPROVE && + tokenStandard === TokenStandard.ERC20 + ) { + if (dappProposedTokenAmount === '0' || customTokenAmount === '0') { + transactionApprovalAmountType = TransactionApprovalAmountType.revoke; + } else if ( + customTokenAmount && + customTokenAmount !== dappProposedTokenAmount + ) { + transactionApprovalAmountType = TransactionApprovalAmountType.custom; + } else if (dappProposedTokenAmount) { + transactionApprovalAmountType = + TransactionApprovalAmountType.dappProposed; + } + transactionApprovalAmountVsProposedRatio = + allowanceAmountInRelationToDappProposedValue( + transactionApprovalAmountType, + originalApprovalAmount, + finalApprovalAmount, + ); + transactionApprovalAmountVsBalanceRatio = + allowanceAmountInRelationToTokenBalance( + transactionApprovalAmountType, + dappProposedTokenAmount, + currentTokenBalance, + ); + } + } + + const replacedTransactionMeta = transactionMetricsRequest.getTransaction( + replacedById as string, + ); + + const TRANSACTION_REPLACEMENT_METHODS = { + RETRY: TransactionType.retry, + CANCEL: TransactionType.cancel, + SAME_NONCE: 'other', + }; + + let transactionReplaced; + if (extraParams?.dropped) { + transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.SAME_NONCE; + if (replacedTransactionMeta?.type === TransactionType.cancel) { + transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.CANCEL; + } else if (replacedTransactionMeta?.type === TransactionType.retry) { + transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.RETRY; + } + } + + let uiCustomizations; + + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + if (securityAlertResponse?.result_type === BlockaidResultType.Failed) { + uiCustomizations = ['security_alert_failed']; + } else { + ///: END:ONLY_INCLUDE_IN + // eslint-disable-next-line no-lonely-if + if (securityProviderResponse?.flagAsDangerous === 1) { + uiCustomizations = ['flagged_as_malicious']; + } else if (securityProviderResponse?.flagAsDangerous === 2) { + uiCustomizations = ['flagged_as_safety_unknown']; + } else { + uiCustomizations = null; + } + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + } + ///: END:ONLY_INCLUDE_IN + + /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ + let properties = { + chain_id: chainId, + referrer, + source, + status, + network, + eip_1559_version: eip1559Version, + gas_edit_type: 'none', + gas_edit_attempted: 'none', + account_type: await transactionMetricsRequest.getAccountType( + transactionMetricsRequest.getSelectedAddress(), + ), + device_model: await transactionMetricsRequest.getDeviceModel( + transactionMetricsRequest.getSelectedAddress(), + ), + asset_type: assetType, + token_standard: tokenStandard, + transaction_type: transactionType, + transaction_speed_up: type === TransactionType.retry, + ui_customizations: uiCustomizations, + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + security_alert_response: + securityAlertResponse?.result_type ?? BlockaidResultType.NotApplicable, + security_alert_reason: + securityAlertResponse?.reason ?? BlockaidReason.notApplicable, + ///: END:ONLY_INCLUDE_IN + } as Record; + + const snapAndHardwareInfo = await getSnapAndHardwareInfoForMetrics( + transactionMetricsRequest.getSelectedAddress, + transactionMetricsRequest.getAccountType, + transactionMetricsRequest.getDeviceModel, + transactionMetricsRequest.snapAndHardwareMessenger, + ); + Object.assign(properties, snapAndHardwareInfo); + + if (transactionContractMethod === contractMethodNames.APPROVE) { + properties = { + ...properties, + transaction_approval_amount_type: transactionApprovalAmountType, + }; + } + + let sensitiveProperties = { + transaction_envelope_type: isEIP1559Transaction(transactionMeta) + ? TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET + : TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + first_seen: time, + gas_limit: gasLimit, + transaction_contract_method: transactionContractMethod, + transaction_replaced: transactionReplaced, + ...extraParams, + ...gasParamsInGwei, + } as Record; + + if (transactionContractMethod === contractMethodNames.APPROVE) { + sensitiveProperties = { + ...sensitiveProperties, + transaction_approval_amount_vs_balance_ratio: + transactionApprovalAmountVsBalanceRatio, + transaction_approval_amount_vs_proposed_ratio: + transactionApprovalAmountVsProposedRatio, + }; + } + + return { properties, sensitiveProperties }; +} + +function getGasValuesInGWEI(gasParams: Record) { + const gasValuesInGwei = {} as Record; + for (const param in gasParams) { + if (isHexString(gasParams[param])) { + gasValuesInGwei[param] = hexWEIToDecGWEI(gasParams[param]); + } else { + gasValuesInGwei[param] = gasParams[param]; + } + } + return gasValuesInGwei; +} + +function getTransactionCompletionTime(submittedTime: number) { + return Math.round((Date.now() - submittedTime) / 1000).toString(); +} + +/** + * The allowance amount in relation to the dapp proposed amount for specific token + * + * @param transactionApprovalAmountType - The transaction approval amount type + * @param originalApprovalAmount - The original approval amount is the originally dapp proposed token amount + * @param finalApprovalAmount - The final approval amount is the chosen amount which will be the same as the + * originally dapp proposed token amount if the user does not edit the amount or will be a custom token amount set by the user + */ +function allowanceAmountInRelationToDappProposedValue( + transactionApprovalAmountType?: TransactionApprovalAmountType, + originalApprovalAmount?: string, + finalApprovalAmount?: string, +) { + if ( + transactionApprovalAmountType === TransactionApprovalAmountType.custom && + originalApprovalAmount && + finalApprovalAmount + ) { + return `${new BigNumber(originalApprovalAmount, 10) + .div(finalApprovalAmount, 10) + .times(100) + .round(2)}`; + } + return null; +} + +/** + * The allowance amount in relation to the balance for that specific token + * + * @param transactionApprovalAmountType - The transaction approval amount type + * @param dappProposedTokenAmount - The dapp proposed token amount + * @param currentTokenBalance - The balance of the token that is being send + */ +function allowanceAmountInRelationToTokenBalance( + transactionApprovalAmountType?: TransactionApprovalAmountType, + dappProposedTokenAmount?: string, + currentTokenBalance?: string, +) { + if ( + (transactionApprovalAmountType === TransactionApprovalAmountType.custom || + transactionApprovalAmountType === + TransactionApprovalAmountType.dappProposed) && + dappProposedTokenAmount && + currentTokenBalance + ) { + return `${new BigNumber(dappProposedTokenAmount, 16) + .div(currentTokenBalance, 10) + .times(100) + .round(2)}`; + } + return null; +} diff --git a/app/scripts/lib/util.test.js b/app/scripts/lib/util.test.js index 3b70d9be7a73..906beff93ee5 100644 --- a/app/scripts/lib/util.test.js +++ b/app/scripts/lib/util.test.js @@ -272,7 +272,6 @@ describe('app utils', () => { origin: 'other', chainId: '0x5', time: 1624408066355, - metamaskNetworkId: '5', hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7', r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e', s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff', @@ -317,7 +316,6 @@ describe('app utils', () => { origin: 'other', chainId: '0x5', time: 1624408066355, - metamaskNetworkId: '5', hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7', r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e', s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff', diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9f0b162b207c..94e13275adce 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -165,7 +165,16 @@ import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens'; import { getTokenValueParam } from '../../shared/lib/metamask-controller-utils'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { hexToDecimal } from '../../shared/modules/conversion.utils'; -import { ACTION_QUEUE_METRICS_E2E_TEST } from '../../shared/constants/test-flags'; +import { convertNetworkId } from '../../shared/modules/network.utils'; +import { + handleTransactionAdded, + handleTransactionApproved, + handleTransactionFinalized, + handleTransactionDropped, + handleTransactionRejected, + handleTransactionSubmitted, + createTransactionEventFragmentWithTxId, +} from './lib/transaction-metrics'; ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) import { keyringSnapPermissionsBuilder } from './lib/keyring-snaps-permissions'; ///: END:ONLY_INCLUDE_IN @@ -436,6 +445,14 @@ export default class MetamaskController extends EventEmitter { this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker; + // TODO: Delete when ready to remove `networkVersion` from provider object + this.deprecatedNetworkId = null; + this.updateDeprecatedNetworkId(); + networkControllerMessenger.subscribe( + 'NetworkController:networkDidChange', + () => this.updateDeprecatedNetworkId(), + ); + const tokenListMessenger = this.controllerMessenger.getRestricted({ name: 'TokenListController', allowedEvents: [ @@ -577,6 +594,9 @@ export default class MetamaskController extends EventEmitter { source, }, }), + getNetworkClientById: this.networkController.getNetworkClientById.bind( + this.networkController, + ), }, {}, initState.NftController, @@ -1065,6 +1085,7 @@ export default class MetamaskController extends EventEmitter { 'SnapsRegistry:get', 'SnapsRegistry:getMetadata', 'SnapsRegistry:update', + 'SnapsRegistry:resolveVersion', ], }); @@ -1239,22 +1260,6 @@ export default class MetamaskController extends EventEmitter { ), }); - // This gets used as a ...spread parameter in two places: new TransactionController() and createRPCMethodTrackingMiddleware() - this.snapAndHardwareMetricsParams = { - getSelectedAddress: this.preferencesController.getSelectedAddress.bind( - this.preferencesController, - ), - getAccountType: this.getAccountType.bind(this), - getDeviceModel: this.getDeviceModel.bind(this), - snapAndHardwareMessenger: this.controllerMessenger.getRestricted({ - name: 'SnapAndHardwareMessenger', - allowedActions: [ - 'KeyringController:getKeyringForAccount', - 'SnapController:get', - ], - }), - }; - this.txController = new TransactionController({ initState: initState.TransactionController || initState.TransactionManager, @@ -1266,7 +1271,6 @@ export default class MetamaskController extends EventEmitter { ), getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind(this), - getNetworkId: () => this.networkController.state.networkId, getNetworkStatus: () => this.networkController.state.networksMetadata?.[ this.networkController.state.selectedNetworkClientId @@ -1278,7 +1282,7 @@ export default class MetamaskController extends EventEmitter { networkControllerMessenger.subscribe( 'NetworkController:stateChange', () => listener(), - ({ networkId }) => networkId, + (state) => state.providerConfig.chainId, ); }, getCurrentChainId: () => @@ -1290,32 +1294,13 @@ export default class MetamaskController extends EventEmitter { ), provider: this.provider, blockTracker: this.blockTracker, - createEventFragment: this.metaMetricsController.createEventFragment.bind( - this.metaMetricsController, - ), - updateEventFragment: this.metaMetricsController.updateEventFragment.bind( - this.metaMetricsController, - ), - finalizeEventFragment: - this.metaMetricsController.finalizeEventFragment.bind( - this.metaMetricsController, - ), - getEventFragmentById: - this.metaMetricsController.getEventFragmentById.bind( - this.metaMetricsController, - ), - trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind( - this.metaMetricsController, - ), getParticipateInMetrics: () => this.metaMetricsController.state.participateInMetaMetrics, getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController), getExternalPendingTransactions: this.getExternalPendingTransactions.bind(this), - getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this), securityProviderRequest: this.securityProviderRequest.bind(this), - ...this.snapAndHardwareMetricsParams, ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) transactionUpdateController: this.transactionUpdateController, ///: END:ONLY_INCLUDE_IN @@ -1329,6 +1314,39 @@ export default class MetamaskController extends EventEmitter { }), }); + const transactionMetricsRequest = this.getTransactionMetricsRequest(); + + this.txController.on( + 'transaction-added', + handleTransactionAdded.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-approved', + handleTransactionApproved.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-dropped', + handleTransactionDropped.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-finalized', + handleTransactionFinalized.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-rejected', + handleTransactionRejected.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-submitted', + handleTransactionSubmitted.bind(null, transactionMetricsRequest), + ); + this.txController.on('transaction-swap-failed', (payload) => + this.metaMetricsController.trackEvent(payload), + ); + this.txController.on('transaction-swap-finalized', (payload) => + this.metaMetricsController.trackEvent(payload), + ); + this.txController.on(`tx:status-update`, async (txId, status) => { if ( status === TransactionStatus.confirmed || @@ -1475,6 +1493,7 @@ export default class MetamaskController extends EventEmitter { `${this.keyringController.name}:signMessage`, `${this.keyringController.name}:signPersonalMessage`, `${this.keyringController.name}:signTypedMessage`, + `${this.loggingController.name}:add`, ], }), isEthSignEnabled: () => @@ -1643,25 +1662,6 @@ export default class MetamaskController extends EventEmitter { }, ); - if (isManifestV3 && globalThis.isFirstTimeProfileLoaded === undefined) { - const { serviceWorkerLastActiveTime } = - this.appStateController.store.getState(); - const metametricsPayload = { - category: MetaMetricsEventCategory.ServiceWorkers, - event: MetaMetricsEventName.ServiceWorkerRestarted, - properties: { - service_worker_restarted_time: - Date.now() - serviceWorkerLastActiveTime, - }, - }; - - try { - this.metaMetricsController.trackEvent(metametricsPayload); - } catch (e) { - log.warn('Failed to track service worker restart metric:', e); - } - } - this.metamaskMiddleware = createMetamaskMiddleware({ static: { eth_syncing: false, @@ -1937,6 +1937,48 @@ export default class MetamaskController extends EventEmitter { checkForMultipleVersionsRunning(); } + getTransactionMetricsRequest() { + const controllerActions = { + // Metametrics Actions + createEventFragment: this.metaMetricsController.createEventFragment.bind( + this.metaMetricsController, + ), + finalizeEventFragment: + this.metaMetricsController.finalizeEventFragment.bind( + this.metaMetricsController, + ), + getEventFragmentById: + this.metaMetricsController.getEventFragmentById.bind( + this.metaMetricsController, + ), + updateEventFragment: this.metaMetricsController.updateEventFragment.bind( + this.metaMetricsController, + ), + // Other dependencies + getAccountType: this.getAccountType.bind(this), + getDeviceModel: this.getDeviceModel.bind(this), + getEIP1559GasFeeEstimates: + this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController), + getSelectedAddress: () => + this.preferencesController.store.getState().selectedAddress, + getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this), + getTransaction: this.txController.txStateManager.getTransaction.bind( + this.txController, + ), + }; + return { + ...controllerActions, + snapAndHardwareMessenger: this.controllerMessenger.getRestricted({ + name: 'SnapAndHardwareMessenger', + allowedActions: [ + 'KeyringController:getKeyringForAccount', + 'SnapController:get', + ], + }), + provider: this.provider, + }; + } + triggerNetworkrequests() { this.accountTracker.start(); this.txController.startIncomingTransactionPolling(); @@ -2204,13 +2246,14 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.subscribe( `${this.snapController.name}:snapInstalled`, - (truncatedSnap) => { + (truncatedSnap, origin) => { this.metaMetricsController.trackEvent({ event: MetaMetricsEventName.SnapInstalled, category: MetaMetricsEventCategory.Snaps, properties: { snap_id: truncatedSnap.id, version: truncatedSnap.version, + origin, }, }); }, @@ -2218,7 +2261,7 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.subscribe( `${this.snapController.name}:snapUpdated`, - (newSnap, oldVersion) => { + (newSnap, oldVersion, origin) => { this.metaMetricsController.trackEvent({ event: MetaMetricsEventName.SnapUpdated, category: MetaMetricsEventCategory.Snaps, @@ -2226,6 +2269,7 @@ export default class MetamaskController extends EventEmitter { snap_id: newSnap.id, old_version: oldVersion, new_version: newSnap.version, + origin, }, }); }, @@ -2291,28 +2335,27 @@ export default class MetamaskController extends EventEmitter { createPublicConfigStore() { // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore(); - const { networkController } = this; - // setup memStore subscription hooks - this.on('update', updatePublicConfigStore); - updatePublicConfigStore(this.getState()); + const selectPublicState = (chainId, { isUnlocked }) => { + return { + isUnlocked, + chainId, + networkVersion: this.deprecatedNetworkId ?? 'loading', + }; + }; - function updatePublicConfigStore(memState) { + const updatePublicConfigStore = (memState) => { const networkStatus = memState.networksMetadata[memState.selectedNetworkClientId]?.status; - const { chainId } = networkController.state.providerConfig; + const { chainId } = this.networkController.state.providerConfig; if (networkStatus === NetworkStatus.Available) { publicConfigStore.putState(selectPublicState(chainId, memState)); } - } + }; - function selectPublicState(chainId, { isUnlocked, networkId }) { - return { - isUnlocked, - chainId, - networkVersion: networkId ?? 'loading', - }; - } + // setup memStore subscription hooks + this.on('update', updatePublicConfigStore); + updatePublicConfigStore(this.getState()); return publicConfigStore; } @@ -2326,26 +2369,64 @@ export default class MetamaskController extends EventEmitter { async getProviderState(origin) { return { isUnlocked: this.isUnlocked(), - ...this.getProviderNetworkState(), accounts: await this.getPermittedAccounts(origin), + ...this.getProviderNetworkState(), }; } /** * Gets network state relevant for external providers. * - * @param {object} [memState] - The MetaMask memState. If not provided, - * this function will retrieve the most recent state. * @returns {object} An object with relevant network state properties. */ - getProviderNetworkState(memState) { - const { networkId } = memState || this.getState(); + getProviderNetworkState() { return { chainId: this.networkController.state.providerConfig.chainId, - networkVersion: networkId ?? 'loading', + networkVersion: this.deprecatedNetworkId ?? 'loading', }; } + /** + * TODO: Delete when ready to remove `networkVersion` from provider object + * Updates the `deprecatedNetworkId` value + */ + async updateDeprecatedNetworkId() { + try { + this.deprecatedNetworkId = await this.deprecatedGetNetworkId(); + } catch (error) { + console.error(error); + this.deprecatedNetworkId = null; + } + this._notifyChainChange(); + } + + /** + * TODO: Delete when ready to remove `networkVersion` from provider object + * Gets current networkId as returned by `net_version` + * + * @returns {string} The networkId for the current network or null on failure + * @throws Will throw if there is a problem getting the network version + */ + async deprecatedGetNetworkId() { + const ethQuery = this.controllerMessenger.call( + 'NetworkController:getEthQuery', + ); + + if (!ethQuery) { + throw new Error('Provider has not been initialized'); + } + + return new Promise((resolve, reject) => { + ethQuery.sendAsync({ method: 'net_version' }, (error, result) => { + if (error) { + reject(error); + } else { + resolve(convertNetworkId(result)); + } + }); + }); + } + //============================================================================= // EXPOSED TO THE UI SUBSYSTEM //============================================================================= @@ -2368,11 +2449,11 @@ export default class MetamaskController extends EventEmitter { isInitialized, ...flatState, ///: BEGIN:ONLY_INCLUDE_IN(snaps) - // Snap state and source code is stripped out to prevent piping to the MetaMask UI. + // Snap state, source code and other files are stripped out to prevent piping to the MetaMask UI. snapStates: {}, snaps: Object.values(flatState.snaps ?? {}).reduce((acc, snap) => { // eslint-disable-next-line no-unused-vars - const { sourceCode, ...rest } = snap; + const { sourceCode, auxiliaryFiles, ...rest } = snap; acc[snap.id] = rest; return acc; }, {}), @@ -2695,7 +2776,10 @@ export default class MetamaskController extends EventEmitter { addTransactionAndWaitForPublish: this.addTransactionAndWaitForPublish.bind(this), createTransactionEventFragment: - txController.createTransactionEventFragment.bind(txController), + createTransactionEventFragmentWithTxId.bind( + null, + this.getTransactionMetricsRequest(), + ), getTransactions: txController.getTransactions.bind(txController), updateEditableParams: @@ -3642,13 +3726,6 @@ export default class MetamaskController extends EventEmitter { * @returns {Promise} The address of the newly-created account. */ async addNewAccount(accountCount) { - const isActionMetricsQueueE2ETest = - this.appStateController.store.getState()[ACTION_QUEUE_METRICS_E2E_TEST]; - - if (process.env.IN_TEST && isActionMetricsQueueE2ETest) { - await new Promise((resolve) => setTimeout(resolve, 5_000)); - } - const oldAccounts = await this.keyringController.getAccounts(); const { addedAccountAddress } = await this.keyringController.addNewAccount( @@ -4285,7 +4362,18 @@ export default class MetamaskController extends EventEmitter { this.metaMetricsController.store, ), securityProviderRequest: this.securityProviderRequest.bind(this), - ...this.snapAndHardwareMetricsParams, + getSelectedAddress: this.preferencesController.getSelectedAddress.bind( + this.preferencesController, + ), + getAccountType: this.getAccountType.bind(this), + getDeviceModel: this.getDeviceModel.bind(this), + snapAndHardwareMessenger: this.controllerMessenger.getRestricted({ + name: 'SnapAndHardwareMessenger', + allowedActions: [ + 'KeyringController:getKeyringForAccount', + 'SnapController:get', + ], + }), }), ); @@ -4439,7 +4527,11 @@ export default class MetamaskController extends EventEmitter { this.permissionController, origin, ), - getAccounts: this.getPermittedAccounts.bind(this, origin), + getSnapFile: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:getFile', + origin, + ), installSnaps: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:install', @@ -4711,10 +4803,7 @@ export default class MetamaskController extends EventEmitter { */ _onStateUpdate(newState) { this.isClientOpenAndUnlocked = newState.isUnlocked && this._isClientOpen; - this.notifyAllConnections({ - method: NOTIFICATION_NAMES.chainChanged, - params: this.getProviderNetworkState(newState), - }); + this._notifyChainChange(); } // misc @@ -5067,4 +5156,11 @@ export default class MetamaskController extends EventEmitter { this.permissionLogController.updateAccountsHistory(origin, newAccounts); } + + _notifyChainChange() { + this.notifyAllConnections({ + method: NOTIFICATION_NAMES.chainChanged, + params: this.getProviderNetworkState(), + }); + } } diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 2a62d2097bb9..9f71713a7323 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -49,7 +49,7 @@ const browserPolyfillMock = { }, }, alarms: { - getAll: jest.fn(), + getAll: jest.fn(() => Promise.resolve([])), create: jest.fn(), clear: jest.fn(), onAlarm: { @@ -105,7 +105,7 @@ jest.mock('../../shared/modules/mv3.utils', () => ({ }, })); -const currentNetworkId = '5'; +const currentChainId = '0x5'; const DEFAULT_LABEL = 'Account 1'; const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'; @@ -891,36 +891,33 @@ describe('MetaMaskController', () => { }); describe('#resetAccount', () => { - it('wipes transactions from only the correct network id and with the selected address', async () => { + it('wipes transactions from only the correct chain id and with the selected address', async function () { jest .spyOn(metamaskController.preferencesController, 'getSelectedAddress') .mockReturnValue('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'); - jest - .spyOn(metamaskController.txController.txStateManager, 'getNetworkId') - .mockReturnValue(42); metamaskController.txController.txStateManager._addTransactionsToState([ createTxMeta({ id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, }), createTxMeta({ id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, }), createTxMeta({ id: 2, status: TransactionStatus.rejected, - metamaskNetworkId: '32', + chainId: '0x32', }), createTxMeta({ id: 3, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4' }, }), ]); diff --git a/app/scripts/migrations/101.test.js b/app/scripts/migrations/101.test.js new file mode 100644 index 000000000000..5130c23ed4b6 --- /dev/null +++ b/app/scripts/migrations/101.test.js @@ -0,0 +1,112 @@ +import { migrate, version } from './101'; + +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + +jest.mock('uuid', () => { + const actual = jest.requireActual('uuid'); + + return { + ...actual, + v4: jest.fn(), + }; +}); + +describe('migration #101', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should update the version metadata', async () => { + const oldStorage = { + meta: { + version: 100, + }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ + version, + }); + }); + + it('should return state unaltered if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 100, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 100, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + + it('should return state unaltered if there is no network controller networkId state', async () => { + const oldData = { + other: 'data', + NetworkController: { + selectedNetworkClientId: 'networkClientId1', + }, + }; + const oldStorage = { + meta: { + version: 100, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should delete the networkId state', async () => { + const oldData = { + other: 'data', + NetworkController: { + selectedNetworkClientId: 'networkClientId1', + networkId: '1337', + }, + }; + const oldStorage = { + meta: { + version: 100, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + other: 'data', + NetworkController: { + selectedNetworkClientId: 'networkClientId1', + }, + }); + }); +}); diff --git a/app/scripts/migrations/101.ts b/app/scripts/migrations/101.ts new file mode 100644 index 000000000000..bd474212e694 --- /dev/null +++ b/app/scripts/migrations/101.ts @@ -0,0 +1,52 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 101; + +/** + * Remove network controller `networkId` state. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if ( + hasProperty(state, 'NetworkController') && + isObject(state.NetworkController) && + hasProperty(state.NetworkController, 'networkId') + ) { + const networkControllerState = state.NetworkController; + delete networkControllerState.networkId; + + return { + ...state, + NetworkController: networkControllerState, + }; + } + if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } + + return state; +} diff --git a/app/scripts/migrations/102.test.ts b/app/scripts/migrations/102.test.ts new file mode 100644 index 000000000000..894c875d524c --- /dev/null +++ b/app/scripts/migrations/102.test.ts @@ -0,0 +1,118 @@ +import { migrate, version } from './102'; + +const oldVersion = 101; +describe('migration #102', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('handles missing TransactionController', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('handles empty transactions', async () => { + const oldState = { + TransactionController: { + transactions: {}, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('handles missing state', async () => { + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: {}, + }); + + expect(transformedState.data).toEqual({}); + }); + + it('adds `error` property in the transaction by copying `err` and deleting it afterwards', async () => { + const oldState = { + TransactionController: { + transactions: { + tx1: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + err: { + message: 'nonce too high', + rpc: 'rpc_error', + stack: 'stacktrace', + }, + otherProp: 'value', + }, + tx2: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + otherProp: 'value', + }, + tx3: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + err: { + message: 'mocked error', + rpc: 'rpc_error', + stack: 'stacktrace', + }, + otherProp: 'value', + }, + }, + }, + }; + const oldStorage = { + meta: { version: oldVersion }, + data: oldState, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toEqual({ + TransactionController: { + transactions: { + tx1: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + error: { + message: 'nonce too high', + rpc: 'rpc_error', + stack: 'stacktrace', + }, + otherProp: 'value', + }, + tx2: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + otherProp: 'value', + }, + tx3: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + error: { + message: 'mocked error', + rpc: 'rpc_error', + stack: 'stacktrace', + }, + otherProp: 'value', + }, + }, + }, + }); + }); +}); diff --git a/app/scripts/migrations/102.ts b/app/scripts/migrations/102.ts new file mode 100644 index 000000000000..820e67605251 --- /dev/null +++ b/app/scripts/migrations/102.ts @@ -0,0 +1,57 @@ +import { cloneDeep, isEmpty } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 102; + +/** + * The core TransactionController uses `error` to log transaction error information. + * For the sake of standardization and minimizing code maintenance, `err` is renamed as part of the unification of the Transaction Controller effort. + * This migration adds an `error` property by copying the old `err` and deleting it afterwards. + * + * @param originalVersionedData + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + const transactionControllerState = state?.TransactionController || {}; + const transactions = transactionControllerState?.transactions || {}; + + if (isEmpty(transactions)) { + return; + } + + const newTxs = Object.keys(transactions).reduce( + (txs: { [key: string]: any }, txId) => { + // Clone the transaction + const transaction = cloneDeep(transactions[txId]); + + // Check if 'err' exists before assigning it to 'error' + if (transaction?.err) { + transaction.error = transaction.err; + delete transaction.err; + } + + return { + ...txs, + [txId]: transaction, + }; + }, + {}, + ); + + state.TransactionController = { + ...transactionControllerState, + transactions: newTxs, + }; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index c163d18c03c4..ca592106da0f 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -107,6 +107,8 @@ import * as m097 from './097'; import * as m098 from './098'; import * as m099 from './099'; import * as m100 from './100'; +import * as m101 from './101'; +import * as m102 from './102'; const migrations = [ m002, @@ -211,5 +213,7 @@ const migrations = [ m098, m099, m100, + m101, + m102, ]; export default migrations; diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 55f5a29e9e9a..3b39404ce2b1 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -198,13 +198,13 @@ export default class ExtensionPlatform { let message = t( 'notificationTransactionFailedMessage', nonce, - errorMessage || txMeta.err.message, + errorMessage || txMeta.error.message, ); ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) if (isNaN(nonce)) { message = t( 'notificationTransactionFailedMessageMMI', - errorMessage || txMeta.err.message, + errorMessage || txMeta.error.message, ); } ///: END:ONLY_INCLUDE_IN diff --git a/app/scripts/platforms/extension.test.js b/app/scripts/platforms/extension.test.js index 1016efef21a7..14ec2e66b40b 100644 --- a/app/scripts/platforms/extension.test.js +++ b/app/scripts/platforms/extension.test.js @@ -129,7 +129,7 @@ describe('extension platform', () => { it('should show failed transaction with nonce', async () => { const txMeta = { txParams: { nonce: '0x1' }, - err: { message: 'Error message' }, + error: { message: 'Error message' }, }; const extensionPlatform = new ExtensionPlatform(); const showNotificationSpy = jest.spyOn( @@ -141,7 +141,7 @@ describe('extension platform', () => { expect(showNotificationSpy).toHaveBeenCalledWith( 'Failed transaction', - `Transaction 1 failed! ${txMeta.err.message}`, + `Transaction 1 failed! ${txMeta.error.message}`, ); }); @@ -149,7 +149,7 @@ describe('extension platform', () => { const errorMessage = 'Test error message'; const txMeta = { txParams: { nonce: '0x1' }, - err: { message: 'Error message' }, + error: { message: 'Error message' }, }; const extensionPlatform = new ExtensionPlatform(); const showNotificationSpy = jest.spyOn( @@ -168,7 +168,7 @@ describe('extension platform', () => { it('should show failed transaction without nonce', async () => { const txMeta = { txParams: {}, - err: { message: 'Error message' }, + error: { message: 'Error message' }, }; const extensionPlatform = new ExtensionPlatform(); const showNotificationSpy = jest.spyOn( @@ -180,7 +180,7 @@ describe('extension platform', () => { expect(showNotificationSpy).toHaveBeenCalledWith( 'Failed transaction', - `Transaction failed! ${txMeta.err.message}`, + `Transaction failed! ${txMeta.error.message}`, ); }); }); diff --git a/app/scripts/ui.js b/app/scripts/ui.js index df85aa2f957d..bda3e4dcf9de 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -38,70 +38,12 @@ const container = document.getElementById('app-content'); const ONE_SECOND_IN_MILLISECONDS = 1_000; -// Service Worker Keep Alive Message Constants -const WORKER_KEEP_ALIVE_INTERVAL = ONE_SECOND_IN_MILLISECONDS; -const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE'; -const ACK_KEEP_ALIVE_WAIT_TIME = 60_000; // 1 minute -const ACK_KEEP_ALIVE_MESSAGE = 'ACK_KEEP_ALIVE_MESSAGE'; - // Timeout for initializing phishing warning page. const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS; const PHISHING_WARNING_SW_STORAGE_KEY = 'phishing-warning-sw-registered'; -let lastMessageReceivedTimestamp = Date.now(); - let extensionPort; -let ackTimeoutToDisplayError; - -/* - * As long as UI is open it will keep sending messages to service worker - * In service worker as this message is received - * if service worker is inactive it is reactivated and script re-loaded - * Time has been kept to 1000ms but can be reduced for even faster re-activation of service worker - */ -if (isManifestV3) { - // Checking for SW aliveness (or stuckness) flow - // 1. Check if we have an extensionPort, if yes - // 2a. Send a keep alive message to the background via extensionPort - // 2b. Add a listener to it (if not already added) - // 3a. Set a timeout to check if we have received an ACK from background - // 3b. If we have not received an ACK within ACK_KEEP_ALIVE_WAIT_TIME, - // we know the background is stuck or dead - // 4. If we recieve an ACK_KEEP_ALIVE_MESSAGE from the service worker, we know it is alive - - const ackKeepAliveListener = (message) => { - if (message.name === ACK_KEEP_ALIVE_MESSAGE) { - lastMessageReceivedTimestamp = Date.now(); - clearTimeout(ackTimeoutToDisplayError); - } - }; - - const keepAliveInterval = setInterval(() => { - browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); - - if (extensionPort !== null && extensionPort !== undefined) { - extensionPort.postMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); - - if (extensionPort.onMessage.hasListener(ackKeepAliveListener) === false) { - extensionPort.onMessage.addListener(ackKeepAliveListener); - } - } - - ackTimeoutToDisplayError = setTimeout(() => { - if ( - Date.now() - lastMessageReceivedTimestamp > - ACK_KEEP_ALIVE_WAIT_TIME - ) { - clearInterval(keepAliveInterval); - displayCriticalError( - 'somethingIsWrong', - new Error("Something's gone wrong. Try reloading the page."), - ); - } - }, ACK_KEEP_ALIVE_WAIT_TIME); - }, WORKER_KEEP_ALIVE_INTERVAL); -} start().catch(log.error); @@ -244,10 +186,6 @@ async function start() { resetExtensionStreamAndListeners, ); - // message below will try to activate service worker - // in MV3 is likely that reason of stream closing is service worker going in-active - browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); - extensionPort = browser.runtime.connect({ name: windowType }); connectionStream = new PortStream(extensionPort); extensionPort.onMessage.addListener(messageListener); diff --git a/builds.yml b/builds.yml index ab1a37c3cba3..6f492f568b8c 100644 --- a/builds.yml +++ b/builds.yml @@ -27,7 +27,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_PROD_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.0.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.1.0/index.html - KEYRING_SNAPS_REGISTRY_URL: https://metamask.github.io/keyring-snaps-registry/prod/registry.json # Main build uses the default browser manifest manifestOverrides: false @@ -61,7 +61,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.0.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.1.0/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -81,7 +81,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.0.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.1.0/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -101,7 +101,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_MMI_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.0.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.1.0/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://mmi-support.zendesk.com/hc/en-us - SUPPORT_REQUEST_LINK: https://mmi-support.zendesk.com/hc/en-us/requests/new diff --git a/development/lib/retry.js b/development/lib/retry.js index ea469958b221..e7ce13dd2dc0 100644 --- a/development/lib/retry.js +++ b/development/lib/retry.js @@ -14,8 +14,10 @@ * @param {string} args.retryUntilFailure - Retries until the function fails. * @param {Function} functionToRetry - The function that is run and tested for * failure. - * @returns {Promise} a promise that either resolves to null if - * the function is successful or is rejected with rejectionMessage otherwise. + * @returns {Promise<* | null | Error>} a promise that either resolves with one of the following: + * - If successful, resolves with the return value of functionToRetry. + * - If functionToRetry fails while retryUntilFailure is true, resolves with null. + * - Otherwise it is rejected with rejectionMessage. */ async function retry( { @@ -33,14 +35,14 @@ async function retry( } try { - await functionToRetry(); + const result = await functionToRetry(); if (!retryUntilFailure) { - return; + return result; } } catch (error) { - console.error(error); + console.error('error caught in retry():', error); if (retryUntilFailure) { - return; + return null; } } finally { attempts += 1; diff --git a/development/ts-migration-dashboard/files-to-convert.json b/development/ts-migration-dashboard/files-to-convert.json index 003db164cfec..0a11ad6fba86 100644 --- a/development/ts-migration-dashboard/files-to-convert.json +++ b/development/ts-migration-dashboard/files-to-convert.json @@ -334,7 +334,6 @@ "ui/components/app/account-menu/account-menu.container.js", "ui/components/app/account-menu/account-menu.test.js", "ui/components/app/account-menu/index.js", - "ui/components/app/account-menu/keyring-label.js", "ui/components/app/add-network/add-network.js", "ui/components/app/add-network/add-network.stories.js", "ui/components/app/add-network/add-network.test.js", diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index ad3f53da7166..9f57b820361a 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -712,8 +712,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -738,6 +768,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -752,15 +783,15 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/controller-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/assets-controllers>@metamask/utils": true, - "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, @@ -787,52 +818,16 @@ "superstruct": true } }, - "@metamask/assets-controllers>@metamask/controller-utils": { + "@metamask/assets-controllers>@metamask/polling-controller": { "globals": { - "URL": true, + "clearTimeout": true, "console.error": true, - "fetch": true, "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors": { - "packages": { - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/assets-controllers>@metamask/utils": { @@ -848,11 +843,6 @@ "superstruct": true } }, - "@metamask/assets-controllers>abort-controller": { - "globals": { - "AbortController": true - } - }, "@metamask/assets-controllers>multiformats": { "globals": { "TextDecoder": true, @@ -1513,13 +1503,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1687,7 +1707,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1696,23 +1716,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1879,43 +1882,13 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/permission-controller>@metamask/utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/rpc-errors": true, "deep-freeze-strict": true, - "eth-rpc-errors": true, - "immer": true, - "json-rpc-engine": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "immer": true } }, "@metamask/permission-controller>@metamask/utils": { @@ -1942,11 +1915,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -1979,24 +1982,41 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/rpc-methods": { + "@metamask/providers>@metamask/json-rpc-engine": { + "packages": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": true, + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true + } + }, + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "webpack>events": true + } + }, + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/utils": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, - "eth-rpc-errors": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, - "@metamask/rpc-methods-flask>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true } }, - "@metamask/rpc-methods>@metamask/utils": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true @@ -2009,6 +2029,11 @@ "superstruct": true } }, + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2049,6 +2074,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2071,12 +2097,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2098,6 +2154,31 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@metamask/snaps-ui": true, + "@metamask/snaps-utils": true, + "superstruct": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/snaps-ui": { "packages": { "@metamask/snaps-ui>@metamask/utils": true, @@ -2147,6 +2228,7 @@ "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index e739b67d6d1c..eb5f196eb50f 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -712,8 +712,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -738,6 +768,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -752,15 +783,15 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/controller-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/assets-controllers>@metamask/utils": true, - "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, @@ -787,52 +818,16 @@ "superstruct": true } }, - "@metamask/assets-controllers>@metamask/controller-utils": { + "@metamask/assets-controllers>@metamask/polling-controller": { "globals": { - "URL": true, + "clearTimeout": true, "console.error": true, - "fetch": true, "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors": { - "packages": { - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/assets-controllers>@metamask/utils": { @@ -848,11 +843,6 @@ "superstruct": true } }, - "@metamask/assets-controllers>abort-controller": { - "globals": { - "AbortController": true - } - }, "@metamask/assets-controllers>multiformats": { "globals": { "TextDecoder": true, @@ -1628,13 +1618,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1827,7 +1847,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1836,23 +1856,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -2026,43 +2029,13 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/permission-controller>@metamask/utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/rpc-errors": true, "deep-freeze-strict": true, - "eth-rpc-errors": true, - "immer": true, - "json-rpc-engine": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "immer": true } }, "@metamask/permission-controller>@metamask/utils": { @@ -2089,11 +2062,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2126,43 +2129,51 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/object-multiplex": { - "globals": { - "console.warn": true - }, + "@metamask/providers>@metamask/json-rpc-engine": { "packages": { - "end-of-stream": true, - "pump>once": true, - "readable-stream": true + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": true, + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true } }, - "@metamask/rate-limit-controller": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": { "globals": { "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "eth-rpc-errors": true + "webpack>events": true } }, - "@metamask/rpc-methods": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/utils": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, - "eth-rpc-errors": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/providers>@metamask/object-multiplex": { "globals": { - "crypto.getRandomValues": true + "console.warn": true + }, + "packages": { + "end-of-stream": true, + "pump>once": true, + "readable-stream": true } }, - "@metamask/rpc-methods>@metamask/utils": { + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true @@ -2175,6 +2186,20 @@ "superstruct": true } }, + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "eth-rpc-errors": true + } + }, + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2215,6 +2240,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2237,12 +2263,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2267,21 +2323,22 @@ "packages": { "@metamask/base-controller": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/providers>@metamask/object-multiplex": true, - "@metamask/rpc-methods": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-controllers>@metamask/post-message-stream": true, "@metamask/snaps-controllers>@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, "@metamask/snaps-controllers>gunzip-maybe": true, + "@metamask/snaps-controllers>json-rpc-middleware-stream": true, "@metamask/snaps-controllers>nanoid": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, + "@metamask/snaps-rpc-methods": true, "@metamask/snaps-utils": true, "@metamask/snaps-utils>@metamask/snaps-registry": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "json-rpc-middleware-stream": true, "stream-browserify": true } }, @@ -2429,6 +2486,24 @@ "watchify>xtend": true } }, + "@metamask/snaps-controllers>json-rpc-middleware-stream": { + "globals": { + "console.warn": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-controllers>concat-stream>readable-stream": true, + "@metamask/snaps-controllers>json-rpc-middleware-stream>@metamask/safe-event-emitter": true + } + }, + "@metamask/snaps-controllers>json-rpc-middleware-stream>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "webpack>events": true + } + }, "@metamask/snaps-controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2452,31 +2527,53 @@ }, "@metamask/snaps-controllers>tar-stream": { "packages": { - "@metamask/snaps-controllers>tar-stream>fs-constants": true, - "@metamask/snaps-controllers>tar-stream>readable-stream": true, - "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "browserify>util": true, - "end-of-stream": true, - "madge>ora>bl": true, - "pumpify>inherits": true + "@metamask/snaps-controllers>tar-stream>b4a": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true, + "browserify>browser-resolve": true + } + }, + "@metamask/snaps-controllers>tar-stream>b4a": { + "globals": { + "TextDecoder": true, + "TextEncoder": true } }, - "@metamask/snaps-controllers>tar-stream>fs-constants": { + "@metamask/snaps-controllers>tar-stream>streamx": { "packages": { - "browserify>constants-browserify": true + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, + "webpack>events": true } }, - "@metamask/snaps-controllers>tar-stream>readable-stream": { + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { + "globals": { + "queueMicrotask": true + } + }, + "@metamask/snaps-rpc-methods": { "packages": { - "browserify>browser-resolve": true, + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@metamask/snaps-ui": true, + "@metamask/snaps-utils": true, + "superstruct": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/snaps-ui": { @@ -2528,6 +2625,7 @@ "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, @@ -4585,24 +4683,6 @@ "Intl": true } }, - "madge>ora>bl": { - "packages": { - "browserify>buffer": true, - "madge>ora>bl>readable-stream": true, - "pumpify>inherits": true - } - }, - "madge>ora>bl>readable-stream": { - "packages": { - "browserify>browser-resolve": true, - "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true - } - }, "mocha>serialize-javascript>randombytes": { "globals": { "crypto": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 298ef0427213..80b2806d64d7 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -712,8 +712,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -738,6 +768,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -752,15 +783,15 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/controller-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/assets-controllers>@metamask/utils": true, - "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, @@ -787,52 +818,16 @@ "superstruct": true } }, - "@metamask/assets-controllers>@metamask/controller-utils": { + "@metamask/assets-controllers>@metamask/polling-controller": { "globals": { - "URL": true, + "clearTimeout": true, "console.error": true, - "fetch": true, "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors": { - "packages": { - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/assets-controllers>@metamask/utils": { @@ -848,11 +843,6 @@ "superstruct": true } }, - "@metamask/assets-controllers>abort-controller": { - "globals": { - "AbortController": true - } - }, "@metamask/assets-controllers>multiformats": { "globals": { "TextDecoder": true, @@ -1628,13 +1618,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1827,7 +1847,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1836,23 +1856,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -2026,43 +2029,13 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/permission-controller>@metamask/utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/rpc-errors": true, "deep-freeze-strict": true, - "eth-rpc-errors": true, - "immer": true, - "json-rpc-engine": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "immer": true } }, "@metamask/permission-controller>@metamask/utils": { @@ -2089,11 +2062,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2108,13 +2111,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/ppom-validator>@metamask/controller-utils": true, "@metamask/ppom-validator>elliptic": true, "await-semaphore": true, "browserify>buffer": true, "eth-query>json-rpc-random-id": true } }, + "@metamask/ppom-validator>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -2142,43 +2175,51 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/object-multiplex": { - "globals": { - "console.warn": true - }, + "@metamask/providers>@metamask/json-rpc-engine": { "packages": { - "end-of-stream": true, - "pump>once": true, - "readable-stream": true + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": true, + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true } }, - "@metamask/rate-limit-controller": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": { "globals": { "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "eth-rpc-errors": true + "webpack>events": true } }, - "@metamask/rpc-methods": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/utils": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, - "eth-rpc-errors": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/providers>@metamask/object-multiplex": { "globals": { - "crypto.getRandomValues": true + "console.warn": true + }, + "packages": { + "end-of-stream": true, + "pump>once": true, + "readable-stream": true } }, - "@metamask/rpc-methods>@metamask/utils": { + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true @@ -2191,6 +2232,20 @@ "superstruct": true } }, + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "eth-rpc-errors": true + } + }, + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2231,6 +2286,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2253,12 +2309,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2283,21 +2369,22 @@ "packages": { "@metamask/base-controller": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/providers>@metamask/object-multiplex": true, - "@metamask/rpc-methods": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-controllers>@metamask/post-message-stream": true, "@metamask/snaps-controllers>@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, "@metamask/snaps-controllers>gunzip-maybe": true, + "@metamask/snaps-controllers>json-rpc-middleware-stream": true, "@metamask/snaps-controllers>nanoid": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, + "@metamask/snaps-rpc-methods": true, "@metamask/snaps-utils": true, "@metamask/snaps-utils>@metamask/snaps-registry": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "json-rpc-middleware-stream": true, "stream-browserify": true } }, @@ -2445,6 +2532,24 @@ "watchify>xtend": true } }, + "@metamask/snaps-controllers>json-rpc-middleware-stream": { + "globals": { + "console.warn": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-controllers>concat-stream>readable-stream": true, + "@metamask/snaps-controllers>json-rpc-middleware-stream>@metamask/safe-event-emitter": true + } + }, + "@metamask/snaps-controllers>json-rpc-middleware-stream>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "webpack>events": true + } + }, "@metamask/snaps-controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2468,31 +2573,53 @@ }, "@metamask/snaps-controllers>tar-stream": { "packages": { - "@metamask/snaps-controllers>tar-stream>fs-constants": true, - "@metamask/snaps-controllers>tar-stream>readable-stream": true, - "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "browserify>util": true, - "end-of-stream": true, - "madge>ora>bl": true, - "pumpify>inherits": true + "@metamask/snaps-controllers>tar-stream>b4a": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true, + "browserify>browser-resolve": true + } + }, + "@metamask/snaps-controllers>tar-stream>b4a": { + "globals": { + "TextDecoder": true, + "TextEncoder": true } }, - "@metamask/snaps-controllers>tar-stream>fs-constants": { + "@metamask/snaps-controllers>tar-stream>streamx": { "packages": { - "browserify>constants-browserify": true + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, + "webpack>events": true + } + }, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { + "globals": { + "queueMicrotask": true } }, - "@metamask/snaps-controllers>tar-stream>readable-stream": { + "@metamask/snaps-rpc-methods": { "packages": { - "browserify>browser-resolve": true, + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@metamask/snaps-ui": true, + "@metamask/snaps-utils": true, + "superstruct": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/snaps-ui": { @@ -2544,6 +2671,7 @@ "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, @@ -4601,24 +4729,6 @@ "Intl": true } }, - "madge>ora>bl": { - "packages": { - "browserify>buffer": true, - "madge>ora>bl>readable-stream": true, - "pumpify>inherits": true - } - }, - "madge>ora>bl>readable-stream": { - "packages": { - "browserify>browser-resolve": true, - "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true - } - }, "mocha>serialize-javascript>randombytes": { "globals": { "crypto": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index cb4202675d97..742690529e0b 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -712,8 +712,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -738,6 +768,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -752,15 +783,15 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/controller-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/assets-controllers>@metamask/utils": true, - "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, @@ -787,52 +818,16 @@ "superstruct": true } }, - "@metamask/assets-controllers>@metamask/controller-utils": { + "@metamask/assets-controllers>@metamask/polling-controller": { "globals": { - "URL": true, + "clearTimeout": true, "console.error": true, - "fetch": true, "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors": { - "packages": { - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/assets-controllers>@metamask/utils": { @@ -848,11 +843,6 @@ "superstruct": true } }, - "@metamask/assets-controllers>abort-controller": { - "globals": { - "AbortController": true - } - }, "@metamask/assets-controllers>multiformats": { "globals": { "TextDecoder": true, @@ -1557,13 +1547,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1756,7 +1776,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1765,23 +1785,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1955,43 +1958,13 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/permission-controller>@metamask/utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/rpc-errors": true, "deep-freeze-strict": true, - "eth-rpc-errors": true, - "immer": true, - "json-rpc-engine": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "immer": true } }, "@metamask/permission-controller>@metamask/utils": { @@ -2018,11 +1991,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2037,13 +2040,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/ppom-validator>@metamask/controller-utils": true, "@metamask/ppom-validator>elliptic": true, "await-semaphore": true, "browserify>buffer": true, "eth-query>json-rpc-random-id": true } }, + "@metamask/ppom-validator>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -2071,43 +2104,51 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/object-multiplex": { - "globals": { - "console.warn": true - }, + "@metamask/providers>@metamask/json-rpc-engine": { "packages": { - "end-of-stream": true, - "pump>once": true, - "readable-stream": true + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": true, + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true } }, - "@metamask/rate-limit-controller": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": { "globals": { "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "eth-rpc-errors": true + "webpack>events": true } }, - "@metamask/rpc-methods": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/utils": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, - "eth-rpc-errors": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/providers>@metamask/object-multiplex": { "globals": { - "crypto.getRandomValues": true + "console.warn": true + }, + "packages": { + "end-of-stream": true, + "pump>once": true, + "readable-stream": true } }, - "@metamask/rpc-methods>@metamask/utils": { + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true @@ -2120,6 +2161,20 @@ "superstruct": true } }, + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "eth-rpc-errors": true + } + }, + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2160,6 +2215,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2182,12 +2238,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2212,21 +2298,22 @@ "packages": { "@metamask/base-controller": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/providers>@metamask/object-multiplex": true, - "@metamask/rpc-methods": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-controllers>@metamask/post-message-stream": true, "@metamask/snaps-controllers>@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, "@metamask/snaps-controllers>gunzip-maybe": true, + "@metamask/snaps-controllers>json-rpc-middleware-stream": true, "@metamask/snaps-controllers>nanoid": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, + "@metamask/snaps-rpc-methods": true, "@metamask/snaps-utils": true, "@metamask/snaps-utils>@metamask/snaps-registry": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "json-rpc-middleware-stream": true, "stream-browserify": true } }, @@ -2374,6 +2461,24 @@ "watchify>xtend": true } }, + "@metamask/snaps-controllers>json-rpc-middleware-stream": { + "globals": { + "console.warn": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-controllers>concat-stream>readable-stream": true, + "@metamask/snaps-controllers>json-rpc-middleware-stream>@metamask/safe-event-emitter": true + } + }, + "@metamask/snaps-controllers>json-rpc-middleware-stream>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "webpack>events": true + } + }, "@metamask/snaps-controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2397,31 +2502,53 @@ }, "@metamask/snaps-controllers>tar-stream": { "packages": { - "@metamask/snaps-controllers>tar-stream>fs-constants": true, - "@metamask/snaps-controllers>tar-stream>readable-stream": true, - "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "browserify>util": true, - "end-of-stream": true, - "madge>ora>bl": true, - "pumpify>inherits": true + "@metamask/snaps-controllers>tar-stream>b4a": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true, + "browserify>browser-resolve": true + } + }, + "@metamask/snaps-controllers>tar-stream>b4a": { + "globals": { + "TextDecoder": true, + "TextEncoder": true } }, - "@metamask/snaps-controllers>tar-stream>fs-constants": { + "@metamask/snaps-controllers>tar-stream>streamx": { "packages": { - "browserify>constants-browserify": true + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, + "webpack>events": true + } + }, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { + "globals": { + "queueMicrotask": true } }, - "@metamask/snaps-controllers>tar-stream>readable-stream": { + "@metamask/snaps-rpc-methods": { "packages": { - "browserify>browser-resolve": true, + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@metamask/snaps-ui": true, + "@metamask/snaps-utils": true, + "superstruct": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/snaps-ui": { @@ -2473,6 +2600,7 @@ "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, @@ -4530,24 +4658,6 @@ "Intl": true } }, - "madge>ora>bl": { - "packages": { - "browserify>buffer": true, - "madge>ora>bl>readable-stream": true, - "pumpify>inherits": true - } - }, - "madge>ora>bl>readable-stream": { - "packages": { - "browserify>browser-resolve": true, - "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true - } - }, "mocha>serialize-javascript>randombytes": { "globals": { "crypto": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index aa97c2160a9c..c8f6ebd7c2d0 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -852,8 +852,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -878,6 +908,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -892,15 +923,15 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/controller-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/assets-controllers>@metamask/utils": true, - "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, @@ -927,52 +958,16 @@ "superstruct": true } }, - "@metamask/assets-controllers>@metamask/controller-utils": { + "@metamask/assets-controllers>@metamask/polling-controller": { "globals": { - "URL": true, + "clearTimeout": true, "console.error": true, - "fetch": true, "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/assets-controllers>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors": { - "packages": { - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true - } - }, - "@metamask/assets-controllers>@metamask/rpc-errors>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, "@metamask/assets-controllers>@metamask/utils": { @@ -988,11 +983,6 @@ "superstruct": true } }, - "@metamask/assets-controllers>abort-controller": { - "globals": { - "AbortController": true - } - }, "@metamask/assets-controllers>multiformats": { "globals": { "TextDecoder": true, @@ -1653,13 +1643,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1827,7 +1847,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1836,23 +1856,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -2026,43 +2029,13 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/permission-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/permission-controller>@metamask/utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/providers>@metamask/rpc-errors": true, "deep-freeze-strict": true, - "eth-rpc-errors": true, - "immer": true, - "json-rpc-engine": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, - "@metamask/permission-controller>@metamask/controller-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "immer": true } }, "@metamask/permission-controller>@metamask/utils": { @@ -2089,11 +2062,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2126,43 +2129,51 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/object-multiplex": { - "globals": { - "console.warn": true - }, + "@metamask/providers>@metamask/json-rpc-engine": { "packages": { - "end-of-stream": true, - "pump>once": true, - "readable-stream": true + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": true, + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true } }, - "@metamask/rate-limit-controller": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/safe-event-emitter": { "globals": { "setTimeout": true }, "packages": { - "@metamask/base-controller": true, - "eth-rpc-errors": true + "webpack>events": true } }, - "@metamask/rpc-methods": { + "@metamask/providers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, "packages": { - "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/permission-controller": true, - "@metamask/rpc-methods>@metamask/utils": true, - "@metamask/snaps-ui": true, - "@metamask/snaps-utils": true, - "eth-rpc-errors": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, - "@metamask/rpc-methods-flask>nanoid": { + "@metamask/providers>@metamask/object-multiplex": { "globals": { - "crypto.getRandomValues": true + "console.warn": true + }, + "packages": { + "end-of-stream": true, + "pump>once": true, + "readable-stream": true } }, - "@metamask/rpc-methods>@metamask/utils": { + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { "globals": { "TextDecoder": true, "TextEncoder": true @@ -2175,6 +2186,20 @@ "superstruct": true } }, + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "eth-rpc-errors": true + } + }, + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2215,6 +2240,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2237,12 +2263,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2267,21 +2323,22 @@ "packages": { "@metamask/base-controller": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/providers>@metamask/object-multiplex": true, - "@metamask/rpc-methods": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-controllers>@metamask/post-message-stream": true, "@metamask/snaps-controllers>@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, "@metamask/snaps-controllers>gunzip-maybe": true, + "@metamask/snaps-controllers>json-rpc-middleware-stream": true, "@metamask/snaps-controllers>nanoid": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, + "@metamask/snaps-rpc-methods": true, "@metamask/snaps-utils": true, "@metamask/snaps-utils>@metamask/snaps-registry": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "json-rpc-middleware-stream": true, "stream-browserify": true } }, @@ -2429,6 +2486,24 @@ "watchify>xtend": true } }, + "@metamask/snaps-controllers>json-rpc-middleware-stream": { + "globals": { + "console.warn": true, + "setTimeout": true + }, + "packages": { + "@metamask/snaps-controllers>concat-stream>readable-stream": true, + "@metamask/snaps-controllers>json-rpc-middleware-stream>@metamask/safe-event-emitter": true + } + }, + "@metamask/snaps-controllers>json-rpc-middleware-stream>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "webpack>events": true + } + }, "@metamask/snaps-controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2452,31 +2527,53 @@ }, "@metamask/snaps-controllers>tar-stream": { "packages": { - "@metamask/snaps-controllers>tar-stream>fs-constants": true, - "@metamask/snaps-controllers>tar-stream>readable-stream": true, - "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "browserify>util": true, - "end-of-stream": true, - "madge>ora>bl": true, - "pumpify>inherits": true + "@metamask/snaps-controllers>tar-stream>b4a": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true, + "browserify>browser-resolve": true + } + }, + "@metamask/snaps-controllers>tar-stream>b4a": { + "globals": { + "TextDecoder": true, + "TextEncoder": true } }, - "@metamask/snaps-controllers>tar-stream>fs-constants": { + "@metamask/snaps-controllers>tar-stream>streamx": { "packages": { - "browserify>constants-browserify": true + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, + "webpack>events": true } }, - "@metamask/snaps-controllers>tar-stream>readable-stream": { + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { + "globals": { + "queueMicrotask": true + } + }, + "@metamask/snaps-rpc-methods": { "packages": { - "browserify>browser-resolve": true, + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/snaps-rpc-methods>@metamask/utils": true, + "@metamask/snaps-ui": true, + "@metamask/snaps-utils": true, + "superstruct": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/snaps-ui": { @@ -2528,6 +2625,7 @@ "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/permission-controller": true, + "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, @@ -4585,24 +4683,6 @@ "Intl": true } }, - "madge>ora>bl": { - "packages": { - "browserify>buffer": true, - "madge>ora>bl>readable-stream": true, - "pumpify>inherits": true - } - }, - "madge>ora>bl>readable-stream": { - "packages": { - "browserify>browser-resolve": true, - "browserify>buffer": true, - "browserify>process": true, - "browserify>string_decoder": true, - "pumpify>inherits": true, - "readable-stream>util-deprecate": true, - "webpack>events": true - } - }, "mocha>serialize-javascript>randombytes": { "globals": { "crypto": true, diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index f6fcc3efe20f..5601c8594ced 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -1192,6 +1192,33 @@ "@metamask/jazzicon>color>color-convert>color-name": true } }, + "@sentry/cli>which": { + "builtin": { + "path.join": true + }, + "globals": { + "process.cwd": true, + "process.env.OSTYPE": true, + "process.env.PATH": true, + "process.env.PATHEXT": true, + "process.platform": true + }, + "packages": { + "@sentry/cli>which>isexe": true + } + }, + "@sentry/cli>which>isexe": { + "builtin": { + "fs": true + }, + "globals": { + "TESTING_WINDOWS": true, + "process.env.PATHEXT": true, + "process.getgid": true, + "process.getuid": true, + "process.platform": true + } + }, "@storybook/addon-knobs>qs": { "packages": { "string.prototype.matchall>side-channel": true @@ -2082,9 +2109,9 @@ "process.platform": true }, "packages": { + "@sentry/cli>which": true, "cross-spawn>path-key": true, - "cross-spawn>shebang-command": true, - "mocha>which": true + "cross-spawn>shebang-command": true } }, "cross-spawn>path-key": { @@ -3160,7 +3187,7 @@ }, "eslint>minimatch>brace-expansion": { "packages": { - "mocha>minimatch>brace-expansion>concat-map": true, + "eslint>minimatch>brace-expansion>concat-map": true, "stylelint>balanced-match": true } }, @@ -6371,33 +6398,6 @@ "chalk>supports-color>has-flag": true } }, - "mocha>which": { - "builtin": { - "path.join": true - }, - "globals": { - "process.cwd": true, - "process.env.OSTYPE": true, - "process.env.PATH": true, - "process.env.PATHEXT": true, - "process.platform": true - }, - "packages": { - "mocha>which>isexe": true - } - }, - "mocha>which>isexe": { - "builtin": { - "fs": true - }, - "globals": { - "TESTING_WINDOWS": true, - "process.env.PATHEXT": true, - "process.getgid": true, - "process.getuid": true, - "process.platform": true - } - }, "nock>debug": { "builtin": { "tty.isatty": true, @@ -8066,7 +8066,7 @@ "process.platform": true }, "packages": { - "mocha>which>isexe": true + "@sentry/cli>which>isexe": true } }, "stylelint>globjoin": { diff --git a/package.json b/package.json index b164ee03c308..26f7d5d764b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "11.4.1", + "version": "11.5.0", "private": true, "repository": { "type": "git", @@ -13,20 +13,21 @@ "start:mmi": "yarn start --build-type mmi", "start:lavamoat": "yarn build:dev dev --apply-lavamoat=true", "dist": "yarn build dist", + "dist:mv3": "ENABLE_MV3=true yarn build dist", "build": "yarn lavamoat:build", "build:dev": "node development/build/index.js", - "start:test": "BLOCKAID_FILE_CDN=static.metafi-dev.codefi.network/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build:dev testDev", - "start:test:flask": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 BLOCKAID_FILE_CDN=static.metafi-dev.codefi.network/api/v1/confirmations/ppom yarn build:dev testDev --build-type flask --apply-lavamoat=false --snow=false", - "start:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build:dev testDev", + "start:test": "BLOCKAID_FILE_CDN=static.metafi-dev.codefi.network/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev", + "start:test:flask": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --build-type flask --apply-lavamoat=false --snow=false", + "start:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev", "benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/benchmark.js", "mv3:stats:chrome": "SELENIUM_BROWSER=chrome ENABLE_MV3=true ts-node test/e2e/mv3-perf-stats/index.js", "user-actions-benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/user-actions-benchmark.js", "benchmark:firefox": "SELENIUM_BROWSER=firefox ts-node test/e2e/benchmark.js", - "build:test": "BLOCKAID_FILE_CDN=static.metafi-dev.codefi.network/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build test", - "build:test:flask": "BLOCKAID_FILE_CDN=static.metafi-dev.codefi.network/api/v1/confirmations/ppom yarn build test --build-type flask", + "build:test": "BLOCKAID_FILE_CDN=static.metafi-dev.codefi.network/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", + "build:test:flask": "yarn build test --build-type flask", "build:test:mmi": "yarn build test --build-type mmi", - "build:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build test", - "build:test:dev:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build:dev testDev --apply-lavamoat=false", + "build:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", + "build:test:dev:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --apply-lavamoat=false", "test": "yarn lint && yarn test:unit && yarn test:unit:jest", "dapp": "node development/static-server.js node_modules/@metamask/test-dapp/dist --port 8080", "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'", @@ -39,13 +40,12 @@ "test:unit:mocha": "node ./test/run-unit-tests.js --mocha", "test:e2e:chrome": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:e2e:chrome:mmi": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mmi", - "test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps", - "test:e2e:chrome:mv3": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mv3", + "test:e2e:chrome:flask": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --build-type flask", + "test:e2e:chrome:mv3": "ENABLE_MV3=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:e2e:chrome:rpc": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --rpc", "test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js", - "test:e2e:firefox:snaps": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --snaps", + "test:e2e:firefox:flask": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --build-type flask", "test:e2e:single": "node test/e2e/run-e2e-test.js", - "test:e2e:report": "node ./test/e2e/e2e-process-report.js && jrm ./test/test-results/e2e.xml \"./test/test-results/e2e/*.xml\"", "test:coverage:mocha": "node ./test/run-unit-tests.js --mocha --coverage", "test:coverage:jest": "node ./test/run-unit-tests.js --jestGlobal --coverage", "test:coverage:jest:dev": "node ./test/run-unit-tests.js --jestDev --coverage", @@ -103,7 +103,7 @@ "add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts", "check-pr-has-required-labels": "ts-node ./.github/scripts/check-pr-has-required-labels.ts", "close-release-bug-report-issue": "ts-node ./.github/scripts/close-release-bug-report-issue.ts", - "check-issue-template-and-add-labels": "ts-node ./.github/scripts/check-issue-template-and-add-labels.ts", + "check-template-and-add-labels": "ts-node ./.github/scripts/check-template-and-add-labels.ts", "audit": "yarn npm audit --recursive --environment production --severity moderate" }, "resolutions": { @@ -205,7 +205,7 @@ "request@^2.88.2": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "lavamoat-core@npm:^14.4.1": "patch:lavamoat-core@npm%3A14.4.1#~/.yarn/patches/lavamoat-core-npm-14.4.1-c4e8bbb016.patch", - "@metamask/signature-controller@^6.0.0": "patch:@metamask/signature-controller@npm%3A6.0.0#~/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch", + "@metamask/signature-controller@^6.1.2": "patch:@metamask/signature-controller@npm%3A6.1.2#~/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch", "semver@7.3.7": "^7.5.4", "semver@7.3.8": "^7.5.4", "@metamask/eth-keyring-controller@npm:^13.0.1": "patch:@metamask/eth-keyring-controller@npm%3A13.0.1#~/.yarn/patches/@metamask-eth-keyring-controller-npm-13.0.1-06ff83faad.patch" @@ -238,11 +238,11 @@ "@metamask/address-book-controller": "^3.0.0", "@metamask/announcement-controller": "^4.0.0", "@metamask/approval-controller": "^3.4.0", - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A13.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-13.0.0-91c45127c5.patch", + "@metamask/assets-controllers": "^16.0.0", "@metamask/base-controller": "^3.2.0", "@metamask/browser-passworder": "^4.1.0", "@metamask/contract-metadata": "^2.3.1", - "@metamask/controller-utils": "^4.2.0", + "@metamask/controller-utils": "^5.0.0", "@metamask/design-tokens": "^1.12.0", "@metamask/desktop": "^0.3.0", "@metamask/eth-json-rpc-middleware": "^11.0.0", @@ -263,25 +263,25 @@ "@metamask/message-manager": "^7.3.0", "@metamask/metamask-eth-abis": "^3.0.0", "@metamask/name-controller": "^3.0.0", - "@metamask/network-controller": "^12.2.0", + "@metamask/network-controller": "^14.0.0", "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", - "@metamask/permission-controller": "^4.0.0", + "@metamask/permission-controller": "^5.0.0", "@metamask/phishing-controller": "^6.0.0", "@metamask/post-message-stream": "^6.2.0", "@metamask/ppom-validator": "^0.8.0", "@metamask/providers": "^13.1.0", "@metamask/rate-limit-controller": "^3.0.0", - "@metamask/rpc-methods": "^3.0.0", "@metamask/safe-event-emitter": "^2.0.0", "@metamask/scure-bip39": "^2.0.3", - "@metamask/selected-network-controller": "^1.0.0", - "@metamask/signature-controller": "^6.0.0", + "@metamask/selected-network-controller": "^2.0.0", + "@metamask/signature-controller": "^6.1.2", "@metamask/slip44": "^3.1.0", "@metamask/smart-transactions-controller": "^4.0.0", - "@metamask/snaps-controllers": "^3.0.0", - "@metamask/snaps-ui": "^3.0.0", - "@metamask/snaps-utils": "^3.0.0", + "@metamask/snaps-controllers": "^3.1.1", + "@metamask/snaps-rpc-methods": "^3.1.0", + "@metamask/snaps-ui": "^3.0.1", + "@metamask/snaps-utils": "^3.1.0", "@metamask/utils": "^5.0.0", "@ngraveio/bc-ur": "^1.1.6", "@popperjs/core": "^2.4.0", @@ -391,10 +391,11 @@ "@metamask/eslint-config-typescript": "^9.0.1", "@metamask/forwarder": "^1.1.0", "@metamask/phishing-warning": "^2.1.0", - "@metamask/test-dapp": "^7.1.0", + "@metamask/test-dapp": "^7.2.0", "@sentry/cli": "^2.19.4", "@storybook/addon-a11y": "^7.4.6", "@storybook/addon-actions": "^7.4.6", + "@storybook/addon-designs": "^7.0.5", "@storybook/addon-essentials": "^7.4.6", "@storybook/addon-knobs": "^7.0.2", "@storybook/addon-mdx-gfm": "^7.4.6", @@ -424,6 +425,7 @@ "@types/gulp-sourcemaps": "^0.0.35", "@types/jest": "^29.1.2", "@types/madge": "^5.0.0", + "@types/mocha": "^10.0.3", "@types/node": "^17.0.21", "@types/pify": "^5.0.1", "@types/pump": "^1.1.1", @@ -433,6 +435,7 @@ "@types/react-router-dom": "^5.3.3", "@types/remote-redux-devtools": "^0.5.5", "@types/sass": "^1.43.1", + "@types/selenium-webdriver": "^4.1.19", "@types/sinon": "^10.0.13", "@types/w3c-web-hid": "^1.0.3", "@types/watchify": "^3.11.1", @@ -449,7 +452,7 @@ "browserify": "^17.0.0", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "chromedriver": "^116.0.0", + "chromedriver": "^118.0.1", "concurrently": "^7.6.0", "copy-webpack-plugin": "^6.0.3", "cross-spawn": "^7.0.3", @@ -504,7 +507,6 @@ "jest-environment-jsdom": "^29.1.2", "js-yaml": "^4.1.0", "jsdom": "^16.7.0", - "junit-report-merger": "^4.0.0", "koa": "^2.7.0", "lavamoat": "^7.1.2", "lavamoat-browserify": "^15.7.4", @@ -512,7 +514,8 @@ "lockfile-lint": "^4.10.6", "loose-envify": "^1.4.0", "madge": "^6.1.0", - "mocha": "^9.2.2", + "mocha": "^10.2.0", + "mocha-junit-reporter": "^2.2.1", "mockttp": "^2.6.0", "nock": "^13.2.9", "node-fetch": "^2.6.1", @@ -606,7 +609,6 @@ "gulp>glob-watcher>chokidar>fsevents": false, "webpack>watchpack>watchpack-chokidar2>chokidar>fsevents": false, "@keystonehq/bc-ur-registry-eth>hdkey>secp256k1": false, - "@metamask/rpc-methods>@metamask/key-tree>secp256k1": false, "eth-lattice-keyring>gridplus-sdk>secp256k1": false, "eth-lattice-keyring>secp256k1": false, "@storybook/react>@pmmmwh/react-refresh-webpack-plugin>core-js-pure": false, diff --git a/shared/constants/app.ts b/shared/constants/app.ts index 60dd772ae1f7..857cc2a1aeaa 100644 --- a/shared/constants/app.ts +++ b/shared/constants/app.ts @@ -1,5 +1,5 @@ ///: BEGIN:ONLY_INCLUDE_IN(snaps) -import { DialogType } from '@metamask/rpc-methods'; +import { DialogType } from '@metamask/snaps-rpc-methods'; ///: END:ONLY_INCLUDE_IN import { RestrictedMethods } from './permissions'; diff --git a/shared/constants/bridge.ts b/shared/constants/bridge.ts index 4bab94d5a4c6..e02c992bbbba 100644 --- a/shared/constants/bridge.ts +++ b/shared/constants/bridge.ts @@ -8,63 +8,6 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [ CHAIN_IDS.AVALANCHE, CHAIN_IDS.OPTIMISM, CHAIN_IDS.ARBITRUM, + CHAIN_IDS.LINEA_MAINNET, + CHAIN_IDS.BASE, ]; - -export const ALLOWED_BRIDGE_TOKEN_ADDRESSES = { - [CHAIN_IDS.MAINNET]: [ - '0xdac17f958d2ee523a2206206994597c13d831ec7', - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - '0x6b175474e89094c44da98b954eedeac495271d0f', - '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', - '0x8965349fb649a33a30cbfda057d8ec2c48abe2a2', - '0xae78736cd615f374d3085123a210448e74fc6393', - '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', - ], - [CHAIN_IDS.BSC]: [ - '0x55d398326f99059ff775485246999027b3197955', - '0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d', - '0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3', - '0x2170ed0880ac9a755fd29b2688956bd959f933f8', - '0xcc42724c6683b7e57334c4e856f4c9965ed682bd', - '0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c', - ], - [CHAIN_IDS.POLYGON]: [ - '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', - '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', - '0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6', - ], - [CHAIN_IDS.ZKSYNC_ERA]: [ - '0x5aea5775959fbc2557cc8789bc1bf90a239d9a91', - '0xbbeb516fb02a01611cbbe0453fe3c580d7281011', - '0x493257fd37edb34451f62edf8d2a0c418852ba4c', - '0x3355df6d4c9c3035724fd0e3914de96a5a83aaf4', - '0x4bef76b6b7f2823c6c1f4fcfeacd85c24548ad7e', - '0x32fd44bb869620c0ef993754c8a00be67c464806', - ], - [CHAIN_IDS.AVALANCHE]: [ - '0xc7198437980c041c805a1edcba50c1ce5db95118', - '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7', - '0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664', - '0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e', - '0xd586e7f844cea2f87f50152665bcbc2c279d8d70', - '0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab', - '0x50b7545627a5162f82a992c33b87adc75187b218', - ], - [CHAIN_IDS.OPTIMISM]: [ - '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', - '0x7f5c764cbc14f9669b88837ca1490cca17c31607', - '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1', - '0x9bcef72be871e61ed4fbbc7630889bee758eb81d', - '0x68f180fcce6836688e9084f035309e29bf0a2095', - ], - [CHAIN_IDS.ARBITRUM]: [ - '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', - '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', - '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1', - '0xec70dcb4a1efa46b8f2d97c310c9c4790ba5ffa8', - '0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f', - '0xaf88d065e77c8cc2239327c5edb3a432268e5831', - ], -}; diff --git a/shared/constants/copy.ts b/shared/constants/copy.ts new file mode 100644 index 000000000000..3f997f4f9998 --- /dev/null +++ b/shared/constants/copy.ts @@ -0,0 +1,3 @@ +export const COPY_OPTIONS = { + format: 'text/plain', +}; diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index e9b26f6c6712..dc58a3b7acf7 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -149,6 +149,10 @@ export type MetaMetricsEventOptions = { }; export type MetaMetricsEventFragment = { + /** + * The action ID of transaction metadata object. + */ + actionId?: string; /** * The event name to fire when the fragment is closed in an affirmative action. */ @@ -558,7 +562,7 @@ export enum MetaMetricsEventName { ProviderMethodCalled = 'Provider Method Called', PublicAddressCopied = 'Public Address Copied', QuoteError = 'Quote Error', - ServiceWorkerRestarted = 'Service Worker Restarted', + SettingsUpdated = 'Settings Updated', SignatureApproved = 'Signature Approved', SignatureFailed = 'Signature Failed', SignatureRejected = 'Signature Rejected', @@ -676,7 +680,6 @@ export enum MetaMetricsEventCategory { Petnames = 'Petnames', Phishing = 'Phishing', Retention = 'Retention', - ServiceWorkers = 'service_workers', Settings = 'Settings', Snaps = 'Snaps', Swaps = 'Swaps', diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 5c206c1bc789..56564fdc32a1 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -108,24 +108,6 @@ export const NETWORK_NAMES = { HOMESTEAD: 'homestead', }; -/** - * The Network ID for our builtin networks. This is the decimal equivalent of - * the chain id for the network, but is expresssed as a string. Many moons ago - * the decision was made on the extension team to expressly use chainId with - * hex encoding over network id. Consider that when accessing this object. Note - * for cross product purposes: alignment with mobile on this matter has not - * been fully achieved, thus it is possible for some dependencies to still - * ask for or require network id. - */ -export const NETWORK_IDS = { - MAINNET: '1', - GOERLI: '5', - LOCALHOST: '1337', - SEPOLIA: '11155111', - LINEA_GOERLI: '59140', - LINEA_MAINNET: '59144', -} as const; - /** * An object containing all of the chain ids for networks both built in and * those that we have added custom code to support our feature set. @@ -296,35 +278,29 @@ export const TEST_NETWORK_TICKER_MAP: { */ export const BUILT_IN_NETWORKS = { [NETWORK_TYPES.GOERLI]: { - networkId: NETWORK_IDS.GOERLI, chainId: CHAIN_IDS.GOERLI, ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], blockExplorerUrl: `https://${NETWORK_TYPES.GOERLI}.etherscan.io`, }, [NETWORK_TYPES.SEPOLIA]: { - networkId: NETWORK_IDS.SEPOLIA, chainId: CHAIN_IDS.SEPOLIA, ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], blockExplorerUrl: `https://${NETWORK_TYPES.SEPOLIA}.etherscan.io`, }, [NETWORK_TYPES.LINEA_GOERLI]: { - networkId: NETWORK_IDS.LINEA_GOERLI, chainId: CHAIN_IDS.LINEA_GOERLI, ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_GOERLI], blockExplorerUrl: 'https://goerli.lineascan.build', }, [NETWORK_TYPES.MAINNET]: { - networkId: NETWORK_IDS.MAINNET, chainId: CHAIN_IDS.MAINNET, blockExplorerUrl: `https://etherscan.io`, }, [NETWORK_TYPES.LINEA_MAINNET]: { - networkId: NETWORK_IDS.LINEA_MAINNET, chainId: CHAIN_IDS.LINEA_MAINNET, blockExplorerUrl: 'https://lineascan.build', }, [NETWORK_TYPES.LOCALHOST]: { - networkId: NETWORK_IDS.LOCALHOST, chainId: CHAIN_IDS.LOCALHOST, }, } as const; @@ -352,13 +328,6 @@ export const NETWORK_TO_NAME_MAP = { [NETWORK_TYPES.LINEA_MAINNET]: LINEA_MAINNET_DISPLAY_NAME, [NETWORK_TYPES.LOCALHOST]: LOCALHOST_DISPLAY_NAME, - [NETWORK_IDS.GOERLI]: GOERLI_DISPLAY_NAME, - [NETWORK_IDS.SEPOLIA]: SEPOLIA_DISPLAY_NAME, - [NETWORK_IDS.LINEA_GOERLI]: LINEA_GOERLI_DISPLAY_NAME, - [NETWORK_IDS.MAINNET]: MAINNET_DISPLAY_NAME, - [NETWORK_IDS.LINEA_MAINNET]: LINEA_MAINNET_DISPLAY_NAME, - [NETWORK_IDS.LOCALHOST]: LOCALHOST_DISPLAY_NAME, - [CHAIN_IDS.GOERLI]: GOERLI_DISPLAY_NAME, [CHAIN_IDS.SEPOLIA]: SEPOLIA_DISPLAY_NAME, [CHAIN_IDS.LINEA_GOERLI]: LINEA_GOERLI_DISPLAY_NAME, @@ -402,21 +371,12 @@ export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { [CHAIN_IDS.GNOSIS]: GNOSIS_TOKEN_IMAGE_URL, } as const; -export const NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP = { - [NETWORK_IDS.GOERLI]: NETWORK_TYPES.GOERLI, - [NETWORK_IDS.SEPOLIA]: NETWORK_TYPES.SEPOLIA, - [NETWORK_IDS.LINEA_GOERLI]: NETWORK_TYPES.LINEA_GOERLI, - [NETWORK_IDS.MAINNET]: NETWORK_NAMES.HOMESTEAD, - [NETWORK_IDS.LINEA_MAINNET]: NETWORK_TYPES.LINEA_MAINNET, -} as const; - -export const CHAIN_ID_TO_NETWORK_ID_MAP = { - [CHAIN_IDS.MAINNET]: NETWORK_IDS.MAINNET, - [CHAIN_IDS.GOERLI]: NETWORK_IDS.GOERLI, - [CHAIN_IDS.SEPOLIA]: NETWORK_IDS.SEPOLIA, - [CHAIN_IDS.LINEA_GOERLI]: NETWORK_IDS.LINEA_GOERLI, - [CHAIN_IDS.LINEA_MAINNET]: NETWORK_IDS.LINEA_MAINNET, - [CHAIN_IDS.LOCALHOST]: NETWORK_IDS.LOCALHOST, +export const CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP = { + [CHAIN_IDS.GOERLI]: NETWORK_TYPES.GOERLI, + [CHAIN_IDS.SEPOLIA]: NETWORK_TYPES.SEPOLIA, + [CHAIN_IDS.LINEA_GOERLI]: NETWORK_TYPES.LINEA_GOERLI, + [CHAIN_IDS.MAINNET]: NETWORK_NAMES.HOMESTEAD, + [CHAIN_IDS.LINEA_MAINNET]: NETWORK_TYPES.LINEA_MAINNET, } as const; export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = { @@ -445,99 +405,80 @@ export const ETHERSCAN_SUPPORTED_NETWORKS = { subdomain: `${defaultEtherscanSubdomainPrefix}-${ CHAIN_ID_TO_TYPE_MAP[CHAIN_IDS.GOERLI] }`, - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.GOERLI], }, [CHAIN_IDS.MAINNET]: { domain: defaultEtherscanDomain, subdomain: defaultEtherscanSubdomainPrefix, - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.MAINNET], }, [CHAIN_IDS.SEPOLIA]: { domain: defaultEtherscanDomain, subdomain: `${defaultEtherscanSubdomainPrefix}-${ CHAIN_ID_TO_TYPE_MAP[CHAIN_IDS.SEPOLIA] }`, - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.SEPOLIA], }, [CHAIN_IDS.LINEA_GOERLI]: { domain: 'lineascan.build', subdomain: 'goerli', - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.LINEA_GOERLI], }, [CHAIN_IDS.LINEA_MAINNET]: { domain: 'lineascan.build', subdomain: defaultEtherscanSubdomainPrefix, - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.LINEA_MAINNET], }, [CHAIN_IDS.BSC]: { domain: 'bscscan.com', subdomain: defaultEtherscanSubdomainPrefix, - networkId: parseInt(CHAIN_IDS.BSC, 16).toString(), }, [CHAIN_IDS.BSC_TESTNET]: { domain: 'bscscan.com', subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`, - networkId: parseInt(CHAIN_IDS.BSC_TESTNET, 16).toString(), }, [CHAIN_IDS.OPTIMISM]: { domain: defaultEtherscanDomain, subdomain: `${defaultEtherscanSubdomainPrefix}-optimistic`, - networkId: parseInt(CHAIN_IDS.OPTIMISM, 16).toString(), }, [CHAIN_IDS.OPTIMISM_TESTNET]: { domain: defaultEtherscanDomain, subdomain: `${defaultEtherscanSubdomainPrefix}-goerli-optimistic`, - networkId: parseInt(CHAIN_IDS.OPTIMISM_TESTNET, 16).toString(), }, [CHAIN_IDS.POLYGON]: { domain: 'polygonscan.com', subdomain: defaultEtherscanSubdomainPrefix, - networkId: parseInt(CHAIN_IDS.POLYGON, 16).toString(), }, [CHAIN_IDS.POLYGON_TESTNET]: { domain: 'polygonscan.com', subdomain: `${defaultEtherscanSubdomainPrefix}-mumbai`, - networkId: parseInt(CHAIN_IDS.POLYGON_TESTNET, 16).toString(), }, [CHAIN_IDS.AVALANCHE]: { domain: 'snowtrace.io', subdomain: defaultEtherscanSubdomainPrefix, - networkId: parseInt(CHAIN_IDS.AVALANCHE, 16).toString(), }, [CHAIN_IDS.AVALANCHE_TESTNET]: { domain: 'snowtrace.io', subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`, - networkId: parseInt(CHAIN_IDS.AVALANCHE_TESTNET, 16).toString(), }, [CHAIN_IDS.FANTOM]: { domain: 'ftmscan.com', subdomain: defaultEtherscanSubdomainPrefix, - networkId: parseInt(CHAIN_IDS.FANTOM, 16).toString(), }, [CHAIN_IDS.FANTOM_TESTNET]: { domain: 'ftmscan.com', subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`, - networkId: parseInt(CHAIN_IDS.FANTOM_TESTNET, 16).toString(), }, [CHAIN_IDS.MOONBEAM]: { domain: 'moonscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-moonbeam`, - networkId: parseInt(CHAIN_IDS.MOONBEAM, 16).toString(), }, [CHAIN_IDS.MOONBEAM_TESTNET]: { domain: 'moonscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-moonbase`, - networkId: parseInt(CHAIN_IDS.MOONBEAM_TESTNET, 16).toString(), }, [CHAIN_IDS.MOONRIVER]: { domain: 'moonscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-moonriver`, - networkId: parseInt(CHAIN_IDS.MOONRIVER, 16).toString(), }, [CHAIN_IDS.GNOSIS]: { domain: 'gnosisscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-gnosis`, - networkId: parseInt(CHAIN_IDS.GNOSIS, 16).toString(), }, }; diff --git a/shared/constants/permissions.test.js b/shared/constants/permissions.test.js index 7ea0bb47eb5d..27f1da5d4f6e 100644 --- a/shared/constants/permissions.test.js +++ b/shared/constants/permissions.test.js @@ -1,5 +1,5 @@ import { endowmentPermissionBuilders } from '@metamask/snaps-controllers'; -import { restrictedMethodPermissionBuilders } from '@metamask/rpc-methods'; +import { restrictedMethodPermissionBuilders } from '@metamask/snaps-rpc-methods'; import { EndowmentPermissions, ExcludedSnapEndowments, diff --git a/shared/constants/security-provider.ts b/shared/constants/security-provider.ts index 3bb16f2f0b7e..d2d7373d24b2 100644 --- a/shared/constants/security-provider.ts +++ b/shared/constants/security-provider.ts @@ -43,12 +43,10 @@ export enum BlockaidReason { transferFarming = 'transfer_farming', /** Direct theft of assets using transferFrom */ transferFromFarming = 'transfer_from_farming', - /** Malicious trade that results in the victim being drained */ - unfairTrade = 'unfair_trade', other = 'other', - // Locally defined + // MetaMask defined reasons failed = 'Failed', notApplicable = 'NotApplicable', } @@ -57,7 +55,8 @@ export enum BlockaidResultType { Malicious = 'Malicious', Warning = 'Warning', Benign = 'Benign', - // Locally defined + + // MetaMask defined result types Failed = 'Failed', NotApplicable = 'NotApplicable', } diff --git a/shared/constants/snaps.ts b/shared/constants/snaps.ts index 7c15f6d71d3b..b6843afb9cf7 100644 --- a/shared/constants/snaps.ts +++ b/shared/constants/snaps.ts @@ -156,7 +156,7 @@ export const SNAPS_DERIVATION_PATHS: SnapsDerivationPath[] = [ name: 'Secret Network', }, { - path: ['m', `44'`, `397'`], + path: ['m', `44'`, `397'`, `0'`], curve: 'ed25519', name: 'NEAR Protocol', }, diff --git a/shared/constants/test-flags.js b/shared/constants/test-flags.js deleted file mode 100644 index a5018d6be90d..000000000000 --- a/shared/constants/test-flags.js +++ /dev/null @@ -1 +0,0 @@ -export const ACTION_QUEUE_METRICS_E2E_TEST = 'action_queue_metrics_e2e_test'; diff --git a/shared/constants/transaction.ts b/shared/constants/transaction.ts index bd80d34bb3cc..24ee75d5d194 100644 --- a/shared/constants/transaction.ts +++ b/shared/constants/transaction.ts @@ -1,4 +1,5 @@ import { AccessList } from '@ethereumjs/tx'; +import { Hex } from '@metamask/utils'; export enum TransactionType { /** @@ -284,12 +285,16 @@ export interface TxParams { accessList?: AccessList; maxFeePerGas?: string; maxPriorityFeePerGas?: string; + estimateSuggested?: string; + estimateUsed?: string; } export interface TxReceipt { blockHash?: string; blockNumber?: string; transactionIndex?: string; + gasUsed?: string; + status?: string; } export interface TxError { @@ -320,6 +325,8 @@ interface DappSuggestedGasFees { * An object representing a transaction, in whatever state it is in. */ export interface TransactionMeta { + /** Unique ID to prevent duplicate requests.*/ + actionId?: string; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) custodyStatus: string; custodyId?: string; @@ -329,7 +336,7 @@ export interface TransactionMeta { * on incoming transactions! */ blockNumber?: string; - chainId: string; + chainId: Hex; /** An internally unique tx identifier. */ id: string; /** Time the transaction was first suggested, in unix epoch time (ms). */ @@ -342,6 +349,7 @@ export interface TransactionMeta { dappProposedTokenAmount: string; /** The original gas fees suggested by the dapp that proposed this transaction */ dappSuggestedGasFees?: DappSuggestedGasFees; + defaultGasEstimates?: any; /** The balance of the token that is being sent */ currentTokenBalance: string; /** The original dapp proposed token approval amount before edit by user */ @@ -362,8 +370,12 @@ export interface TransactionMeta { originalType: TransactionType; /** The current status of the transaction. */ status: TransactionStatus; - /** The transaction's network ID, used for EIP-155 compliance. */ - metamaskNetworkId: string; + /** + * The transaction's network ID, used for EIP-155 compliance. + * + * @deprecated Use `chainId` instead. + */ + readonly metamaskNetworkId?: string; /** TODO: Find out what this is and document it */ loadingDefaults: boolean; /** The transaction params as passed to the network provider. */ @@ -385,6 +397,7 @@ export interface TransactionMeta { * network. */ rawTx: string; + replacedById?: string; /** * A hex string of the transaction hash, used to identify the transaction * on the network. @@ -399,11 +412,13 @@ export interface TransactionMeta { */ submittedTime?: number; /** The error encountered during the transaction */ - txErr?: TxError; + error?: TxError; /** * Whether the transaction is verified on the blockchain. */ verifiedOnBlockchain?: boolean; + securityProviderResponse?: Record; + securityAlertResponse?: any; } /** diff --git a/shared/lib/ui-utils.js b/shared/lib/ui-utils.js index 6c6183c78bc6..4b1c712200a1 100644 --- a/shared/lib/ui-utils.js +++ b/shared/lib/ui-utils.js @@ -13,8 +13,4 @@ export const AUTO_DETECT_TOKEN_LEARN_MORE_LINK = 'https://consensys.net/privacy-policy/'; export const CONSENSYS_TERMS_OF_USE = 'https://consensys.io/terms-of-use'; -///: BEGIN:ONLY_INCLUDE_IN(blockaid) -export const BLOCKAID_TERMS_OF_USE = - 'https://blockaid.io/legal/metamask-ppom-privacy-policy/'; -///: END:ONLY_INCLUDE_IN export const OPENSEA_TERMS_OF_USE = 'https://opensea.io/securityproviderterms'; diff --git a/shared/modules/network.utils.ts b/shared/modules/network.utils.ts index fa99d5209c0a..d7e14a7c0d3b 100644 --- a/shared/modules/network.utils.ts +++ b/shared/modules/network.utils.ts @@ -1,3 +1,5 @@ +import { isStrictHexString } from '@metamask/utils'; +import { convertHexToDecimal } from '@metamask/controller-utils'; import { CHAIN_IDS, MAX_SAFE_CHAIN_ID } from '../constants/network'; /** @@ -62,3 +64,23 @@ function isSafeInteger(value: unknown): value is number { export function shouldShowLineaMainnet(): boolean { return new Date().getTime() > Date.UTC(2023, 6, 11, 18); } + +/** + * TODO: Delete when ready to remove `networkVersion` from provider object + * Convert the given value into a valid network ID. The ID is accepted + * as either a number, a decimal string, or a 0x-prefixed hex string. + * + * @param value - The network ID to convert, in an unknown format. + * @returns A valid network ID (as a decimal string) + * @throws If the given value cannot be safely parsed. + */ +export function convertNetworkId(value: unknown): string { + if (typeof value === 'number' && !Number.isNaN(value)) { + return `${value}`; + } else if (isStrictHexString(value)) { + return `${convertHexToDecimal(value)}`; + } else if (typeof value === 'string' && /^\d+$/u.test(value)) { + return value; + } + throw new Error(`Cannot parse as a valid network ID: '${value}'`); +} diff --git a/shared/modules/transaction.utils.js b/shared/modules/transaction.utils.js index 44a681519e1e..558eaa99ee7d 100644 --- a/shared/modules/transaction.utils.js +++ b/shared/modules/transaction.utils.js @@ -39,13 +39,6 @@ const erc20Interface = new Interface(abiERC20); const erc721Interface = new Interface(abiERC721); const erc1155Interface = new Interface(abiERC1155); -export function transactionMatchesNetwork(transaction, chainId, networkId) { - if (typeof transaction.chainId !== 'undefined') { - return transaction.chainId === chainId; - } - return transaction.metamaskNetworkId === networkId; -} - /** * Determines if the maxFeePerGas and maxPriorityFeePerGas fields are supplied * and valid inputs. This will return false for non hex string inputs. diff --git a/test/data/mock-pending-transaction-data.json b/test/data/mock-pending-transaction-data.json index 26ce780eb027..36f1d74767fe 100644 --- a/test/data/mock-pending-transaction-data.json +++ b/test/data/mock-pending-transaction-data.json @@ -5,7 +5,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "approved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": false, "dappSuggestedGasFees": null, @@ -25,7 +24,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "unapproved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": true, "dappSuggestedGasFees": null, @@ -81,7 +79,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "approved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": false, "dappSuggestedGasFees": null, @@ -101,7 +98,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "unapproved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": true, "dappSuggestedGasFees": null, @@ -158,7 +154,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "approved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": false, "dappSuggestedGasFees": null, @@ -178,7 +173,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "unapproved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": true, "dappSuggestedGasFees": null, diff --git a/test/data/mock-send-state.json b/test/data/mock-send-state.json index 14d4967d70f9..94283e5c29e5 100644 --- a/test/data/mock-send-state.json +++ b/test/data/mock-send-state.json @@ -32,7 +32,6 @@ "id": 3111025347726181, "time": 1620723786838, "status": "unapproved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": false, "txParams": { @@ -302,7 +301,7 @@ "id": 8393540981007587, "time": 1536268017676, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", @@ -316,7 +315,7 @@ "id": 8393540981007587, "time": 1536268017676, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", @@ -350,7 +349,7 @@ "id": 3387511061307736, "time": 1528133130531, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -365,7 +364,7 @@ "id": 3387511061307736, "time": 1528133130531, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -482,7 +481,7 @@ "id": 3387511061307737, "time": 1528133149983, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -497,7 +496,7 @@ "id": 3387511061307737, "time": 1528133149983, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -614,7 +613,7 @@ "id": 3387511061307738, "time": 1528133180635, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -629,7 +628,7 @@ "id": 3387511061307738, "time": 1528133180635, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -746,7 +745,7 @@ "id": 3387511061307739, "time": 1528133223918, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -762,7 +761,7 @@ "id": 3387511061307739, "time": 1528133223918, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -883,7 +882,7 @@ "id": 3387511061307740, "time": 1528133291381, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -899,7 +898,7 @@ "id": 3387511061307740, "time": 1528133291381, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1017,7 +1016,7 @@ "id": 3387511061307741, "time": 1528133318440, "status": "rejected", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1031,7 +1030,7 @@ "id": 3387511061307741, "time": 1528133318440, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 8431ea490caa..803a2447df90 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -117,7 +117,6 @@ "unconnectedAccount": true }, "featureFlags": {}, - "networkId": "5", "providerConfig": { "type": "rpc", "nickname": "goerli", @@ -613,7 +612,6 @@ "id": 8393540981007587, "time": 1536268017676, "status": "unapproved", - "metamaskNetworkId": "4", "loadingDefaults": false, "txParams": { "data": "0xa9059cbb000000000000000000000000b19ac54efa18cc3a14a5b821bfec73d284bf0c5e0000000000000000000000000000000000000000000000003782dace9d900000", @@ -628,7 +626,7 @@ "id": 8393540981007587, "time": 1536268017676, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", @@ -662,7 +660,7 @@ "id": 3387511061307736, "time": 1528133130531, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -677,7 +675,7 @@ "id": 3387511061307736, "time": 1528133130531, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -794,7 +792,7 @@ "id": 3387511061307737, "time": 1528133149983, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -809,7 +807,7 @@ "id": 3387511061307737, "time": 1528133149983, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -926,7 +924,7 @@ "id": 3387511061307738, "time": 1528133180635, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -941,7 +939,7 @@ "id": 3387511061307738, "time": 1528133180635, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1058,7 +1056,7 @@ "id": 3387511061307739, "time": 1528133223918, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1074,7 +1072,7 @@ "id": 3387511061307739, "time": 1528133223918, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1195,7 +1193,7 @@ "id": 3387511061307740, "time": 1528133291381, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1211,7 +1209,7 @@ "id": 3387511061307740, "time": 1528133291381, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1329,7 +1327,7 @@ "id": 3387511061307741, "time": 1528133318440, "status": "rejected", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1343,7 +1341,7 @@ "id": 3387511061307741, "time": 1528133318440, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", diff --git a/test/data/mock-tx-history.json b/test/data/mock-tx-history.json index 1291e9fb5b69..9a5b82e0ed66 100644 --- a/test/data/mock-tx-history.json +++ b/test/data/mock-tx-history.json @@ -5,21 +5,20 @@ "id": 6616756286038869, "time": 1502438908445, "status": "confirmed", - "metamaskNetworkId": "5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "value": "0x0", "gasPrice": "0x3b9aca00", "gas": "0x7b0d", - "chainId": 5 + "chainId": "0x5" }, "history": [ { "id": 6616756286038869, "time": 1502438908445, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -32,7 +31,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -45,7 +44,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -58,7 +57,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -71,7 +70,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -85,7 +84,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -99,7 +98,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -113,7 +112,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -127,7 +126,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -142,7 +141,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -157,7 +156,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -173,7 +172,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -189,7 +188,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -205,7 +204,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -221,7 +220,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -238,7 +237,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -260,7 +259,7 @@ "id": 6616756286038870, "time": 1502573153664, "status": "rejected", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -273,7 +272,7 @@ "id": 6616756286038870, "time": 1502573153664, "status": "rejected", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -286,7 +285,7 @@ "id": 6616756286038870, "time": 1502573153664, "status": "rejected", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -301,7 +300,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -316,7 +315,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -329,7 +328,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -342,7 +341,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -355,7 +354,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -368,7 +367,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -382,7 +381,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -396,7 +395,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -411,7 +410,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -426,7 +425,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -442,7 +441,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -458,7 +457,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -475,7 +474,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -492,7 +491,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -509,7 +508,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -526,7 +525,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -544,7 +543,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -567,7 +566,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -582,7 +581,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -595,7 +594,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -608,7 +607,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -621,7 +620,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -634,7 +633,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -648,7 +647,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -662,7 +661,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -677,7 +676,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -692,7 +691,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -708,7 +707,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -724,7 +723,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -741,7 +740,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -758,7 +757,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -775,7 +774,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -792,7 +791,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -810,7 +809,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -833,7 +832,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -848,7 +847,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -861,7 +860,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -874,7 +873,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -887,7 +886,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -900,7 +899,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -914,7 +913,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -928,7 +927,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -943,7 +942,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -958,7 +957,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -974,7 +973,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -990,7 +989,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1007,7 +1006,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1024,7 +1023,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1041,7 +1040,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1058,7 +1057,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1076,7 +1075,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1099,7 +1098,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1114,7 +1113,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1127,7 +1126,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1140,7 +1139,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1153,7 +1152,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1166,7 +1165,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1180,7 +1179,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1194,7 +1193,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1209,7 +1208,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1224,7 +1223,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1240,7 +1239,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1256,7 +1255,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1273,7 +1272,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1290,7 +1289,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1307,7 +1306,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1324,7 +1323,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1342,7 +1341,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1365,7 +1364,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1380,7 +1379,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1393,7 +1392,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1406,7 +1405,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1419,7 +1418,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1432,7 +1431,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1446,7 +1445,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1460,7 +1459,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1475,7 +1474,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1490,7 +1489,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1506,7 +1505,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1522,7 +1521,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1539,7 +1538,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1556,7 +1555,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1573,7 +1572,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1590,7 +1589,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1608,7 +1607,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", diff --git a/test/data/transaction-data.json b/test/data/transaction-data.json index e9189cf15f6b..a9155363ff19 100644 --- a/test/data/transaction-data.json +++ b/test/data/transaction-data.json @@ -5,7 +5,7 @@ "id": 4243712234858512, "time": 1589314601567, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -43,7 +43,7 @@ "id": 4243712234858512, "time": 1589314601567, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -81,7 +81,7 @@ "id": 4243712234858512, "time": 1589314601567, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -123,7 +123,7 @@ "id": 4243712234858507, "time": 1589314355872, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -161,7 +161,7 @@ "id": 4243712234858507, "time": 1589314355872, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -199,7 +199,7 @@ "id": 4243712234858507, "time": 1589314355872, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -241,7 +241,7 @@ "id": 4243712234858506, "time": 1589314345433, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -280,7 +280,7 @@ "id": 4243712234858506, "time": 1589314345433, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -319,7 +319,7 @@ "id": 4243712234858506, "time": 1589314345433, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -360,7 +360,7 @@ "initialTransaction": { "blockNumber": "6477257", "id": 4243712234858505, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1589314295000, "txParams": { @@ -378,7 +378,7 @@ { "blockNumber": "6477257", "id": 4243712234858505, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1589314295000, "txParams": { @@ -396,7 +396,7 @@ "primaryTransaction": { "blockNumber": "6477257", "id": 4243712234858505, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1589314295000, "txParams": { @@ -417,7 +417,7 @@ "initialTransaction": { "blockNumber": "6454493", "id": 4243712234858475, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1588972833000, "txParams": { @@ -435,7 +435,7 @@ { "blockNumber": "6454493", "id": 4243712234858475, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1588972833000, "txParams": { @@ -453,7 +453,7 @@ "primaryTransaction": { "blockNumber": "6454493", "id": 4243712234858475, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1588972833000, "txParams": { @@ -474,7 +474,7 @@ "initialTransaction": { "blockNumber": "6195526", "id": 4243712234858466, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585087013000, "txParams": { @@ -492,7 +492,7 @@ { "blockNumber": "6195526", "id": 4243712234858466, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585087013000, "txParams": { @@ -510,7 +510,7 @@ "primaryTransaction": { "blockNumber": "6195526", "id": 4243712234858466, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585087013000, "txParams": { @@ -531,7 +531,7 @@ "initialTransaction": { "blockNumber": "6195527", "id": 4243712234858467, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -551,7 +551,7 @@ { "blockNumber": "6195527", "id": 4243712234858467, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -572,7 +572,7 @@ "primaryTransaction": { "blockNumber": "6195527", "id": 4243712234858467, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -596,7 +596,7 @@ "initialTransaction": { "blockNumber": "6195527", "id": 4243712234858468, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -615,7 +615,7 @@ { "blockNumber": "6195527", "id": 4243712234858468, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -633,7 +633,7 @@ "primaryTransaction": { "blockNumber": "6195527", "id": 4243712234858468, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -657,7 +657,6 @@ "id": 1441203963845330, "time": 1652206763566, "status": "confirmed", - "metamaskNetworkId": "5", "originalGasEstimate": "0x118e0", "userEditedGasLimit": false, "chainId": "0x5", @@ -750,7 +749,6 @@ "id": 1441203963845330, "time": 1652206763566, "status": "confirmed", - "metamaskNetworkId": "5", "originalGasEstimate": "0x118e0", "userEditedGasLimit": false, "chainId": "0x5", @@ -842,7 +840,6 @@ "id": 1441203963845330, "time": 1652206763566, "status": "confirmed", - "metamaskNetworkId": "5", "originalGasEstimate": "0x118e0", "userEditedGasLimit": false, "chainId": "0x5", @@ -937,7 +934,7 @@ "initialTransaction": { "blockNumber": "6195527", "id": 8401691827212777, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -958,7 +955,7 @@ { "blockNumber": "6195527", "id": 8401691827212777, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -978,7 +975,7 @@ "primaryTransaction": { "blockNumber": "6195527", "id": 8401691827212777, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { diff --git a/test/e2e/accounts/common.ts b/test/e2e/accounts/common.ts new file mode 100644 index 000000000000..c3f5e21f7234 --- /dev/null +++ b/test/e2e/accounts/common.ts @@ -0,0 +1,348 @@ +import { privateToAddress } from 'ethereumjs-util'; +import FixtureBuilder from '../fixture-builder'; +import { + PRIVATE_KEY, + PRIVATE_KEY_TWO, + WINDOW_TITLES, + clickSignOnSignatureConfirmation, + convertETHToHexGwei, + switchToOrOpenDapp, + unlockWallet, + validateContractDetails, +} from '../helpers'; +import { Driver } from '../webdriver/driver'; + +export const TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL = + 'https://metamask.github.io/snap-simple-keyring/1.0.1/'; + +export const ganacheOptions = { + accounts: [ + { + secretKey: PRIVATE_KEY, + balance: convertETHToHexGwei(25), + }, + { + secretKey: PRIVATE_KEY_TWO, + balance: convertETHToHexGwei(25), + }, + ], +}; + +/** + * These are fixtures specific to Account Snap E2E tests: + * -- connected to Test Dapp + * -- eth_sign enabled + * -- two private keys with 25 ETH each + * + * @param title + */ +export const accountSnapFixtures = (title: string | undefined) => { + return { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp(false) + .withPreferencesController({ + disabledRpcMethodPreferences: { + eth_sign: true, + }, + }) + .build(), + ganacheOptions, + failOnConsoleError: false, + title, + }; +}; + +// convert PRIVATE_KEY to public key +export const PUBLIC_KEY = privateToAddress( + Buffer.from(PRIVATE_KEY.slice(2), 'hex'), +).toString('hex'); + +export async function installSnapSimpleKeyring( + driver: Driver, + isAsyncFlow: boolean, +) { + await driver.navigate(); + + await unlockWallet(driver); + + // navigate to test Snaps page and connect + await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL); + await driver.clickElement('#connectButton'); + + await driver.delay(500); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + + await driver.delay(500); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); + + await driver.waitForSelector({ text: 'Install' }); + + await driver.clickElement({ + text: 'Install', + tag: 'button', + }); + + await driver.waitForSelector({ text: 'OK' }); + + await driver.clickElement({ + text: 'OK', + tag: 'button', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + await driver.waitForSelector({ + text: 'Connected', + tag: 'span', + }); + + if (isAsyncFlow) { + await toggleAsyncFlow(driver); + } +} + +async function toggleAsyncFlow(driver: Driver) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + // click the parent of #use-sync-flow-toggle (trying to click the element itself gives "ElementNotInteractableError: could not be scrolled into view") + await driver.clickElement({ + xpath: '//input[@id="use-sync-flow-toggle"]/..', + }); +} + +export async function importKeyAndSwitch(driver: Driver) { + await driver.clickElement({ + text: 'Import account', + tag: 'div', + }); + + await driver.fill('#import-account-private-key', PRIVATE_KEY_TWO); + + await driver.clickElement({ + text: 'Import Account', + tag: 'button', + }); + + // Click "Create" on the Snap's confirmation popup + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + await driver.clickElement({ + css: '[data-testid="confirmation-submit-button"]', + text: 'Create', + }); + await driver.clickElement({ + css: '[data-testid="confirmation-submit-button"]', + text: 'Ok', + }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + await switchToAccount2(driver); +} + +export async function makeNewAccountAndSwitch(driver: Driver) { + await driver.clickElement({ + text: 'Create account', + tag: 'div', + }); + + await driver.clickElement({ + text: 'Create Account', + tag: 'button', + }); + + // Click "Create" on the Snap's confirmation popup + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + await driver.clickElement({ + css: '[data-testid="confirmation-submit-button"]', + text: 'Create', + }); + await driver.clickElement({ + css: '[data-testid="confirmation-submit-button"]', + text: 'Ok', + }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + const newPublicKey = await ( + await driver.findElement({ + text: '0x', + tag: 'p', + }) + ).getText(); + + await switchToAccount2(driver); + + return newPublicKey; +} + +async function switchToAccount2(driver: Driver) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + + // click on Accounts + await driver.clickElement('[data-testid="account-menu-icon"]'); + + await driver.clickElement({ + tag: 'Button', + text: 'Account 2', + }); + + await driver.waitForElementNotPresent({ + tag: 'header', + text: 'Select an account', + }); +} + +export async function connectAccountToTestDapp(driver: Driver) { + await switchToOrOpenDapp(driver); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); +} + +export async function disconnectFromTestDapp(driver: Driver) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + await driver.clickElement('[data-testid="account-options-menu-button"]'); + await driver.clickElement('[data-testid="global-menu-connected-sites"]'); + await driver.clickElement({ text: 'Disconnect', tag: 'a' }); + await driver.clickElement({ text: 'Disconnect', tag: 'button' }); +} + +export async function approveOrRejectRequest(driver: Driver, flowType: string) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.SnapSimpleKeyringDapp); + + await driver.clickElementUsingMouseMove({ + text: 'List requests', + tag: 'div', + }); + + await driver.clickElement({ + text: 'List Requests', + tag: 'button', + }); + + // get the JSON from the screen + const requestJSON = await ( + await driver.findElement({ + text: '"scope": "",', + tag: 'div', + }) + ).getText(); + + const requestID = JSON.parse(requestJSON)[0].id; + + if (flowType === 'approve') { + await driver.clickElementUsingMouseMove({ + text: 'Approve request', + tag: 'div', + }); + + await driver.fill('#approve-request-request-id', requestID); + + await driver.clickElement({ + text: 'Approve Request', + tag: 'button', + }); + } else if (flowType === 'reject') { + await driver.clickElementUsingMouseMove({ + text: 'Reject request', + tag: 'div', + }); + + await driver.fill('#reject-request-request-id', requestID); + + await driver.clickElement({ + text: 'Reject Request', + tag: 'button', + }); + } + + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); +} + +export async function signData( + driver: Driver, + locatorID: string, + newPublicKey: string, + flowType: string, +) { + const isAsyncFlow = flowType !== 'sync'; + + await switchToOrOpenDapp(driver); + + await driver.clickElement(locatorID); + + // behavior of chrome and firefox is different, + // chrome needs extra time to load the popup + if (driver.browser === 'chrome') { + await driver.delay(500); + } + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + + // these three don't have a contract details page + if (!['#ethSign', '#personalSign', '#signTypedData'].includes(locatorID)) { + await validateContractDetails(driver); + } + + await clickSignOnSignatureConfirmation(driver, 3, locatorID); + + if (isAsyncFlow) { + await driver.delay(1000); + + // // Navigate to the Notification window and click 'Go to site' button + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + await driver.clickElement({ + text: 'Go to site', + tag: 'button', + }); + + await driver.delay(1000); + await approveOrRejectRequest(driver, flowType); + } + + await driver.delay(500); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + if (flowType === 'sync' || flowType === 'approve') { + if (locatorID === '#ethSign') { + // there is no Verify button for #ethSign + await driver.findElement({ + css: '#ethSignResult', + text: '0x', // we are just making sure that it contains ANY hex value + }); + } else { + await driver.clickElement(`${locatorID}Verify`); + + const resultLocator = + locatorID === '#personalSign' + ? '#personalSignVerifyECRecoverResult' // the verify span IDs are different with Personal Sign + : `${locatorID}VerifyResult`; + + await driver.findElement({ + css: resultLocator, + text: newPublicKey.toLowerCase(), + }); + } + } else if (flowType === 'reject') { + // ensure the transaction was rejected by the Snap + await driver.findElement({ + text: 'Error: Request rejected by user or snap.', + }); + } +} diff --git a/test/e2e/accounts/test-create-snap-account.spec.js b/test/e2e/accounts/create-snap-account.spec.ts similarity index 93% rename from test/e2e/accounts/test-create-snap-account.spec.js rename to test/e2e/accounts/create-snap-account.spec.ts index eb4ffdf93a3e..70209d1915c8 100644 --- a/test/e2e/accounts/test-create-snap-account.spec.js +++ b/test/e2e/accounts/create-snap-account.spec.ts @@ -1,23 +1,25 @@ -const { - withFixtures, - defaultGanacheOptions, - unlockWallet, +import { Suite } from 'mocha'; +import FixtureBuilder from '../fixture-builder'; +import { WINDOW_TITLES, + defaultGanacheOptions, switchToNotificationWindow, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } = require('./utils'); + unlockWallet, + withFixtures, +} from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } from './common'; -describe('Create Snap Account', function () { +describe('Create Snap Account', function (this: Suite) { it('create Snap account popup contains correct Snap name and snapId', async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), ganacheOptions: defaultGanacheOptions, failOnConsoleError: false, - title: this.test.title, + title: this.test?.fullTitle(), }, - async ({ driver }) => { + async ({ driver }: { driver: Driver }) => { await driver.navigate(); await unlockWallet(driver); @@ -101,9 +103,9 @@ describe('Create Snap Account', function () { fixtures: new FixtureBuilder().build(), ganacheOptions: defaultGanacheOptions, failOnConsoleError: false, - title: this.test.title, + title: this.test?.fullTitle(), }, - async ({ driver }) => { + async ({ driver }: { driver: Driver }) => { await driver.navigate(); await unlockWallet(driver); @@ -196,9 +198,9 @@ describe('Create Snap Account', function () { fixtures: new FixtureBuilder().build(), ganacheOptions: defaultGanacheOptions, failOnConsoleError: false, - title: this.test.title, + title: this.test?.fullTitle(), }, - async ({ driver }) => { + async ({ driver }: { driver: Driver }) => { await driver.navigate(); await unlockWallet(driver); diff --git a/test/e2e/accounts/remove-account-snap.spec.ts b/test/e2e/accounts/remove-account-snap.spec.ts new file mode 100644 index 000000000000..dc22d117783d --- /dev/null +++ b/test/e2e/accounts/remove-account-snap.spec.ts @@ -0,0 +1,76 @@ +import { Suite } from 'mocha'; +import FixtureBuilder from '../fixture-builder'; +import { WINDOW_TITLES, defaultGanacheOptions, withFixtures } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { installSnapSimpleKeyring, makeNewAccountAndSwitch } from './common'; + +describe('Remove Account Snap', function (this: Suite) { + it('disable a snap and remove it', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + failOnConsoleError: false, + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await installSnapSimpleKeyring(driver, false); + + await makeNewAccountAndSwitch(driver); + + // Navigate to settings. + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + + await driver.clickElement({ text: 'Snaps', tag: 'div' }); + await driver.clickElement({ + text: 'MetaMask Simple Snap Keyring', + tag: 'p', + }); + + // Disable the snap. + await driver.clickElement('.toggle-button > div'); + + // Remove the snap. + const removeButton = await driver.findElement( + '[data-testid="remove-snap-button"]', + ); + await driver.scrollToElement(removeButton); + await driver.clickElement('[data-testid="remove-snap-button"]'); + + await driver.clickElement({ + text: 'Continue', + tag: 'button', + }); + + await driver.fill( + '[data-testid="remove-snap-confirmation-input"]', + 'MetaMask Simple Snap Keyring', + ); + + await driver.clickElement({ + text: 'Remove Snap', + tag: 'button', + }); + + // Checking result modal + await driver.findVisibleElement({ + text: 'MetaMask Simple Snap Keyring removed', + tag: 'p', + }); + + // Assert that the snap was removed. + await driver.findElement({ + css: '.mm-box', + text: "You don't have any snaps installed.", + tag: 'p', + }); + }, + ); + }); +}); diff --git a/test/e2e/accounts/snap-account-signatures-and-disconnects.spec.ts b/test/e2e/accounts/snap-account-signatures-and-disconnects.spec.ts new file mode 100644 index 000000000000..72e0b358c330 --- /dev/null +++ b/test/e2e/accounts/snap-account-signatures-and-disconnects.spec.ts @@ -0,0 +1,49 @@ +import { Suite } from 'mocha'; +import FixtureBuilder from '../fixture-builder'; +import { withFixtures } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { + installSnapSimpleKeyring, + makeNewAccountAndSwitch, + connectAccountToTestDapp, + disconnectFromTestDapp, + ganacheOptions, + signData, +} from './common'; + +describe('Snap Account Signatures and Disconnects', function (this: Suite) { + it('can connect to the Test Dapp, then #signTypedDataV3, disconnect then connect, then #signTypedDataV4 (async flow approve)', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + ganacheOptions, + failOnConsoleError: false, + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + const flowType = 'approve'; + const isAsyncFlow = true; + + await installSnapSimpleKeyring(driver, isAsyncFlow); + + const newPublicKey = await makeNewAccountAndSwitch(driver); + + // open the Test Dapp and connect Account 2 to it + await connectAccountToTestDapp(driver); + + // do #signTypedDataV3 + await signData(driver, '#signTypedDataV3', newPublicKey, flowType); + + // disconnect from the Test Dapp + await disconnectFromTestDapp(driver); + + // reconnect Account 2 to the Test Dapp + await connectAccountToTestDapp(driver); + + // do #signTypedDataV4 + await signData(driver, '#signTypedDataV4', newPublicKey, flowType); + }, + ); + }); +}); diff --git a/test/e2e/accounts/snap-account-signatures.spec.ts b/test/e2e/accounts/snap-account-signatures.spec.ts new file mode 100644 index 000000000000..5c1a00a2c00c --- /dev/null +++ b/test/e2e/accounts/snap-account-signatures.spec.ts @@ -0,0 +1,49 @@ +import { Suite } from 'mocha'; +import { openDapp, withFixtures } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { + accountSnapFixtures, + installSnapSimpleKeyring, + makeNewAccountAndSwitch, + signData, +} from './common'; + +describe('Snap Account Signatures', function (this: Suite) { + this.timeout(120000); // This test is very long, so we need an unusually high timeout + + // Run sync, async approve, and async reject flows + // (in Jest we could do this with test.each, but that does not exist here) + ['sync', 'approve', 'reject'].forEach((flowType) => { + // generate title of the test from flowType + const title = `can sign with ${flowType} flow`; + + it(title, async () => { + await withFixtures( + accountSnapFixtures(title), + async ({ driver }: { driver: Driver }) => { + const isAsyncFlow = flowType !== 'sync'; + + await installSnapSimpleKeyring(driver, isAsyncFlow); + + const newPublicKey = await makeNewAccountAndSwitch(driver); + + await openDapp(driver); + + // Run all 6 signature types + const locatorIDs = [ + '#ethSign', + '#personalSign', + '#signTypedData', + '#signTypedDataV3', + '#signTypedDataV4', + '#signPermit', + ]; + + for (const locatorID of locatorIDs) { + await signData(driver, locatorID, newPublicKey, flowType); + } + }, + ); + }); + }); +}); diff --git a/test/e2e/accounts/snap-account-transfers.spec.ts b/test/e2e/accounts/snap-account-transfers.spec.ts new file mode 100644 index 000000000000..57da6be4c953 --- /dev/null +++ b/test/e2e/accounts/snap-account-transfers.spec.ts @@ -0,0 +1,90 @@ +import { Suite } from 'mocha'; +import { sendTransaction, withFixtures, WINDOW_TITLES } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import { + accountSnapFixtures, + PUBLIC_KEY, + installSnapSimpleKeyring, + importKeyAndSwitch, + approveOrRejectRequest, +} from './common'; + +describe('Snap Account Transfers', function (this: Suite) { + it('can import a private key and transfer 1 ETH (sync flow)', async function () { + await withFixtures( + accountSnapFixtures(this.test?.fullTitle()), + async ({ driver }: { driver: Driver }) => { + await importPrivateKeyAndTransfer1ETH(driver, 'sync'); + }, + ); + }); + + it('can import a private key and transfer 1 ETH (async flow approve)', async function () { + await withFixtures( + accountSnapFixtures(this.test?.fullTitle()), + async ({ driver }: { driver: Driver }) => { + await importPrivateKeyAndTransfer1ETH(driver, 'approve'); + }, + ); + }); + + it('can import a private key and transfer 1 ETH (async flow reject)', async function () { + await withFixtures( + accountSnapFixtures(this.test?.fullTitle()), + async ({ driver }: { driver: Driver }) => { + await importPrivateKeyAndTransfer1ETH(driver, 'reject'); + }, + ); + }); + + /** + * @param driver + * @param flowType + */ + async function importPrivateKeyAndTransfer1ETH( + driver: Driver, + flowType: string, + ) { + const isAsyncFlow = flowType !== 'sync'; + + await installSnapSimpleKeyring(driver, isAsyncFlow); + await importKeyAndSwitch(driver); + + // send 1 ETH from Account 2 to Account 1 + await sendTransaction(driver, PUBLIC_KEY, 1, isAsyncFlow); + + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + return; + } + + if (isAsyncFlow) { + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.navigate(); + await driver.clickElement({ + text: 'Go to site', + tag: 'button', + }); + + await driver.delay(1000); + await approveOrRejectRequest(driver, flowType); + } + + if (flowType === 'sync' || flowType === 'approve') { + // click on Accounts + await driver.clickElement('[data-testid="account-menu-icon"]'); + + // ensure one account has 26 ETH and the other has 24 ETH + await driver.findElement('[title="26 ETH"]'); + await driver.findElement('[title="24 ETH"]'); + } else if (flowType === 'reject') { + // ensure the transaction was rejected by the Snap + await driver.clickElement({ text: 'Activity', tag: 'button' }); + await driver.findElement( + '[data-original-title="Request rejected by user or snap."]', + ); + } + } +}); diff --git a/test/e2e/accounts/test-remove-accounts-snap.spec.js b/test/e2e/accounts/test-remove-accounts-snap.spec.js deleted file mode 100644 index 114964acacd8..000000000000 --- a/test/e2e/accounts/test-remove-accounts-snap.spec.js +++ /dev/null @@ -1,142 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - defaultGanacheOptions, - unlockWallet, - WINDOW_TITLES, - switchToNotificationWindow, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } = require('./utils'); - -describe('Remove Account Snap', function () { - it('disable a snap and remove it', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - failOnConsoleError: false, - title: this.test.title, - }, - async ({ driver }) => { - await driver.navigate(); - - await unlockWallet(driver); - - // Navigate to test Snaps page and connect. - await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL); - - // Connect the dapp. - await driver.clickElement('#connectButton'); - await switchToNotificationWindow(driver); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Scroll to the bottom of the page. - await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); - - // Click the install button to install the snap. - await driver.waitForSelector({ text: 'Install' }); - await driver.clickElement({ - text: 'Install', - tag: 'button', - }); - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElement({ - text: 'OK', - tag: 'button', - }); - - // Move back to the snap window to test the create account flow. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.SnapSimpleKeyringDapp, - ); - - // Check the dapp connection status. - await driver.waitForSelector({ - css: '#snapConnected', - text: 'Connected', - }); - - // Create new account on dapp. - await driver.clickElement({ - text: 'Create account', - tag: 'div', - }); - await driver.clickElement({ - text: 'Create Account', - tag: 'button', - }); - await switchToNotificationWindow(driver); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.findElement({ - tag: 'div', - text: 'Your account is ready!', - }); - - // Click the OK button. - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - - // Switch back to the test dapp window. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.SnapSimpleKeyringDapp, - ); - - await driver.findElement({ - tag: 'p', - text: 'Successful request', - }); - - // Navigate to settings. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Snaps', tag: 'div' }); - await driver.clickElement({ - text: 'MetaMask Simple Snap Keyring', - tag: 'p', - }); - - // Disable the snap. - await driver.clickElement('.toggle-button > div'); - - // Remove the snap. - const removeButton = await driver.findElement( - '[data-testid="remove-snap-button"]', - ); - await driver.scrollToElement(removeButton); - await driver.clickElement('[data-testid="remove-snap-button"]'); - - await driver.clickElement({ - text: 'Continue', - tag: 'button', - }); - - await driver.fill( - '[data-testid="remove-snap-confirmation-input"]', - 'MetaMask Simple Snap Keyring', - ); - - await driver.clickElement({ - text: 'Remove Snap', - tag: 'button', - }); - - // Assert that the snap was removed. - const removeResult = await driver.findElement( - '.snap-list-tab__container--no-snaps_inner', - ); - assert.equal( - await removeResult.getText(), - "You don't have any snaps installed.", - ); - }, - ); - }); -}); diff --git a/test/e2e/accounts/test-snap-accounts.spec.js b/test/e2e/accounts/test-snap-accounts.spec.js deleted file mode 100644 index 97dbf69e75fc..000000000000 --- a/test/e2e/accounts/test-snap-accounts.spec.js +++ /dev/null @@ -1,597 +0,0 @@ -const { strict: assert } = require('assert'); -const util = require('ethereumjs-util'); -const FixtureBuilder = require('../fixture-builder'); -const { - convertETHToHexGwei, - openDapp, - PRIVATE_KEY, - PRIVATE_KEY_TWO, - defaultGanacheOptions, - sendTransaction, - switchToNotificationWindow, - switchToOrOpenDapp, - unlockWallet, - validateContractDetails, - WINDOW_TITLES, - withFixtures, -} = require('../helpers'); -const Driver = require('../webdriver/driver'); // eslint-disable-line no-unused-vars -- this is imported for JSDoc -const { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } = require('./utils'); - -describe('Test Snap Account', function () { - const ganacheOptions = { - accounts: [ - { - secretKey: PRIVATE_KEY, - balance: convertETHToHexGwei(25), - }, - { - secretKey: PRIVATE_KEY_TWO, - balance: convertETHToHexGwei(25), - }, - ], - }; - - const accountSnapFixtures = (title) => { - return { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp(false) - .build(), - ganacheOptions, - failOnConsoleError: false, - title, - }; - }; - - // convert PRIVATE_KEY to public key - const PUBLIC_KEY = util - .privateToAddress(Buffer.from(PRIVATE_KEY.slice(2), 'hex')) - .toString('hex'); - - it('can create a new Snap account', async function () { - await withFixtures( - accountSnapFixtures(this.test.title), - async ({ driver }) => { - await installSnapSimpleKeyring(driver, false); - - await makeNewAccountAndSwitch(driver); - }, - ); - }); - - it('will display the keyring snap account removal warning', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - failOnConsoleError: false, - title: this.test.title, - }, - async ({ driver }) => { - await installSnapSimpleKeyring(driver, false); - - await makeNewAccountAndSwitch(driver); - - const windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - - // switch to metamask extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - windowHandles, - ); - - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - - await driver.clickElement({ text: 'Settings', tag: 'div' }); - - await driver.clickElement({ text: 'Snaps', tag: 'div' }); - - await driver.clickElement( - '[data-testid="npm:@metamask/snap-simple-keyring-snap"]', - ); - - const removeButton = await driver.findElement( - '[data-testid="remove-snap-button"]', - ); - await driver.scrollToElement(removeButton); - await driver.clickElement('[data-testid="remove-snap-button"]'); - - assert.equal( - await driver.isElementPresentAndVisible({ text: 'Account 2' }), - true, - ); - - await driver.clickElement({ - text: 'Continue', - tag: 'button', - }); - - await driver.fill( - '[data-testid="remove-snap-confirmation-input"]', - 'MetaMask Simple Snap Keyring', - ); - - await driver.clickElement({ - text: 'Remove Snap', - tag: 'button', - }); - - // Checking result modal - assert.equal( - await driver.isElementPresentAndVisible({ - text: 'MetaMask Simple Snap Keyring removed', - tag: 'p', - }), - true, - ); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (sync flow)', async function () { - await withFixtures( - accountSnapFixtures(this.test.title), - async ({ driver }) => { - await importPrivateKeyAndTransfer1ETH(driver, 'sync'); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (async flow approve)', async function () { - await withFixtures( - accountSnapFixtures(this.test.title), - async ({ driver }) => { - await importPrivateKeyAndTransfer1ETH(driver, 'approve'); - }, - ); - }); - - it('can import a private key and transfer 1 ETH (async flow reject)', async function () { - await withFixtures( - accountSnapFixtures(this.test.title), - async ({ driver }) => { - await importPrivateKeyAndTransfer1ETH(driver, 'reject'); - }, - ); - }); - - // run the full matrix of sign types and sync/async approve/async reject flows - // (in Jest we could do this with test.each, but that does not exist here) - [ - ['#personalSign', 'sync'], - ['#personalSign', 'approve'], - ['#personalSign', 'reject'], - ['#signTypedData', 'sync'], - ['#signTypedData', 'approve'], - ['#signTypedData', 'reject'], - ['#signTypedDataV3', 'sync'], - ['#signTypedDataV3', 'approve'], - ['#signTypedDataV3', 'reject'], - ['#signTypedDataV4', 'sync'], - ['#signTypedDataV4', 'approve'], - ['#signTypedDataV4', 'reject'], - ['#signPermit', 'sync'], - ['#signPermit', 'approve'], - ['#signPermit', 'reject'], - ].forEach(([locatorID, flowType]) => { - // generate title of the test from the locatorID and flowType - let title = `can ${locatorID} (${ - flowType === 'sync' ? 'sync' : 'async' - } flow`; - - title += flowType === 'sync' ? ')' : ` ${flowType})`; - - it(title, async function () { - await withFixtures( - accountSnapFixtures(this.test.title), - async ({ driver }) => { - const isAsyncFlow = flowType !== 'sync'; - - await installSnapSimpleKeyring(driver, isAsyncFlow); - - const newPublicKey = await makeNewAccountAndSwitch(driver); - - await openDapp(driver); - - await signData(driver, locatorID, newPublicKey, flowType); - }, - ); - }); - }); - - it('can connect to the Test Dapp, then #signTypedDataV3, disconnect then connect, then #signTypedDataV4 (async flow approve)', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().build(), - ganacheOptions, - failOnConsoleError: false, - title: this.test.title, - }, - async ({ driver }) => { - const flowType = 'approve'; - const isAsyncFlow = flowType !== 'sync'; - - await installSnapSimpleKeyring(driver, isAsyncFlow); - - const newPublicKey = await makeNewAccountAndSwitch(driver); - - // open the Test Dapp and connect Account 2 to it - await connectAccountToTestDapp(driver); - - // do #signTypedDataV3 - await signData(driver, '#signTypedDataV3', newPublicKey, flowType); - - // disconnect from the Test Dapp - await disconnectFromTestDapp(driver); - - // reconnect Account 2 to the Test Dapp - await connectAccountToTestDapp(driver); - - // do #signTypedDataV4 - await signData(driver, '#signTypedDataV4', newPublicKey, flowType); - }, - ); - }); - - /** - * @param {Driver} driver - * @param {string} flowType - */ - async function importPrivateKeyAndTransfer1ETH(driver, flowType) { - const isAsyncFlow = flowType !== 'sync'; - - await installSnapSimpleKeyring(driver, isAsyncFlow); - await importKeyAndSwitch(driver); - - // send 1 ETH from Account 2 to Account 1 - await sendTransaction(driver, PUBLIC_KEY, 1, isAsyncFlow); - - if (isAsyncFlow) { - await approveOrRejectRequest(driver, flowType); - } - - if (flowType === 'sync' || flowType === 'approve') { - // click on Accounts - await driver.clickElement('[data-testid="account-menu-icon"]'); - - // ensure one account has 26 ETH and the other has 24 ETH - await driver.findElement('[title="26 ETH"]'); - await driver.findElement('[title="24 ETH"]'); - } else if (flowType === 'reject') { - // ensure the transaction was rejected by the Snap - await driver.findElement( - '[data-original-title="Request rejected by user or snap."]', - ); - } - } - - /** - * @param {Driver} driver - * @param {string} locatorID - * @param {string} newPublicKey - * @param {string} flowType - */ - async function signData(driver, locatorID, newPublicKey, flowType) { - const isAsyncFlow = flowType !== 'sync'; - - await switchToOrOpenDapp(driver); - - await driver.clickElement(locatorID); - - // behaviour of chrome and firefox is different, - // chrome needs extra time to load the popup - if (driver.browser === 'chrome') { - await driver.delay(500); - } - const handles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - handles, - 2000, - ); - - // these two don't have a contract details page - if (locatorID !== '#personalSign' && locatorID !== '#signTypedData') { - await validateContractDetails(driver); - } - - await driver.clickElement({ text: 'Sign', tag: 'button' }); - - if (isAsyncFlow) { - await approveOrRejectRequest(driver, flowType); - } - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - if (flowType === 'sync' || flowType === 'approve') { - await driver.clickElement(`${locatorID}Verify`); - - const resultLocator = - locatorID === '#personalSign' - ? '#personalSignVerifyECRecoverResult' // the verify span IDs are different with Personal Sign - : `${locatorID}VerifyResult`; - - await driver.findElement({ - css: resultLocator, - text: newPublicKey.toLowerCase(), - }); - } else if (flowType === 'reject') { - // ensure the transaction was rejected by the Snap - await driver.findElement({ - text: 'Error: Request rejected by user or snap.', - }); - } - } - - /** - * @param {Driver} driver - * @param {boolean} isAsyncFlow - */ - async function installSnapSimpleKeyring(driver, isAsyncFlow) { - driver.navigate(); - - await unlockWallet(driver); - - // navigate to test Snaps page and connect - await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL); - const connectButton = await driver.findElement('#connectButton'); - await driver.scrollToElement(connectButton); - await connectButton.click(); - - await switchToNotificationWindow(driver); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.clickElementSafe('[data-testid="snap-install-scroll"]', 1000); - - await driver.waitForSelector({ text: 'Install' }); - - await driver.clickElement({ - text: 'Install', - tag: 'button', - }); - - await driver.waitForSelector({ text: 'OK' }); - - await driver.clickElement({ - text: 'OK', - tag: 'button', - }); - - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - await driver.waitForSelector({ - text: 'Connected', - tag: 'span', - }); - - if (isAsyncFlow) { - await toggleAsyncFlow(driver); - } - } - - /** - * @param {Driver} driver - */ - async function toggleAsyncFlow(driver) { - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - // click the parent of #use-sync-flow-toggle (trying to click the element itself gives "ElementNotInteractableError: could not be scrolled into view") - await driver.clickElement({ - xpath: '//input[@id="use-sync-flow-toggle"]/..', - }); - } - - /** - * @param {Driver} driver - */ - async function importKeyAndSwitch(driver) { - await driver.clickElement({ - text: 'Import account', - tag: 'div', - }); - - await driver.fill('#import-account-private-key', PRIVATE_KEY_TWO); - - await driver.clickElement({ - text: 'Import Account', - tag: 'button', - }); - - // Click "Create" on the Snap's confirmation popup - await switchToNotificationWindow(driver, 3); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - await switchToAccount2(driver); - } - - /** - * @param {Driver} driver - */ - async function makeNewAccountAndSwitch(driver) { - await driver.clickElement({ - text: 'Create account', - tag: 'div', - }); - - await driver.clickElement({ - text: 'Create Account', - tag: 'button', - }); - - // Click "Create" on the Snap's confirmation popup - const handles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - handles, - 2000, - ); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.clickElement('[data-testid="confirmation-submit-button"]'); - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - const newPublicKey = await ( - await driver.findElement({ - text: '0x', - tag: 'p', - }) - ).getText(); - - await switchToAccount2(driver); - - return newPublicKey; - } - - /** - * @param {Driver} driver - */ - async function switchToAccount2(driver) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // click on Accounts - await driver.clickElement('[data-testid="account-menu-icon"]'); - - const label = await driver.findElement({ - css: '.mm-tag', - text: 'Snaps (Beta)', - }); - - label.click(); - - await driver.waitForElementNotPresent('.mm-tag'); - } - - /** - * @param {Driver} driver - */ - async function connectAccountToTestDapp(driver) { - await switchToOrOpenDapp(driver); - await driver.clickElement('#connectButton'); - const handles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - handles, - 2000, - ); - await driver.clickElement('[data-testid="page-container-footer-next"]'); - await driver.clickElement('[data-testid="page-container-footer-next"]'); - } - - /** - * @param {Driver} driver - */ - async function disconnectFromTestDapp(driver) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement('[data-testid="account-options-menu-button"]'); - await driver.clickElement('[data-testid="global-menu-connected-sites"]'); - await driver.clickElement({ text: 'Disconnect', tag: 'a' }); - await driver.clickElement({ text: 'Disconnect', tag: 'button' }); - } - - /** - * @param {Driver} driver - * @param {string} flowType - */ - async function approveOrRejectRequest(driver, flowType) { - // Click redirect button for async flows - if (flowType === 'approve' || flowType === 'reject') { - // There is a try catch here because when using the send eth flow, - // MetaMask is still active, and therefore the popup notification will - // no appear. The workaround is to reload the extension and - // force the notification to appear in the full window. - try { - // Adding a delay here because there is a race condition where - // the driver tries to switch to the first notification window - // and not the second notification window with the redirect button - await driver.delay(500); - const handles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.Notification, - handles, - 2000, - ); - } catch (error) { - console.log('SNAPS/ error switching to notification window', error); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.navigate(); - } - await driver.clickElement({ - text: 'Go to site', - tag: 'button', - }); - - await driver.delay(500); - } - - await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); - - await driver.clickElementUsingMouseMove({ - text: 'List requests', - tag: 'div', - }); - - await driver.clickElement({ - text: 'List Requests', - tag: 'button', - }); - - // get the JSON from the screen - const requestJSON = await ( - await driver.findElement({ - text: '"scope": "",', - tag: 'div', - }) - ).getText(); - - const requestID = JSON.parse(requestJSON)[0].id; - - if (flowType === 'approve') { - await driver.clickElementUsingMouseMove({ - text: 'Approve request', - tag: 'div', - }); - - await driver.fill('#approve-request-request-id', requestID); - - await driver.clickElement({ - text: 'Approve Request', - tag: 'button', - }); - } else if (flowType === 'reject') { - await driver.clickElementUsingMouseMove({ - text: 'Reject request', - tag: 'div', - }); - - await driver.fill('#reject-request-request-id', requestID); - - await driver.clickElement({ - text: 'Reject Request', - tag: 'button', - }); - } - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - } -}); diff --git a/test/e2e/accounts/utils.ts b/test/e2e/accounts/utils.ts deleted file mode 100644 index 7087531c91b2..000000000000 --- a/test/e2e/accounts/utils.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL = - 'https://metamask.github.io/snap-simple-keyring/1.0.1/'; diff --git a/test/e2e/e2e-process-report.js b/test/e2e/e2e-process-report.js deleted file mode 100644 index a9146ec12958..000000000000 --- a/test/e2e/e2e-process-report.js +++ /dev/null @@ -1,10 +0,0 @@ -const fs = require('fs'); - -const dir = 'test/test-results/e2e'; - -fs.readdirSync(dir).forEach((file) => { - const currentFile = `${dir}/${file}`; - let data = fs.readFileSync(currentFile, { encoding: 'utf8', flag: 'r' }); - data = data.substring(data.indexOf(' { - await driver.navigate(); - await unlockWallet(driver); - await openDapp(driver); - - const testBenignConfigs = [ - { - logExpectedDetail: 'Benign eth_sendTransaction with no value', - btnSelector: '#sendButton', - }, - { - logExpectedDetail: 'Benign eth_sendTransaction with value', - method: 'eth_sendTransaction', - params: [ - { - from: selectedAddress, - to: '0xf977814e90da44bfa03b6295a0616a897441acec', - value: '0x9184e72a000', - }, - ], - }, - { - logExpectedDetail: 'Benign Blur eth_sendTransaction', - method: 'eth_signTypedData_v4', - params: [ - selectedAddress, - '{"types":{"Order":[{"name":"trader","type":"address"},{"name":"side","type":"uint8"},{"name":"matchingPolicy","type":"address"},{"name":"collection","type":"address"},{"name":"tokenId","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"paymentToken","type":"address"},{"name":"price","type":"uint256"},{"name":"listingTime","type":"uint256"},{"name":"expirationTime","type":"uint256"},{"name":"fees","type":"Fee[]"},{"name":"salt","type":"uint256"},{"name":"extraParams","type":"bytes"},{"name":"nonce","type":"uint256"}],"Fee":[{"name":"rate","type":"uint16"},{"name":"recipient","type":"address"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Blur Exchange","version":"1.0","chainId":"1","verifyingContract":"0x000000000000ad05ccc4f10045630fb830b95127"},"primaryType":"Order","message":{"trader":"0xd854343f41b2138b686f2d3ba38402a9f7fb4337","side":"1","matchingPolicy":"0x0000000000dab4a563819e8fd93dba3b25bc3495","collection":"0xc4a5025c4563ad0acc09d92c2506e6744dad58eb","tokenId":"30420","amount":"1","paymentToken":"0x0000000000000000000000000000000000000000","price":"1000000000000000000","listingTime":"1679418212","expirationTime":"1680023012","salt":"154790208154270131670189427454206980105","extraParams":"0x01","nonce":"0"}}', - ], - }, - { - logExpectedDetail: 'Benign Seaport eth_sendTransaction', - method: 'eth_signTypedData_v4', - params: [ - selectedAddress, - '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.4","chainId":"1","verifyingContract":"0x00000000000001ad428e4906aE43D8F9852d0dD6"},"message":{"offerer":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC","offer":[{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"2500000000000000","endAmount":"2500000000000000"}],"consideration":[{"itemType":"2","token":"0xaA7200ee500dE2dcde75E996De83CBD73BCa9705","identifierOrCriteria":"11909","startAmount":"1","endAmount":"1","recipient":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"62500000000000","endAmount":"62500000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"12500000000000","endAmount":"12500000000000","recipient":"0x8324BdEF2F30E08E368f2Fa2F14143cDCA77423D"}],"startTime":"1681835413","endTime":"1682094598","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929812618382526293324216","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}}', - ], - }, - { - logExpectedDetail: 'Benign eth_signTypedData', - btnSelector: '#signTypedData', - }, - ]; - - for (const config of testBenignConfigs) { - const { btnSelector, logExpectedDetail, method, params } = config; - - // Either click TestDapp button to send JSON-RPC request or manually send request - if (btnSelector) { - await driver.clickElement(btnSelector); - } else { - const request = JSON.stringify({ - jsonrpc: '2.0', - method, - params, - }); - await driver.executeScript( - `window.transactionHash = window.ethereum.request(${request})`, - ); - } - - // Wait for confirmation pop-up - await driver.waitUntilXWindowHandles(3); - const windowHandles = await getWindowHandles(driver, 3); - await driver.switchToWindowWithTitle('MetaMask Notification'); - - const isPresent = await driver.isElementPresent(bannerAlertSelector); - assert.equal( - isPresent, - false, - `Banner alert unexpectedly found. \nExpected detail: ${logExpectedDetail}`, - ); - - // Wait for confirmation pop-up to close - await driver.clickElement({ text: 'Reject', tag: 'button' }); - await driver.switchToWindow(windowHandles.dapp); - } - }, - ); - }); - - /** - * Disclaimer: This test does not test all reason types. e.g. 'blur_farming', - * 'malicious_domain', 'unfair_trade'. Some other tests are found in other files: - * e.g. test/e2e/flask/ppom-blockaid-alert-.spec.js - */ - it('should show security alerts for malicious requests', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerOnMainnet() - .withPermissionControllerConnectedToTestDapp() - .withPreferencesController({ - securityAlertsEnabled: true, - }) - .build(), - defaultGanacheOptions, - testSpecificMock: mockInfura, - title: this.test.title, - }, - - async ({ driver }) => { - await driver.navigate(); - await unlockWallet(driver); - await openDapp(driver); - - const expectedTitle = 'This is a deceptive request'; - - const testMaliciousConfigs = [ - { - btnSelector: '#maliciousPermit', - expectedDescription: - 'If you approve this request, a third party known for scams might take all your assets.', - expectedReason: 'permit_farming', - }, - { - btnSelector: '#maliciousRawEthButton', - expectedDescription: - 'If you approve this request, a third party known for scams will take all your assets.', - expectedReason: 'raw_native_token_transfer', - }, - { - btnSelector: '#maliciousSeaport', - expectedDescription: - 'If you approve this request, someone can steal your assets listed on OpenSea.', - expectedReason: 'seaport_farming', - }, - { - btnSelector: '#maliciousTradeOrder', - expectedDescription: - 'If you approve this request, you might lose your assets.', - expectedReason: 'trade_order_farming', - }, - ]; - - for (const config of testMaliciousConfigs) { - const { expectedDescription, expectedReason, btnSelector } = config; - - // Click TestDapp button to send JSON-RPC request - await driver.clickElement(btnSelector); - - // Wait for confirmation pop-up - await driver.waitUntilXWindowHandles(3); - const windowHandles = await getWindowHandles(driver, 3); - await driver.switchToWindowWithTitle('MetaMask Notification'); - - const bannerAlertFoundByTitle = await driver.findElement({ - css: bannerAlertSelector, - text: expectedTitle, - }); - const bannerAlertText = await bannerAlertFoundByTitle.getText(); - - assert( - bannerAlertFoundByTitle, - `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: ${expectedReason}\n`, - ); - assert( - bannerAlertText.includes(expectedDescription), - `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: ${expectedReason}\n`, - ); - - // Wait for confirmation pop-up to close - await driver.clickElement({ text: 'Reject', tag: 'button' }); - await driver.switchToWindow(windowHandles.dapp); - } - }, - ); - }); - - it('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerOnMainnet() - .withPermissionControllerConnectedToTestDapp() - .withPreferencesController({ - securityAlertsEnabled: true, - }) - .build(), - defaultGanacheOptions, - title: this.test.title, - }, - - async ({ driver }) => { - await driver.navigate(); - await unlockWallet(driver); - await openDapp(driver); - - // Click TestDapp button to send JSON-RPC request - await driver.clickElement('#maliciousApprovalButton'); - - // Wait for confirmation pop-up - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle('MetaMask Notification'); - - const expectedTitle = 'Request may not be safe'; - - const bannerAlert = await driver.findElement({ - css: bannerAlertSelector, - text: expectedTitle, - }); - - assert( - bannerAlert, - `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: transfer_farming\n`, - ); - }, - ); - }); -}); diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 6aafa80fe82d..e590eb3de2a5 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -254,11 +254,13 @@ async function withFixtures(options, testSuite) { const WINDOW_TITLES = Object.freeze({ ExtensionInFullScreenView: 'MetaMask', - TestDApp: 'E2E Test Dapp', + InstalledExtensions: 'Extensions', Notification: 'MetaMask Notification', + Phishing: 'MetaMask Phishing Detection', ServiceWorkerSettings: 'Inspect with Chrome Developer Tools', - InstalledExtensions: 'Extensions', SnapSimpleKeyringDapp: 'SSK - Simple Snap Keyring', + TestDApp: 'E2E Test Dapp', + TestSnaps: 'Test Snaps', }); /** @@ -509,8 +511,7 @@ const testSRPDropdownIterations = async (options, driver, iterations) => { const passwordUnlockOpenSRPRevealQuiz = async (driver) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate settings to reveal SRP await driver.clickElement('[data-testid="account-options-menu-button"]'); @@ -572,7 +573,13 @@ const switchToOrOpenDapp = async ( dappURL = DAPP_URL, ) => { try { - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + // Do an unusually fast switchToWindowWithTitle, just 1 second + await driver.switchToWindowWithTitle( + WINDOW_TITLES.TestDApp, + null, + 1000, + 1000, + ); } catch { await openDapp(driver, contract, dappURL); } @@ -590,7 +597,15 @@ const defaultGanacheOptions = { accounts: [{ secretKey: PRIVATE_KEY, balance: convertETHToHexGwei(25) }], }; -const SERVICE_WORKER_URL = 'chrome://inspect/#service-workers'; +const openActionMenuAndStartSendFlow = async (driver) => { + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + await driver.clickElement('[data-testid="app-footer-actions-button"]'); + await driver.clickElement('[data-testid="select-action-modal-item-send"]'); + } else { + await driver.clickElement('[data-testid="eth-overview-send"]'); + } +}; const sendTransaction = async ( driver, @@ -598,7 +613,11 @@ const sendTransaction = async ( quantity, isAsyncFlow = false, ) => { - await driver.clickElement('[data-testid="eth-overview-send"]'); + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + return; + } + await openActionMenuAndStartSendFlow(driver); await driver.fill('[data-testid="ens-input"]', recipientAddress); await driver.fill('.unit-input__input', quantity); await driver.clickElement({ @@ -647,10 +666,18 @@ const TEST_SEED_PHRASE_TWO = // Usually happens when onboarded to make sure the state is retrieved from metamaskState properly, or after txn is made const locateAccountBalanceDOM = async (driver, ganacheServer) => { const balance = await ganacheServer.getBalance(); - await driver.findElement({ - css: '[data-testid="eth-overview__primary-currency"]', - text: `${balance} ETH`, - }); + if (process.env.MULTICHAIN) { + await driver.clickElement(`[data-testid="home__asset-tab"]`); + await driver.findElement({ + css: '[data-testid="token-balance-overview-currency-display"]', + text: `${balance} ETH`, + }); + } else { + await driver.findElement({ + css: '[data-testid="eth-overview__primary-currency"]', + text: `${balance} ETH`, + }); + } }; const WALLET_PASSWORD = 'correct horse battery staple'; @@ -671,24 +698,22 @@ const generateGanacheOptions = (overrides) => ({ async function waitForAccountRendered(driver) { await driver.waitForSelector( - '[data-testid="eth-overview__primary-currency"]', + process.env.MULTICHAIN + ? '[data-testid="token-balance-overview-currency-display"]' + : '[data-testid="eth-overview__primary-currency"]', ); } -const unlockWallet = async (driver) => { - await driver.fill('#password', 'correct horse battery staple'); +async function unlockWallet(driver) { + await driver.fill('#password', WALLET_PASSWORD); await driver.press('#password', driver.Key.ENTER); -}; +} const logInWithBalanceValidation = async (driver, ganacheServer) => { await unlockWallet(driver); await locateAccountBalanceDOM(driver, ganacheServer); }; -async function sleepSeconds(sec) { - return new Promise((resolve) => setTimeout(resolve, sec * 1000)); -} - function roundToXDecimalPlaces(number, decimalPlaces) { return Math.round(number * 10 ** decimalPlaces) / 10 ** decimalPlaces; } @@ -712,45 +737,29 @@ function genRandInitBal(minETHBal = 10, maxETHBal = 100, decimalPlaces = 4) { return { initialBalance, initialBalanceInHex }; } -async function terminateServiceWorker(driver) { - await driver.openNewPage(SERVICE_WORKER_URL); - - await driver.waitForSelector({ - text: 'Service workers', - tag: 'button', - }); - await driver.clickElement({ - text: 'Service workers', - tag: 'button', - }); - - await driver.delay(tinyDelayMs); - const serviceWorkerElements = await driver.findClickableElements({ - text: 'terminate', - tag: 'span', - }); - - // 1st one is app-init.js; while 2nd one is service-worker.js - await serviceWorkerElements[serviceWorkerElements.length - 1].click(); - - const serviceWorkerTab = await driver.switchToWindowWithTitle( - WINDOW_TITLES.ServiceWorkerSettings, - ); - - await driver.closeWindowHandle(serviceWorkerTab); -} - /** * This method handles clicking the sign button on signature confrimation * screen. * * @param {WebDriver} driver - * @param numHandles + * @param {number} numHandles + * @param {string} locatorID */ -async function clickSignOnSignatureConfirmation(driver, numHandles = 2) { +async function clickSignOnSignatureConfirmation( + driver, + numHandles = 2, // eslint-disable-line no-unused-vars + locatorID = null, +) { await driver.clickElement({ text: 'Sign', tag: 'button' }); - await driver.waitUntilXWindowHandles(numHandles); - await driver.getAllWindowHandles(); + + // #ethSign has a second Sign confirmation button that says "Your funds may be at risk" + if (locatorID === '#ethSign') { + await driver.clickElement({ + text: 'Sign', + tag: 'button', + css: '[data-testid="signature-warning-sign-button"]', + }); + } } /** @@ -788,8 +797,8 @@ async function validateContractDetails(driver) { * @param numHandles */ async function switchToNotificationWindow(driver, numHandles = 3) { - await driver.waitUntilXWindowHandles(numHandles); - const windowHandles = await driver.getAllWindowHandles(); + const windowHandles = await driver.waitUntilXWindowHandles(numHandles); + await driver.switchToWindowWithTitle( WINDOW_TITLES.Notification, windowHandles, @@ -858,7 +867,6 @@ function assertInAnyOrder(requests, assertions) { module.exports = { DAPP_URL, DAPP_ONE_URL, - SERVICE_WORKER_URL, TEST_SEED_PHRASE, TEST_SEED_PHRASE_TWO, PRIVATE_KEY, @@ -897,8 +905,6 @@ module.exports = { convertETHToHexGwei, roundToXDecimalPlaces, generateRandNumBetween, - sleepSeconds, - terminateServiceWorker, clickSignOnSignatureConfirmation, validateContractDetails, switchToNotificationWindow, @@ -911,4 +917,5 @@ module.exports = { onboardingPinExtension, assertInAnyOrder, genRandInitBal, + openActionMenuAndStartSendFlow, }; diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js new file mode 100644 index 000000000000..722e889cce4d --- /dev/null +++ b/test/e2e/json-rpc/switchEthereumChain.spec.js @@ -0,0 +1,317 @@ +const { strict: assert } = require('assert'); +const { + withFixtures, + defaultGanacheOptions, + openDapp, + DAPP_URL, + DAPP_ONE_URL, + unlockWallet, + switchToNotificationWindow, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); + +describe('Switch Ethereum Chain for two dapps', function () { + it('switches the chainId of two dapps when switchEthereumChain of one dapp is confirmed', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTwoTestDapps() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: { port: 8546, chainId: 1338 }, + }, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Window Handling + const windowHandles = await driver.getAllWindowHandles(); + const dappOne = windowHandles[1]; + const dappTwo = windowHandles[2]; + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Confirm switchEtheruemChain + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + + // Switch to Dapp One + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Wait for chain id element to change, there's a page reload. + await driver.waitForSelector({ + css: '#chainId', + text: '0x53a', + }); + + // Dapp One ChainId assertion + await driver.findElement({ css: '#chainId', text: '0x53a' }); + + // Switch to Dapp Two + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // Dapp Two ChainId Assertion + await driver.findElement({ css: '#chainId', text: '0x53a' }); + }, + ); + }); + + it('should queue switchEthereumChain request from second dapp after send tx request', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTwoTestDapps() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: { port: 8546, chainId: 1338 }, + }, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Window Handling + const windowHandles = await driver.getAllWindowHandles(); + const dappOne = windowHandles[1]; + + // Initiate send transaction on Dapp two + await driver.clickElement('#sendButton'); + + // Switch Ethereum chain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Switch to Dapp One + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to tx and confirm send tx. + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + // Delay here after notification for second notification popup for switchEthereumChain + await driver.delay(1000); + + // Switch and confirm to queued notification for switchEthereumChain + await switchToNotificationWindow(driver, 4); + + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + }, + ); + }); + + it('should queue send tx after switchEthereum request with a warning, confirming removes pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTwoTestDapps() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: { port: 8546, chainId: 1338 }, + }, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Window Handling + let windowHandles = await driver.getAllWindowHandles(); + const dappOne = windowHandles[1]; + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to notification of switchEthereumChain + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + + // Switch to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + + // Switch to nofication that should still be switchEthereumChain request but with an warning. + await switchToNotificationWindow(driver, 4); + + await driver.findElement({ + span: 'span', + text: 'Switching networks will cancel all pending confirmations', + }); + + // Confirm switchEtheruemChain with queued pending tx + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + + // Window handles should only be expanded mm, dapp one, dapp 2 (3 total) + await driver.wait(async () => { + windowHandles = await driver.getAllWindowHandles(); + return windowHandles.length === 3; + }); + }, + ); + }); + + it('should queue send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTwoTestDapps() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: { port: 8546, chainId: 1338 }, + }, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Window Handling + const windowHandles = await driver.getAllWindowHandles(); + const dappOne = windowHandles[1]; + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to notification of switchEthereumChain + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + + // Switch to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + + // Switch to notification that should still be switchEthereumChain request but with an warning. + await switchToNotificationWindow(driver, 4); + + await driver.findElement({ + span: 'span', + text: 'Switching networks will cancel all pending confirmations', + }); + + // Cancel switchEtheruemChain with queued pending tx + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + + // Delay for second notification of the pending tx + await driver.delay(1000); + + // Switch to new pending tx notification + await switchToNotificationWindow(driver, 4); + await driver.findElement({ + text: 'Sending ETH', + tag: 'span', + }); + + // Confirm pending tx + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + }, + ); + }); +}); diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index a562fddf5bac..92590c1a0ed0 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -5,11 +5,13 @@ const enLocaleMessages = require('../../app/_locales/en/messages.json'); const createStaticServer = require('../../development/create-static-server'); const { TEST_SEED_PHRASE_TWO, + WALLET_PASSWORD, tinyDelayMs, regularDelayMs, largeDelayMs, veryLargeDelayMs, openDapp, + openActionMenuAndStartSendFlow, } = require('./helpers'); const { buildWebDriver } = require('./webdriver'); const Ganache = require('./ganache'); @@ -85,9 +87,11 @@ describe('MetaMask @no-mmi', function () { }); it('accepts a secure password', async function () { - const password = 'correct horse battery staple'; - await driver.fill('[data-testid="create-password-new"]', password); - await driver.fill('[data-testid="create-password-confirm"]', password); + await driver.fill('[data-testid="create-password-new"]', WALLET_PASSWORD); + await driver.fill( + '[data-testid="create-password-confirm"]', + WALLET_PASSWORD, + ); await driver.clickElement('[data-testid="create-password-terms"]'); await driver.clickElement('[data-testid="create-password-wallet"]'); }); @@ -152,8 +156,8 @@ describe('MetaMask @no-mmi', function () { TEST_SEED_PHRASE_TWO, ); - await driver.fill('#password', 'correct horse battery staple'); - await driver.fill('#confirm-password', 'correct horse battery staple'); + await driver.fill('#password', WALLET_PASSWORD); + await driver.fill('#confirm-password', WALLET_PASSWORD); await driver.clickElement({ text: enLocaleMessages.restore.message, tag: 'button', @@ -162,9 +166,12 @@ describe('MetaMask @no-mmi', function () { }); it('balance renders', async function () { + const balanceSelector = process.env.MULTICHAIN + ? '[data-testid="token-balance-overview-currency-display"]' + : '[data-testid="eth-overview__primary-currency"]'; await driver.waitForSelector({ - css: '[data-testid="eth-overview__primary-currency"] .currency-display-component__text', - text: '1000', + css: `${balanceSelector} .currency-display-component__text`, + text: process.env.MULTICHAIN ? '0' : '1000', }); await driver.delay(regularDelayMs); }); @@ -232,7 +239,7 @@ describe('MetaMask @no-mmi', function () { await driver.delay(tinyDelayMs); const tokenContractAddress = await driver.waitForSelector({ - css: '#tokenAddress', + css: '#tokenAddresses', text: '0x', }); tokenAddress = await tokenContractAddress.getText(); @@ -282,8 +289,14 @@ describe('MetaMask @no-mmi', function () { }); describe('Send token from inside MetaMask', function () { + if (process.env.MULTICHAIN) { + return; + } it('starts to send a transaction', async function () { - await driver.clickElement('[data-testid="eth-overview-send"]'); + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.delay(regularDelayMs); await driver.fill( @@ -433,6 +446,9 @@ describe('MetaMask @no-mmi', function () { }); it('checks balance', async function () { + if (process.env.MULTICHAIN) { + return; + } await driver.clickElement({ text: 'Tokens', tag: 'button', diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index ade050b640f7..f54507bef438 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -27,7 +27,7 @@ const emptyHtmlPage = () => ` * are not requests that the extension itself makes. */ const browserAPIRequestDomains = - /^.*\.(googleapis\.com|google\.com|mozilla\.net|mozilla\.com|gvt1\.com)$/iu; + /^.*\.(googleapis\.com|google\.com|mozilla\.net|mozilla\.com|mozilla\.org|gvt1\.com)$/iu; /** * @typedef {import('mockttp').Mockttp} Mockttp @@ -531,4 +531,4 @@ async function mockTokenNameProvider(server) { } } -module.exports = { setupMocking }; +module.exports = { setupMocking, emptyHtmlPage }; diff --git a/test/e2e/mv3/multiple-restarts.spec.js b/test/e2e/mv3/multiple-restarts.spec.js deleted file mode 100644 index 9dad3a34ab48..000000000000 --- a/test/e2e/mv3/multiple-restarts.spec.js +++ /dev/null @@ -1,426 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - openDapp, - generateGanacheOptions, - WALLET_PASSWORD, - WINDOW_TITLES, - DEFAULT_GANACHE_OPTIONS, - generateRandNumBetween, - sleepSeconds, - terminateServiceWorker, - unlockWallet, - largeDelayMs, - genRandInitBal, - roundToXDecimalPlaces, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); - -describe('MV3 - Restart service worker multiple times', function () { - it('Simple simple send flow within full screen view should still be usable', async function () { - const { initialBalance, initialBalanceInHex } = genRandInitBal(); - - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: generateGanacheOptions({ - accounts: [ - { - secretKey: DEFAULT_GANACHE_OPTIONS.accounts[0].secretKey, - balance: initialBalanceInHex, - }, - ], - }), - title: this.test.title, - driverOptions: { openDevToolsForTabs: true }, - }, - async ({ driver }) => { - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - await assertETHBalance(driver, initialBalance); - - // first send ETH and then terminate SW - const RECIPIENT_ADDRESS = '0x985c30949c92df7a0bd42e0f3e3d539ece98db24'; - const amountFirstTx = roundToXDecimalPlaces( - generateRandNumBetween(0.5, 2), - 4, - ); - - const gasFeesFirstTx = await simpleSendETH( - driver, - amountFirstTx, - RECIPIENT_ADDRESS, - ); - const totalAfterFirstTx = roundToXDecimalPlaces( - initialBalance - amountFirstTx - gasFeesFirstTx, - 4, - ); - - await terminateServiceWorker(driver); - - await assertETHBalance(driver, totalAfterFirstTx); - - // first send ETH #2 and then terminate SW - const amountSecondTx = roundToXDecimalPlaces( - generateRandNumBetween(0.5, 2), - 4, - ); - const gasFeesSecondTx = await simpleSendETH( - driver, - amountSecondTx, - RECIPIENT_ADDRESS, - ); - const totalAfterSecondTx = roundToXDecimalPlaces( - initialBalance - - amountFirstTx - - gasFeesFirstTx - - amountSecondTx - - gasFeesSecondTx, - 4, - ); - - await terminateServiceWorker(driver); - - await assertETHBalance(driver, totalAfterSecondTx); - - // first terminate SW and then send ETH - const amountThirdTx = roundToXDecimalPlaces( - generateRandNumBetween(0.5, 2), - 4, - ); - const gasFeesThirdTx = await simpleSendETH( - driver, - amountThirdTx, - RECIPIENT_ADDRESS, - ); - const totalAfterThirdTx = roundToXDecimalPlaces( - initialBalance - - amountFirstTx - - gasFeesFirstTx - - amountSecondTx - - gasFeesSecondTx - - amountThirdTx - - gasFeesThirdTx, - 4, - ); - - await assertETHBalance(driver, totalAfterThirdTx); - }, - ); - - async function simpleSendETH(driver, value, recipient) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.clickElement('[data-testid="eth-overview-send"]'); - await driver.fill('[data-testid="ens-input"]', recipient); - const formattedValue = `${value}`.replace('.', ','); - await driver.fill('.unit-input__input', formattedValue); - - await driver.clickElement('[data-testid="page-container-footer-next"]'); - - const gasFeesEl = await driver.findElement( - '.transaction-detail-item__detail-values .currency-display-component', - ); - const gasFees = await gasFeesEl.getText(); - - await driver.clickElement('[data-testid="page-container-footer-next"]'); - await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.findElement('.activity-list-item'); - // reset view to assets tab - await driver.clickElement('[data-testid="home__asset-tab"]'); - - return gasFees; - } - - async function assertETHBalance(driver, expectedBalance) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - const isETHBalanceOverviewPresentAndVisible = - await driver.isElementPresentAndVisible({ - css: '[data-testid="eth-overview__primary-currency"]', - text: `${expectedBalance} ETH`, - }); - - assert.equal( - isETHBalanceOverviewPresentAndVisible, - true, - `Balance DOM element should be visible and match ${expectedBalance} ETH.`, - ); - } - }); - - it('Should continue to support add network dApp interactions after service worker re-starts multiple times', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: generateGanacheOptions({ - concurrent: { port: 8546, chainId: 1338 }, - }), - title: this.test.title, - driverOptions: { openDevToolsForTabs: true }, - }, - async ({ driver }) => { - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - await openDapp(driver); - - // Click add Ethereum chain - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#addEthereumChain'); - - // Notification pop up opens - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - let notification = await driver.isElementPresent({ - text: 'Allow this site to add a network?', - tag: 'h3', - }); - assert.ok(notification, 'Dapp action does not appear in Metamask'); - - // Cancel Notification - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - - // Terminate Service Worker - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - await terminateServiceWorker(driver); - - // Click add Ethereum chain #2 - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#addEthereumChain'); - - // Notification pop up opens - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - notification = await driver.isElementPresent({ - text: 'Allow this site to add a network?', - tag: 'h3', - }); - assert.ok(notification, 'Dapp action does not appear in Metamask'); - - // Cancel Notification - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - - // Terminate Service Worker - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await terminateServiceWorker(driver); - - // Click add Ethereum chain #3 - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#addEthereumChain'); - - // Notification pop up opens - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - notification = await driver.isElementPresent({ - text: 'Allow this site to add a network?', - tag: 'h3', - }); - assert.ok(notification, 'Dapp action does not appear in Metamask'); - - // Accept Notification - await driver.clickElement({ text: 'Approve', tag: 'button' }); - await driver.clickElement({ text: 'Switch network', tag: 'button' }); - }, - ); - }); - - it('Should continue to support send ETH dApp interactions after service worker re-starts multiple times', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: generateGanacheOptions({ - concurrent: { port: 8546, chainId: 1338 }, - }), - title: this.test.title, - driverOptions: { openDevToolsForTabs: true }, - }, - async ({ driver }) => { - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - await openDapp(driver); - - await driver.delay(largeDelayMs); - - await clickSendButton(driver); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - await terminateServiceWorker(driver); - - await clickSendButton(driver); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await terminateServiceWorker(driver); - - await clickSendButton(driver); - - await assertNumberOfTransactionsInPopUp(driver, 3); - - await confirmETHSendNotification(driver, 1); - - await assertNumberOfTransactionsInPopUp(driver, 2); - - await confirmETHSendNotification(driver, 1); - - await confirmETHSendNotification(driver, 1); - }, - ); - - async function clickSendButton(driver) { - // Click send button - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - await driver.waitForSelector({ - css: '#sendButton', - text: 'Send', - }); - await driver.clickElement('#sendButton'); - } - - async function confirmETHSendNotification(driver, amount) { - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - - await driver.clickElement({ - text: 'Edit', - tag: 'span', - }); - - await driver.fill('[data-testid="currency-input"]', amount); - - await driver.clickElement({ - text: 'Next', - tag: 'button', - }); - - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - } - - async function assertNumberOfTransactionsInPopUp(driver, number) { - await driver.delay(largeDelayMs); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - - const foundElement = await driver.findElements({ - css: '.confirm-page-container-navigation__navtext', - text: `1 of ${number}`, - }); - - assert.ok(foundElement, true); - } - }); - - it('Should lock wallet when a browser session ends (after turning off the extension)', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: generateGanacheOptions({ - concurrent: { port: 8546, chainId: 1338 }, - }), - title: this.test.title, - }, - async ({ driver }) => { - const { extensionUrl } = driver; - const extensionId = extensionUrl.split('//')[1]; - - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - await reloadExtension(driver, extensionId); - - // ensure extension finishes reloading before reopening full screen extension - await sleepSeconds(0.1); - - await driver.openNewPage(`${extensionUrl}/home.html`); - - const passwordField = await driver.isElementPresent('#password'); - assert.ok( - passwordField, - 'Password screen is not visible. Wallet should have been locked.', - ); - }, - ); - - async function reloadExtension(driver, extensionId) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.openNewPage('chrome://extensions/'); - - // extensions-manager - const extensionsManager = await driver.findElement('extensions-manager'); - - // shadowRoot - const extensionsManagerShadowRoot = await driver.executeScript( - 'return arguments[0][0].shadowRoot', - extensionsManager, - ); - - // cr-view-manager - const viewManager = await extensionsManagerShadowRoot.findElement({ - css: '#viewManager', - }); - - // extensions-item-list - const itemList = await viewManager.findElement({ - css: '#items-list', - }); - - // shadowRoot - const itemListShadowRoot = await driver.executeScript( - 'return arguments[0][0].shadowRoot', - itemList, - ); - - // extension-item - const extensionItem = await await itemListShadowRoot.findElement({ - css: `#${extensionId}`, - }); - - // shadowRoot - const extensionItemShadowRoot = await driver.executeScript( - 'return arguments[0][0].shadowRoot', - extensionItem, - ); - - // cr-icon-button - const devReloadButton = await extensionItemShadowRoot.findElement({ - css: '#dev-reload-button', - }); - - // shadowRoot - const devReloadButtonShadowRoot = await driver.executeScript( - 'return arguments[0][0].shadowRoot', - devReloadButton, - ); - - // cr-icon-button - const reloadBtn = await devReloadButtonShadowRoot.findElement({ - css: '#maskedImage', - }); - - await reloadBtn.click(); - } - }); -}); diff --git a/test/e2e/mv3/phishing-warning-sw-restart.spec.js b/test/e2e/mv3/phishing-warning-sw-restart.spec.js deleted file mode 100644 index 65e9345bbd97..000000000000 --- a/test/e2e/mv3/phishing-warning-sw-restart.spec.js +++ /dev/null @@ -1,78 +0,0 @@ -const { strict: assert } = require('assert'); -const FixtureBuilder = require('../fixture-builder'); -const { - withFixtures, - openDapp, - defaultGanacheOptions, - locateAccountBalanceDOM, - SERVICE_WORKER_URL, - regularDelayMs, - WALLET_PASSWORD, - unlockWallet, - terminateServiceWorker, -} = require('../helpers'); - -const { - setupPhishingDetectionMocks, - BlockProvider, -} = require('../tests/phishing-controller/helpers'); - -describe('Phishing warning page', function () { - const driverOptions = { openDevToolsForTabs: true }; - - it('should restore the transaction when service worker restarts', async function () { - let windowHandles; - - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.title, - driverOptions, - testSpecificMock: async (mockServer) => { - return setupPhishingDetectionMocks(mockServer, { - blockProvider: BlockProvider.MetaMask, - blocklist: ['127.0.0.1'], - }); - }, - dapp: true, - }, - async ({ driver, ganacheServer }) => { - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - // DAPP is detected as phishing page - await openDapp(driver); - - const phishingPageHeader = await driver.findElements({ - text: 'Deceptive site ahead', - tag: 'h1', - }); - assert.ok(phishingPageHeader.length, 1); - - // Restart service worker - await driver.openNewPage(SERVICE_WORKER_URL); - await terminateServiceWorker(driver); - - await driver.delay(regularDelayMs); - // wait until extension is reloaded - windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - await driver.switchToWindow(extension); - await locateAccountBalanceDOM(driver, ganacheServer); - - // Open the dapp site and extension detect it as phishing warning page - await openDapp(driver); - // - extension, dapp, service worker and new dapp - await driver.waitUntilXWindowHandles(4); - - const newPhishingPageHeader = await driver.findElements({ - text: 'Deceptive site ahead', - tag: 'h1', - }); - assert.ok(newPhishingPageHeader.length, 1); - }, - ); - }); -}); diff --git a/test/e2e/mv3/service-worker-restart.spec.js b/test/e2e/mv3/service-worker-restart.spec.js deleted file mode 100644 index 20061834ad80..000000000000 --- a/test/e2e/mv3/service-worker-restart.spec.js +++ /dev/null @@ -1,255 +0,0 @@ -const { strict: assert } = require('assert'); - -const { - convertToHexValue, - withFixtures, - openDapp, - SERVICE_WORKER_URL, - defaultGanacheOptions, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { - ACTION_QUEUE_METRICS_E2E_TEST, -} = require('../../../shared/constants/test-flags'); -const { - MetaMetricsEventName, - MetaMetricsEventCategory, -} = require('../../../shared/constants/metametrics'); - -const numberOfSegmentRequests = 3; - -async function mockSegment(mockServer) { - return await mockServer - .forPost('https://api.segment.io/v1/batch') - .withJsonBodyIncluding({ - batch: [ - { - event: MetaMetricsEventName.ServiceWorkerRestarted, - }, - ], - }) - .times(numberOfSegmentRequests) - .thenCallback(() => { - return { - statusCode: 200, - }; - }); -} - -describe('MV3 - Service worker restart', function () { - let windowHandles; - const driverOptions = { openDevToolsForTabs: true }; - - it('should continue to add new a account when service worker can not restart immediately', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withMetaMetricsController({ - metaMetricsId: 'fake-metrics-id', - participateInMetaMetrics: true, - }) - .withAppStateController({ - [ACTION_QUEUE_METRICS_E2E_TEST]: true, - }) - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.title, - // because of segment - failOnConsoleError: false, - testSpecificMock: mockSegment, - driverOptions, - }, - async ({ driver, mockedEndpoint }) => { - await driver.navigate(); - - // unlock wallet - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // open the account menu - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement({ text: 'Create account', tag: 'div' }); - - await driver.fill('.new-account-create-form__input', 'Test Account'); - - await driver.clickElement({ text: 'Create', tag: 'button' }); - - await driver.openNewPage(SERVICE_WORKER_URL); - await driver.clickElement({ - text: 'Service workers', - tag: 'button', - }); - - await driver.clickElement({ - text: 'terminate', - tag: 'span', - }); - - windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - await driver.switchToWindow(extension); - - // balance renders - await driver.waitForSelector( - { - css: '[class="eth-overview__primary-container"]', - // balance is 0 because we switched to an empty account - text: '0 ETH', - }, - { timeout: 50_000 }, - ); - - await driver.findElement({ text: '0x097...7950', tag: 'div' }); - - // assert that the segment request has been sent through inspecting the mock - await driver.wait(async () => { - const isPending = await mockedEndpoint.isPending(); - return isPending === false; - }, 10_000); - const mockedRequests = await mockedEndpoint.getSeenRequests(); - - assert.equal(mockedRequests.length, numberOfSegmentRequests); - - await assertSWRestartTimeEvent(mockedRequests[0]); - await assertSWRestartTimeEvent(mockedRequests[1]); - await assertSWProcessActionQueueEvent( - mockedRequests[2], - 'addNewAccount', - ); - }, - ); - }); - - it('should restore the transaction when service worker restarts', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.title, - // because of segment - failOnConsoleError: false, - driverOptions, - }, - async ({ driver }) => { - await driver.navigate(); - // log in wallet - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // initialize a transaction of send from dapp - await openDapp(driver); - await driver.clickElement('#sendButton'); - - // A popup window is initialized - windowHandles = await driver.getAllWindowHandles(); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - - // Assert recipient and eth quantity are correct - await assertTransactionDetails(driver); - - // Restart service worker in a new window - // Because if we stay in the same window we will lose the popup when opening a new tab - await driver.switchToNewWindow(); - await driver.openNewURL(SERVICE_WORKER_URL); - windowHandles = await driver.getAllWindowHandles(); - // MM expanded view, Dapp, Notification popup, console and service worker - await driver.waitUntilXWindowHandles(5); - await driver.clickElement({ - text: 'terminate', - tag: 'span', - }); - - // Should still have only 1 popup - windowHandles = await driver.getAllWindowHandles(); - await driver.waitUntilXWindowHandles(5); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - - // And popup has the same value - await assertTransactionDetails(driver); - - // Confirm the transaction - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.switchToWindowWithTitle('MetaMask', windowHandles); - await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .transaction-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); -}); - -async function assertSWRestartTimeEvent(request) { - assert.equal(request.url, 'https://api.segment.io/v1/batch'); - - assert.equal(request.body.json.batch.length, 1); - - const [firstResult] = request.body.json.batch; - - assert.equal(firstResult.event, MetaMetricsEventName.ServiceWorkerRestarted); - - assert.equal( - typeof firstResult.properties.service_worker_restarted_time, - 'number', - ); - - assert.equal(firstResult.properties.service_worker_restarted_time > 0, true); - assert.equal( - firstResult.properties.category, - MetaMetricsEventCategory.ServiceWorkers, - ); - assert.equal(firstResult.properties.chain_id, convertToHexValue(1337)); - assert.equal(firstResult.properties.environment_type, 'background'); - assert.equal(firstResult.properties.locale, 'en'); -} - -async function assertSWProcessActionQueueEvent(request, method) { - assert.equal(request.url, 'https://api.segment.io/v1/batch'); - - assert.equal(request.body.json.batch.length, 1); - - const [firstResult] = request.body.json.batch; - - assert.equal(firstResult.event, MetaMetricsEventName.ServiceWorkerRestarted); - - assert.equal( - firstResult.properties.service_worker_action_queue_methods.indexOf( - method, - ) !== '-1', - true, - ); - assert.equal( - firstResult.properties.category, - MetaMetricsEventCategory.ServiceWorkers, - ); - assert.equal(firstResult.properties.chain_id, convertToHexValue(1337)); - assert.equal(firstResult.properties.environment_type, 'background'); - assert.equal(firstResult.properties.locale, 'en'); -} - -async function assertTransactionDetails(driver) { - const TRUNCATED_RECIPIENT_ADDRESS = '0x0c5...AaFb'; - const recipientAddress = await driver.findElement( - '[data-testid="sender-to-recipient__name"]', - ); - assert.equal(await recipientAddress.getText(), TRUNCATED_RECIPIENT_ADDRESS); - const transactionAmounts = await driver.findElements( - '.currency-display-component__text', - ); - const transactionAmount = transactionAmounts[0]; - assert.equal(await transactionAmount.getText(), '0'); -} diff --git a/test/e2e/nft/view-erc1155-details.spec.js b/test/e2e/nft/view-erc1155-details.spec.js deleted file mode 100644 index 29794412cf10..000000000000 --- a/test/e2e/nft/view-erc1155-details.spec.js +++ /dev/null @@ -1,65 +0,0 @@ -const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); - -describe('View ERC1155 NFT details', function () { - const smartContract = SMART_CONTRACTS.ERC1155; - const ganacheOptions = { - accounts: [ - { - secretKey: - '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', - balance: convertToHexValue(25000000000000000000), - }, - ], - }; - - it('user should be able to view ERC1155 NFT details @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().withNftControllerERC1155().build(), - ganacheOptions, - smartContract, - title: this.test.title, - }, - async ({ driver }) => { - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // Click to open the NFT details page and check displayed account - await driver.clickElement('[data-testid="home__nfts-tab"]'); - const importedNftImage = await driver.findVisibleElement( - '.nft-item__container', - ); - await importedNftImage.click(); - const detailsPageAccount = await driver.findElement( - '.asset-breadcrumb span:nth-of-type(2)', - ); - assert.equal(await detailsPageAccount.getText(), 'Account 1'); - - // Check the displayed ERC1155 NFT details - const nftName = await driver.findElement('.nft-details__info h4'); - assert.equal(await nftName.getText(), 'Rocks'); - - const nftDescription = await driver.findElement( - '.nft-details__info h6:nth-of-type(2)', - ); - assert.equal( - await nftDescription.getText(), - 'This is a collection of Rock NFTs.', - ); - - const nftImage = await driver.findElement('.nft-item__container'); - assert.equal(await nftImage.isDisplayed(), true); - - const nftContract = await driver.findElement( - '.nft-details__contract-wrapper', - ); - assert.equal(await nftContract.getText(), '0x581c3...45947'); - }, - ); - }); -}); diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index cc73837fb2cf..0ef581bcf2c7 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -1,13 +1,23 @@ const path = require('path'); -const { promises: fs } = require('fs'); +const fs = require('fs'); +const { execSync } = require('child_process'); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const { runInShell } = require('../../development/lib/run-command'); const { exitWithError } = require('../../development/lib/exit-with-error'); const { loadBuildTypesConfig } = require('../../development/lib/build-type'); +// These tests should only be run on Flask for now. +const FLASK_ONLY_TESTS = [ + 'test-snap-lifecycle.spec.js', + 'test-snap-get-locale.spec.js', + 'petnames.spec.js', +]; + const getTestPathsForTestDir = async (testDir) => { - const testFilenames = await fs.readdir(testDir, { withFileTypes: true }); + const testFilenames = await fs.promises.readdir(testDir, { + withFileTypes: true, + }); const testPaths = []; for (const itemInDirectory of testFilenames) { @@ -16,7 +26,7 @@ const getTestPathsForTestDir = async (testDir) => { if (itemInDirectory.isDirectory()) { const subDirPaths = await getTestPathsForTestDir(fullPath); testPaths.push(...subDirPaths); - } else if (fullPath.endsWith('.spec.js')) { + } else if (fullPath.endsWith('.spec.js') || fullPath.endsWith('.spec.ts')) { testPaths.push(fullPath); } } @@ -24,15 +34,37 @@ const getTestPathsForTestDir = async (testDir) => { return testPaths; }; -// Heavily inspired by: https://stackoverflow.com/a/51514813 -// Splits the array into totalChunks chunks with a decent spread of items in each chunk -function chunk(array, totalChunks) { - const copyArray = [...array]; - const result = []; - for (let chunkIndex = totalChunks; chunkIndex > 0; chunkIndex--) { - result.push(copyArray.splice(0, Math.ceil(copyArray.length / chunkIndex))); +// For running E2Es in parallel in CI +function runningOnCircleCI(testPaths) { + const fullTestList = testPaths.join('\n'); + console.log('Full test list:', fullTestList); + fs.writeFileSync('test/test-results/fullTestList.txt', fullTestList); + + // Use `circleci tests run` on `testList.txt` to do two things: + // 1. split the test files into chunks based on how long they take to run + // 2. support "Rerun failed tests" on CircleCI + const result = execSync( + 'circleci tests run --command=">test/test-results/myTestList.txt xargs echo" --split-by=timings --timings-type=filename --time-default=30s < test/test-results/fullTestList.txt', + ).toString('utf8'); + + // Report if no tests found, exit gracefully + if (result.indexOf('There were no tests found') !== -1) { + console.log(`run-all.js info: Skipping this node because "${result}"`); + return []; + } + + // If there's no text file, it means this node has no tests, so exit gracefully + if (!fs.existsSync('test/test-results/myTestList.txt')) { + console.log( + 'run-all.js info: Skipping this node because there is no myTestList.txt', + ); + return []; } - return result; + + // take the space-delimited result and split into an array + return fs + .readFileSync('test/test-results/myTestList.txt', { encoding: 'utf8' }) + .split(' '); } async function main() { @@ -57,14 +89,6 @@ async function main() { description: `Run only mmi related tests`, type: 'boolean', }) - .option('snaps', { - description: `run snaps e2e tests`, - type: 'boolean', - }) - .option('mv3', { - description: `run mv3 specific e2e tests`, - type: 'boolean', - }) .option('rpc', { description: `run json-rpc specific e2e tests`, type: 'boolean', @@ -100,8 +124,6 @@ async function main() { debug, retries, mmi, - snaps, - mv3, rpc, buildType, updateSnapshot, @@ -110,46 +132,39 @@ async function main() { let testPaths; - if (snaps) { + // These test paths should be run against both flask and main builds. + // Eventually we should move all features to this array and test them all + // on every build type in which they are running to avoid regressions across + // builds. + const featureTestsOnMain = [ + ...(await getTestPathsForTestDir(path.join(__dirname, 'accounts'))), + ...(await getTestPathsForTestDir(path.join(__dirname, 'snaps'))), + ]; + + if (buildType === 'flask') { testPaths = [ - ...(await getTestPathsForTestDir(path.join(__dirname, 'snaps'))), - ...(await getTestPathsForTestDir(path.join(__dirname, 'accounts'))), ...(await getTestPathsForTestDir(path.join(__dirname, 'flask'))), + ...featureTestsOnMain, ]; } else if (rpc) { const testDir = path.join(__dirname, 'json-rpc'); testPaths = await getTestPathsForTestDir(testDir); - } else { + } else if (buildType === 'mmi') { const testDir = path.join(__dirname, 'tests'); testPaths = [ ...(await getTestPathsForTestDir(testDir)), - ...(await getTestPathsForTestDir(path.join(__dirname, 'swaps'))), - ...(await getTestPathsForTestDir(path.join(__dirname, 'nft'))), - ...(await getTestPathsForTestDir(path.join(__dirname, 'metrics'))), path.join(__dirname, 'metamask-ui.spec.js'), ]; - - if (mv3) { - testPaths.push( - ...(await getTestPathsForTestDir(path.join(__dirname, 'mv3'))), - ); - } - } - - // These tests should only be run on Flask for now. - if (buildType !== 'flask') { - const filteredTests = [ - 'test-snap-lifecycle.spec.js', - 'test-snap-get-locale.spec.js', - 'ppom-blockaid-alert.spec.js', - 'ppom-blockaid-alert-erc20-approval.spec.js', - 'ppom-blockaid-alert-erc20-transfer.spec.js', - 'ppom-toggle-settings.spec.js', - 'petnames.spec.js', - ]; - testPaths = testPaths.filter((p) => - filteredTests.every((filteredTest) => !p.endsWith(filteredTest)), + } else { + const testDir = path.join(__dirname, 'tests'); + const filteredFlaskAndMainTests = featureTestsOnMain.filter((p) => + FLASK_ONLY_TESTS.every((filteredTest) => !p.endsWith(filteredTest)), ); + testPaths = [ + ...(await getTestPathsForTestDir(testDir)), + ...filteredFlaskAndMainTests, + path.join(__dirname, 'metamask-ui.spec.js'), + ]; } const runE2eTestPath = path.join(__dirname, 'run-e2e-test.js'); @@ -174,16 +189,23 @@ async function main() { args.push('--mmi'); } - // For running E2Es in parallel in CI - const currentChunkIndex = process.env.CIRCLE_NODE_INDEX ?? 0; - const totalChunks = process.env.CIRCLE_NODE_TOTAL ?? 1; - const chunks = chunk(testPaths, totalChunks); - const currentChunk = chunks[currentChunkIndex]; + await fs.promises.mkdir('test/test-results/e2e', { recursive: true }); + + let myTestList; + if (process.env.CIRCLECI) { + myTestList = runningOnCircleCI(testPaths); + } else { + myTestList = testPaths; + } + + console.log('My test list:', myTestList); - for (const testPath of currentChunk) { - const dir = 'test/test-results/e2e'; - fs.mkdir(dir, { recursive: true }); - await runInShell('node', [...args, testPath]); + // spawn `run-e2e-test.js` for each test in myTestList + for (let testPath of myTestList) { + if (testPath !== '') { + testPath = testPath.replace('\n', ''); // sometimes there's a newline at the end of the testPath + await runInShell('node', [...args, testPath]); + } } } diff --git a/test/e2e/run-e2e-test.js b/test/e2e/run-e2e-test.js index 4f1b3d66619a..ed25d13443a0 100644 --- a/test/e2e/run-e2e-test.js +++ b/test/e2e/run-e2e-test.js @@ -107,8 +107,6 @@ async function main() { throw error; } - const testFileName = path.basename(e2eTestPath); - if (debug) { process.env.E2E_DEBUG = 'true'; } @@ -144,19 +142,17 @@ async function main() { fs.mkdir(dir, { recursive: true }); await retry({ retries, retryUntilFailure }, async () => { - await runInShell( - 'yarn', - [ - 'mocha', - `--config=${configFile}`, - `--timeout=${testTimeoutInMilliseconds}`, - '--reporter=xunit', - ...extraArgs, - e2eTestPath, - exit, - ], - `${dir}/${testFileName}.xml`, - ); + await runInShell('yarn', [ + 'mocha', + `--config=${configFile}`, + `--timeout=${testTimeoutInMilliseconds}`, + '--reporter=mocha-junit-reporter', + '--reporter-options', + `mochaFile=test/test-results/e2e/[hash].xml`, + ...extraArgs, + e2eTestPath, + exit, + ]); }); } diff --git a/test/e2e/snaps/test-snap-bip-32.spec.js b/test/e2e/snaps/test-snap-bip-32.spec.js index 5b69c576619c..468167e70124 100644 --- a/test/e2e/snaps/test-snap-bip-32.spec.js +++ b/test/e2e/snaps/test-snap-bip-32.spec.js @@ -1,4 +1,9 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + unlockWallet, + switchToNotificationWindow, + WINDOW_TITLES, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -21,10 +26,7 @@ describe('Test Snap bip-32', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page and connect await driver.driver.get(TEST_SNAPS_WEBSITE_URL); @@ -38,15 +40,7 @@ describe('Test Snap bip-32', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -54,7 +48,7 @@ describe('Test Snap bip-32', function () { await driver.waitForSelector({ text: 'Install' }); - await driver.clickElement('[data-testid="snap-install-scroll"]'); + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); await driver.clickElement({ text: 'Install', @@ -80,7 +74,7 @@ describe('Test Snap bip-32', function () { }); // switch back to test-snaps window - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // wait for npm installation success await driver.waitForSelector({ @@ -114,19 +108,19 @@ describe('Test Snap bip-32', function () { await driver.clickElement('#sendBip32-secp256k1'); // hit 'approve' on the signature confirmation - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Approve', tag: 'button', }); // switch back to the test-snaps window - windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + let windowHandles = await driver.waitUntilXWindowHandles( + 1, + 1000, + 10000, + ); + await driver.switchToWindow(windowHandles[0]); // check results of the secp256k1 signature with waitForSelector await driver.waitForSelector({ @@ -144,18 +138,14 @@ describe('Test Snap bip-32', function () { await driver.clickElement('#sendBip32-ed25519'); // hit 'approve' on the custom confirm - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Approve', tag: 'button', }); windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindow(windowHandles[0]); // check results of ed25519 signature with waitForSelector await driver.waitForSelector({ diff --git a/test/e2e/snaps/test-snap-bip-44.spec.js b/test/e2e/snaps/test-snap-bip-44.spec.js index 48b2bbfc26a3..cb913fd7a78e 100644 --- a/test/e2e/snaps/test-snap-bip-44.spec.js +++ b/test/e2e/snaps/test-snap-bip-44.spec.js @@ -1,4 +1,9 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + unlockWallet, + switchToNotificationWindow, + WINDOW_TITLES, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -22,10 +27,7 @@ describe('Test Snap bip-44', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page and connect await driver.driver.get(TEST_SNAPS_WEBSITE_URL); @@ -39,15 +41,7 @@ describe('Test Snap bip-44', function () { await driver.delay(1000); // switch to metamask extension and click connect and approve - let windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -76,7 +70,7 @@ describe('Test Snap bip-44', function () { }); // switch back to test-snaps window - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // wait for npm installation success await driver.waitForSelector({ @@ -102,19 +96,19 @@ describe('Test Snap bip-44', function () { await driver.clickElement('#signBip44Message'); // Switch to approve signature message window and approve - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Approve', tag: 'button', }); // switch back to test-snaps page - windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + const windowHandles = await driver.waitUntilXWindowHandles( + 1, + 1000, + 10000, + ); + await driver.switchToWindow(windowHandles[0]); // check the results of the message signature using waitForSelector await driver.waitForSelector({ diff --git a/test/e2e/snaps/test-snap-cronjob.spec.js b/test/e2e/snaps/test-snap-cronjob.spec.js index 8495e953d409..b3935cc5d38b 100644 --- a/test/e2e/snaps/test-snap-cronjob.spec.js +++ b/test/e2e/snaps/test-snap-cronjob.spec.js @@ -1,5 +1,4 @@ -const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); +const { withFixtures, unlockWallet } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -24,9 +23,7 @@ describe('Test Snap Cronjob', function () { async ({ driver }) => { await driver.navigate(); - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page and connect await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -84,12 +81,10 @@ describe('Test Snap Cronjob', function () { await driver.delay(1000); // look for the dialog popup to verify cronjob fired - const error = await driver.findElement('.snap-delineator__content'); - const text = await error.getText(); - assert.equal( - text.includes(`Cronjob\nThis dialog was triggered by a cronjob.`), - true, - ); + await driver.findElement({ + css: '.snap-delineator__content', + text: 'This dialog was triggered by a cronjob', + }); // try to click on the Ok button and pass test if it works await driver.clickElement({ diff --git a/test/e2e/snaps/test-snap-management.spec.js b/test/e2e/snaps/test-snap-management.spec.js index 6af006bd9ab1..4d6f8e922f1b 100644 --- a/test/e2e/snaps/test-snap-management.spec.js +++ b/test/e2e/snaps/test-snap-management.spec.js @@ -1,5 +1,5 @@ const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); +const { withFixtures, unlockWallet } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -23,10 +23,7 @@ describe('Test Snap Management', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // open a new tab and navigate to test snaps page and connect await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -78,10 +75,6 @@ describe('Test Snap Management', function () { '[data-testid="account-options-menu-button"]', ); - // try to click on the notification item - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.delay(1000); - // try to click on the snaps item await driver.clickElement({ text: 'Snaps', @@ -148,14 +141,11 @@ describe('Test Snap Management', function () { await driver.delay(1000); // check the results of the removal - await driver.delay(2000); - const removeResult = await driver.findElement( - '.snap-list-tab__container--no-snaps_inner', - ); - assert.equal( - await removeResult.getText(), - "You don't have any snaps installed.", - ); + await driver.findElement({ + css: '.mm-box', + text: "You don't have any snaps installed.", + tag: 'p', + }); }, ); }); diff --git a/test/e2e/snaps/test-snap-notification.spec.js b/test/e2e/snaps/test-snap-notification.spec.js index fd272ea2ca16..d9eaa4436673 100644 --- a/test/e2e/snaps/test-snap-notification.spec.js +++ b/test/e2e/snaps/test-snap-notification.spec.js @@ -1,5 +1,4 @@ -const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); +const { withFixtures, unlockWallet } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -23,10 +22,7 @@ describe('Test Snap Notification', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -88,10 +84,10 @@ describe('Test Snap Notification', function () { await driver.clickElement( '[data-testid="account-options-menu-button"]', ); - const notificationResult = await driver.findElement( - '[data-testid="global-menu-notification-count"]', - ); - assert.equal(await notificationResult.getText(), '1'); + await driver.findElement({ + css: '[data-testid="global-menu-notification-count"]', + text: '1', + }); await driver.clickElement('.menu__background'); // try to click on the account menu icon (via xpath) @@ -108,13 +104,10 @@ describe('Test Snap Notification', function () { await driver.delay(500); // look for the correct text in notifications (via xpath) - const notificationResultMessage = await driver.findElement( - '.notifications__item__details__message', - ); - assert.equal( - await notificationResultMessage.getText(), - 'Hello from within MetaMask!', - ); + await driver.findElement({ + css: '.notifications__item__details__message', + text: 'Hello from within MetaMask!', + }); }, ); }); diff --git a/test/e2e/snaps/test-snap-revoke-perm.spec.js b/test/e2e/snaps/test-snap-revoke-perm.spec.js index 8001a41a8949..5f56f9325246 100644 --- a/test/e2e/snaps/test-snap-revoke-perm.spec.js +++ b/test/e2e/snaps/test-snap-revoke-perm.spec.js @@ -1,4 +1,9 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + WINDOW_TITLES, + switchToNotificationWindow, + unlockWallet, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -22,10 +27,7 @@ describe('Test Snap revoke permission', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page and connect await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); @@ -39,15 +41,7 @@ describe('Test Snap revoke permission', function () { await driver.delay(1000); // switch to metamask extension and click connect - const windowHandles = await driver.waitUntilXWindowHandles( - 3, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 3); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -68,7 +62,7 @@ describe('Test Snap revoke permission', function () { }); // switch to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // wait for npm installation success await driver.waitForSelector({ @@ -85,15 +79,7 @@ describe('Test Snap revoke permission', function () { await driver.clickElement('#sendEthproviderAccounts'); // switch to metamask window and click through confirmations - const windowHandles2 = await driver.waitUntilXWindowHandles( - 3, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles2, - ); + await switchToNotificationWindow(driver, 3); await driver.clickElement({ text: 'Next', tag: 'button', @@ -105,7 +91,7 @@ describe('Test Snap revoke permission', function () { }); // switch to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // check the results of the message signature using waitForSelector await driver.waitForSelector({ @@ -114,8 +100,9 @@ describe('Test Snap revoke permission', function () { }); // switch to the original MM tab - const extensionPage = windowHandles[0]; - await driver.switchToWindow(extensionPage); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); await driver.delay(1000); // click on the global action menu @@ -123,10 +110,6 @@ describe('Test Snap revoke permission', function () { '[data-testid="account-options-menu-button"]', ); - // try to click on the notification item - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.delay(1000); - // try to click on the snaps item await driver.clickElement({ text: 'Snaps', @@ -149,7 +132,7 @@ describe('Test Snap revoke permission', function () { }); // switch to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // find and click on send get version const snapButton4 = await driver.findElement( @@ -160,15 +143,8 @@ describe('Test Snap revoke permission', function () { await driver.clickElement('#sendEthproviderAccounts'); // switch to metamask window and click through confirmations - const windowHandles3 = await driver.waitUntilXWindowHandles( - 3, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles3, - ); + await driver.delay(500); + await switchToNotificationWindow(driver, 3); await driver.clickElement({ text: 'Next', tag: 'button', @@ -180,7 +156,7 @@ describe('Test Snap revoke permission', function () { }); // switch to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); // check the results of the message signature using waitForSelector await driver.waitForSelector({ diff --git a/test/e2e/snaps/test-snap-rpc.spec.js b/test/e2e/snaps/test-snap-rpc.spec.js index 2c955e9e7d6e..2c6e7421695a 100644 --- a/test/e2e/snaps/test-snap-rpc.spec.js +++ b/test/e2e/snaps/test-snap-rpc.spec.js @@ -1,4 +1,8 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + switchToNotificationWindow, + unlockWallet, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -22,10 +26,7 @@ describe('Test Snap RPC', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // navigate to test snaps page await driver.driver.get(TEST_SNAPS_WEBSITE_URL); @@ -39,15 +40,7 @@ describe('Test Snap RPC', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -55,7 +48,7 @@ describe('Test Snap RPC', function () { await driver.waitForSelector({ text: 'Install' }); - await driver.clickElement('[data-testid="snap-install-scroll"]'); + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); await driver.clickElement({ text: 'Install', @@ -82,7 +75,7 @@ describe('Test Snap RPC', function () { }); // switch back to test-snaps window - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle('Test Snaps'); const snapButton2 = await driver.findElement('#connectjson-rpc'); await driver.scrollToElement(snapButton2); @@ -90,11 +83,7 @@ describe('Test Snap RPC', function () { await driver.clickElement('#connectjson-rpc'); await driver.delay(1000); - windowHandles = await driver.waitUntilXWindowHandles(2, 1000, 10000); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -114,7 +103,7 @@ describe('Test Snap RPC', function () { tag: 'button', }); - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + await driver.switchToWindowWithTitle('Test Snaps'); // wait for npm installation success await driver.waitForSelector({ diff --git a/test/e2e/snaps/test-snap-update.spec.js b/test/e2e/snaps/test-snap-update.spec.js index ea8dcbc11962..cab5e416e624 100644 --- a/test/e2e/snaps/test-snap-update.spec.js +++ b/test/e2e/snaps/test-snap-update.spec.js @@ -1,4 +1,8 @@ -const { withFixtures } = require('../helpers'); +const { + withFixtures, + switchToNotificationWindow, + unlockWallet, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); @@ -22,10 +26,7 @@ describe('Test Snap update', function () { }, async ({ driver }) => { await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // open a new tab and navigate to test snaps page and connect await driver.driver.get(TEST_SNAPS_WEBSITE_URL); @@ -39,15 +40,7 @@ describe('Test Snap update', function () { await driver.delay(1000); // switch to metamask extension and click connect - let windowHandles = await driver.waitUntilXWindowHandles( - 2, - 1000, - 10000, - ); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.clickElement({ text: 'Connect', tag: 'button', @@ -55,7 +48,7 @@ describe('Test Snap update', function () { await driver.waitForSelector({ text: 'Install' }); - await driver.clickElement('[data-testid="snap-install-scroll"]'); + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); await driver.clickElement({ text: 'Install', @@ -82,7 +75,12 @@ describe('Test Snap update', function () { }); // navigate to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + let windowHandles = await driver.waitUntilXWindowHandles( + 1, + 1000, + 10000, + ); + await driver.switchToWindow(windowHandles[0]); // wait for npm installation success await driver.waitForSelector({ @@ -98,16 +96,11 @@ describe('Test Snap update', function () { await driver.delay(1000); // switch to metamask extension and update - await driver.waitUntilXWindowHandles(2, 1000, 10000); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await switchToNotificationWindow(driver, 2); await driver.waitForSelector({ text: 'Update' }); - await driver.clickElement('[data-testid="snap-update-scroll"]'); + await driver.clickElementSafe('[data-testid="snap-update-scroll"]'); await driver.clickElement({ text: 'Update', @@ -122,7 +115,8 @@ describe('Test Snap update', function () { }); // navigate to test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + windowHandles = await driver.waitUntilXWindowHandles(1, 1000, 10000); + await driver.switchToWindow(windowHandles[0]); // look for the correct version text await driver.waitForSelector({ diff --git a/test/e2e/tests/account-token-list.spec.js b/test/e2e/tests/account-token-list.spec.js index 233f4c8629d5..65d1dd8f31ba 100644 --- a/test/e2e/tests/account-token-list.spec.js +++ b/test/e2e/tests/account-token-list.spec.js @@ -24,18 +24,20 @@ describe('Settings', function () { await driver.clickElement('[data-testid="home__asset-tab"]'); - const tokenValue = '0 ETH'; + const tokenValue = process.env.MULTICHAIN ? '0\nETH' : '0 ETH'; const tokenListAmount = await driver.findElement( - '[data-testid="multichain-token-list-item-value"]', + process.env.MULTICHAIN + ? '[data-testid="token-balance-overview-currency-display"]' + : '[data-testid="multichain-token-list-item-value"]', ); assert.equal(await tokenListAmount.getText(), tokenValue); await driver.clickElement('[data-testid="account-menu-icon"]'); const accountTokenValue = await driver.waitForSelector( - '.currency-display-component__text', + '.multichain-account-list-item .currency-display-component__text', ); - assert.equal(await accountTokenValue.getText(), '0', `ETH`); + assert.equal(await accountTokenValue.getText(), '0', 'ETH'); }, ); }); @@ -67,18 +69,21 @@ describe('Settings', function () { '.settings-page__header__title-container__close-button', ); await driver.clickElement('[data-testid="home__asset-tab"]'); - const tokenValue = '0 ETH'; + + const tokenValue = process.env.MULTICHAIN ? '0\nETH' : '0 ETH'; const tokenListAmount = await driver.findElement( - '[data-testid="multichain-token-list-item-value"]', + process.env.MULTICHAIN + ? '[data-testid="token-balance-overview-currency-display"]' + : '[data-testid="multichain-token-list-item-value"]', ); assert.equal(await tokenListAmount.getText(), tokenValue); await driver.clickElement('[data-testid="account-menu-icon"]'); const accountTokenValue = await driver.waitForSelector( - '.currency-display-component__text', + '.multichain-account-list-item .currency-display-component__text', ); - assert.equal(await accountTokenValue.getText(), '0', `ETH`); + assert.equal(await accountTokenValue.getText(), '0', 'ETH'); }, ); }); diff --git a/test/e2e/tests/add-account.spec.js b/test/e2e/tests/add-account.spec.js index 5b2551b86294..0dbd14f2b951 100644 --- a/test/e2e/tests/add-account.spec.js +++ b/test/e2e/tests/add-account.spec.js @@ -9,13 +9,13 @@ const { convertToHexValue, regularDelayMs, unlockWallet, + WALLET_PASSWORD, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { shortenAddress } = require('../../../ui/helpers/utils/util'); describe('Add account', function () { - const testPassword = 'correct horse battery staple'; const firstAccount = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; const secondAccount = '0x3ED0eE22E0685Ebbf07b2360A8331693c413CC59'; @@ -59,6 +59,9 @@ describe('Add account', function () { }); it('should not affect public address when using secret recovery phrase to recover account with non-zero balance @no-mmi', async function () { + if (process.env.MULTICHAIN) { + return; + } await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), @@ -73,13 +76,15 @@ describe('Add account', function () { await completeImportSRPOnboardingFlow( driver, TEST_SEED_PHRASE, - testPassword, + WALLET_PASSWORD, ); // Check address of 1st account await waitForAccountRendered(driver); await driver.findElement({ - css: '.multichain-address-copy-button', + css: process.env.MULTICHAIN + ? '.multichain-account-picker-container p' + : '.multichain-address-copy-button', text: shortenAddress(firstAccount), }); @@ -93,12 +98,13 @@ describe('Add account', function () { ); await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.clickElement({ text: 'Create', tag: 'button' }); - await waitForAccountRendered(driver); // Check address of 2nd account await waitForAccountRendered(driver); await driver.findElement({ - css: '.multichain-address-copy-button', + css: process.env.MULTICHAIN + ? '.multichain-account-picker-container p' + : '.multichain-address-copy-button', text: shortenAddress(secondAccount), }); @@ -143,11 +149,16 @@ describe('Add account', function () { // Land in 1st account home page await driver.findElement('.home__main-view'); - await waitForAccountRendered(driver); + + if (!process.env.MULTICHAIN) { + await waitForAccountRendered(driver); + } // Check address of 1st account await driver.findElement({ - css: '.multichain-address-copy-button', + css: process.env.MULTICHAIN + ? '.multichain-account-picker-container p' + : '.multichain-address-copy-button', text: shortenAddress(firstAccount), }); @@ -160,7 +171,9 @@ describe('Add account', function () { await driver.clickElement(accountTwoSelector); await driver.findElement({ - css: '.multichain-address-copy-button', + css: process.env.MULTICHAIN + ? '.multichain-account-picker-container p' + : '.multichain-address-copy-button', text: shortenAddress(secondAccount), }); }, diff --git a/test/e2e/tests/add-custom-network.spec.js b/test/e2e/tests/add-custom-network.spec.js index 82233d09b34b..c4c3c3c1a31c 100644 --- a/test/e2e/tests/add-custom-network.spec.js +++ b/test/e2e/tests/add-custom-network.spec.js @@ -458,7 +458,7 @@ describe('Custom network', function () { }); // verify network switched const networkDisplayed = await driver.findElement({ - tag: 'p', + tag: 'span', text: 'Arbitrum One', }); assert.equal( diff --git a/test/e2e/tests/add-multiple-tokens.spec.js b/test/e2e/tests/add-multiple-tokens.spec.js new file mode 100644 index 000000000000..a249e3ce36b1 --- /dev/null +++ b/test/e2e/tests/add-multiple-tokens.spec.js @@ -0,0 +1,110 @@ +const { strict: assert } = require('assert'); +const { + withFixtures, + defaultGanacheOptions, + openDapp, + switchToNotificationWindow, + WINDOW_TITLES, + DAPP_URL, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); + +describe('Multiple ERC20 Watch Asset', function () { + // TODO: This assertion will change once the method is fixed. + it('should show multiple erc20 watchAsset token list, only confirms one bug', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await openDapp(driver, undefined, DAPP_URL); + + // Create Token 1 + await driver.clickElement({ text: 'Create Token', tag: 'button' }); + await switchToNotificationWindow(driver); + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Wait for token 1 address to populate in dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.wait(async () => { + const tokenAddressesElement = await driver.findElement( + '#tokenAddresses', + ); + const tokenAddresses = await tokenAddressesElement.getText(); + return tokenAddresses !== ''; + }, 10000); + + // Create Token 2 + await driver.clickElement({ text: 'Create Token', tag: 'button' }); + await switchToNotificationWindow(driver); + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Wait for token 2 address to populate in dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.wait(async () => { + const tokenAddressesElement = await driver.findElement( + '#tokenAddresses', + ); + const tokenAddresses = await tokenAddressesElement.getText(); + return tokenAddresses.split(',').length === 2; + }, 10000); + + // Create Token 3 + await driver.clickElement({ text: 'Create Token', tag: 'button' }); + await switchToNotificationWindow(driver); + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Wait for token 3 address to populate in dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.wait(async () => { + const tokenAddressesElement = await driver.findElement( + '#tokenAddresses', + ); + const tokenAddresses = await tokenAddressesElement.getText(); + return tokenAddresses.split(',').length === 3; + }, 10000); + + // Watch all 3 tokens + await driver.clickElement({ + text: 'Add Token(s) to Wallet', + tag: 'button', + }); + + // Switch to watchAsset notificaiton + await switchToNotificationWindow(driver); + const multipleSuggestedtokens = await driver.findElements( + '.confirm-add-suggested-token__token-list-item', + ); + + // Confirm all 3 tokens are present as suggested token list + assert.equal(multipleSuggestedtokens.length, 3); + await driver.findClickableElement({ text: 'Add token', tag: 'button' }); + await driver.clickElement({ text: 'Add token', tag: 'button' }); + + // Switch to fullscreen extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Check all three tokens have been added to the token list. + const addedTokens = await driver.findElements({ + tag: 'p', + text: 'TST', + }); + assert.equal(addedTokens.length, 3); + }, + ); + }); +}); diff --git a/test/e2e/tests/address-book.spec.js b/test/e2e/tests/address-book.spec.js index d88ea9e20014..0627cf3564e9 100644 --- a/test/e2e/tests/address-book.spec.js +++ b/test/e2e/tests/address-book.spec.js @@ -3,6 +3,7 @@ const { convertToHexValue, withFixtures, logInWithBalanceValidation, + openActionMenuAndStartSendFlow, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -42,7 +43,11 @@ describe('Address Book', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); + await openActionMenuAndStartSendFlow(driver); + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + return; + } const recipientRowTitle = await driver.findElement( '.send__select-recipient-wrapper__group-item__title', ); diff --git a/test/e2e/tests/advanced-settings.spec.js b/test/e2e/tests/advanced-settings.spec.js index 58f5d61da64a..aff3137f1ab9 100644 --- a/test/e2e/tests/advanced-settings.spec.js +++ b/test/e2e/tests/advanced-settings.spec.js @@ -25,7 +25,10 @@ describe('Advanced Settings', function () { await driver.navigate(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - + // TODO: Remove this test since we are not showing any secondary balance + if (process.env.MULTICHAIN) { + return; + } await driver.clickElement( '[data-testid="account-options-menu-button"]', ); diff --git a/test/e2e/tests/custom-rpc-history.spec.js b/test/e2e/tests/custom-rpc-history.spec.js index 46bde39df39e..93709a98817b 100644 --- a/test/e2e/tests/custom-rpc-history.spec.js +++ b/test/e2e/tests/custom-rpc-history.spec.js @@ -70,7 +70,7 @@ describe('Stores custom RPC history', function () { '.networks-tab__add-network-form-footer .btn-primary', ); - await driver.findElement({ text: networkName, tag: 'p' }); + await driver.findElement({ text: networkName, tag: 'span' }); }, ); }); diff --git a/test/e2e/tests/ens.spec.js b/test/e2e/tests/ens.spec.js index 7ed1ab3bb63e..5e9b537bce54 100644 --- a/test/e2e/tests/ens.spec.js +++ b/test/e2e/tests/ens.spec.js @@ -1,5 +1,9 @@ -const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + openActionMenuAndStartSendFlow, + unlockWallet, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('ENS', function () { @@ -85,13 +89,15 @@ describe('ENS', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); await driver.waitForElementNotPresent('.loading-overlay'); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + return; + } await driver.pasteIntoField( 'input[placeholder="Enter public address (0x) or ENS name"]', sampleEnsDomain, @@ -101,25 +107,15 @@ describe('ENS', function () { '.send__select-recipient-wrapper__group-item__title', ); - const currentEnsDomain = await driver.findElement( - '.ens-input__selected-input__title', - ); - - assert.equal( - await currentEnsDomain.getText(), - 'test.eth', - 'Domain name not correct', - ); + await driver.findElement({ + css: '.ens-input__selected-input__title', + text: 'test.eth', + }); - const resolvedAddress = await driver.findElement( - '.ens-input__selected-input__subtitle', - ); - - assert.equal( - await resolvedAddress.getText(), - `0x${sampleAddress}`, - 'Resolved address not correct', - ); + await driver.findElement({ + css: '.ens-input__selected-input__subtitle', + text: `0x${sampleAddress}`, + }); }, ); }); diff --git a/test/e2e/tests/gas-estimates.spec.js b/test/e2e/tests/gas-estimates.spec.js index f9f27029a0d6..a9cd69d66f0a 100644 --- a/test/e2e/tests/gas-estimates.spec.js +++ b/test/e2e/tests/gas-estimates.spec.js @@ -2,6 +2,7 @@ const { convertToHexValue, withFixtures, logInWithBalanceValidation, + openActionMenuAndStartSendFlow, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { CHAIN_IDS } = require('../../../shared/constants/network'); @@ -37,8 +38,10 @@ describe('Gas estimates generated by MetaMask', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', @@ -80,8 +83,10 @@ describe('Gas estimates generated by MetaMask', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', @@ -120,8 +125,10 @@ describe('Gas estimates generated by MetaMask', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', @@ -154,8 +161,10 @@ describe('Gas estimates generated by MetaMask', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', @@ -200,8 +209,10 @@ describe('Gas estimates generated by MetaMask', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', @@ -229,8 +240,10 @@ describe('Gas estimates generated by MetaMask', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', diff --git a/test/e2e/tests/import-flow.spec.js b/test/e2e/tests/import-flow.spec.js index d19296cb121a..a510d3ef5ada 100644 --- a/test/e2e/tests/import-flow.spec.js +++ b/test/e2e/tests/import-flow.spec.js @@ -9,8 +9,12 @@ const { completeImportSRPOnboardingFlow, completeImportSRPOnboardingFlowWordByWord, findAnotherAccountFromAccountList, + openActionMenuAndStartSendFlow, + unlockWallet, + WALLET_PASSWORD, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); +const { emptyHtmlPage } = require('../mock-e2e'); const ganacheOptions = { accounts: [ @@ -22,10 +26,22 @@ const ganacheOptions = { ], }; +async function mockTrezor(mockServer) { + return await mockServer + .forGet('https://connect.trezor.io/9/popup.html') + .thenCallback(() => { + return { + statusCode: 200, + body: emptyHtmlPage(), + }; + }); +} + describe('Import flow @no-mmi', function () { it('Import wallet using Secret Recovery Phrase', async function () { - const testPassword = 'correct horse battery staple'; - + if (process.env.MULTICHAIN) { + return; + } await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }).build(), @@ -39,7 +55,7 @@ describe('Import flow @no-mmi', function () { await completeImportSRPOnboardingFlow( driver, TEST_SEED_PHRASE, - testPassword, + WALLET_PASSWORD, ); // Show account information @@ -53,10 +69,10 @@ describe('Import flow @no-mmi', function () { // shows a QR code for the account await driver.findVisibleElement('.mm-modal'); // shows the correct account address - const address = await driver.findElement( - '.multichain-address-copy-button', - ); - assert.equal(await address.getText(), '0x0Cc52...7afD3'); + await driver.findElement({ + css: '.multichain-address-copy-button', + text: '0x0Cc52...7afD3', + }); await driver.clickElement('.mm-modal button[aria-label="Close"]'); @@ -64,15 +80,13 @@ describe('Import flow @no-mmi', function () { await driver.clickElement( '[data-testid="account-options-menu-button"]', ); - const lockButton = await driver.findClickableElement( - '[data-testid="global-menu-lock"]', - ); - assert.equal(await lockButton.getText(), 'Lock MetaMask'); - await lockButton.click(); + await driver.clickElement({ + css: '[data-testid="global-menu-lock"]', + text: 'Lock MetaMask', + }); // accepts the account password after lock - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Create a new account // switches to localhost @@ -110,13 +124,15 @@ describe('Import flow @no-mmi', function () { // Send ETH from inside MetaMask // starts a send transaction - await driver.clickElement('[data-testid="eth-overview-send"]'); + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', ); await driver.fill('.unit-input__input', '1'); - // Continue to next screen await driver.clickElement({ text: 'Next', tag: 'button' }); @@ -142,7 +158,6 @@ describe('Import flow @no-mmi', function () { }); it('Import wallet using Secret Recovery Phrase with pasting word by word', async function () { - const testPassword = 'correct horse battery staple'; const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; await withFixtures( @@ -158,7 +173,7 @@ describe('Import flow @no-mmi', function () { await completeImportSRPOnboardingFlowWordByWord( driver, TEST_SEED_PHRASE, - testPassword, + WALLET_PASSWORD, ); // Show account information @@ -169,11 +184,10 @@ describe('Import flow @no-mmi', function () { await driver.clickElement('[data-testid="account-list-menu-details"'); await driver.findVisibleElement('.qr-code__wrapper'); // shows the correct account address - const address = await driver.findElement( - '.qr-code [data-testid="address-copy-button-text"]', - ); - - assert.equal(await address.getText(), testAddress); + await driver.findElement({ + css: '.qr-code [data-testid="address-copy-button-text"]', + text: testAddress, + }); }, ); }); @@ -195,8 +209,7 @@ describe('Import flow @no-mmi', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( @@ -212,7 +225,7 @@ describe('Import flow @no-mmi', function () { ); // New imported account has correct name and label - await driver.findElement({ + await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 4', }); @@ -239,7 +252,7 @@ describe('Import flow @no-mmi', function () { ); // New imported account has correct name and label - await driver.findElement({ + await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 5', }); @@ -256,7 +269,7 @@ describe('Import flow @no-mmi', function () { // Account 5 can be removed await driver.clickElement('[data-testid="account-list-menu-remove"]'); await driver.clickElement({ text: 'Remove', tag: 'button' }); - await driver.findElement({ + await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 1', }); @@ -281,8 +294,7 @@ describe('Import flow @no-mmi', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Imports an account with JSON file await driver.clickElement('[data-testid="account-menu-icon"]'); @@ -310,7 +322,7 @@ describe('Import flow @no-mmi', function () { ); // New imported account has correct name and label - await driver.findElement({ + await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 4', }); @@ -347,8 +359,7 @@ describe('Import flow @no-mmi', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // choose Import Account from the account menu await driver.clickElement('[data-testid="account-menu-icon"]'); @@ -373,39 +384,41 @@ describe('Import flow @no-mmi', function () { ); }); - if (process.env.ENABLE_MV3) { - it('Connects to a Hardware wallet for trezor', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions, - title: this.test.title, - }, - async ({ driver }) => { - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // choose Connect hardware wallet from the account menu - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement({ - text: 'Add hardware wallet', - tag: 'button', - }); - await driver.delay(regularDelayMs); - - // should open the TREZOR Connect popup - await driver.clickElement('.hw-connect__btn:nth-of-type(2)'); - await driver.delay(largeDelayMs * 2); - await driver.clickElement({ text: 'Continue', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - const allWindows = await driver.getAllWindowHandles(); - assert.equal(allWindows.length, 2); - }, - ); - }); - } + it('Connects to a Hardware wallet for trezor', async function () { + if (process.env.ENABLE_MV3) { + // Hardware wallets not supported in MV3 build yet + this.skip(); + } + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions, + title: this.test.title, + testSpecificMock: mockTrezor, + }, + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + + // choose Connect hardware wallet from the account menu + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); + await driver.clickElement({ + text: 'Add hardware wallet', + tag: 'button', + }); + await driver.delay(regularDelayMs); + + // should open the TREZOR Connect popup + await driver.clickElement('.hw-connect__btn:nth-of-type(2)'); + await driver.delay(largeDelayMs * 2); + await driver.clickElement({ text: 'Continue', tag: 'button' }); + + const allWindows = await driver.waitUntilXWindowHandles(2); + assert.equal(allWindows.length, 2); + }, + ); + }); }); diff --git a/test/e2e/tests/localization.spec.js b/test/e2e/tests/localization.spec.js index 0e41d1d71069..e188d61198a0 100644 --- a/test/e2e/tests/localization.spec.js +++ b/test/e2e/tests/localization.spec.js @@ -32,9 +32,14 @@ describe('Localization', function () { await driver.navigate(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - const secondaryBalance = await driver.findElement( - '[data-testid="eth-overview__secondary-currency"]', - ); + + const secondaryBalance = process.env.MULTICHAIN + ? await driver.findElement( + '[data-testid="multichain-token-list-item-secondary-value"]', + ) + : await driver.findElement( + '[data-testid="eth-overview__secondary-currency"]', + ); const secondaryBalanceText = await secondaryBalance.getText(); const [fiatAmount, fiatUnit] = secondaryBalanceText .trim() diff --git a/test/e2e/tests/lock-account.spec.js b/test/e2e/tests/lock-account.spec.js index 5b03fd7241e6..957be232e1ba 100644 --- a/test/e2e/tests/lock-account.spec.js +++ b/test/e2e/tests/lock-account.spec.js @@ -35,9 +35,11 @@ describe('Lock and unlock', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - const walletBalance = await driver.findElement( - '.eth-overview__primary-balance', - ); + const walletBalance = process.env.MULTICHAIN + ? await driver.findElement( + '.token-balance-overview__secondary-balance', + ) + : await driver.findElement('.eth-overview__primary-balance'); assert.equal(/^25\s*ETH$/u.test(await walletBalance.getText()), true); }, ); diff --git a/test/e2e/tests/metamask-responsive-ui.spec.js b/test/e2e/tests/metamask-responsive-ui.spec.js index fe01902dfc15..f8d94707513e 100644 --- a/test/e2e/tests/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/metamask-responsive-ui.spec.js @@ -4,6 +4,7 @@ const { convertToHexValue, withFixtures, locateAccountBalanceDOM, + openActionMenuAndStartSendFlow, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -72,7 +73,9 @@ describe('MetaMask Responsive UI', function () { // assert balance const balance = await driver.findElement( - '[data-testid="eth-overview__primary-currency"]', + process.env.MULTICHAIN + ? '[data-testid="token-balance-overview-currency-display"]' + : '[data-testid="eth-overview__primary-currency"]', ); assert.ok(/^0\sETH$/u.test(await balance.getText())); }, @@ -139,8 +142,11 @@ describe('MetaMask Responsive UI', function () { // Send ETH from inside MetaMask // starts to send a transaction - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', diff --git a/test/e2e/metrics/mock-data.js b/test/e2e/tests/metrics/mock-data.js similarity index 100% rename from test/e2e/metrics/mock-data.js rename to test/e2e/tests/metrics/mock-data.js diff --git a/test/e2e/metrics/permissions-approved.spec.js b/test/e2e/tests/metrics/permissions-approved.spec.js similarity index 97% rename from test/e2e/metrics/permissions-approved.spec.js rename to test/e2e/tests/metrics/permissions-approved.spec.js index 9bca8354b92c..08db0511fe99 100644 --- a/test/e2e/metrics/permissions-approved.spec.js +++ b/test/e2e/tests/metrics/permissions-approved.spec.js @@ -6,8 +6,8 @@ const { openDapp, unlockWallet, getEventPayloads, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); /** * mocks the segment api multiple times for specific payloads that we expect to diff --git a/test/e2e/metrics/signature-approved.spec.js b/test/e2e/tests/metrics/signature-approved.spec.js similarity index 99% rename from test/e2e/metrics/signature-approved.spec.js rename to test/e2e/tests/metrics/signature-approved.spec.js index 52b3eac7c774..53b512c4cd48 100644 --- a/test/e2e/metrics/signature-approved.spec.js +++ b/test/e2e/tests/metrics/signature-approved.spec.js @@ -9,8 +9,8 @@ const { getEventPayloads, clickSignOnSignatureConfirmation, validateContractDetails, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); /** * mocks the segment api multiple times for specific payloads that we expect to diff --git a/test/e2e/metrics/swaps.spec.js b/test/e2e/tests/metrics/swaps.spec.js similarity index 99% rename from test/e2e/metrics/swaps.spec.js rename to test/e2e/tests/metrics/swaps.spec.js index 1f49b5899737..68006315ea10 100644 --- a/test/e2e/metrics/swaps.spec.js +++ b/test/e2e/tests/metrics/swaps.spec.js @@ -1,6 +1,6 @@ const { strict: assert } = require('assert'); const { toHex } = require('@metamask/controller-utils'); -const FixtureBuilder = require('../fixture-builder'); +const FixtureBuilder = require('../../fixture-builder'); const { withFixtures, generateGanacheOptions, @@ -9,7 +9,7 @@ const { getEventPayloads, assertInAnyOrder, genRandInitBal, -} = require('../helpers'); +} = require('../../helpers'); const { buildQuote, reviewQuote, @@ -20,7 +20,7 @@ const { const { MetaMetricsEventCategory, MetaMetricsEventName, -} = require('../../../shared/constants/metametrics'); +} = require('../../../../shared/constants/metametrics'); const { TOKENS_API_MOCK_RESULT, TOP_ASSETS_API_MOCK_RESULT, diff --git a/test/e2e/metrics/transaction-finalized.spec.js b/test/e2e/tests/metrics/transaction-finalized.spec.js similarity index 97% rename from test/e2e/metrics/transaction-finalized.spec.js rename to test/e2e/tests/metrics/transaction-finalized.spec.js index b8a77077454a..d905c29da6a5 100644 --- a/test/e2e/metrics/transaction-finalized.spec.js +++ b/test/e2e/tests/metrics/transaction-finalized.spec.js @@ -7,8 +7,8 @@ const { getEventPayloads, assertInAnyOrder, logInWithBalanceValidation, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); /** * mocks the segment api multiple times for specific payloads that we expect to @@ -150,7 +150,10 @@ describe('Transaction Finalized Event', function () { async ({ driver, mockedEndpoint: mockedEndpoints, ganacheServer }) => { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + return; + } await sendTransaction(driver, RECIPIENT, '2.0'); const events = await getEventPayloads(driver, mockedEndpoints); diff --git a/test/e2e/metrics/unlock-wallet.spec.js b/test/e2e/tests/metrics/unlock-wallet.spec.js similarity index 87% rename from test/e2e/metrics/unlock-wallet.spec.js rename to test/e2e/tests/metrics/unlock-wallet.spec.js index 5c6afc76d8d2..f4f0e2c89f63 100644 --- a/test/e2e/metrics/unlock-wallet.spec.js +++ b/test/e2e/tests/metrics/unlock-wallet.spec.js @@ -4,8 +4,8 @@ const { unlockWallet, waitForAccountRendered, defaultGanacheOptions, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); describe('Unlock wallet', function () { async function mockSegment(mockServer) { @@ -37,11 +37,14 @@ describe('Unlock wallet', function () { await driver.navigate(); await unlockWallet(driver); await waitForAccountRendered(driver); + + let mockedRequests; await driver.wait(async () => { const isPending = await mockedEndpoint.isPending(); - return isPending === false; + mockedRequests = await mockedEndpoint.getSeenRequests(); + return isPending === false && mockedRequests.length === 3; }, 10000); - const mockedRequests = await mockedEndpoint.getSeenRequests(); + assert.equal(mockedRequests.length, 3); const [firstMock, secondMock, thirdMock] = mockedRequests; assertBatchValue(firstMock, 'Home', '/'); diff --git a/test/e2e/metrics/wallet-created.spec.js b/test/e2e/tests/metrics/wallet-created.spec.js similarity index 97% rename from test/e2e/metrics/wallet-created.spec.js rename to test/e2e/tests/metrics/wallet-created.spec.js index 7f9979fd4086..c699d1e7c3a3 100644 --- a/test/e2e/metrics/wallet-created.spec.js +++ b/test/e2e/tests/metrics/wallet-created.spec.js @@ -10,8 +10,8 @@ const { onboardingCompleteWalletCreation, onboardingPinExtension, getEventPayloads, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); /** * mocks the segment api multiple times for specific payloads that we expect to diff --git a/test/e2e/tests/network-error.spec.js b/test/e2e/tests/network-error.spec.js index 959f1ea297d8..05a723a7fee6 100644 --- a/test/e2e/tests/network-error.spec.js +++ b/test/e2e/tests/network-error.spec.js @@ -3,6 +3,7 @@ const { convertToHexValue, withFixtures, logInWithBalanceValidation, + openActionMenuAndStartSendFlow, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -69,8 +70,11 @@ describe('Gas API fallback', function () { async ({ driver, ganacheServer }) => { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', diff --git a/test/e2e/tests/nft/auto-detect-nft.spec.js b/test/e2e/tests/nft/auto-detect-nft.spec.js new file mode 100644 index 000000000000..4a621e9fca6f --- /dev/null +++ b/test/e2e/tests/nft/auto-detect-nft.spec.js @@ -0,0 +1,40 @@ +const { strict: assert } = require('assert'); +const { + defaultGanacheOptions, + unlockWallet, + withFixtures, +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); +const { setupAutoDetectMocking } = require('./mocks'); + +describe('NFT detection', function () { + it('displays NFT media', async function () { + const driverOptions = { mock: true }; + await withFixtures( + { + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPreferencesControllerNftDetectionEnabled() + .build(), + driverOptions, + ganacheOptions: defaultGanacheOptions, + title: this.test.title, + testSpecificMock: setupAutoDetectMocking, + }, + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await driver.clickElement('[data-testid="home__nfts-tab"]'); + const collection = await driver.findElement( + '[data-testid="collection-expander-button"]', + ); + const nftImage = await driver.findElement('[data-testid="nft-item"]'); + assert.equal( + await collection.getText(), + 'ENS: Ethereum Name Service (1)', + ); + assert.equal(await nftImage.isDisplayed(), true); + }, + ); + }); +}); diff --git a/test/e2e/nft/erc1155-interaction.spec.js b/test/e2e/tests/nft/erc1155-interaction.spec.js similarity index 94% rename from test/e2e/nft/erc1155-interaction.spec.js rename to test/e2e/tests/nft/erc1155-interaction.spec.js index 948458ef7c8e..5b2ab2d36d4a 100644 --- a/test/e2e/nft/erc1155-interaction.spec.js +++ b/test/e2e/tests/nft/erc1155-interaction.spec.js @@ -4,9 +4,10 @@ const { withFixtures, DAPP_URL, openDapp, -} = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('ERC1155 NFTs testdapp interaction', function () { const smartContract = SMART_CONTRACTS.ERC1155; @@ -35,8 +36,7 @@ describe('ERC1155 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open Dapp and wait for deployed contract await openDapp(driver, contract); @@ -93,8 +93,7 @@ describe('ERC1155 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); await openDapp(driver, contract); @@ -151,8 +150,7 @@ describe('ERC1155 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Create a set approval for all erc1155 token request in test dapp await openDapp(driver, contract); @@ -240,8 +238,7 @@ describe('ERC1155 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Create a revoke approval for all erc1155 token request in test dapp await openDapp(driver, contract); diff --git a/test/e2e/nft/erc721-interaction.spec.js b/test/e2e/tests/nft/erc721-interaction.spec.js similarity index 94% rename from test/e2e/nft/erc721-interaction.spec.js rename to test/e2e/tests/nft/erc721-interaction.spec.js index 9d169e6ebce2..cb4f09050d80 100644 --- a/test/e2e/nft/erc721-interaction.spec.js +++ b/test/e2e/tests/nft/erc721-interaction.spec.js @@ -1,7 +1,12 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +const { + convertToHexValue, + withFixtures, + openDapp, + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('ERC721 NFTs testdapp interaction', function () { const smartContract = SMART_CONTRACTS.NFTS; @@ -30,8 +35,7 @@ describe('ERC721 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open Dapp and wait for deployed contract await openDapp(driver, contract); @@ -153,8 +157,7 @@ describe('ERC721 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open Dapp and wait for deployed contract await openDapp(driver, contract); @@ -250,8 +253,7 @@ describe('ERC721 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open Dapp and wait for deployed contract await openDapp(driver, contract); @@ -302,8 +304,7 @@ describe('ERC721 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open Dapp and wait for deployed contract await openDapp(driver, contract); @@ -374,8 +375,7 @@ describe('ERC721 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open Dapp and wait for deployed contract await openDapp(driver, contract); @@ -445,8 +445,7 @@ describe('ERC721 NFTs testdapp interaction', function () { async ({ driver, _, contractRegistry }) => { const contract = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open Dapp and wait for deployed contract await openDapp(driver, contract); diff --git a/test/e2e/nft/import-erc1155.spec.js b/test/e2e/tests/nft/import-erc1155.spec.js similarity index 88% rename from test/e2e/nft/import-erc1155.spec.js rename to test/e2e/tests/nft/import-erc1155.spec.js index b3640f8c453b..0f598330a601 100644 --- a/test/e2e/nft/import-erc1155.spec.js +++ b/test/e2e/tests/nft/import-erc1155.spec.js @@ -1,7 +1,11 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +const { + convertToHexValue, + withFixtures, + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('Import ERC1155 NFT', function () { const smartContract = SMART_CONTRACTS.ERC1155; @@ -30,8 +34,7 @@ describe('Import ERC1155 NFT', function () { const contractAddress = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // After login, go to NFTs tab, open the import NFT/ERC1155 form await driver.clickElement('[data-testid="home__nfts-tab"]'); @@ -80,8 +83,7 @@ describe('Import ERC1155 NFT', function () { const contractAddress = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // After login, go to NFTs tab, open the import NFT form await driver.clickElement('[data-testid="home__nfts-tab"]'); diff --git a/test/e2e/nft/import-nft.spec.js b/test/e2e/tests/nft/import-nft.spec.js similarity index 88% rename from test/e2e/nft/import-nft.spec.js rename to test/e2e/tests/nft/import-nft.spec.js index e98c9dbb9ff2..320e43ad78a1 100644 --- a/test/e2e/nft/import-nft.spec.js +++ b/test/e2e/tests/nft/import-nft.spec.js @@ -1,7 +1,11 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +const { + convertToHexValue, + withFixtures, + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('Import NFT', function () { const smartContract = SMART_CONTRACTS.NFTS; @@ -30,8 +34,7 @@ describe('Import NFT', function () { const contractAddress = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // After login, go to NFTs tab, open the import NFT form await driver.clickElement('[data-testid="home__nfts-tab"]'); @@ -79,8 +82,7 @@ describe('Import NFT', function () { const contractAddress = contractRegistry.getContractAddress(smartContract); await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // After login, go to NFTs tab, open the import NFT form await driver.clickElement('[data-testid="home__nfts-tab"]'); diff --git a/test/e2e/tests/nft/mocks.js b/test/e2e/tests/nft/mocks.js new file mode 100644 index 000000000000..7a94c76f76b5 --- /dev/null +++ b/test/e2e/tests/nft/mocks.js @@ -0,0 +1,328 @@ +function setupAutoDetectMocking(server) { + const assets = { + assets: [ + { + id: 29383543, + token_id: + '86818186862637897590416402377730948900221574858925543698968316530334305793541', + num_sales: 0, + background_color: null, + image_url: 'https://metamask.github.io/test-dapp/metamask-fox.svg', // 'https://openseauserdata.com/files/ed6a6e7598980ee8ef8e5a08ea0304c9.svg', + image_preview_url: + 'https://metamask.github.io/test-dapp/metamask-fox.svg', // 'https://openseauserdata.com/files/ed6a6e7598980ee8ef8e5a08ea0304c9.svg', + image_thumbnail_url: + 'https://metamask.github.io/test-dapp/metamask-fox.svg', // 'https://openseauserdata.com/files/ed6a6e7598980ee8ef8e5a08ea0304c9.svg', + image_original_url: + 'https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/0xbff15a37063625e314d681fa9d4099080b40fcc7dc2bd4a338a0b5ecb24eb605/image', + animation_url: null, + animation_original_url: null, + name: 'peteryinusa.eth', + description: 'peteryinusa.eth, an ENS name.', + external_link: 'https://app.ens.domains/name/peteryinusa.eth', + asset_contract: { + address: '0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85', + asset_contract_type: 'non-fungible', + chain_identifier: 'ethereum', + created_date: '2019-05-08T21:59:29.327544', + name: 'Unidentified contract', + nft_version: null, + opensea_version: null, + owner: 111982386, + schema_name: 'ERC721', + symbol: '', + total_supply: '0', + description: + 'Ethereum Name Service (ENS) domains are secure domain names for the decentralized world. ENS domains provide a way for users to map human readable names to blockchain and non-blockchain resources, like Ethereum addresses, IPFS hashes, or website URLs. ENS domains can be bought and sold on secondary markets.', + external_link: 'https://ens.domains', + image_url: 'https://metamask.github.io/test-dapp/metamask-fox.svg', // 'https://i.seadn.io/gae/0cOqWoYA7xL9CkUjGlxsjreSYBdrUBE0c6EO1COG4XE8UeP-Z30ckqUNiL872zHQHQU5MUNMNhfDpyXIP17hRSC5HQ?w=500&auto=format', + default_to_fiat: false, + dev_buyer_fee_basis_points: 0, + dev_seller_fee_basis_points: 0, + only_proxied_transfers: false, + opensea_buyer_fee_basis_points: 0, + opensea_seller_fee_basis_points: 250, + buyer_fee_basis_points: 0, + seller_fee_basis_points: 250, + payout_address: null, + }, + permalink: + 'https://opensea.io/assets/ethereum/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/86818186862637897590416402377730948900221574858925543698968316530334305793541', + collection: { + banner_image_url: null, + chat_url: null, + created_date: '2019-05-08T21:59:36.282454+00:00', + default_to_fiat: false, + description: + 'Ethereum Name Service (ENS) domains are secure domain names for the decentralized world. ENS domains provide a way for users to map human readable names to blockchain and non-blockchain resources, like Ethereum addresses, IPFS hashes, or website URLs. ENS domains can be bought and sold on secondary markets.', + dev_buyer_fee_basis_points: '0', + dev_seller_fee_basis_points: '0', + discord_url: null, + display_data: { + card_display_style: 'cover', + }, + external_url: 'https://ens.domains', + featured: false, + featured_image_url: + 'https://i.seadn.io/gae/BBj09xD7R4bBtg1lgnAAS9_TfoYXKwMtudlk-0fVljlURaK7BWcARCpkM-1LGNGTAcsGO6V1TgrtmQFvCo8uVYW_QEfASK-9j6Nr?w=500&auto=format', + hidden: false, + safelist_request_status: 'verified', + image_url: 'https://metamask.github.io/test-dapp/metamask-fox.svg', // 'https://i.seadn.io/gae/0cOqWoYA7xL9CkUjGlxsjreSYBdrUBE0c6EO1COG4XE8UeP-Z30ckqUNiL872zHQHQU5MUNMNhfDpyXIP17hRSC5HQ?w=500&auto=format', + is_subject_to_whitelist: false, + large_image_url: + 'https://i.seadn.io/gae/BBj09xD7R4bBtg1lgnAAS9_TfoYXKwMtudlk-0fVljlURaK7BWcARCpkM-1LGNGTAcsGO6V1TgrtmQFvCo8uVYW_QEfASK-9j6Nr?w=500&auto=format', + medium_username: 'the-ethereum-name-service', + name: 'ENS: Ethereum Name Service', + only_proxied_transfers: false, + opensea_buyer_fee_basis_points: '0', + opensea_seller_fee_basis_points: 250, + payout_address: null, + require_email: false, + short_description: null, + slug: 'ens', + telegram_url: null, + twitter_username: 'ensdomains', + instagram_username: null, + wiki_url: null, + is_nsfw: false, + fees: { + seller_fees: {}, + opensea_fees: { + '0x0000a26b00c1f0df003000390027140000faa719': 250, + }, + }, + is_rarity_enabled: false, + is_creator_fees_enforced: false, + }, + decimals: null, + token_metadata: + 'https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/86818186862637897590416402377730948900221574858925543698968316530334305793541', + is_nsfw: false, + owner: null, + seaport_sell_orders: null, + creator: { + user: { + username: 'ENSDeployer', + }, + address: '0x4fe4e666be5752f1fdd210f4ab5de2cc26e3e0e8', + config: 'verified', + profile_img_url: '', + }, + traits: [ + { + trait_type: 'Length', + display_type: 'number', + max_value: null, + trait_count: 93526, + order: null, + value: 11, + }, + { + trait_type: 'Segment Length', + display_type: 'number', + max_value: null, + trait_count: 22322, + order: null, + value: 11, + }, + { + trait_type: 'Character Set', + display_type: null, + max_value: null, + trait_count: 233279, + order: null, + value: 'letter', + }, + { + trait_type: 'Created Date', + display_type: 'date', + max_value: null, + trait_count: 1, + order: null, + value: 1624394771.0, + }, + { + trait_type: 'Registration Date', + display_type: 'date', + max_value: null, + trait_count: 1, + order: null, + value: 1624394771.0, + }, + { + trait_type: 'Expiration Date', + display_type: 'date', + max_value: null, + trait_count: 1, + order: null, + value: 1687508675.0, + }, + ], + last_sale: null, + top_bid: null, + listing_date: null, + supports_wyvern: true, + rarity_data: null, + transfer_fee: null, + transfer_fee_payment_token: null, + }, + ], + }; + const assetEmpty = { + assets: [], + }; + const assetContract = { + collection: { + banner_image_url: null, + chat_url: null, + created_date: '2019-05-08T21:59:36.282454+00:00', + default_to_fiat: false, + description: + 'Ethereum Name Service (ENS) domains are secure domain names for the decentralized world. ENS domains provide a way for users to map human readable names to blockchain and non-blockchain resources, like Ethereum addresses, IPFS hashes, or website URLs. ENS domains can be bought and sold on secondary markets.', + dev_buyer_fee_basis_points: '0', + dev_seller_fee_basis_points: '0', + discord_url: null, + display_data: { + card_display_style: 'cover', + }, + external_url: 'https://ens.domains', + featured: false, + featured_image_url: + 'https://i.seadn.io/gae/BBj09xD7R4bBtg1lgnAAS9_TfoYXKwMtudlk-0fVljlURaK7BWcARCpkM-1LGNGTAcsGO6V1TgrtmQFvCo8uVYW_QEfASK-9j6Nr?w=500&auto=format', + hidden: false, + safelist_request_status: 'verified', + image_url: 'https://metamask.github.io/test-dapp/metamask-fox.svg', // 'https://i.seadn.io/gae/0cOqWoYA7xL9CkUjGlxsjreSYBdrUBE0c6EO1COG4XE8UeP-Z30ckqUNiL872zHQHQU5MUNMNhfDpyXIP17hRSC5HQ?w=500&auto=format', + is_subject_to_whitelist: false, + large_image_url: + 'https://i.seadn.io/gae/BBj09xD7R4bBtg1lgnAAS9_TfoYXKwMtudlk-0fVljlURaK7BWcARCpkM-1LGNGTAcsGO6V1TgrtmQFvCo8uVYW_QEfASK-9j6Nr?w=500&auto=format', + medium_username: 'the-ethereum-name-service', + name: 'ENS: Ethereum Name Service', + only_proxied_transfers: false, + opensea_buyer_fee_basis_points: '0', + opensea_seller_fee_basis_points: 250, + payout_address: null, + require_email: false, + short_description: null, + slug: 'ens', + telegram_url: null, + twitter_username: 'ensdomains', + instagram_username: null, + wiki_url: null, + is_nsfw: false, + fees: { + seller_fees: {}, + opensea_fees: { + '0x0000a26b00c1f0df003000390027140000faa719': 250, + }, + }, + is_rarity_enabled: false, + is_creator_fees_enforced: false, + }, + address: '0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85', + asset_contract_type: 'non-fungible', + chain_identifier: 'ethereum', + created_date: '2019-05-08T21:59:29.327544', + name: 'Unidentified contract', + nft_version: null, + opensea_version: null, + owner: 111982386, + schema_name: 'ERC721', + symbol: '', + total_supply: '0', + description: + 'Ethereum Name Service (ENS) domains are secure domain names for the decentralized world. ENS domains provide a way for users to map human readable names to blockchain and non-blockchain resources, like Ethereum addresses, IPFS hashes, or website URLs. ENS domains can be bought and sold on secondary markets.', + external_link: 'https://ens.domains', + image_url: 'https://metamask.github.io/test-dapp/metamask-fox.svg', // 'https://i.seadn.io/gae/0cOqWoYA7xL9CkUjGlxsjreSYBdrUBE0c6EO1COG4XE8UeP-Z30ckqUNiL872zHQHQU5MUNMNhfDpyXIP17hRSC5HQ?w=500&auto=format', + default_to_fiat: false, + dev_buyer_fee_basis_points: 0, + dev_seller_fee_basis_points: 0, + only_proxied_transfers: false, + opensea_buyer_fee_basis_points: 0, + opensea_seller_fee_basis_points: 250, + buyer_fee_basis_points: 0, + seller_fee_basis_points: 250, + payout_address: null, + }; + + // Get assets for owner + server + .forGet('https://proxy.metafi.codefi.network/opensea/v1/api/v1/assets') + .withQuery({ offset: 0 }) + .thenCallback(() => { + return { + statusCode: 200, + json: assets, + }; + }); + + // Get assets for owner continued + server + .forGet('https://proxy.metafi.codefi.network/opensea/v1/api/v1/assets') + .withQuery({ offset: 50 }) + .thenCallback(() => { + return { + statusCode: 200, + json: assetEmpty, + }; + }); + + // Get asset contract details + server + .forGet( + 'https://proxy.metafi.codefi.network/opensea/v1/api/v1/asset_contract/0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85', + ) + .thenCallback(() => { + return { + statusCode: 200, + json: assetContract, + }; + }); + + // eth_blockNumber + server + .forPost('/v3/00000000000000000000000000000000') + .withBodyIncluding('eth_blockNumber') + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: 1111111111111111, + result: '0x1', + }, + }; + }); + + // eth_getBlockByNumber + server + .forPost('/v3/00000000000000000000000000000000') + .withBodyIncluding('eth_getBlockByNumber') + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: 1111111111111111, + result: {}, + }, + }; + }); + + // eth_call + server + .forPost('/v3/00000000000000000000000000000000') + .withBodyIncluding('eth_call') + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: 1111111111111111, + result: '0x1', + }, + }; + }); +} + +module.exports = { + setupAutoDetectMocking, +}; diff --git a/test/e2e/nft/remove-erc1155.spec.js b/test/e2e/tests/nft/remove-erc1155.spec.js similarity index 70% rename from test/e2e/nft/remove-erc1155.spec.js rename to test/e2e/tests/nft/remove-erc1155.spec.js index 7923277cc4d7..843fb0deeedb 100644 --- a/test/e2e/nft/remove-erc1155.spec.js +++ b/test/e2e/tests/nft/remove-erc1155.spec.js @@ -1,7 +1,21 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +const { + convertToHexValue, + withFixtures, + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); + +async function mockIPFSRequest(mockServer) { + return [ + await mockServer + .forGet( + 'https://bafkreifvhjdf6ve4jfv6qytqtux5nd4nwnelioeiqx5x2ez5yrgrzk7ypi.ipfs.dweb.link/', + ) + .thenCallback(() => ({ statusCode: 200 })), + ]; +} describe('Remove ERC1155 NFT', function () { const smartContract = SMART_CONTRACTS.ERC1155; @@ -22,19 +36,16 @@ describe('Remove ERC1155 NFT', function () { fixtures: new FixtureBuilder().withNftControllerERC1155().build(), ganacheOptions, smartContract, - title: this.test.title, + title: this.test.fullTitle(), + testSpecificMock: mockIPFSRequest, }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open the details page and click remove nft button await driver.clickElement('[data-testid="home__nfts-tab"]'); - const importedNftImage = await driver.findVisibleElement( - '.nft-item__container', - ); - await importedNftImage.click(); + await driver.clickElement('[data-testid="nft-image"]'); await driver.clickElement('[data-testid="nft-options__button"]'); await driver.clickElement('[data-testid="nft-item-remove"]'); diff --git a/test/e2e/nft/remove-nft.spec.js b/test/e2e/tests/nft/remove-nft.spec.js similarity index 83% rename from test/e2e/nft/remove-nft.spec.js rename to test/e2e/tests/nft/remove-nft.spec.js index d491f488f516..aa08f43435ba 100644 --- a/test/e2e/nft/remove-nft.spec.js +++ b/test/e2e/tests/nft/remove-nft.spec.js @@ -1,7 +1,11 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +const { + convertToHexValue, + withFixtures, + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('Remove NFT', function () { const smartContract = SMART_CONTRACTS.NFTS; @@ -26,8 +30,7 @@ describe('Remove NFT', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Open the details and click remove nft button await driver.clickElement('[data-testid="home__nfts-tab"]'); diff --git a/test/e2e/nft/send-nft.spec.js b/test/e2e/tests/nft/send-nft.spec.js similarity index 87% rename from test/e2e/nft/send-nft.spec.js rename to test/e2e/tests/nft/send-nft.spec.js index 4ec37faf2728..590664d2737e 100644 --- a/test/e2e/nft/send-nft.spec.js +++ b/test/e2e/tests/nft/send-nft.spec.js @@ -1,7 +1,11 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +const { + convertToHexValue, + withFixtures, + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('Send NFT', function () { const smartContract = SMART_CONTRACTS.NFTS; @@ -26,12 +30,15 @@ describe('Send NFT', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Fill the send NFT form and confirm the transaction await driver.clickElement('[data-testid="home__nfts-tab"]'); await driver.clickElement('.nft-item__container'); + // TODO: Update Test when Multichain Send Flow is added + if (process.env.MULTICHAIN) { + return; + } await driver.clickElement({ text: 'Send', tag: 'button' }); await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', diff --git a/test/e2e/tests/nft/view-erc1155-details.spec.js b/test/e2e/tests/nft/view-erc1155-details.spec.js new file mode 100644 index 000000000000..9e0e1f5c4a1d --- /dev/null +++ b/test/e2e/tests/nft/view-erc1155-details.spec.js @@ -0,0 +1,75 @@ +const { + convertToHexValue, + withFixtures, + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); + +async function mockIPFSRequest(mockServer) { + return [ + await mockServer + .forGet( + 'https://bafkreifvhjdf6ve4jfv6qytqtux5nd4nwnelioeiqx5x2ez5yrgrzk7ypi.ipfs.dweb.link/', + ) + .thenCallback(() => ({ statusCode: 200 })), + ]; +} + +describe('View ERC1155 NFT details', function () { + const smartContract = SMART_CONTRACTS.ERC1155; + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + + it('user should be able to view ERC1155 NFT details @no-mmi', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().withNftControllerERC1155().build(), + ganacheOptions, + smartContract, + title: this.test.fullTitle(), + testSpecificMock: mockIPFSRequest, + }, + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + + // Click to open the NFT details page and check displayed account + await driver.clickElement('[data-testid="home__nfts-tab"]'); + + await driver.clickElement('.nft-item__container'); + + await driver.findElement({ + css: '.asset-breadcrumb span:nth-of-type(2)', + text: 'Account 1', + }); + + // Check the displayed ERC1155 NFT details + await driver.findElement({ + css: '.nft-details__info h4', + text: 'Rocks', + }); + + await driver.findElement({ + css: '.nft-details__info h6:nth-of-type(2)', + text: 'This is a collection of Rock NFTs.', + }); + + await driver.findVisibleElement('.nft-item__container'); + + await driver.findElement({ + css: '.nft-details__contract-wrapper', + text: '0x581c3...45947', + }); + }, + ); + }); +}); diff --git a/test/e2e/nft/view-nft-details.spec.js b/test/e2e/tests/nft/view-nft-details.spec.js similarity index 85% rename from test/e2e/nft/view-nft-details.spec.js rename to test/e2e/tests/nft/view-nft-details.spec.js index fa80ba7ad1f8..a2ebedaa531f 100644 --- a/test/e2e/nft/view-nft-details.spec.js +++ b/test/e2e/tests/nft/view-nft-details.spec.js @@ -1,7 +1,11 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const FixtureBuilder = require('../fixture-builder'); +const { + convertToHexValue, + withFixtures, + unlockWallet, +} = require('../../helpers'); +const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); +const FixtureBuilder = require('../../fixture-builder'); describe('View NFT details', function () { const smartContract = SMART_CONTRACTS.NFTS; @@ -26,8 +30,7 @@ describe('View NFT details', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // Click to open the NFT details page and check title await driver.clickElement('[data-testid="home__nfts-tab"]'); diff --git a/test/e2e/tests/onboarding.spec.js b/test/e2e/tests/onboarding.spec.js index 6c506104959f..861b1d647982 100644 --- a/test/e2e/tests/onboarding.spec.js +++ b/test/e2e/tests/onboarding.spec.js @@ -11,11 +11,11 @@ const { testSRPDropdownIterations, locateAccountBalanceDOM, defaultGanacheOptions, + WALLET_PASSWORD, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('MetaMask onboarding @no-mmi', function () { - const testPassword = 'correct horse battery staple'; const wrongSeedPhrase = 'test test test test test test test test test test test test'; const wrongTestPassword = 'test test test test'; @@ -41,7 +41,7 @@ describe('MetaMask onboarding @no-mmi', function () { async ({ driver }) => { await driver.navigate(); - await completeCreateNewWalletOnboardingFlow(driver, testPassword); + await completeCreateNewWalletOnboardingFlow(driver, WALLET_PASSWORD); const homePage = await driver.findElement('.home__main-view'); const homePageDisplayed = await homePage.isDisplayed(); @@ -65,7 +65,7 @@ describe('MetaMask onboarding @no-mmi', function () { await completeImportSRPOnboardingFlow( driver, TEST_SEED_PHRASE, - testPassword, + WALLET_PASSWORD, ); const homePage = await driver.findElement('.home__main-view'); @@ -156,7 +156,10 @@ describe('MetaMask onboarding @no-mmi', function () { await driver.clickElement('[data-testid="metametrics-no-thanks"]'); // Fill in confirm password field with incorrect password - await driver.fill('[data-testid="create-password-new"]', testPassword); + await driver.fill( + '[data-testid="create-password-new"]', + WALLET_PASSWORD, + ); await driver.fill( '[data-testid="create-password-confirm"]', wrongTestPassword, @@ -188,7 +191,11 @@ describe('MetaMask onboarding @no-mmi', function () { async ({ driver }) => { await driver.navigate(); - await importSRPOnboardingFlow(driver, TEST_SEED_PHRASE, testPassword); + await importSRPOnboardingFlow( + driver, + TEST_SEED_PHRASE, + WALLET_PASSWORD, + ); // Verify site assert.equal( await driver.isElementPresent({ @@ -219,10 +226,13 @@ describe('MetaMask onboarding @no-mmi', function () { await driver.clickElement('[data-testid="metametrics-no-thanks"]'); // Fill in confirm password field with correct password - await driver.fill('[data-testid="create-password-new"]', testPassword); + await driver.fill( + '[data-testid="create-password-new"]', + WALLET_PASSWORD, + ); await driver.fill( '[data-testid="create-password-confirm"]', - testPassword, + WALLET_PASSWORD, ); await driver.clickElement('[data-testid="create-password-terms"]'); await driver.clickElement('[data-testid="create-password-wallet"]'); @@ -257,7 +267,11 @@ describe('MetaMask onboarding @no-mmi', function () { async ({ driver, secondaryGanacheServer }) => { await driver.navigate(); - await importSRPOnboardingFlow(driver, TEST_SEED_PHRASE, testPassword); + await importSRPOnboardingFlow( + driver, + TEST_SEED_PHRASE, + WALLET_PASSWORD, + ); // Add custome network localhost 8546 during onboarding await driver.clickElement({ text: 'Advanced configuration', tag: 'a' }); @@ -289,6 +303,7 @@ describe('MetaMask onboarding @no-mmi', function () { text: notificationMessage, }); assert.equal(networkNotification, true); + await driver.clickElement({ text: 'Dismiss', tag: 'h6' }); // Check localhost 8546 is selected and its balance value is correct await driver.findElement({ diff --git a/test/e2e/tests/portfolio-site.spec.js b/test/e2e/tests/portfolio-site.spec.js index b708460e0a13..57ee1ac1b74c 100644 --- a/test/e2e/tests/portfolio-site.spec.js +++ b/test/e2e/tests/portfolio-site.spec.js @@ -1,6 +1,7 @@ const { strict: assert } = require('assert'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); +const { emptyHtmlPage } = require('../mock-e2e'); describe('Portfolio site', function () { const ganacheOptions = { @@ -12,6 +13,22 @@ describe('Portfolio site', function () { }, ], }; + + async function mockPortfolioSite(mockServer) { + return await mockServer + .forGet('https://portfolio.metamask.io/') + .withQuery({ + metamaskEntry: 'ext_portfolio_button', + metametricsId: 'null', + }) + .thenCallback(() => { + return { + statusCode: 200, + body: emptyHtmlPage(), + }; + }); + } + it('should link to the portfolio site @no-mmi', async function () { await withFixtures( { @@ -19,6 +36,7 @@ describe('Portfolio site', function () { fixtures: new FixtureBuilder().build(), ganacheOptions, title: this.test.title, + testSpecificMock: mockPortfolioSite, }, async ({ driver }) => { await driver.navigate(); @@ -26,15 +44,19 @@ describe('Portfolio site', function () { await driver.press('#password', driver.Key.ENTER); // Click Portfolio site - await driver.clickElement('[data-testid="eth-overview-portfolio"]'); + if (process.env.MULTICHAIN) { + await driver.clickElement('[data-testid="token-balance-portfolio"]'); + } else { + await driver.clickElement('[data-testid="eth-overview-portfolio"]'); + } await driver.waitUntilXWindowHandles(2); const windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + await driver.switchToWindowWithTitle('E2E Test Page', windowHandles); // Verify site assert.equal( await driver.getCurrentUrl(), - 'http://127.0.0.1:8080/?metamaskEntry=ext_portfolio_button&metametricsId=null', + 'https://portfolio.metamask.io/?metamaskEntry=ext_portfolio_button&metametricsId=null', ); }, ); diff --git a/test/e2e/flask/ppom-blockaid-alert-erc20-approval.spec.js b/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js similarity index 96% rename from test/e2e/flask/ppom-blockaid-alert-erc20-approval.spec.js rename to test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js index e796fc638adc..8de02f62edaf 100644 --- a/test/e2e/flask/ppom-blockaid-alert-erc20-approval.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js @@ -3,6 +3,7 @@ const FixtureBuilder = require('../fixture-builder'); const { mockServerJsonRpc } = require('../mock-server-json-rpc'); const { + WINDOW_TITLES, defaultGanacheOptions, openDapp, unlockWallet, @@ -52,6 +53,7 @@ async function mockInfura(mockServer) { }, ], ['eth_estimateGas'], + ['eth_feeHistory'], ['eth_gasPrice'], ['eth_getBalance'], ['eth_getBlockByNumber'], @@ -62,6 +64,7 @@ async function mockInfura(mockServer) { params: [CONTRACT_ADDRESS.BUSD], }, ], + ['eth_getTransactionCount'], ]); await mockServer @@ -160,7 +163,7 @@ async function mockInfura(mockServer) { }); } -describe('PPOM Blockaid Alert - Malicious ERC20 Approval', function () { +describe('PPOM Blockaid Alert - Malicious ERC20 Approval @no-mmi', function () { it('should show banner alert', async function () { await withFixtures( { @@ -191,7 +194,7 @@ describe('PPOM Blockaid Alert - Malicious ERC20 Approval', function () { // Wait for confirmation pop-up await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle('MetaMask Notification'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); const bannerAlertFoundByTitle = await driver.findElement({ css: bannerAlertSelector, diff --git a/test/e2e/flask/ppom-blockaid-alert-erc20-transfer.spec.js b/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js similarity index 96% rename from test/e2e/flask/ppom-blockaid-alert-erc20-transfer.spec.js rename to test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js index 26c60f2104c6..795f0cc056b0 100644 --- a/test/e2e/flask/ppom-blockaid-alert-erc20-transfer.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js @@ -3,6 +3,7 @@ const FixtureBuilder = require('../fixture-builder'); const { mockServerJsonRpc } = require('../mock-server-json-rpc'); const { + WINDOW_TITLES, defaultGanacheOptions, openDapp, unlockWallet, @@ -52,6 +53,7 @@ async function mockInfura(mockServer) { }, ], ['eth_estimateGas'], + ['eth_feeHistory'], ['eth_gasPrice'], ['eth_getBalance'], ['eth_getBlockByNumber'], @@ -62,6 +64,7 @@ async function mockInfura(mockServer) { params: [CONTRACT_ADDRESS.USDC], }, ], + ['eth_getTransactionCount'], ]); await mockServer @@ -157,7 +160,7 @@ async function mockInfura(mockServer) { }); } -describe('PPOM Blockaid Alert - Malicious ERC20 Transfer', function () { +describe('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { it('should show banner alert', async function () { await withFixtures( { @@ -188,7 +191,7 @@ describe('PPOM Blockaid Alert - Malicious ERC20 Transfer', function () { // Wait for confirmation pop-up await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle('MetaMask Notification'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); const bannerAlertFoundByTitle = await driver.findElement({ css: bannerAlertSelector, diff --git a/test/e2e/tests/ppom-blockaid-alert.spec.js b/test/e2e/tests/ppom-blockaid-alert.spec.js new file mode 100644 index 000000000000..a36b674eade2 --- /dev/null +++ b/test/e2e/tests/ppom-blockaid-alert.spec.js @@ -0,0 +1,309 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { mockServerJsonRpc } = require('../mock-server-json-rpc'); + +const { + WINDOW_TITLES, + defaultGanacheOptions, + openDapp, + unlockWallet, + withFixtures, + switchToNotificationWindow, +} = require('../helpers'); + +const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; +const selectedAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; +const mockMaliciousAddress = '0x5fbdb2315678afecb367f032d93f642f64180aa3'; + +const expectedMaliciousTitle = 'This is a deceptive request'; + +const testBenignConfigs = [ + { + logExpectedDetail: 'benign eth_sendTransaction with no value', + btnSelector: '#sendButton', + }, + { + logExpectedDetail: 'benign eth_sendTransaction with value', + method: 'eth_sendTransaction', + params: [ + { + from: selectedAddress, + to: '0xf977814e90da44bfa03b6295a0616a897441acec', + value: '0x9184e72a000', + }, + ], + }, + { + logExpectedDetail: 'benign Blur eth_sendTransaction', + method: 'eth_signTypedData_v4', + params: [ + selectedAddress, + '{"types":{"Order":[{"name":"trader","type":"address"},{"name":"side","type":"uint8"},{"name":"matchingPolicy","type":"address"},{"name":"collection","type":"address"},{"name":"tokenId","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"paymentToken","type":"address"},{"name":"price","type":"uint256"},{"name":"listingTime","type":"uint256"},{"name":"expirationTime","type":"uint256"},{"name":"fees","type":"Fee[]"},{"name":"salt","type":"uint256"},{"name":"extraParams","type":"bytes"},{"name":"nonce","type":"uint256"}],"Fee":[{"name":"rate","type":"uint16"},{"name":"recipient","type":"address"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Blur Exchange","version":"1.0","chainId":"1","verifyingContract":"0x000000000000ad05ccc4f10045630fb830b95127"},"primaryType":"Order","message":{"trader":"0xd854343f41b2138b686f2d3ba38402a9f7fb4337","side":"1","matchingPolicy":"0x0000000000dab4a563819e8fd93dba3b25bc3495","collection":"0xc4a5025c4563ad0acc09d92c2506e6744dad58eb","tokenId":"30420","amount":"1","paymentToken":"0x0000000000000000000000000000000000000000","price":"1000000000000000000","listingTime":"1679418212","expirationTime":"1680023012","salt":"154790208154270131670189427454206980105","extraParams":"0x01","nonce":"0"}}', + ], + }, + { + logExpectedDetail: 'benign Seaport eth_sendTransaction', + method: 'eth_signTypedData_v4', + params: [ + selectedAddress, + '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.4","chainId":"1","verifyingContract":"0x00000000000001ad428e4906aE43D8F9852d0dD6"},"message":{"offerer":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC","offer":[{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"2500000000000000","endAmount":"2500000000000000"}],"consideration":[{"itemType":"2","token":"0xaA7200ee500dE2dcde75E996De83CBD73BCa9705","identifierOrCriteria":"11909","startAmount":"1","endAmount":"1","recipient":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"62500000000000","endAmount":"62500000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"12500000000000","endAmount":"12500000000000","recipient":"0x8324BdEF2F30E08E368f2Fa2F14143cDCA77423D"}],"startTime":"1681835413","endTime":"1682094598","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929812618382526293324216","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}}', + ], + }, + { + logExpectedDetail: 'benign eth_signTypedData', + btnSelector: '#signTypedData', + }, +]; + +const testMaliciousConfigs = [ + { + btnSelector: '#maliciousPermit', + expectedDescription: + 'If you approve this request, a third party known for scams might take all your assets.', + expectedReason: 'permit_farming', + }, + { + btnSelector: '#maliciousRawEthButton', + expectedDescription: + 'If you approve this request, a third party known for scams will take all your assets.', + expectedReason: 'raw_native_token_transfer', + }, + { + btnSelector: '#maliciousSeaport', + expectedDescription: + 'If you approve this request, someone can steal your assets listed on OpenSea.', + expectedReason: 'seaport_farming', + }, + { + btnSelector: '#maliciousTradeOrder', + expectedDescription: + 'If you approve this request, you might lose your assets.', + expectedReason: 'trade_order_farming', + }, +]; + +async function mockInfura(mockServer) { + await mockServerJsonRpc(mockServer, [ + ['eth_blockNumber'], + ['eth_call'], + ['eth_estimateGas'], + ['eth_feeHistory'], + ['eth_gasPrice'], + ['eth_getBalance'], + ['eth_getBlockByNumber'], + ['eth_getCode'], + ['eth_getTransactionCount'], + ]); +} + +async function mockInfuraWithMaliciousResponses(mockServer) { + await mockInfura(mockServer); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [{ accessList: [], data: '0x00000000' }], + }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: { + calls: [ + { + error: 'execution reverted', + from: '0x0000000000000000000000000000000000000000', + gas: '0x1d55c2cb', + gasUsed: '0x39c', + input: '0x00000000', + to: mockMaliciousAddress, + type: 'DELEGATECALL', + value: '0x0', + }, + ], + error: 'execution reverted', + from: '0x0000000000000000000000000000000000000000', + gas: '0x1dcd6500', + gasUsed: '0x721e', + input: '0x00000000', + to: mockMaliciousAddress, + type: 'CALL', + value: '0x0', + }, + }, + }; + }); +} + +/** + * Tests various Blockaid PPOM security alerts. Some other tests live in separate files due to + * the need for more sophisticated JSON-RPC mock requests. Some example PPOM Blockaid + * requests and responses are provided here: + * + * @see {@link https://wobbly-nutmeg-8a5.notion.site/MM-E2E-Testing-1e51b617f79240a49cd3271565c6e12d} + */ +describe('Confirmation Security Alert - Blockaid @no-mmi', function () { + it('should not show security alerts for benign requests', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfura, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + for (const config of testBenignConfigs) { + const { btnSelector, logExpectedDetail, method, params } = config; + + // Either click TestDapp button to send JSON-RPC request or manually send request + if (btnSelector) { + await driver.clickElement(btnSelector); + } else { + const request = JSON.stringify({ + jsonrpc: '2.0', + method, + params, + }); + await driver.executeScript( + `window.transactionHash = window.ethereum.request(${request})`, + ); + } + + // Wait for confirmation pop-up + await driver.delay(500); + await switchToNotificationWindow(driver, 3); + + const isPresent = await driver.isElementPresent(bannerAlertSelector); + assert.equal( + isPresent, + false, + `Banner alert unexpectedly found. \nExpected detail: ${logExpectedDetail}`, + ); + + // Wait for confirmation pop-up to close + await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + } + }, + ); + }); + + /** + * Disclaimer: This test does not test all reason types. e.g. 'blur_farming', + * 'malicious_domain'. Some other tests are found in other files: + * e.g. test/e2e/flask/ppom-blockaid-alert-.spec.js + */ + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show security alerts for malicious requests', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfuraWithMaliciousResponses, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + for (const config of testMaliciousConfigs) { + const { expectedDescription, expectedReason, btnSelector } = config; + console.log('config', config); + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement(btnSelector); + + // Wait for confirmation pop-up + await driver.delay(500); + await switchToNotificationWindow(driver, 3); + + // Find element by title + const bannerAlertFoundByTitle = await driver.findElement({ + css: bannerAlertSelector, + text: expectedMaliciousTitle, + }); + const bannerAlertText = await bannerAlertFoundByTitle.getText(); + + assert( + bannerAlertFoundByTitle, + `Banner alert not found. Expected Title: ${expectedMaliciousTitle} \nExpected reason: ${expectedReason}\n`, + ); + assert( + bannerAlertText.includes(expectedDescription), + `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: ${expectedReason}\n`, + ); + + // Wait for confirmation pop-up to close + await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + } + }, + ); + }); + + it('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement('#maliciousApprovalButton'); + + // Wait for confirmation pop-up + await driver.delay(500); + await switchToNotificationWindow(driver, 3); + + const expectedTitle = 'Request may not be safe'; + + const bannerAlert = await driver.findElement({ + css: bannerAlertSelector, + text: expectedTitle, + }); + + assert( + bannerAlert, + `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: transfer_farming\n`, + ); + }, + ); + }); +}); diff --git a/test/e2e/flask/ppom-toggle-settings.spec.js b/test/e2e/tests/ppom-toggle-settings.spec.js similarity index 98% rename from test/e2e/flask/ppom-toggle-settings.spec.js rename to test/e2e/tests/ppom-toggle-settings.spec.js index 94afb1181509..fc2035746cbf 100644 --- a/test/e2e/flask/ppom-toggle-settings.spec.js +++ b/test/e2e/tests/ppom-toggle-settings.spec.js @@ -8,7 +8,7 @@ const { } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -describe('PPOM Settings', function () { +describe('PPOM Settings @no-mmi', function () { it('should not show the PPOM warning when toggle is off', async function () { await withFixtures( { diff --git a/test/e2e/tests/provider-api.spec.js b/test/e2e/tests/provider-api.spec.js index 79ac8f9fe7f1..eee5daa32d3b 100644 --- a/test/e2e/tests/provider-api.spec.js +++ b/test/e2e/tests/provider-api.spec.js @@ -32,15 +32,10 @@ describe('MetaMask', function () { await driver.press('#password', driver.Key.ENTER); await openDapp(driver); - const networkDiv = await driver.waitForSelector({ - css: '#network', - text: '1337', - }); const chainIdDiv = await driver.waitForSelector({ css: '#chainId', text: '0x539', }); - assert.equal(await networkDiv.getText(), '1337'); assert.equal(await chainIdDiv.getText(), '0x539'); const windowHandles = await driver.getAllWindowHandles(); @@ -50,17 +45,12 @@ describe('MetaMask', function () { await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'button' }); await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - const switchedNetworkDiv = await driver.waitForSelector({ - css: '#network', - text: '0x1', - }); const switchedChainIdDiv = await driver.waitForSelector({ css: '#chainId', text: '0x1', }); const accountsDiv = await driver.findElement('#accounts'); - assert.equal(await switchedNetworkDiv.getText(), '0x1'); assert.equal(await switchedChainIdDiv.getText(), '0x1'); assert.equal(await accountsDiv.getText(), publicAddress); }, diff --git a/test/e2e/tests/send-edit.spec.js b/test/e2e/tests/send-edit.spec.js index 4d9955660367..6893d8096a0d 100644 --- a/test/e2e/tests/send-edit.spec.js +++ b/test/e2e/tests/send-edit.spec.js @@ -26,7 +26,9 @@ describe('Editing Confirm Transaction', function () { await driver.navigate(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - + if (process.env.MULTICHAIN) { + return; + } const transactionAmounts = await driver.findElements( '.currency-display-component__text', ); @@ -107,7 +109,9 @@ describe('Editing Confirm Transaction', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - + if (process.env.MULTICHAIN) { + return; + } const transactionAmounts = await driver.findElements( '.currency-display-component__text', ); diff --git a/test/e2e/tests/send-eth.spec.js b/test/e2e/tests/send-eth.spec.js index cf9f0bbdb912..1c6adaa25979 100644 --- a/test/e2e/tests/send-eth.spec.js +++ b/test/e2e/tests/send-eth.spec.js @@ -6,6 +6,8 @@ const { openDapp, locateAccountBalanceDOM, logInWithBalanceValidation, + openActionMenuAndStartSendFlow, + unlockWallet, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -30,8 +32,10 @@ describe('Send ETH from inside MetaMask using default gas', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', @@ -40,12 +44,10 @@ describe('Send ETH from inside MetaMask using default gas', function () { const inputAmount = await driver.findElement('.unit-input__input'); await inputAmount.fill('1000'); - const errorAmount = await driver.findElement('.send-v2__error-amount'); - assert.equal( - await errorAmount.getText(), - 'Insufficient funds for gas', - 'send screen should render an insufficient fund for gas error message', - ); + await driver.findElement({ + css: '.send-v2__error-amount', + text: 'Insufficient funds for gas', + }); await inputAmount.press(driver.Key.BACK_SPACE); await inputAmount.press(driver.Key.BACK_SPACE); @@ -120,8 +122,10 @@ describe('Send ETH non-contract address with data that matches ERC20 transfer da await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0xc427D562164062a23a5cFf596A4a3208e72Acd28', @@ -170,11 +174,12 @@ describe('Send ETH from inside MetaMask using advanced gas modal', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - await driver.clickElement('[data-testid="eth-overview-send"]'); + await unlockWallet(driver); + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', @@ -189,6 +194,7 @@ describe('Send ETH from inside MetaMask using advanced gas modal', function () { // Continue to next screen await driver.clickElement({ text: 'Next', tag: 'button' }); + await driver.delay(1000); const transactionAmounts = await driver.findElements( '.currency-display-component__text', ); @@ -236,8 +242,7 @@ describe('Send ETH from dapp using advanced gas controls', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // initiates a send from the dapp await openDapp(driver); @@ -287,15 +292,13 @@ describe('Send ETH from dapp using advanced gas controls', function () { }); // the transaction has the expected gas price - const txValue = await driver.findClickableElement( + driver.clickElement( '[data-testid="transaction-list-item-primary-currency"]', ); - await txValue.click(); - const gasPrice = await driver.waitForSelector({ + await driver.waitForSelector({ css: '[data-testid="transaction-breakdown__gas-price"]', text: '100', }); - assert.equal(await gasPrice.getText(), '100'); }, ); }); @@ -315,14 +318,13 @@ describe('Send ETH from dapp using advanced gas controls', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // initiates a transaction from the dapp await openDapp(driver); await driver.clickElement({ text: 'Create Token', tag: 'button' }); - await driver.waitUntilXWindowHandles(3); - const windowHandles = await driver.getAllWindowHandles(); + const windowHandles = await driver.waitUntilXWindowHandles(3); + const extension = windowHandles[0]; await driver.switchToWindowWithTitle( 'MetaMask Notification', @@ -351,14 +353,15 @@ describe('Send ETH from dapp using advanced gas controls', function () { text: '0.04503836 ETH', }); - await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.waitUntilXWindowHandles(2); await driver.switchToWindow(extension); // Identify the transaction in the transactions list await driver.waitForSelector( - '[data-testid="eth-overview__primary-currency"]', + process.env.MULTICHAIN + ? '[data-testid="token-balance-overview-currency-display"]' + : '[data-testid="eth-overview__primary-currency"]', ); await driver.clickElement('[data-testid="home__activity-tab"]'); @@ -371,17 +374,13 @@ describe('Send ETH from dapp using advanced gas controls', function () { }); // the transaction has the expected gas value - const txValue = await driver.findClickableElement( + await driver.clickElement( '[data-testid="transaction-list-item-primary-currency"]', ); - await txValue.click(); - const baseFeeValue = await driver.waitForSelector( - { - text: '0.000000025', - }, - { timeout: 15000 }, - ); - assert.equal(await baseFeeValue.getText(), '0.000000025'); + + await driver.waitForSelector({ + text: '0.000000025', + }); }, ); }); @@ -414,8 +413,10 @@ describe('Send ETH from inside MetaMask to a Multisig Address', function () { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', contractAddress, @@ -431,12 +432,10 @@ describe('Send ETH from inside MetaMask to a Multisig Address', function () { // Go back to home screen to check txn await locateAccountBalanceDOM(driver, ganacheServer); await driver.clickElement('[data-testid="home__activity-tab"]'); - const txn = await driver.isElementPresent( + + await driver.findElement( '.transaction-list__completed-transactions .activity-list-item', ); - - assert.equal(txn, true); - await driver.assertElementNotPresent( '.transaction-status-label--failed', ); diff --git a/test/e2e/tests/send-hex-address.spec.js b/test/e2e/tests/send-hex-address.spec.js index 8b4851ce07b4..c01290e7b257 100644 --- a/test/e2e/tests/send-hex-address.spec.js +++ b/test/e2e/tests/send-hex-address.spec.js @@ -2,6 +2,7 @@ const { convertToHexValue, withFixtures, logInWithBalanceValidation, + openActionMenuAndStartSendFlow, } = require('../helpers'); const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); const FixtureBuilder = require('../fixture-builder'); @@ -32,8 +33,10 @@ describe('Send ETH to a 40 character hexadecimal address', function () { await logInWithBalanceValidation(driver, ganacheServer); // Send ETH - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } // Paste address without hex prefix await driver.pasteIntoField( 'input[placeholder="Enter public address (0x) or ENS name"]', @@ -76,8 +79,10 @@ describe('Send ETH to a 40 character hexadecimal address', function () { await logInWithBalanceValidation(driver, ganacheServer); // Send ETH - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } // Type address without hex prefix await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', @@ -139,7 +144,9 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { '[data-testid="multichain-token-list-button"]', ); await driver.clickElement('[data-testid="eth-overview-send"]'); - + if (process.env.MULTICHAIN) { + return; + } // Paste address without hex prefix await driver.pasteIntoField( 'input[placeholder="Enter public address (0x) or ENS name"]', @@ -193,7 +200,9 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { async ({ driver, ganacheServer }) => { await driver.navigate(); await logInWithBalanceValidation(driver, ganacheServer); - + if (process.env.MULTICHAIN) { + return; + } // Send TST await driver.clickElement('[data-testid="home__asset-tab"]'); await driver.clickElement( diff --git a/test/e2e/tests/send-to-contract.spec.js b/test/e2e/tests/send-to-contract.spec.js index 9b9ae3dd76eb..68db254a3e5b 100644 --- a/test/e2e/tests/send-to-contract.spec.js +++ b/test/e2e/tests/send-to-contract.spec.js @@ -37,6 +37,9 @@ describe('Send ERC20 token to contract address', function () { await driver.clickElement( '[data-testid="multichain-token-list-button"]', ); + if (process.env.MULTICHAIN) { + return; + } await driver.clickElement('[data-testid="eth-overview-send"]'); // Type contract address diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index a5530bbd167a..f0ee0a9ac8de 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -33,8 +33,7 @@ "termsOfUseLastAgreed": "number", "qrHardware": {}, "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, - "snapsInstallPrivacyWarningShown": true, - "serviceWorkerLastActiveTime": 0 + "snapsInstallPrivacyWarningShown": true }, "ApprovalController": { "pendingApprovals": "object", @@ -83,7 +82,6 @@ }, "NetworkController": { "selectedNetworkClientId": "string", - "networkId": "1337", "providerConfig": { "chainId": "0x539", "nickname": "Localhost 8545", @@ -180,11 +178,7 @@ "smartTransactions": "object" } }, - "SnapController": { - "snapErrors": "object", - "snaps": "object", - "snapStates": "object" - }, + "SnapController": { "snaps": "object", "snapStates": "object" }, "SnapsRegistry": { "database": "object", "lastUpdated": "object" }, "SubjectMetadataController": { "subjectMetadata": "object" }, "SwapsController": { diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index df838b39b2b8..4f59db54f920 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -57,13 +57,11 @@ "qrHardware": {}, "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, "snapsInstallPrivacyWarningShown": true, - "serviceWorkerLastActiveTime": 0, "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": "number", "selectedNetworkClientId": "string", - "networkId": "1337", "providerConfig": { "chainId": "0x539", "nickname": "Localhost 8545", @@ -150,7 +148,6 @@ "domains": {}, "perDomainNetwork": "boolean", "logs": "object", - "snapErrors": "object", "snaps": "object", "snapStates": "object", "jobs": "object", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index 75bd62d27908..766636cbf671 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -50,7 +50,6 @@ "traits": "object" }, "NetworkController": { - "networkId": "1337", "selectedNetworkClientId": "string", "networksMetadata": { "networkConfigurationId": { "EIPS": {}, "status": "available" } diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 5fa13c429985..722b0dc854d8 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -50,7 +50,6 @@ "traits": "object" }, "NetworkController": { - "networkId": "1337", "selectedNetworkClientId": "string", "networksMetadata": { "networkConfigurationId": { "EIPS": {}, "status": "available" } diff --git a/test/e2e/swaps/shared.js b/test/e2e/tests/swaps/shared.js similarity index 85% rename from test/e2e/swaps/shared.js rename to test/e2e/tests/swaps/shared.js index 0d961a65443a..6cadbd7d3c87 100644 --- a/test/e2e/swaps/shared.js +++ b/test/e2e/tests/swaps/shared.js @@ -1,6 +1,6 @@ const { strict: assert } = require('assert'); -const FixtureBuilder = require('../fixture-builder'); -const { regularDelayMs, veryLargeDelayMs } = require('../helpers'); +const FixtureBuilder = require('../../fixture-builder'); +const { regularDelayMs, veryLargeDelayMs } = require('../../helpers'); const ganacheOptions = { accounts: [ @@ -24,7 +24,12 @@ const loadExtension = async (driver) => { }; const buildQuote = async (driver, options) => { - await driver.clickElement('[data-testid="token-overview-button-swap"]'); + if (process.env.MULTICHAIN) { + await driver.clickElement('[data-testid="app-footer-actions-button"]'); + await driver.clickElement('[data-testid="select-action-modal-item-swap"]'); + } else { + await driver.clickElement('[data-testid="token-overview-button-swap"]'); + } await driver.fill( 'input[data-testid="prepare-swap-page-from-token-amount"]', options.amount, @@ -156,17 +161,22 @@ const checkActivityTransaction = async (driver, options) => { }; const checkNotification = async (driver, options) => { - const boxTitle = await driver.findElement( - '[data-testid="swaps-banner-title"]', - ); - assert.equal(await boxTitle.getText(), options.title, 'Invalid box title'); - const boxContent = await driver.findElement( - '[data-testid="mm-banner-alert-notification-text"]', - ); - const bodyText = await boxContent.getText(); - console.log(`test: ${bodyText}`); + const isExpectedBoxTitlePresentAndVisible = + await driver.isElementPresentAndVisible({ + css: '[data-testid="swaps-banner-title"]', + text: options.title, + }); + + assert.equal(isExpectedBoxTitlePresentAndVisible, true, 'Invalid box title'); + + const isExpectedBoxContentPresentAndVisible = + await driver.isElementPresentAndVisible({ + css: '[data-testid="mm-banner-alert-notification-text"]', + text: options.text, + }); + assert.equal( - bodyText.includes(options.text), + isExpectedBoxContentPresentAndVisible, true, 'Invalid box text content', ); diff --git a/test/e2e/swaps/swap-eth.spec.js b/test/e2e/tests/swaps/swap-eth.spec.js similarity index 98% rename from test/e2e/swaps/swap-eth.spec.js rename to test/e2e/tests/swaps/swap-eth.spec.js index d6693368ed39..6f8bdad1fdfe 100644 --- a/test/e2e/swaps/swap-eth.spec.js +++ b/test/e2e/tests/swaps/swap-eth.spec.js @@ -1,4 +1,4 @@ -const { withFixtures } = require('../helpers'); +const { withFixtures } = require('../../helpers'); const { withFixturesOptions, loadExtension, diff --git a/test/e2e/swaps/swaps-notifications.spec.js b/test/e2e/tests/swaps/swaps-notifications.spec.js similarity index 99% rename from test/e2e/swaps/swaps-notifications.spec.js rename to test/e2e/tests/swaps/swaps-notifications.spec.js index 1c8ab9488c1c..6abf5312dcc0 100644 --- a/test/e2e/swaps/swaps-notifications.spec.js +++ b/test/e2e/tests/swaps/swaps-notifications.spec.js @@ -1,4 +1,4 @@ -const { withFixtures } = require('../helpers'); +const { withFixtures } = require('../../helpers'); const { withFixturesOptions, loadExtension, diff --git a/test/e2e/tests/switch-custom-network.spec.js b/test/e2e/tests/switch-custom-network.spec.js index 67c3ad1fc5d8..ca40232d13a4 100644 --- a/test/e2e/tests/switch-custom-network.spec.js +++ b/test/e2e/tests/switch-custom-network.spec.js @@ -88,7 +88,7 @@ describe('Switch ethereum chain', function () { await driver.switchToWindow(extension); const currentNetworkName = await driver.findElement({ - tag: 'p', + tag: 'span', text: 'Localhost 8546', }); diff --git a/test/e2e/user-actions-benchmark.js b/test/e2e/user-actions-benchmark.js index 3c2d71045a68..4fc390d60231 100644 --- a/test/e2e/user-actions-benchmark.js +++ b/test/e2e/user-actions-benchmark.js @@ -7,7 +7,11 @@ const { isWritable, getFirstParentDirectoryThatExists, } = require('../helpers/file'); -const { convertToHexValue, withFixtures } = require('./helpers'); +const { + convertToHexValue, + withFixtures, + openActionMenuAndStartSendFlow, +} = require('./helpers'); const FixtureBuilder = require('./fixture-builder'); const ganacheOptions = { @@ -66,8 +70,10 @@ async function confirmTx() { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('[data-testid="eth-overview-send"]'); - + await openActionMenuAndStartSendFlow(driver); + if (process.env.MULTICHAIN) { + return; + } await driver.fill( 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index 1b32bd6752d0..1bf470cc47ff 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -1,6 +1,7 @@ const { Builder } = require('selenium-webdriver'); const chrome = require('selenium-webdriver/chrome'); const proxy = require('selenium-webdriver/proxy'); +const { ThenableWebDriver } = require('selenium-webdriver'); // eslint-disable-line no-unused-vars -- this is imported for JSDoc /** * Proxy host to use for HTTPS requests @@ -14,7 +15,7 @@ const HTTPS_PROXY_HOST = '127.0.0.1:8000'; */ class ChromeDriver { static async build({ openDevToolsForTabs, port }) { - const args = [`load-extension=dist/chrome`]; + const args = [`load-extension=${process.cwd()}/dist/chrome`]; if (openDevToolsForTabs) { args.push('--auto-open-devtools-for-tabs'); } diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 5dfaf22258b8..ae88a7970c7e 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -6,15 +6,25 @@ const { error: webdriverError, Key, until, + ThenableWebDriver, // eslint-disable-line no-unused-vars -- this is imported for JSDoc + WebElement, // eslint-disable-line no-unused-vars -- this is imported for JSDoc } = require('selenium-webdriver'); const cssToXPath = require('css-to-xpath'); +const { retry } = require('../../../development/lib/retry'); + +const PAGES = { + BACKGROUND: 'background', + HOME: 'home', + NOTIFICATION: 'notification', + POPUP: 'popup', +}; /** * Temporary workaround to patch selenium's element handle API with methods * that match the playwright API for Elements * * @param {object} element - Selenium Element - * @param driver + * @param {!ThenableWebDriver} driver * @returns {object} modified Selenium Element */ function wrapElementWithAPI(element, driver) { @@ -48,13 +58,14 @@ function wrapElementWithAPI(element, driver) { until.elementIsNotPresent = function elementIsNotPresent(locator) { return new Condition(`Element not present`, function (driver) { - return driver.findElements(By.css(locator)).then(function (elements) { + return driver.findElements(locator).then(function (elements) { return elements.length === 0; }); }); }; /** + * This is MetaMask's custom E2E test driver, wrapping the Selenium WebDriver. * For Selenium WebDriver API documentation, see: * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html */ @@ -194,8 +205,9 @@ class Driver { }, this.timeout); } - async waitForElementNotPresent(element) { - return await this.driver.wait(until.elementIsNotPresent(element)); + async waitForElementNotPresent(rawLocator) { + const locator = this.buildLocator(rawLocator); + return await this.driver.wait(until.elementIsNotPresent(locator)); } async quit() { @@ -204,6 +216,10 @@ class Driver { // Element interactions + /** + * @param {*} rawLocator + * @returns {WebElement} + */ async findElement(rawLocator) { const locator = this.buildLocator(rawLocator); const element = await this.driver.wait( @@ -368,7 +384,7 @@ class Driver { // Navigation - async navigate(page = Driver.PAGES.HOME) { + async navigate(page = PAGES.HOME) { const response = await this.driver.get(`${this.extensionUrl}/${page}.html`); // Wait for asyncronous JavaScript to load await this.driver.wait( @@ -435,15 +451,25 @@ class Driver { initialWindowHandles, delayStep = 1000, timeout = this.timeout, + { retries = 8, retryDelay = 2500 } = {}, ) { let windowHandles = initialWindowHandles || (await this.driver.getAllWindowHandles()); let timeElapsed = 0; + while (timeElapsed <= timeout) { for (const handle of windowHandles) { - await this.driver.switchTo().window(handle); + const handleTitle = await retry( + { + retries, + delay: retryDelay, + }, + async () => { + await this.driver.switchTo().window(handle); + return await this.driver.getTitle(); + }, + ); - const handleTitle = await this.driver.getTitle(); if (handleTitle === title) { return handle; } @@ -603,11 +629,4 @@ function collectMetrics() { return results; } -Driver.PAGES = { - BACKGROUND: 'background', - HOME: 'home', - NOTIFICATION: 'notification', - POPUP: 'popup', -}; - -module.exports = Driver; +module.exports = { Driver, PAGES }; diff --git a/test/e2e/webdriver/firefox.js b/test/e2e/webdriver/firefox.js index 52e936f09210..5740a0bd179f 100644 --- a/test/e2e/webdriver/firefox.js +++ b/test/e2e/webdriver/firefox.js @@ -1,9 +1,15 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); -const { Builder, By, until } = require('selenium-webdriver'); +const { + Builder, + By, + until, + ThenableWebDriver, // eslint-disable-line no-unused-vars -- this is imported for JSDoc +} = require('selenium-webdriver'); const firefox = require('selenium-webdriver/firefox'); const proxy = require('selenium-webdriver/proxy'); +const { retry } = require('../../../development/lib/retry'); /** * The prefix for temporary Firefox profiles. All Firefox profiles used for e2e tests @@ -87,18 +93,37 @@ class FirefoxDriver { } /** - * Returns the Internal UUID for the given extension + * Returns the Internal UUID for the given extension, with retries * * @returns {Promise} the Internal UUID for the given extension */ async getInternalId() { await this._driver.get('about:debugging#addons'); - return await this._driver - .wait( - until.elementLocated(By.xpath("//dl/div[contains(., 'UUID')]/dd")), - 1000, - ) - .getText(); + + // This method with 2 retries to find the UUID seems more stable on local e2e tests + let uuid; + await retry({ retries: 2 }, () => (uuid = this._waitOnceForUUID())); + + return uuid; + } + + /** + * Waits once to locate the temporary Firefox UUID, can be put in a retry loop + * + * @returns {Promise} the UUID for the given extension, or null if not found + * @private + */ + async _waitOnceForUUID() { + const uuidElement = await this._driver.wait( + until.elementLocated(By.xpath("//dl/div[contains(., 'UUID')]/dd")), + 1000, + ); + + if (uuidElement.getText) { + return uuidElement.getText(); + } + + return null; } } diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js index aeb6bc96487b..900e2f599157 100644 --- a/test/e2e/webdriver/index.js +++ b/test/e2e/webdriver/index.js @@ -1,5 +1,5 @@ const { Browser } = require('selenium-webdriver'); -const Driver = require('./driver'); +const { Driver } = require('./driver'); const ChromeDriver = require('./chrome'); const FirefoxDriver = require('./firefox'); diff --git a/test/jest/background.js b/test/jest/background.js deleted file mode 100644 index 566a0d397b19..000000000000 --- a/test/jest/background.js +++ /dev/null @@ -1,5 +0,0 @@ -import { _setBackgroundConnection } from '../../ui/store/action-queue'; - -export const setBackgroundConnection = (backgroundConnection = {}) => { - _setBackgroundConnection(backgroundConnection); -}; diff --git a/test/jest/index.js b/test/jest/index.js index 1b4174afa8b0..06ee400b24fe 100644 --- a/test/jest/index.js +++ b/test/jest/index.js @@ -1,6 +1,5 @@ export { screen, fireEvent, waitFor } from '@testing-library/react'; export { createSwapsMockStore } from './mock-store'; export { renderWithProvider } from './rendering'; -export { setBackgroundConnection } from './background'; export * as MOCKS from './mocks'; export * as CONSTANTS from './constants'; diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index 96697f354f23..8544b6ce233f 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -159,7 +159,6 @@ export const createSwapsMockStore = () => { id: 6571648590592143, time: 1667403993369, status: 'confirmed', - metamaskNetworkId: '5', originalGasEstimate: '0x7548', userEditedGasLimit: false, chainId: CHAIN_IDS.MAINNET, diff --git a/test/jest/mocks.js b/test/jest/mocks.js index bec4b41556ea..b68e27685054 100644 --- a/test/jest/mocks.js +++ b/test/jest/mocks.js @@ -142,6 +142,7 @@ export const getInitialSendStateWithExistingTxState = (draftTxState) => ({ ...draftTxState.recipient, }, history: draftTxState.history ?? [], + userInputHexData: draftTxState.userInputHexData ?? null, // Use this key if you want to console.log inside the send.js file. test: draftTxState.test ?? 'yo', }, diff --git a/test/mocks/json-rpc-result.ts b/test/mocks/json-rpc-result.ts index 6e07c821d881..a5166eba8b7c 100644 --- a/test/mocks/json-rpc-result.ts +++ b/test/mocks/json-rpc-result.ts @@ -23,6 +23,31 @@ export const mockJsonRpcResult: mockJsonRpcResultType = { '0x0000000000000000000000000000000000000000000000000ddfe4d79cbd3de5', }, + eth_feeHistory: { + default: { + baseFeePerGas: [ + '0x69b11e562', + '0x666a7c239', + '0x6d9e609f6', + '0x6e9ab5408', + '0x6bca983cb', + '0x6a6f790c3', + ], + gasUsedRatio: [ + 0.37602026666666666, 0.7813118333333333, 0.5359671, 0.39827006666666664, + 0.44968263333333336, + ], + oldestBlock: '0x115e9c0', + reward: [ + ['0xfbc521', '0x21239e6', '0x5f5e100'], + ['0x5f5e100', '0x68e7780', '0x314050eb'], + ['0xfbc521', '0xfbc521', '0xfbc521'], + ['0x21239e6', '0x5f5e100', '0x5f5e100'], + ['0x21239e6', '0x5f5e100', '0x5f5e100'], + ], + }, + }, + eth_gasPrice: { default: '0x09184e72a000', }, @@ -275,7 +300,12 @@ export const mockJsonRpcResult: mockJsonRpcResultType = { }, eth_getCode: { + default: '0x', BUSD: '0x60806040526004361061006c5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633659cfe681146100765780634f1ef286146100975780635c60da1b146100b75780638f283970146100e8578063f851a44014610109575b61007461011e565b005b34801561008257600080fd5b50610074600160a060020a0360043516610138565b61007460048035600160a060020a03169060248035908101910135610172565b3480156100c357600080fd5b506100cc6101ea565b60408051600160a060020a039092168252519081900360200190f35b3480156100f457600080fd5b50610074600160a060020a0360043516610227565b34801561011557600080fd5b506100cc610339565b610126610364565b610136610131610411565b610436565b565b61014061045a565b600160a060020a031633600160a060020a03161415610167576101628161047f565b61016f565b61016f61011e565b50565b61017a61045a565b600160a060020a031633600160a060020a031614156101dd5761019c8361047f565b30600160a060020a03163483836040518083838082843782019150509250505060006040518083038185875af19250505015156101d857600080fd5b6101e5565b6101e561011e565b505050565b60006101f461045a565b600160a060020a031633600160a060020a0316141561021c57610215610411565b9050610224565b61022461011e565b90565b61022f61045a565b600160a060020a031633600160a060020a0316141561016757600160a060020a03811615156102e557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f60448201527f787920746f20746865207a65726f206164647265737300000000000000000000606482015290519081900360840190fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61030e61045a565b60408051600160a060020a03928316815291841660208301528051918290030190a1610162816104c7565b600061034361045a565b600160a060020a031633600160a060020a0316141561021c5761021561045a565b61036c61045a565b600160a060020a031633141561040957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e20667260448201527f6f6d207468652070726f78792061646d696e0000000000000000000000000000606482015290519081900360840190fd5b610136610136565b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35490565b3660008037600080366000845af43d6000803e808015610455573d6000f35b3d6000fd5b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b5490565b610488816104eb565b60408051600160a060020a038316815290517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b9181900360200190a150565b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b55565b60006104f6826105ae565b151561058957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f43616e6e6f742073657420612070726f787920696d706c656d656e746174696f60448201527f6e20746f2061206e6f6e2d636f6e747261637420616464726573730000000000606482015290519081900360840190fd5b507f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c355565b6000903b11905600a165627a7a72305820b274fe16b200679a229fcce27c65314a32b3cff995c434133f535dd565bba4740029', USDC: '0x60806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633659cfe6146100775780634f1ef286146100ba5780635c60da1b146101085780638f2839701461015f578063f851a440146101a2575b6100756101f9565b005b34801561008357600080fd5b506100b8600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610213565b005b610106600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001908201803590602001919091929391929390505050610268565b005b34801561011457600080fd5b5061011d610308565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561016b57600080fd5b506101a0600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610360565b005b3480156101ae57600080fd5b506101b761051e565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610201610576565b61021161020c610651565b610682565b565b61021b6106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561025c57610257816106d9565b610265565b6102646101f9565b5b50565b6102706106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102fa576102ac836106d9565b3073ffffffffffffffffffffffffffffffffffffffff163483836040518083838082843782019150509250505060006040518083038185875af19250505015156102f557600080fd5b610303565b6103026101f9565b5b505050565b60006103126106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156103545761034d610651565b905061035d565b61035c6101f9565b5b90565b6103686106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561051257600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151515610466576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260368152602001807f43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f81526020017f787920746f20746865207a65726f20616464726573730000000000000000000081525060400191505060405180910390fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61048f6106a8565b82604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a161050d81610748565b61051b565b61051a6101f9565b5b50565b60006105286106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561056a576105636106a8565b9050610573565b6105726101f9565b5b90565b61057e6106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151515610647576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001807f43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e20667281526020017f6f6d207468652070726f78792061646d696e000000000000000000000000000081525060400191505060405180910390fd5b61064f610777565b565b6000807f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c36001029050805491505090565b3660008037600080366000845af43d6000803e80600081146106a3573d6000f35b3d6000fd5b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b6001029050805491505090565b6106e281610779565b7fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b60010290508181555050565b565b60006107848261084b565b151561081e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b8152602001807f43616e6e6f742073657420612070726f787920696d706c656d656e746174696f81526020017f6e20746f2061206e6f6e2d636f6e74726163742061646472657373000000000081525060400191505060405180910390fd5b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c360010290508181555050565b600080823b9050600081119150509190505600a165627a7a72305820a4a547cfc7202c5acaaae74d428e988bc62ad5024eb0165532d3a8f91db4ed240029', }, + + eth_getTransactionCount: { + default: MOCK_BLOCK_NUMBER, + }, }; diff --git a/test/scenarios/11. gas fee/EIP-1559 gas.csv b/test/scenarios/11. gas fee/EIP-1559 gas.csv new file mode 100644 index 000000000000..14c68f03b263 --- /dev/null +++ b/test/scenarios/11. gas fee/EIP-1559 gas.csv @@ -0,0 +1,19 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,"In test dapp, click ""send EIP 1559 transaction"".",,"MetaMask notification screen is shown. Current gas fee is displayed. ""Site suggested"" button is displayed.",Take a note for the current gas fee. +8,"On notification screen, click ""Site suggested"" button.",,"Edit gas fee screen is shown. Three gas options are shown: low, market, aggressive. Site suggested gas fee is shown. ""Advanced"" button is displayed. ", +9,"On edit gas fee screen, click ""Advanced"" button.",,Advanced gas fee edit screen is shown. Editable max base fee and priority fee input fields are displayed., +10,"In max base fee and priority fee input fields, enter new values, and click ""Save"".",e.g. 25 for max base fee and 2 for priority fee,"On notification screen, the displayed gas fee is different from previous one.","Take a note for the new gas fee. " +11,"On notification screen , click ""Confirm"" to proceed the transaction.",,The transaction appears in the activity list. The amount is shown in the activity list item., +12,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount are shown in the item details. Values of displayed total gas fee, priority fee, and max fee per gas are the new values that we changed in previous step.", +13,Expand the activity log for the transaction.,,"The created, submitted and confirmed activity are shown in the activity log.", +14,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., \ No newline at end of file diff --git a/test/scenarios/11. gas fee/legacy gas.csv b/test/scenarios/11. gas fee/legacy gas.csv new file mode 100644 index 000000000000..f21c89dda786 --- /dev/null +++ b/test/scenarios/11. gas fee/legacy gas.csv @@ -0,0 +1,19 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,"In test dapp, click ""send legacy transaction"".",,MetaMask notification screen is shown. Estimated gas fee is displayed. Edit button is displayed.,Take a note for the estimated gas fee. +8,"On notification screen, click ""Edit"".",,"Edit screen is shown. Gas fee information message is shown. ""Edit suggested gas fee"" button is displayed.", +9,"On edit screen, click ""Edit suggested gas fee"".",,Editable gas limit and gas price input fields are displayed., +10,"In gas price input field, enter a different value, and click ""Save"".",e.g. 50,"On notification screen, the displayed gas fee is different from previous one.","Take a note for the current gas fee. " +11,"On notification screen , click ""Confirm"" to proceed the transaction.",,The transaction appears in the activity list. The amount is shown in the activity list item., +12,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount are shown in the item details. Value of displayed gas price and total gas fee are the new values that we changed in previous step.", +13,Expand the activity log for the transaction.,,"The created, submitted and confirmed activity are shown in the activity log.", +14,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., \ No newline at end of file diff --git a/test/scenarios/11. sign/eth sign.csv b/test/scenarios/11. sign/eth sign.csv new file mode 100644 index 000000000000..e9fa3f62f22f --- /dev/null +++ b/test/scenarios/11. sign/eth sign.csv @@ -0,0 +1,14 @@ +Steps,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,"Open the accounts options menu, select settings. In advanced tab, enable the eth_sign request.",,The eth_sign toggle is ON., +5,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +6,Proceed to Connect with MetaMask.,,, +7,Connect with the current account.,,, +8,"Sign ""Eth Sign"" messages",,The message is in hexadecimal format., +9,Verify signed hash.,,The signed address is verified., diff --git a/test/scenarios/11. sign/personal sign.csv b/test/scenarios/11. sign/personal sign.csv new file mode 100644 index 000000000000..7e06389194f5 --- /dev/null +++ b/test/scenarios/11. sign/personal sign.csv @@ -0,0 +1,13 @@ +Steps,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,"Sign ""Personal Sign"" message",,The message is in plain text format., +8,Verify signed hash.,,The signed address is verified., diff --git a/test/scenarios/11. sign/sign in with ethereum.csv b/test/scenarios/11. sign/sign in with ethereum.csv new file mode 100644 index 000000000000..fffc77e701e4 --- /dev/null +++ b/test/scenarios/11. sign/sign in with ethereum.csv @@ -0,0 +1,13 @@ +Steps,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,"Sign ""Sign In With Ethereum"" message",,The message is in JSON formatting,Verify that alternative parameters of Sign In with Ethereum messages are shown and formatted correctly. +8,Verify signed hash.,,The signed address is verified., diff --git a/test/scenarios/11. sign/sign typed with data.csv b/test/scenarios/11. sign/sign typed with data.csv new file mode 100644 index 000000000000..e9ed60e1a297 --- /dev/null +++ b/test/scenarios/11. sign/sign typed with data.csv @@ -0,0 +1,13 @@ +Steps,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,"Sign ""Sign Typed Data"" transaction",,The message is in JSON formatting,"Verify that v1, v3, v4 of Sign Typed Data messages are shown and formatted correctly." +8,Verify signed hash.,,The signed address is verified., diff --git a/test/scenarios/12. encrypt and decrypt/encrypt and decrypt.csv b/test/scenarios/12. encrypt and decrypt/encrypt and decrypt.csv new file mode 100644 index 000000000000..d1d65c9ec9b5 --- /dev/null +++ b/test/scenarios/12. encrypt and decrypt/encrypt and decrypt.csv @@ -0,0 +1,16 @@ +Steps,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,"On testdapp, click ""Get encryption key"".",,Request encryption public key notification screen is shown., +8,Proceed to confirm get encryption key in notification window.,,The encryption key is displayed on testdapp page., +9,"On testdapp, in encrypt/decrypt section, enter a message in input field and click ""Encrypt"".",e.g. Hello,The encrypted message is displayed in ciphertext format.,Make a copy of the encrypted message. +10,"On testdapp, paste the copied message in previous step in the input field, then click ""Decrypt"". ",,Decrypt request notification screen is shown., +11,Proceed to confirm decrypt message in notification.,e.g. Hello,"The decrypted message is displayed, and the value is the same as the previously entered one.", diff --git a/test/scenarios/17. permissions/connecting and disconnecting from a dapp.csv b/test/scenarios/17. permissions/connecting and disconnecting from a dapp.csv new file mode 100644 index 000000000000..f3a3fb5556d3 --- /dev/null +++ b/test/scenarios/17. permissions/connecting and disconnecting from a dapp.csv @@ -0,0 +1,12 @@ +Steps,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,Open the connection menu,,"The current dapp is shown. +The current list of connected accounts are shown.","Connection icon is in the app header, next to the account options icon." +8,Disconnect current account from dapp via account options.,,"The previously connected address is removed from the list. +The dapp's eth_accounts permission losses that address.", diff --git a/test/scenarios/2. keyring/connect hardware wallet.csv b/test/scenarios/2. keyring/connect hardware wallet.csv new file mode 100644 index 000000000000..636a6df04ab8 --- /dev/null +++ b/test/scenarios/2. keyring/connect hardware wallet.csv @@ -0,0 +1,10 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. ", +3,"Keep Ethereum Mainnet as the selected network. Click on account menu icon. Click ""Add account or hardware wallet"".",,,"The ""Add account"" modal is shown.", +4,"On ""Add account"" modal, click ""Add hardware wallet"" button.",,,"""Connect a hardware wallet"" screen is shown. User can choose between different options to connect a hardware: Ledger, Trezor, Lattice, or QR-based. ""Continue"" button is disabled.", +5,Choose an option to connect hardware wallet.,We need to have a hardware wallet setted up to test this functionality.,"e.g. choose ""Ledger""","""Continue"" button is enabled.", +6,"Plug the hardware wallet directly into computer, then unlock it.",,password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","If you use Ledger, you need to open the Ethereum app on Ledger. If you use Trazor, make sure you use the correct passphrase." +7,"Choose one or multiples accounts that user wants to connect. Then click ""Unlock"".",,,, +8,Click account menu icon to open accounts list.,,,"In accounts list, all selected hardware wallet accounts are shown, and they are all flagged with harware wallet name to be distinguished from other accounts.", +9,"Select one hardware wallet account. ",,,"The Ether balance for the selected hardware wallet account is shown on the overview. The selected account address is shown on the overview. The selected network is Ethereum Mainnet. ", \ No newline at end of file diff --git a/test/scenarios/3. transactions/send erc20 token origin dapp.csv b/test/scenarios/3. transactions/send erc20 token origin dapp.csv new file mode 100644 index 000000000000..617e669c25c1 --- /dev/null +++ b/test/scenarios/3. transactions/send erc20 token origin dapp.csv @@ -0,0 +1,20 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,"In test dapp, click ""create token"" to deploy the Token contract.",,, +8,Confirm the transaction and wait for it to complete.,,, +9,Switch to the wallet and open the activity list item.,,"""Contract deployment"" item is shown in list item. The transaction status, nonce, amount, gas and total are shown in the item details.", +10,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., +11,"In test dapp, proceed to transfer token.",,"In confirmation notification screen, the title is shown as ""Send TST"". The recipient's hexadecimal address is shown as . The amount is shown. The estimated gas details are shown. The total amount is shown.", +12,Confirm the transaction.,,"The transaction ""Send TST"" appears in the activity list. The recipient's hexadecimal address is shown in the activity list item. The amount is shown in the activity list item.", +13,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +14,Expand the Activity log.,,"The created, submitted and confirmed activity is shown in the activity log.", +15,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., \ No newline at end of file diff --git a/test/scenarios/3. transactions/send erc721 token origin dapp.csv b/test/scenarios/3. transactions/send erc721 token origin dapp.csv new file mode 100644 index 000000000000..55e446a02325 --- /dev/null +++ b/test/scenarios/3. transactions/send erc721 token origin dapp.csv @@ -0,0 +1,26 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,Deploy the NFT contract.,,, +8,Confirm the transaction and wait for the it to complete.,,, +9,Proceed to Mint an NFT.,,, +10,Confirm the transaction and wait for it to complete.,,, +11,Close the test dapp and switch back to the wallet.,,, +12,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +13,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., +14,Close the block explorer and switch back to the wallet.,,, +15,"In test dapp, proceed and confirm Watch NFT.",,"The NFT is shown in the wallet's NFTs ", +16,"In test dapp, proceed to transfer token in NFT section.",,"In the confirmation notification screen, The TestDappNFTs NFT is shown in the asset field on the send flow. The specified NFT symbol is shown. The specified NFT image is shown. The specified NFT token id is shown. The recipient's hexadecimal address is shown. The estimated gas details are shown. The total amount is shown. ", +17,Confirm the transaction.,,"The transaction ""Send TDN"" appears in the activity list. The amount is shown in the activity list item.", +18,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +19,Expand the Activity log.,,"The created, submitted and confirmed activity is shown in the activity log.", +20,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., +21,Go back to wallet's NFT tab.,,"The test dapp NFT becomes ""previously owned"".", \ No newline at end of file diff --git a/test/scenarios/3. transactions/send native token origin dapp.csv b/test/scenarios/3. transactions/send native token origin dapp.csv new file mode 100644 index 000000000000..3430d0c38aa2 --- /dev/null +++ b/test/scenarios/3. transactions/send native token origin dapp.csv @@ -0,0 +1,22 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Enter a public address.,,, +6,Proceed to connect with MetaMask.,,, +7,Connect with the current account.,,, +8,"On test dapp, proceed send legacy transaction.",,"In confirmation notification screen, The recipient's hexadecimal address is shown. The amount is shown. The estimated gas details are shown. The total amount is shown. ", +9,Confirm the transaction.,,The transaction appears in the activity list. The recipient's hexadecimal address is shown in the activity list item. The amount is shown in the activity list item., +10,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +11,Expand the Activity log.,,"The created, submitted and confirmed activity are shown in the activity log.", +12,Proceed to view the transaction on the block explorer. ,,The block explorer opens in a new tab., +13,"Go back to test dapp, proceed to send EIP1559 transaction.",,"In confirmation notification screen, The recipient's hexadecimal address is shown. The amount is shown. The estimated gas details are shown. The total amount is shown.", +14,Confirm the transaction.,,"Second transaction appears in the activity list. For the second transaction, the recipient's hexadecimal address is shown in the activity list item. The amount is shown in the activity list item.", +15,Open the activity list item.,,"Two transactions are shown in total. For the second transaction, the status, recipient's address, nonce, amount, gas and total are shown in the item details.", +16,Expand the activity log for the second transaction.,,"The created, submitted and confirmed activity are shown in the activity log.", +17,Proceed to view the second transaction on the block explorer. ,,The block explorer opens in a new tab., \ No newline at end of file diff --git a/test/scenarios/4. tokens/autodetect NFTs.csv b/test/scenarios/4. tokens/autodetect NFTs.csv new file mode 100644 index 000000000000..42e2664816b2 --- /dev/null +++ b/test/scenarios/4. tokens/autodetect NFTs.csv @@ -0,0 +1,9 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Keep Ethereum Mainnet as the selected network.,,,, +4,Select or import an account with NFT balance.,,e.g.: orangefox.eth,,Please request access to this test account from George Balolong (Business Operations). +5,Switch to the NFTs list.,Make sure the autodetect NFT toggle is OFF in Settings.,,A prompt is shown to Turn on NFT detection in Settings.,Note that NFT autodetection is only available on Ethereum Mainnet. The option will not appear in settings if you have a different network selected. +6,Proceed to the Turn on NFT detection in Settings hyperlink.,,,The Security & Privacy Menu opens to Token autodetection., +7,Enable Autodetect NFTs.,,,,Enable OpenSea API toggle will turn ON automatically at this point. +8,Go back to the NFTs list.,,,The tokens list is updated with all imported tokens., \ No newline at end of file diff --git a/test/scenarios/4. tokens/autodetect tokens.csv b/test/scenarios/4. tokens/autodetect tokens.csv new file mode 100644 index 000000000000..e3645bb2cb15 --- /dev/null +++ b/test/scenarios/4. tokens/autodetect tokens.csv @@ -0,0 +1,16 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Keep Ethereum Mainnet as the selected network.,,,, +4,Select or import an account with a token balance.,,e.g.: orangefox.eth,,Please request access to this test account from George Balolong (Business Operations). +5,Switch back to the tokens list.,,,A prompt is shown to Import tokens found in the account.,Note that token detection takes place on Ethereum Mainnet regardless of whether this setting is on or off. +6,Proceed to Import tokens.,,,, +7,Select the tokens to import or import all.,,,The tokens list is updated with all imported tokens., +8,Switch to a network other than Ethereum Mainnet.,,e.g.: BNB Smart Chain,,"Note that token autodetection is currently available on: Ethereum Mainnet, Avalanche, BNB Smart Chain, Polygon, Aurora and Linea Mainnet." +9,Go to the Tokens list.,Make sure the autodetect tokens toggle is OFF in Settings.,,No prompt to Import tokens is shown., +10,Proceed to Privacy & Security Settings.,,,, +11,Search for Token autodetection.,,,, +12,Enable Autodetect tokens.,,,, +13,Go back to the Tokens list and Refresh list.,,,A prompt is shown to Import tokens found in the account., +14,Proceed to Import tokens.,,,, +15,Select the tokens to import or import all.,,,The tokens list is updated with all imported tokens., \ No newline at end of file diff --git a/test/stub/tx-meta-stub.js b/test/stub/tx-meta-stub.js index 88b8bf99e72f..3523f131f0c1 100644 --- a/test/stub/tx-meta-stub.js +++ b/test/stub/tx-meta-stub.js @@ -11,7 +11,7 @@ export const txMetaStub = { { id: 405984854664302, loadingDefaults: true, - metamaskNetworkId: '5', + chainId: '0x5', status: TransactionStatus.unapproved, time: 1572395156620, type: TransactionType.simpleSend, @@ -136,7 +136,6 @@ export const txMetaStub = { ], id: 405984854664302, loadingDefaults: false, - metamaskNetworkId: '5', origin: 'MetaMask', r: '0x5f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57', rawTx: diff --git a/ui/components/app/account-menu/keyring-label.js b/ui/components/app/account-menu/keyring-label.js deleted file mode 100644 index 7abc718d1516..000000000000 --- a/ui/components/app/account-menu/keyring-label.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { HardwareKeyringNames } from '../../../../shared/constants/hardware-wallets'; -import { KeyringType } from '../../../../shared/constants/keyring'; - -export default function KeyringLabel({ keyring }) { - const t = useI18nContext(); - let label = null; - - // Keyring value might take a while to get a value - if (!keyring) { - return null; - } - const { type } = keyring; - - switch (type) { - case KeyringType.qr: - label = HardwareKeyringNames.qr; - break; - case KeyringType.imported: - label = t('imported'); - break; - case KeyringType.trezor: - label = HardwareKeyringNames.trezor; - break; - case KeyringType.ledger: - label = HardwareKeyringNames.ledger; - break; - case KeyringType.lattice: - label = HardwareKeyringNames.lattice; - break; - ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) - case KeyringType.snap: - label = t('snaps'); - break; - ///: END:ONLY_INCLUDE_IN - default: - label = null; - } - - ///: BEGIN:ONLY_INCLUDE_IN(mmi) - if (type.startsWith('Custody') && /JSONRPC/u.test(type)) { - label = type.split(' - ')[1]; - return null; - } - ///: END:ONLY_INCLUDE_IN - - if (label === null) { - return label; - } - - return ( - <>{label ?
{label}
: null} - ); -} - -KeyringLabel.propTypes = { - keyring: PropTypes.object, -}; diff --git a/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js b/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js index 1a64b0aa0bbb..173cd008b9e5 100644 --- a/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js +++ b/ui/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js @@ -22,7 +22,7 @@ import Checkbox from '../../../ui/check-box'; import Tooltip from '../../../ui/tooltip'; import ConnectedAccountsList from '../../connected-accounts-list'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { Icon, IconName } from '../../../component-library'; +import { Icon, IconName, Text } from '../../../component-library'; const { ERROR, LOADING } = ALERT_STATE; @@ -63,7 +63,7 @@ const UnconnectedAccountAlert = () => { className="unconnected-account-alert__checkbox-label" htmlFor="unconnectedAccount_dontShowThisAgain" > - {t('dontShowThisAgain')} + {t('dontShowThisAgain')} { const [showDetectedTokens, setShowDetectedTokens] = useState(false); @@ -115,7 +116,7 @@ const AssetList = ({ onClickAsset }) => { > {shouldShowBuy ? ( { openBuyCryptoInPdapp(); trackEvent({ @@ -133,7 +134,7 @@ const AssetList = ({ onClickAsset }) => { ) : null} {shouldShowReceive ? ( setShowReceiveModal(true)} /> ) : null} diff --git a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js index a6afdcfc6368..72f5e3d3a6ca 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js @@ -33,7 +33,7 @@ const props = { id: 8783053010106567, time: 1656448479005, status: 'unapproved', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0x5208', userEditedGasLimit: false, loadingDefaults: false, @@ -66,7 +66,6 @@ const props = { id: 1230035278491151, time: 1671022500513, status: 'unapproved', - metamaskNetworkId: '80001', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x13881', @@ -92,7 +91,6 @@ const props = { id: 1230035278491151, time: 1671022500513, status: 'unapproved', - metamaskNetworkId: '80001', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x13881', diff --git a/ui/components/app/connected-sites-list/connected-snaps.js b/ui/components/app/connected-sites-list/connected-snaps.js index 30e6fcfb48ad..d02b48ed10c3 100644 --- a/ui/components/app/connected-sites-list/connected-snaps.js +++ b/ui/components/app/connected-sites-list/connected-snaps.js @@ -3,11 +3,10 @@ import PropTypes from 'prop-types'; import { useHistory } from 'react-router-dom'; import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-utils'; import { useDispatch, useSelector } from 'react-redux'; -import { SnapCaveatType } from '@metamask/rpc-methods'; +import { SnapCaveatType } from '@metamask/snaps-rpc-methods'; import { Box, IconName, IconSize, Text } from '../../component-library'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { MenuItem } from '../../ui/menu'; -import { SNAPS_VIEW_ROUTE } from '../../../helpers/constants/routes'; import SnapAvatar from '../snaps/snap-avatar'; import { AlignItems, @@ -23,6 +22,7 @@ import { getPermissionSubjects, } from '../../../selectors'; import { removePermissionsFor, updateCaveat } from '../../../store/actions'; +import { getSnapRoute } from '../../../helpers/utils/util'; export default function ConnectedSnaps({ connectedSubjects }) { const [showOptions, setShowOptions] = useState(); @@ -74,9 +74,7 @@ export default function ConnectedSnaps({ connectedSubjects }) { - history.push(`${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snapId)}`) - } + onClick={() => history.push(getSnapRoute(snapId))} > {t('snapsSettings')} diff --git a/ui/components/app/detected-token/detected-token-selection-popover/detected-token-selection-popover.js b/ui/components/app/detected-token/detected-token-selection-popover/detected-token-selection-popover.js index 65fa8d0b3fd4..2556cc767623 100644 --- a/ui/components/app/detected-token/detected-token-selection-popover/detected-token-selection-popover.js +++ b/ui/components/app/detected-token/detected-token-selection-popover/detected-token-selection-popover.js @@ -35,10 +35,6 @@ const DetectedTokenSelectionPopover = ({ const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork); const { selected: selectedTokens = [] } = sortingBasedOnTokenSelection(tokensListDetected); - const numOfTokensImporting = - selectedTokens.length === detectedTokens.length - ? `All` - : `(${selectedTokens.length})`; const onClose = () => { setShowDetectedTokens(false); @@ -71,7 +67,7 @@ const DetectedTokenSelectionPopover = ({ onClick={onImport} disabled={selectedTokens.length === 0} > - {t('importWithCount', [numOfTokensImporting])} + {t('importWithCount', [`(${selectedTokens.length})`])} ); diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js index eb8287a2da99..94f0df605d36 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js @@ -6,6 +6,11 @@ import { renderWithProvider } from '../../../../test/lib/render-helpers'; import { ETH } from '../../../helpers/constants/common'; import configureStore from '../../../store/store'; import { GasFeeContextProvider } from '../../../contexts/gasFee'; + +import { + TransactionStatus, + TransactionType, +} from '../../../../shared/constants/transaction'; import { NETWORK_TYPES, CHAIN_IDS, @@ -120,13 +125,25 @@ describe('EditGasFeePopover', () => { }); it('should not show insufficient balance message if transaction value is less than balance', () => { - render({ txProps: { userFeeLevel: 'high', txParams: { value: '0x64' } } }); + render({ + txProps: { + status: TransactionStatus.unapproved, + type: TransactionType.simpleSend, + userFeeLevel: 'high', + txParams: { value: '0x64', from: '0xAddress' }, + }, + }); expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument(); }); it('should show insufficient balance message if transaction value is more than balance', () => { render({ - txProps: { userFeeLevel: 'high', txParams: { value: '0x5208' } }, + txProps: { + status: TransactionStatus.unapproved, + type: TransactionType.simpleSend, + userFeeLevel: 'high', + txParams: { value: '0x5208', from: '0xAddress' }, + }, }); expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument(); }); diff --git a/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap b/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap index 0113330cccdd..465ba53bbb89 100644 --- a/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap +++ b/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap @@ -16,11 +16,11 @@ exports[`IncomingTransactionToggle should render existing incoming transaction p This relies on different third-party APIs for each network, which expose your Ethereum address and your IP address.

Ethereum Mainnet

-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+
diff --git a/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap b/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap index 15ec6ad35b0f..c82d870b2466 100644 --- a/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap +++ b/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap @@ -47,7 +47,9 @@ exports[`Eth Sign Modal should match snapshot 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-error-default" style="mask-image: url('./images/icons/danger.svg');" /> -
+
If you've been asked to turn this setting on, you might be getting scammed
diff --git a/ui/components/app/nft-details/nft-details.test.js b/ui/components/app/nft-details/nft-details.test.js index 7733e75aed3b..3a78c969285a 100644 --- a/ui/components/app/nft-details/nft-details.test.js +++ b/ui/components/app/nft-details/nft-details.test.js @@ -8,6 +8,7 @@ import { startNewDraftTransaction } from '../../../ducks/send'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import mockState from '../../../../test/data/mock-state.json'; import { DEFAULT_ROUTE, SEND_ROUTE } from '../../../helpers/constants/routes'; +import { COPY_OPTIONS } from '../../../../shared/constants/copy'; import { AssetType } from '../../../../shared/constants/transaction'; import { removeAndIgnoreNft, @@ -111,7 +112,7 @@ describe('NFT Details', () => { const copyAddressButton = queryByTestId('nft-address-copy'); fireEvent.click(copyAddressButton); - expect(copyToClipboard).toHaveBeenCalledWith(nfts[5].address); + expect(copyToClipboard).toHaveBeenCalledWith(nfts[5].address, COPY_OPTIONS); }); it('should navigate to draft transaction send route with ERC721 data', async () => { diff --git a/ui/components/app/nfts-tab/nfts-tab.js b/ui/components/app/nfts-tab/nfts-tab.js index 80adfc62a8be..66a8c1056b54 100644 --- a/ui/components/app/nfts-tab/nfts-tab.js +++ b/ui/components/app/nfts-tab/nfts-tab.js @@ -25,6 +25,7 @@ import { Box, ButtonLink, IconName, Text } from '../../component-library'; import NFTsDetectionNoticeNFTsTab from '../nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab'; import NftsItems from '../nfts-items'; import { AssetListConversionButton } from '../../multichain'; +import { ASSET_LIST_CONVERSION_BUTTON_VARIANT_TYPES } from '../../multichain/asset-list-conversion-button/asset-list-conversion-button'; export default function NftsTab() { const useNftDetection = useSelector(getUseNftDetection); @@ -76,7 +77,7 @@ export default function NftsTab() { paddingTop={4} > global.platform.openTab({ url: ZENDESK_URLS.NFT_TOKENS }) } diff --git a/ui/components/app/nfts-tab/nfts-tab.test.js b/ui/components/app/nfts-tab/nfts-tab.test.js index 1521eae1d0cb..c4f3d6c73de0 100644 --- a/ui/components/app/nfts-tab/nfts-tab.test.js +++ b/ui/components/app/nfts-tab/nfts-tab.test.js @@ -2,9 +2,9 @@ import React from 'react'; import { fireEvent, screen } from '@testing-library/react'; import reactRouterDom from 'react-router-dom'; import configureStore from '../../../store/store'; -import { renderWithProvider } from '../../../../test/jest/rendering'; +import { renderWithProvider } from '../../../../test/jest'; import { SECURITY_ROUTE } from '../../../helpers/constants/routes'; -import { setBackgroundConnection } from '../../../../test/jest'; +import { setBackgroundConnection } from '../../../store/background-connection'; import NftsTab from '.'; const NFTS = [ diff --git a/ui/components/app/permission-page-container/permission-page-container.component.js b/ui/components/app/permission-page-container/permission-page-container.component.js index 1f817edf893e..22fa4338f9ec 100644 --- a/ui/components/app/permission-page-container/permission-page-container.component.js +++ b/ui/components/app/permission-page-container/permission-page-container.component.js @@ -5,7 +5,7 @@ import { isEqual } from 'lodash'; import { SnapCaveatType, WALLET_SNAP_PERMISSION_KEY, -} from '@metamask/rpc-methods'; +} from '@metamask/snaps-rpc-methods'; ///: END:ONLY_INCLUDE_IN import { SubjectType } from '@metamask/permission-controller'; import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics'; diff --git a/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap b/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap index 84815da0dc0b..277990b592da 100644 --- a/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap +++ b/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap @@ -10,7 +10,9 @@ exports[`Security Provider Banner Alert should match snapshot 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-error-default" style="mask-image: url('./images/icons/danger.svg');" /> -
+

diff --git a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap index 3234ac02be94..dc6765ba143f 100644 --- a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap +++ b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/__snapshots__/blockaid-banner-alert.test.js.snap @@ -9,7 +9,9 @@ exports[`Blockaid Banner Alert should render 'danger' UI when securityAlertRespo class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-error-default" style="mask-image: url('./images/icons/danger.svg');" /> -

+

@@ -55,7 +57,9 @@ exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResp class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-warning-default" style="mask-image: url('./images/icons/danger.svg');" /> -

+

@@ -79,7 +83,9 @@ exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResp class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-warning-default" style="mask-image: url('./images/icons/danger.svg');" /> -

+

@@ -126,7 +132,9 @@ exports[`Blockaid Banner Alert should render details when provided 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-warning-default" style="mask-image: url('./images/icons/danger.svg');" /> -

+

@@ -158,7 +166,7 @@ exports[`Blockaid Banner Alert should render details when provided 1`] = ` class="disclosure__content normal" >

  • • diff --git a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js index a7901097c02d..9e58f3d9fca1 100644 --- a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js +++ b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js @@ -3,7 +3,10 @@ import PropTypes from 'prop-types'; import { captureException } from '@sentry/browser'; import { Text } from '../../../component-library'; -import { Severity } from '../../../../helpers/constants/design-system'; +import { + OverflowWrap, + Severity, +} from '../../../../helpers/constants/design-system'; import { I18nContext } from '../../../../contexts/i18n'; import { @@ -29,7 +32,6 @@ const REASON_TO_DESCRIPTION_TKEY = Object.freeze({ [BlockaidReason.rawSignatureFarming]: 'blockaidDescriptionMightLoseAssets', [BlockaidReason.tradeOrderFarming]: 'blockaidDescriptionMightLoseAssets', - [BlockaidReason.unfairTrade]: 'blockaidDescriptionMightLoseAssets', [BlockaidReason.rawNativeTokenTransfer]: 'blockaidDescriptionTransferFarming', [BlockaidReason.transferFarming]: 'blockaidDescriptionTransferFarming', @@ -64,7 +66,7 @@ function BlockaidBannerAlert({ securityAlertResponse, ...props }) { const description = t(REASON_TO_DESCRIPTION_TKEY[reason] || 'other'); const details = features?.length ? ( - + {features.map((feature, i) => (
  • • {feature}
  • ))} diff --git a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js index 9e9e3bd072f1..11ae6f72163a 100644 --- a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js +++ b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js @@ -170,8 +170,6 @@ describe('Blockaid Banner Alert', () => { 'If you approve this request, a third party known for scams will take all your assets.', [BlockaidReason.transferFarming]: 'If you approve this request, a third party known for scams will take all your assets.', - [BlockaidReason.unfairTrade]: - 'If you approve this request, you might lose your assets.', }).forEach(([reason, expectedDescription]) => { it(`should render for '${reason}' correctly`, () => { const { getByText } = renderWithLocalization( diff --git a/ui/components/app/selected-account/selected-account-component.test.js b/ui/components/app/selected-account/selected-account-component.test.js index 61a6a833d8ed..5116c7155a78 100644 --- a/ui/components/app/selected-account/selected-account-component.test.js +++ b/ui/components/app/selected-account/selected-account-component.test.js @@ -4,6 +4,7 @@ import copyToClipboard from 'copy-to-clipboard'; import { fireEvent } from '@testing-library/react'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import mockState from '../../../../test/data/mock-state.json'; +import { COPY_OPTIONS } from '../../../../shared/constants/copy'; import { getCustodyAccountDetails, getIsCustodianSupportedChain, @@ -68,6 +69,7 @@ describe('SelectedAccount Component', () => { expect(copyToClipboard).toHaveBeenCalledWith( '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc', + COPY_OPTIONS, ); }); diff --git a/ui/components/app/selected-account/selected-account.component.js b/ui/components/app/selected-account/selected-account.component.js index 271211f358bb..0245a35696b1 100644 --- a/ui/components/app/selected-account/selected-account.component.js +++ b/ui/components/app/selected-account/selected-account.component.js @@ -13,6 +13,7 @@ import CustodyLabels from '../../institutional/custody-labels/custody-labels'; ///: END:ONLY_INCLUDE_IN import { Icon, IconName, IconSize } from '../../component-library'; import { IconColor } from '../../../helpers/constants/design-system'; +import { COPY_OPTIONS } from '../../../../shared/constants/copy'; class SelectedAccount extends Component { state = { @@ -104,7 +105,7 @@ class SelectedAccount extends Component { () => this.setState({ copied: false }), SECOND * 3, ); - copyToClipboard(checksummedAddress); + copyToClipboard(checksummedAddress, COPY_OPTIONS); }} >
    diff --git a/ui/components/app/snaps/snap-content-footer/snap-content-footer.js b/ui/components/app/snaps/snap-content-footer/snap-content-footer.js index c0c90e8fb2bb..46b3161f7fcf 100644 --- a/ui/components/app/snaps/snap-content-footer/snap-content-footer.js +++ b/ui/components/app/snaps/snap-content-footer/snap-content-footer.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import { useHistory } from 'react-router-dom'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { SNAPS_VIEW_ROUTE } from '../../../../helpers/constants/routes'; import { TextVariant, JustifyContent, @@ -23,6 +22,7 @@ import { IconName, Text, } from '../../../component-library'; +import { getSnapRoute } from '../../../../helpers/utils/util'; export default function SnapContentFooter({ snapName, snapId }) { const t = useI18nContext(); @@ -30,7 +30,7 @@ export default function SnapContentFooter({ snapName, snapId }) { const handleNameClick = (e) => { e.stopPropagation(); - history.push(`${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snapId)}`); + history.push(getSnapRoute(snapId)); }; return ( diff --git a/ui/components/app/snaps/snap-list-item/index.js b/ui/components/app/snaps/snap-list-item/index.js new file mode 100644 index 000000000000..8ed864927c75 --- /dev/null +++ b/ui/components/app/snaps/snap-list-item/index.js @@ -0,0 +1 @@ +export { default } from './snap-list-item'; diff --git a/ui/components/app/snaps/snap-list-item/index.scss b/ui/components/app/snaps/snap-list-item/index.scss new file mode 100644 index 000000000000..4455e3dc07d7 --- /dev/null +++ b/ui/components/app/snaps/snap-list-item/index.scss @@ -0,0 +1,7 @@ +.snap-list-item { + cursor: pointer; + + &:hover { + background: var(--color-background-default-hover); + } +} diff --git a/ui/components/app/snaps/snap-settings-card/snap-settings-card.js b/ui/components/app/snaps/snap-list-item/snap-list-item.js similarity index 67% rename from ui/components/app/snaps/snap-settings-card/snap-settings-card.js rename to ui/components/app/snaps/snap-list-item/snap-list-item.js index fc0a1a1f7929..12c1c0e42dff 100644 --- a/ui/components/app/snaps/snap-settings-card/snap-settings-card.js +++ b/ui/components/app/snaps/snap-list-item/snap-list-item.js @@ -7,50 +7,43 @@ import { JustifyContent, Display, BlockSize, - IconColor, TextVariant, } from '../../../../helpers/constants/design-system'; -import { - Icon, - IconName, - IconSize, - Text, - Box, -} from '../../../component-library'; +import { Text, Box } from '../../../component-library'; import SnapAvatar from '../snap-avatar'; -const SnapSettingsCard = ({ name, packageName, onClick, snapId }) => { +const SnapListItem = ({ name, packageName, onClick, snapId }) => { return ( {name} @@ -58,18 +51,11 @@ const SnapSettingsCard = ({ name, packageName, onClick, snapId }) => { - - - ); }; -SnapSettingsCard.propTypes = { +SnapListItem.propTypes = { /** * Name of the snap */ @@ -87,4 +73,4 @@ SnapSettingsCard.propTypes = { */ snapId: PropTypes.string.isRequired, }; -export default SnapSettingsCard; +export default SnapListItem; diff --git a/ui/components/app/snaps/snap-settings-card/snap-settings-card.stories.js b/ui/components/app/snaps/snap-list-item/snap-list-item.stories.js similarity index 68% rename from ui/components/app/snaps/snap-settings-card/snap-settings-card.stories.js rename to ui/components/app/snaps/snap-list-item/snap-list-item.stories.js index 484deb152894..2b77af25a323 100644 --- a/ui/components/app/snaps/snap-settings-card/snap-settings-card.stories.js +++ b/ui/components/app/snaps/snap-list-item/snap-list-item.stories.js @@ -1,9 +1,9 @@ import React from 'react'; -import SnapSettingsCard from '.'; +import SnapListItem from '.'; export default { - title: 'Components/App/Snaps/SnapSettingsCard', - component: SnapSettingsCard, + title: 'Components/App/Snaps/SnapListItem', + component: SnapListItem, argTypes: { name: { control: 'text', @@ -25,6 +25,6 @@ export default { }, }; -export const DefaultStory = (args) => ; +export const DefaultStory = (args) => ; DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/snaps/snap-settings-card/snap-settings-card.test.js b/ui/components/app/snaps/snap-list-item/snap-list-item.test.js similarity index 82% rename from ui/components/app/snaps/snap-settings-card/snap-settings-card.test.js rename to ui/components/app/snaps/snap-list-item/snap-list-item.test.js index cbdfe98927ad..d818b0c3f80f 100644 --- a/ui/components/app/snaps/snap-settings-card/snap-settings-card.test.js +++ b/ui/components/app/snaps/snap-list-item/snap-list-item.test.js @@ -4,9 +4,9 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import mockState from '../../../../../test/data/mock-state.json'; -import SnapSettingsCard from '.'; +import SnapListItem from '.'; -describe('SnapSettingsCard', () => { +describe('SnapListItem', () => { const args = { name: 'Snap name', packageName: '@metamask/test-snap-bip44', @@ -15,9 +15,9 @@ describe('SnapSettingsCard', () => { }; const mockStore = configureMockStore([thunk])(mockState); - it('should render the SnapsSettingCard without crashing', () => { + it('should render the SnapListItem without crashing', () => { const { getByText } = renderWithProvider( - , + , mockStore, ); expect(getByText('Snap name')).toBeDefined(); @@ -25,7 +25,7 @@ describe('SnapSettingsCard', () => { it('should render the icon fallback using the first letter of the name', async () => { const { getByText } = renderWithProvider( - , + , mockStore, ); @@ -38,7 +38,7 @@ describe('SnapSettingsCard', () => { it('should render the package name', () => { const { getByText } = renderWithProvider( - , + , mockStore, ); expect(getByText('@metamask/test-snap-bip44')).toBeDefined(); diff --git a/ui/components/app/snaps/snap-settings-card/index.js b/ui/components/app/snaps/snap-settings-card/index.js deleted file mode 100644 index e3a7f5872eea..000000000000 --- a/ui/components/app/snaps/snap-settings-card/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './snap-settings-card'; diff --git a/ui/components/app/snaps/snap-settings-card/index.scss b/ui/components/app/snaps/snap-settings-card/index.scss deleted file mode 100644 index 8bb75cd2db5a..000000000000 --- a/ui/components/app/snaps/snap-settings-card/index.scss +++ /dev/null @@ -1,9 +0,0 @@ -.snap-settings-card { - &__inner-wrapper { - cursor: pointer; - } - - &__caret { - cursor: pointer; - } -} diff --git a/ui/components/app/token-cell/__snapshots__/token-cell.test.js.snap b/ui/components/app/token-cell/__snapshots__/token-cell.test.js.snap index a4426da499ff..34f90c5dee1c 100644 --- a/ui/components/app/token-cell/__snapshots__/token-cell.test.js.snap +++ b/ui/components/app/token-cell/__snapshots__/token-cell.test.js.snap @@ -15,21 +15,26 @@ exports[`Token Cell should match snapshot 1`] = ` class="mm-box mm-badge-wrapper mm-box--margin-right-3 mm-box--display-inline-block" >
    - T + + TEST logo
    - Ethereum Mainnet logo + ?
    @@ -51,7 +56,10 @@ exports[`Token Cell should match snapshot 1`] = `

+ data-testid="multichain-token-list-item-secondary-value" + > + 5.00 +

token.symbol === symbol, + (token) => + token.symbol === symbol && isEqualCaseInsensitive(token.address, address), ); const title = tokenData?.name || symbol; const tokenImage = tokenData?.iconUrl || image; diff --git a/ui/components/app/token-cell/token-cell.test.js b/ui/components/app/token-cell/token-cell.test.js index 896e6aefe4ef..be05bcb7213e 100644 --- a/ui/components/app/token-cell/token-cell.test.js +++ b/ui/components/app/token-cell/token-cell.test.js @@ -2,9 +2,26 @@ import React from 'react'; import thunk from 'redux-thunk'; import configureMockStore from 'redux-mock-store'; import { fireEvent } from '@testing-library/react'; +import { useSelector } from 'react-redux'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount'; + import TokenCell from '.'; +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + + return { + ...actual, + useSelector: jest.fn(), + }; +}); + +jest.mock('../../../hooks/useTokenFiatAmount', () => { + return { + useTokenFiatAmount: jest.fn(), + }; +}); describe('Token Cell', () => { const mockState = { metamask: { @@ -23,6 +40,28 @@ describe('Token Cell', () => { }, }; + // two tokens with the same symbol but different addresses + const MOCK_GET_TOKEN_LIST = { + '0xAddress': { + name: 'TEST-2', + erc20: true, + symbol: 'TEST', + decimals: 18, + address: '0xAddress', + iconUrl: './images/test_1_image.svg', + aggregators: [], + }, + '0xAnotherToken': { + name: 'TEST', + erc20: true, + symbol: 'TEST', + decimals: 18, + address: '0xANoTherToKen', + iconUrl: './images/test_image.svg', + aggregators: [], + }, + }; + const mockStore = configureMockStore([thunk])(mockState); const props = { @@ -33,6 +72,9 @@ describe('Token Cell', () => { onClick: jest.fn(), }; + useSelector.mockReturnValue(MOCK_GET_TOKEN_LIST); + useTokenFiatAmount.mockReturnValue('5.00'); + it('should match snapshot', () => { const { container } = renderWithProvider( , @@ -52,4 +94,17 @@ describe('Token Cell', () => { expect(props.onClick).toHaveBeenCalled(); }); + + it('should render the correct token and filter by symbol and address', () => { + const { queryByText, getByAltText } = renderWithProvider( + , + mockStore, + ); + + const image = getByAltText('TEST logo'); + + expect(queryByText('TEST')).toBeInTheDocument(); + expect(image).toBeInTheDocument(); + expect(image).toHaveAttribute('src', './images/test_image.svg'); + }); }); diff --git a/ui/components/app/transaction-activity-log/transaction-activity-log.util.js b/ui/components/app/transaction-activity-log/transaction-activity-log.util.js index 9e12dbdbe426..77946c3dcc30 100644 --- a/ui/components/app/transaction-activity-log/transaction-activity-log.util.js +++ b/ui/components/app/transaction-activity-log/transaction-activity-log.util.js @@ -54,7 +54,6 @@ export function getActivities(transaction, isFirstTransaction = false) { const { id, chainId, - metamaskNetworkId, hash, history = [], txParams: { @@ -98,7 +97,6 @@ export function getActivities(transaction, isFirstTransaction = false) { id, hash, chainId, - metamaskNetworkId, eventKey: TRANSACTION_CREATED_EVENT, timestamp, value, @@ -152,7 +150,6 @@ export function getActivities(transaction, isFirstTransaction = false) { eventKey, timestamp, chainId, - metamaskNetworkId, value: gasFee, }); } @@ -209,7 +206,6 @@ export function getActivities(transaction, isFirstTransaction = false) { id, hash, chainId, - metamaskNetworkId, eventKey: TRANSACTION_UPDATED_EVENT, timestamp, }); @@ -231,7 +227,6 @@ export function getActivities(transaction, isFirstTransaction = false) { id, hash, chainId, - metamaskNetworkId, eventKey: TRANSACTION_ERRORED_EVENT, }) : historyActivities; diff --git a/ui/components/app/transaction-activity-log/transaction-activity-log.util.test.js b/ui/components/app/transaction-activity-log/transaction-activity-log.util.test.js index fb77a5c94a9b..b667b78c7bdd 100644 --- a/ui/components/app/transaction-activity-log/transaction-activity-log.util.test.js +++ b/ui/components/app/transaction-activity-log/transaction-activity-log.util.test.js @@ -23,7 +23,6 @@ describe('TransactionActivityLog utils', () => { id: 6400627574331058, time: 1543958845581, status: TransactionStatus.unapproved, - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: true, txParams: { @@ -71,7 +70,6 @@ describe('TransactionActivityLog utils', () => { ], id: 6400627574331058, loadingDefaults: false, - metamaskNetworkId: '5', chainId: '0x5', status: TransactionStatus.dropped, submittedTime: 1543958848135, @@ -93,7 +91,6 @@ describe('TransactionActivityLog utils', () => { id: 6400627574331060, time: 1543958857697, status: TransactionStatus.unapproved, - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: false, txParams: { @@ -164,7 +161,6 @@ describe('TransactionActivityLog utils', () => { id: 6400627574331060, lastGasPrice: '0x4190ab00', loadingDefaults: false, - metamaskNetworkId: '5', chainId: '0x5', status: TransactionStatus.confirmed, submittedTime: 1543958860054, @@ -187,7 +183,6 @@ describe('TransactionActivityLog utils', () => { const expected = [ { id: 6400627574331058, - metamaskNetworkId: '5', chainId: '0x5', hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', eventKey: 'transactionCreated', @@ -196,7 +191,6 @@ describe('TransactionActivityLog utils', () => { }, { id: 6400627574331058, - metamaskNetworkId: '5', chainId: '0x5', hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3', eventKey: 'transactionSubmitted', @@ -205,7 +199,6 @@ describe('TransactionActivityLog utils', () => { }, { id: 6400627574331060, - metamaskNetworkId: '5', chainId: '0x5', hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', eventKey: 'transactionResubmitted', @@ -214,7 +207,6 @@ describe('TransactionActivityLog utils', () => { }, { id: 6400627574331060, - metamaskNetworkId: '5', chainId: '0x5', hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33', eventKey: 'transactionConfirmed', @@ -252,7 +244,6 @@ describe('TransactionActivityLog utils', () => { { id: 5559712943815343, loadingDefaults: true, - metamaskNetworkId: '5', chainId: '0x5', status: TransactionStatus.unapproved, time: 1535507561452, @@ -385,7 +376,6 @@ describe('TransactionActivityLog utils', () => { }, hash: '0xabc', chainId: '0x5', - metamaskNetworkId: '5', }; const expectedResult = [ @@ -396,7 +386,6 @@ describe('TransactionActivityLog utils', () => { id: 1, hash: '0xabc', chainId: '0x5', - metamaskNetworkId: '5', }, { eventKey: 'transactionSubmitted', @@ -405,7 +394,6 @@ describe('TransactionActivityLog utils', () => { id: 1, hash: '0xabc', chainId: '0x5', - metamaskNetworkId: '5', }, { eventKey: 'transactionConfirmed', @@ -414,7 +402,6 @@ describe('TransactionActivityLog utils', () => { id: 1, hash: '0xabc', chainId: '0x5', - metamaskNetworkId: '5', }, ]; diff --git a/ui/components/app/transaction-alerts/transaction-alerts.stories.js b/ui/components/app/transaction-alerts/transaction-alerts.stories.js index a53cd7a23ff0..3da1ef79409c 100644 --- a/ui/components/app/transaction-alerts/transaction-alerts.stories.js +++ b/ui/components/app/transaction-alerts/transaction-alerts.stories.js @@ -20,7 +20,6 @@ const customTransaction = ({ userFeeLevel: estimateUsed ? 'low' : 'medium', blockNumber: `${10902987 + i}`, id: 4678200543090545 + i, - metamaskNetworkId: testData?.metamask?.networkId, chainId: testData?.metamask?.providerConfig?.chainId, status: 'confirmed', time: 1600654021000, diff --git a/ui/components/app/transaction-decoding/components/decoding/address/address.component.js b/ui/components/app/transaction-decoding/components/decoding/address/address.component.js index 21220448ab5c..5801ed5adac2 100644 --- a/ui/components/app/transaction-decoding/components/decoding/address/address.component.js +++ b/ui/components/app/transaction-decoding/components/decoding/address/address.component.js @@ -10,6 +10,7 @@ import { getMemoizedAddressBook, } from '../../../../../../selectors'; import NicknamePopovers from '../../../../modals/nickname-popovers'; +import { COPY_OPTIONS } from '../../../../../../../shared/constants/copy'; const Address = ({ checksummedRecipientAddress, @@ -47,7 +48,7 @@ const Address = ({

{ - copyToClipboard(checksummedRecipientAddress); + copyToClipboard(checksummedRecipientAddress, COPY_OPTIONS); if (onRecipientClick) { onRecipientClick(); } diff --git a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js index 1aa4cac6c109..6a47e40f8446 100644 --- a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -29,6 +29,7 @@ import { getURLHostName } from '../../../helpers/utils/util'; import TransactionDecoding from '../transaction-decoding'; import { NETWORKS_ROUTE } from '../../../helpers/constants/routes'; import TransactionInsightsDeprecationAlert from '../confirm-data/transaction-insights-deprecation-alert'; +import { COPY_OPTIONS } from '../../../../shared/constants/copy'; export default class TransactionListItemDetails extends PureComponent { static contextTypes = { @@ -138,7 +139,7 @@ export default class TransactionListItemDetails extends PureComponent { }); this.setState({ justCopied: true }, () => { - copyToClipboard(hash); + copyToClipboard(hash, COPY_OPTIONS); setTimeout(() => this.setState({ justCopied: false }), SECOND); }); }; diff --git a/ui/components/app/transaction-list-item/transaction-list-item.component.js b/ui/components/app/transaction-list-item/transaction-list-item.component.js index 53eb97acf6b0..04a90bb20947 100644 --- a/ui/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/components/app/transaction-list-item/transaction-list-item.component.js @@ -87,7 +87,7 @@ function TransactionListItemInner({ const { initialTransaction: { id }, - primaryTransaction: { err, status }, + primaryTransaction: { error, status }, } = transactionGroup; const trackEvent = useContext(MetaMetricsContext); @@ -313,7 +313,7 @@ function TransactionListItemInner({ statusOnly isPending={isPending} isEarliestNonce={isEarliestNonce} - error={err} + error={error} date={date} status={displayedStatusKey} ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) @@ -402,7 +402,7 @@ function TransactionListItemInner({ { const chainId = useSelector(getCurrentChainId); const isSwapsChain = useSelector(getIsSwapsChain); ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) - const isBridgeToken = useSelector(getIsBridgeToken(token.address)); + const isBridgeChain = useSelector(getIsBridgeChain); const isBuyableChain = useSelector(getIsBuyableChain); const metaMetricsId = useSelector(getMetaMetricsId); @@ -294,7 +294,7 @@ const TokenOverview = ({ className, token }) => { { ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) - isBridgeToken && ( + isBridgeChain && ( { ); }); - it('should show the Bridge button if chain id and token are supported', async () => { + it('should show the Bridge button if chain id is supported', async () => { const mockToken = { name: 'test', isERC721: false, @@ -305,32 +305,6 @@ describe('TokenOverview', () => { expect(bridgeButton).not.toBeInTheDocument(); }); - it('should not show the Bridge button if token is not supported', async () => { - const mockToken = { - name: 'test', - isERC721: false, - address: '0x7ceb23fd6bc0add59e62ac25578270cff1B9f620', - symbol: 'test', - }; - - const mockedStoreWithBridgeableChainId = { - metamask: { - ...mockStore.metamask, - providerConfig: { type: 'test', chainId: CHAIN_IDS.POLYGON }, - }, - }; - const mockedStore = configureMockStore([thunk])( - mockedStoreWithBridgeableChainId, - ); - - const { queryByTestId } = renderWithProvider( - , - mockedStore, - ); - const bridgeButton = queryByTestId('token-overview-bridge'); - expect(bridgeButton).not.toBeInTheDocument(); - }); - it('should show the MMI Portfolio and Stake buttons', () => { const { queryByTestId } = renderWithProvider( , diff --git a/ui/components/component-library/banner-alert/__snapshots__/banner-alert.test.tsx.snap b/ui/components/component-library/banner-alert/__snapshots__/banner-alert.test.tsx.snap index 2224365dfb01..bbc92e6ecbfe 100644 --- a/ui/components/component-library/banner-alert/__snapshots__/banner-alert.test.tsx.snap +++ b/ui/components/component-library/banner-alert/__snapshots__/banner-alert.test.tsx.snap @@ -10,7 +10,9 @@ exports[`BannerAlert should render BannerAlert element correctly 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-primary-default" style="mask-image: url('./images/icons/info.svg');" /> -
+

diff --git a/ui/components/component-library/banner-base/__snapshots__/banner-base.test.tsx.snap b/ui/components/component-library/banner-base/__snapshots__/banner-base.test.tsx.snap index b7186d2acafa..07bfb167b90d 100644 --- a/ui/components/component-library/banner-base/__snapshots__/banner-base.test.tsx.snap +++ b/ui/components/component-library/banner-base/__snapshots__/banner-base.test.tsx.snap @@ -6,7 +6,9 @@ exports[`BannerBase should render BannerBase element correctly 1`] = ` class="mm-box mm-banner-base mm-box--padding-3 mm-box--display-flex mm-box--gap-2 mm-box--background-color-background-default mm-box--rounded-sm" data-testid="banner-base" > -

+

diff --git a/ui/components/component-library/banner-base/banner-base.tsx b/ui/components/component-library/banner-base/banner-base.tsx index 9947d7a6e001..1dc0369b3025 100644 --- a/ui/components/component-library/banner-base/banner-base.tsx +++ b/ui/components/component-library/banner-base/banner-base.tsx @@ -4,6 +4,7 @@ import { useI18nContext } from '../../../hooks/useI18nContext'; import { BackgroundColor, + BlockSize, BorderRadius, Display, TextVariant, @@ -55,7 +56,8 @@ export const BannerBase: BannerBaseComponent = React.forwardRef( > {startAccessory && <>{startAccessory}} -

+ {/* min-Width: 0 style is used to prevent grid/flex blowout */} + {title && ( {title} @@ -76,7 +78,8 @@ export const BannerBase: BannerBaseComponent = React.forwardRef( {actionButtonLabel} )} -
+ + {onClose && (
-
+

diff --git a/ui/components/component-library/box/box.scss b/ui/components/component-library/box/box.scss index 841afe88c6b9..b7b08040cc50 100644 --- a/ui/components/component-library/box/box.scss +++ b/ui/components/component-library/box/box.scss @@ -231,6 +231,9 @@ $attributesToApplyExtraProperties: margin; width: 100%; } + &--min-width-full { + min-width: 100%; + } &--height-full { height: 100%; @@ -241,6 +244,10 @@ $attributesToApplyExtraProperties: margin; width: $value; } + &--min-width-#{$fraction} { + min-width: $value; + } + &--height-#{$fraction} { height: $value; } @@ -252,6 +259,10 @@ $attributesToApplyExtraProperties: margin; &--#{$breakpoint}\:width-#{$fraction} { width: $value; } + &--#{$breakpoint}\:min-width-#{$fraction} { + min-width: $value; + } + &--#{$breakpoint}\:height-#{$fraction} { height: $value; } @@ -268,6 +279,14 @@ $attributesToApplyExtraProperties: margin; } } + @each $breakpoint, $min-width in $screen-sizes-map { + @media screen and (min-width: $min-width) { + &--#{$breakpoint}\:min-width-full { + min-width: 100%; + } + } + } + // breakpoint classes @each $breakpoint, $min-width in $screen-sizes-map { @media screen and (min-width: $min-width) { @@ -301,6 +320,18 @@ $attributesToApplyExtraProperties: margin; } } + &--min-width-screen { + min-width: 100vw; + } + // breakpoint classes + @each $breakpoint, $min-width in $screen-sizes-map { + @media screen and (min-width: $min-width) { + &--#{$breakpoint}\:min-width-screen { + min-width: 100vw; + } + } + } + &--height-max { height: max-content; } @@ -319,8 +350,20 @@ $attributesToApplyExtraProperties: margin; // breakpoint classes @each $breakpoint, $min-width in $screen-sizes-map { @media screen and (min-width: $min-width) { - &--#{$breakpoint}\:height-max { - height: max-content; + &--#{$breakpoint}\:width-max { + width: max-content; + } + } + } + + &--min-width-max { + min-width: max-content; + } + // breakpoint classes + @each $breakpoint, $min-width in $screen-sizes-map { + @media screen and (min-width: $min-width) { + &--#{$breakpoint}\:min-width-max { + min-width: max-content; } } } @@ -349,6 +392,18 @@ $attributesToApplyExtraProperties: margin; } } + &--min-width-min { + min-width: min-content; + } + // breakpoint classes + @each $breakpoint, $min-width in $screen-sizes-map { + @media screen and (min-width: $min-width) { + &--#{$breakpoint}\:min-width-min { + min-width: min-content; + } + } + } + // text @each $alignment in design-system.$text-align { &--text-align-#{$alignment} { diff --git a/ui/components/component-library/box/box.stories.tsx b/ui/components/component-library/box/box.stories.tsx index c9d14bc4af31..cb51a2dfce95 100644 --- a/ui/components/component-library/box/box.stories.tsx +++ b/ui/components/component-library/box/box.stories.tsx @@ -62,6 +62,11 @@ export default { control: 'multi-select', table: { category: 'display' }, }, + minWidth: { + options: Object.values(BlockSize), + control: 'multi-select', + table: { category: 'display' }, + }, height: { options: Object.values(BlockSize), control: 'select', @@ -224,6 +229,7 @@ BoxDefaultStory.args = { display: Display.Flex, justifyContent: JustifyContent.center, alignItems: AlignItems.center, + minWidth: BlockSize.Zero, width: BlockSize.Half, height: BlockSize.Half, borderColor: BorderColor.borderDefault, diff --git a/ui/components/component-library/box/box.test.tsx b/ui/components/component-library/box/box.test.tsx index e49eddcff02d..a8b9354b23e3 100644 --- a/ui/components/component-library/box/box.test.tsx +++ b/ui/components/component-library/box/box.test.tsx @@ -1964,6 +1964,39 @@ describe('Box', () => { expect(getByText('Box content')).toHaveClass('mm-box--md:width-max'); expect(getByText('Box content')).toHaveClass('mm-box--lg:width-min'); }); + it('should render the Box with the min-width class', () => { + const { getByText } = render( + <> + Box zero + Box one fourth + Box max + Box min + , + ); + expect(getByText('Box zero')).toHaveClass('mm-box--width-0'); + expect(getByText('Box one fourth')).toHaveClass('mm-box--width-1/4'); + expect(getByText('Box max')).toHaveClass('mm-box--width-max'); + expect(getByText('Box min')).toHaveClass('mm-box--width-min'); + }); + it('should render the Box with the responsive min-width classes', () => { + const { getByText } = render( + + Box content + , + ); + const boxElement = getByText('Box content'); + expect(boxElement).toHaveClass('mm-box--width-0'); + expect(boxElement).toHaveClass('mm-box--sm:width-1/4'); + expect(boxElement).toHaveClass('mm-box--md:width-screen'); + expect(boxElement).toHaveClass('mm-box--lg:width-max'); + }); it('should render the Box with the height class', () => { const { getByText } = render( <> diff --git a/ui/components/component-library/box/box.tsx b/ui/components/component-library/box/box.tsx index d09d0a6d6bed..4a41d638c997 100644 --- a/ui/components/component-library/box/box.tsx +++ b/ui/components/component-library/box/box.tsx @@ -165,6 +165,7 @@ export const Box: BoxComponent = React.forwardRef( gap, display, width, + minWidth, height, children, className = '', @@ -208,6 +209,7 @@ export const Box: BoxComponent = React.forwardRef( generateClassNames('align-items', alignItems, isValidString), generateClassNames('text-align', textAlign, isValidString), generateClassNames('width', width, isValidString), + generateClassNames('min-width', minWidth, isValidString), generateClassNames('height', height, isValidString), generateClassNames('color', color, isValidString), generateClassNames('background-color', backgroundColor, isValidString), diff --git a/ui/components/component-library/box/box.types.ts b/ui/components/component-library/box/box.types.ts index 46fa1404d05c..90afd917e8a3 100644 --- a/ui/components/component-library/box/box.types.ts +++ b/ui/components/component-library/box/box.types.ts @@ -42,6 +42,7 @@ export type StyleDeclarationType = | 'align-items' | 'text-align' | 'width' + | 'min-width' | 'height' | 'color' | 'background-color' @@ -391,6 +392,12 @@ export interface StyleUtilityProps { * Accepts responsive props in the form of an array. */ width?: BlockSize | BlockSizeArray; + /** + * The min-width of the component. + * Use BlockSize enum from '../../../helpers/constants/design-system'; + * Accepts responsive props in the form of an array. + */ + minWidth?: BlockSize | BlockSizeArray; /** * The height of the component. * Use BlockSize enum from '../../../helpers/constants/design-system'; diff --git a/ui/components/component-library/button-base/button-base.stories.tsx b/ui/components/component-library/button-base/button-base.stories.tsx index 90a50a27e51f..948ad6ea4890 100644 --- a/ui/components/component-library/button-base/button-base.stories.tsx +++ b/ui/components/component-library/button-base/button-base.stories.tsx @@ -38,6 +38,10 @@ export default { docs: { page: README, }, + design: { + type: 'figma', + url: 'https://www.figma.com/file/HKpPKij9V3TpsyMV1TpV7C/DS-Components?type=design&node-id=9970-48395&mode=design&t=K6JSdtG11z2wzcXG-4', + }, }, argTypes: { as: { diff --git a/ui/components/component-library/input/input.tsx b/ui/components/component-library/input/input.tsx index 5ff59c7a696b..7dc30ea4460d 100644 --- a/ui/components/component-library/input/input.tsx +++ b/ui/components/component-library/input/input.tsx @@ -47,7 +47,7 @@ export const Input: InputComponent = React.forwardRef( }, className, )} - aria-invalid={error} + {...(error && { 'aria-invalid': error })} as="input" autoComplete={autoComplete ? 'on' : 'off'} autoFocus={autoFocus} diff --git a/ui/components/component-library/picker-network/README.mdx b/ui/components/component-library/picker-network/README.mdx index 2e31576f8f8b..74481d251a95 100644 --- a/ui/components/component-library/picker-network/README.mdx +++ b/ui/components/component-library/picker-network/README.mdx @@ -16,7 +16,7 @@ The `PickerNetwork` is used for the action of changing a network. ### Label -Use the `label` prop for the text content of the `PickerNetwork` component. For long labels set a `max-width` using a `className` and the text will truncate showing an ellipsis. +Use the `label` prop for the text content of the `PickerNetwork` component. For long labels set a `max-width` using a `className` and the text will truncate showing an ellipsis. If the `src` prop is not set, the `label` prop will be used to generate fallback initial for `AvatarNetwork`. @@ -24,6 +24,7 @@ Use the `label` prop for the text content of the `PickerNetwork` component. For ```jsx import { PickerNetwork } from '../../ui/component-library'; + @@ -32,7 +33,7 @@ import { PickerNetwork } from '../../ui/component-library'; ### Src -Use the `src` prop with an image url to render the `AvatarNetwork`. Use the `avatarNetworkProps` to pass additional props to the `AvatarNetwork` component. +Use the `src` prop with an image url to render the `AvatarNetwork`. Use the `avatarNetworkProps` to pass additional props to the `AvatarNetwork` component. If the `src` prop is not set, the `label` prop will be used to generate fallback initial for `AvatarNetwork`. @@ -40,7 +41,27 @@ Use the `src` prop with an image url to render the `AvatarNetwork`. Use the `ava ```jsx import { PickerNetwork } from '../../ui/component-library'; - - - + + + +``` + +### Width + +The width of the `PickerNetwork` is set to auto by default. Use the style utility `width` prop with the `BlockSize` enum to set the width of the `PickerNetwork` component. + + + + + +```jsx +import { PickerNetwork } from '../../ui/component-library'; +import { BlockSize } from '../../../helpers/constants/design-system'; + +; +; ``` diff --git a/ui/components/component-library/picker-network/__snapshots__/picker-network.test.tsx.snap b/ui/components/component-library/picker-network/__snapshots__/picker-network.test.tsx.snap index 2b2226998a18..0827e30f349a 100644 --- a/ui/components/component-library/picker-network/__snapshots__/picker-network.test.tsx.snap +++ b/ui/components/component-library/picker-network/__snapshots__/picker-network.test.tsx.snap @@ -11,13 +11,13 @@ exports[`PickerNetwork should render the label inside the PickerNetwork 1`] = ` > I

-

Imported -

+ diff --git a/ui/components/component-library/picker-network/picker-network.scss b/ui/components/component-library/picker-network/picker-network.scss index 1e2e58cf083e..76ac895cbb91 100644 --- a/ui/components/component-library/picker-network/picker-network.scss +++ b/ui/components/component-library/picker-network/picker-network.scss @@ -1,10 +1,9 @@ .mm-picker-network { --picker-network-height: 32px; - max-width: fit-content; height: var(--picker-network-height); - &:active { + &:not([disabled]):active { background-color: var(--color-background-default-hover); } } diff --git a/ui/components/component-library/picker-network/picker-network.stories.tsx b/ui/components/component-library/picker-network/picker-network.stories.tsx index be2629223525..91536945bc1c 100644 --- a/ui/components/component-library/picker-network/picker-network.stories.tsx +++ b/ui/components/component-library/picker-network/picker-network.stories.tsx @@ -3,6 +3,7 @@ import { StoryFn, Meta } from '@storybook/react'; import { Display, FlexDirection, + BlockSize, } from '../../../helpers/constants/design-system'; import { Box } from '..'; @@ -31,15 +32,24 @@ export default { }, } as Meta; -export const DefaultStory = (args) => ; +const Template = (args) => ; + +export const DefaultStory = Template.bind({}); +DefaultStory.storyName = 'Default'; export const Label: StoryFn = (args) => ( - - - - + + + + + @@ -47,7 +57,11 @@ export const Label: StoryFn = (args) => ( ); export const Src: StoryFn = (args) => ( - + = (args) => ( ); -DefaultStory.storyName = 'Default'; +export const Width: StoryFn = (args) => ( + <> + + + +); diff --git a/ui/components/component-library/picker-network/picker-network.test.tsx b/ui/components/component-library/picker-network/picker-network.test.tsx index f0a5da9d4d2c..a942276c4ab0 100644 --- a/ui/components/component-library/picker-network/picker-network.test.tsx +++ b/ui/components/component-library/picker-network/picker-network.test.tsx @@ -65,4 +65,17 @@ describe('PickerNetwork', () => { ); expect(getByTestId('picker-network')).toHaveClass('test-class'); }); + it('should render with labelProps', () => { + const { getByTestId } = render( + , + ); + expect(getByTestId('picker-network-label')).toHaveClass('test-class'); + }); }); diff --git a/ui/components/component-library/picker-network/picker-network.tsx b/ui/components/component-library/picker-network/picker-network.tsx index 282269b00b8d..fd2a2a78b8bc 100644 --- a/ui/components/component-library/picker-network/picker-network.tsx +++ b/ui/components/component-library/picker-network/picker-network.tsx @@ -30,6 +30,7 @@ export const PickerNetwork: PickerNetworkComponent = React.forwardRef( avatarNetworkProps, iconProps, label, + labelProps, src, ...props }: PickerNetworkProps, @@ -56,7 +57,7 @@ export const PickerNetwork: PickerNetworkComponent = React.forwardRef( size={AvatarNetworkSize.Xs} {...avatarNetworkProps} /> - + {label} diff --git a/ui/components/component-library/picker-network/picker-network.types.ts b/ui/components/component-library/picker-network/picker-network.types.ts index 48d97be43c9d..4946f38ad4f4 100644 --- a/ui/components/component-library/picker-network/picker-network.types.ts +++ b/ui/components/component-library/picker-network/picker-network.types.ts @@ -4,6 +4,7 @@ import type { } from '../box'; import { IconProps } from '../icon/icon.types'; import { AvatarNetworkProps } from '../avatar-network/avatar-network.types'; +import { TextProps } from '../text'; export interface PickerNetworkStyleUtilityProps extends StyleUtilityProps { /** @@ -26,6 +27,10 @@ export interface PickerNetworkStyleUtilityProps extends StyleUtilityProps { * The text content of the PickerNetwork component */ label: string; + /** + * Additional props to pass to the label wrapper Text component + */ + labelProps?: TextProps<'span'>; } export type PickerNetworkProps = diff --git a/ui/components/component-library/text-field/text-field.js b/ui/components/component-library/text-field/text-field.js index 9cccd1bb7c6b..40564c9bfb1c 100644 --- a/ui/components/component-library/text-field/text-field.js +++ b/ui/components/component-library/text-field/text-field.js @@ -113,7 +113,7 @@ export const TextField = ({ > {startAccessory} { - if (event.key === 'Tab' && event.target === lastItemRef.current) { - // If Tab is pressed at the last item to close popover and focus to next element in DOM - onClose(); - } - }; + const handleKeyDown = useCallback( + (event) => { + if (event.key === 'Tab' && event.target === lastItemRef.current) { + // If Tab is pressed at the last item to close popover and focus to next element in DOM + onClose(); + } + }, + [onClose], + ); // Handle click outside of the popover to close it const popoverDialogRef = useRef(null); - const handleClickOutside = (event) => { - if ( - popoverDialogRef?.current && - !popoverDialogRef.current.contains(event.target) - ) { - onClose(); - } - }; + const handleClickOutside = useCallback( + (event) => { + if ( + popoverDialogRef?.current && + !popoverDialogRef.current.contains(event.target) + ) { + onClose(); + } + }, + [onClose], + ); useEffect(() => { document.addEventListener('mousedown', handleClickOutside); @@ -112,7 +119,7 @@ export const AccountListItemMenu = ({ return () => { document.removeEventListener('mousedown', handleClickOutside); }; - }, []); + }, [handleClickOutside]); return (
-
`; diff --git a/ui/components/multichain/account-list-item/account-list-item.js b/ui/components/multichain/account-list-item/account-list-item.js index 04210d5490f5..76d7ddcba242 100644 --- a/ui/components/multichain/account-list-item/account-list-item.js +++ b/ui/components/multichain/account-list-item/account-list-item.js @@ -7,7 +7,7 @@ import { toChecksumHexAddress } from '@metamask/controller-utils'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { shortenAddress } from '../../../helpers/utils/util'; -import { AccountListItemMenu } from '..'; +import { AccountListItemMenu, AvatarGroup } from '..'; import { AvatarAccount, Box, @@ -18,6 +18,8 @@ import { IconSize, AvatarAccountVariant, Text, + AvatarToken, + AvatarTokenSize, } from '../../component-library'; import { Color, @@ -37,14 +39,17 @@ import { HardwareKeyringNames } from '../../../../shared/constants/hardware-wall import { KeyringType } from '../../../../shared/constants/keyring'; import UserPreferencedCurrencyDisplay from '../../app/user-preferenced-currency-display/user-preferenced-currency-display.component'; import { SECONDARY, PRIMARY } from '../../../helpers/constants/common'; -import { findKeyringForAddress } from '../../../ducks/metamask/metamask'; +import { + findKeyringForAddress, + getNativeCurrency, +} from '../../../ducks/metamask/metamask'; import Tooltip from '../../ui/tooltip/tooltip'; import { MetaMetricsEventCategory, MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { MetaMetricsContext } from '../../../contexts/metametrics'; -import { getUseBlockie } from '../../../selectors'; +import { getNativeCurrencyImage, getUseBlockie } from '../../../selectors'; import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance'; const MAXIMUM_CURRENCY_DECIMALS = 3; @@ -79,6 +84,7 @@ export const AccountListItem = ({ closeMenu, connectedAvatar, connectedAvatarName, + showOptions = false, }) => { const t = useI18nContext(); const [accountOptionsMenuOpen, setAccountOptionsMenuOpen] = useState(false); @@ -90,7 +96,9 @@ export const AccountListItem = ({ setAccountListItemMenuElement(ref); }; - const { totalWeiBalance } = useAccountTotalFiatBalance(identity.address); + const { totalWeiBalance, orderedTokenList } = useAccountTotalFiatBalance( + identity.address, + ); const balanceToTranslate = process.env.MULTICHAIN ? totalWeiBalance : identity.balance; @@ -110,6 +118,8 @@ export const AccountListItem = ({ const label = getLabel(keyring, t); const trackEvent = useContext(MetaMetricsContext); + const primaryTokenImage = useSelector(getNativeCurrencyImage); + const nativeCurrency = useSelector(getNativeCurrency); return ( - - - + {process.env.MULTICHAIN ? ( + <> + {orderedTokenList.length > 1 ? ( + + ) : ( + + + + + + + )} + + ) : ( + + + + )} {label ? ( ) : null} - { - e.stopPropagation(); - if (!accountOptionsMenuOpen) { - trackEvent({ - event: MetaMetricsEventName.AccountDetailMenuOpened, - category: MetaMetricsEventCategory.Navigation, - properties: { - location: 'Account Options', - }, - }); - } - setAccountOptionsMenuOpen(!accountOptionsMenuOpen); - }} - data-testid="account-list-item-menu-button" - /> - setAccountOptionsMenuOpen(false)} - isOpen={accountOptionsMenuOpen} - isRemovable={keyring?.type !== KeyringType.hdKeyTree} - closeMenu={closeMenu} - /> + {showOptions ? ( + { + e.stopPropagation(); + if (!accountOptionsMenuOpen) { + trackEvent({ + event: MetaMetricsEventName.AccountDetailMenuOpened, + category: MetaMetricsEventCategory.Navigation, + properties: { + location: 'Account Options', + }, + }); + } + setAccountOptionsMenuOpen(!accountOptionsMenuOpen); + }} + data-testid="account-list-item-menu-button" + /> + ) : null} + {showOptions ? ( + setAccountOptionsMenuOpen(false)} + isOpen={accountOptionsMenuOpen} + isRemovable={keyring?.type !== KeyringType.hdKeyTree} + closeMenu={closeMenu} + /> + ) : null} ); }; @@ -307,6 +355,10 @@ AccountListItem.propTypes = { * Text used as the avatar alt text */ connectedAvatarName: PropTypes.string, + /** + * Represents if the "Options" 3-dot menu should display + */ + showOptions: PropTypes.bool, }; AccountListItem.displayName = 'AccountListItem'; diff --git a/ui/components/multichain/account-list-item/account-list-item.test.js b/ui/components/multichain/account-list-item/account-list-item.test.js index 3badaaadf7dc..7b3478f9c432 100644 --- a/ui/components/multichain/account-list-item/account-list-item.test.js +++ b/ui/components/multichain/account-list-item/account-list-item.test.js @@ -62,8 +62,8 @@ describe('AccountListItem', () => { ).toBeInTheDocument(); }); - it('renders the tree-dot menu to lauch the details menu', () => { - render(); + it('renders the three-dot menu to lauch the details menu', () => { + render({ showOptions: true }); const optionsButton = document.querySelector( '[aria-label="Test Account Options"]', ); @@ -84,7 +84,7 @@ describe('AccountListItem', () => { it('clicking the three-dot menu opens up options', () => { const onClick = jest.fn(); - render({ onClick }); + render({ onClick, showOptions: true }); const item = document.querySelector( '[data-testid="account-list-item-menu-button"]', ); diff --git a/ui/components/multichain/account-list-menu/account-list-menu.js b/ui/components/multichain/account-list-menu/account-list-menu.js index 3edbe481063f..92441a2d6874 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.js +++ b/ui/components/multichain/account-list-menu/account-list-menu.js @@ -37,7 +37,7 @@ import { getIsAddSnapAccountEnabled, ///: END:ONLY_INCLUDE_IN } from '../../../selectors'; -import { toggleAccountMenu, setSelectedAccount } from '../../../store/actions'; +import { setSelectedAccount } from '../../../store/actions'; import { MetaMetricsEventAccountType, MetaMetricsEventCategory, @@ -66,7 +66,11 @@ const ACTION_MODES = { IMPORT: 'import', }; -export const AccountListMenu = ({ onClose }) => { +export const AccountListMenu = ({ + onClose, + showAccountCreation = true, + accountListItemProps = {}, +}) => { const t = useI18nContext(); const trackEvent = useContext(MetaMetricsContext); const accounts = useSelector(getMetaMaskAccountsOrdered); @@ -132,7 +136,7 @@ export const AccountListMenu = ({ onClose }) => { { if (confirmed) { - dispatch(toggleAccountMenu()); + onClose(); } else { setActionMode(ACTION_MODES.LIST); } @@ -150,7 +154,7 @@ export const AccountListMenu = ({ onClose }) => { { if (confirmed) { - dispatch(toggleAccountMenu()); + onClose(); } else { setActionMode(ACTION_MODES.LIST); } @@ -205,7 +209,7 @@ export const AccountListMenu = ({ onClose }) => { size={Size.SM} startIconName={IconName.Hardware} onClick={() => { - dispatch(toggleAccountMenu()); + onClose(); trackEvent({ category: MetaMetricsEventCategory.Navigation, event: MetaMetricsEventName.AccountAddSelected, @@ -234,7 +238,7 @@ export const AccountListMenu = ({ onClose }) => { size={Size.SM} startIconName={IconName.Snaps} onClick={() => { - dispatch(toggleAccountMenu()); + onClose(); getEnvironmentType() === ENVIRONMENT_TYPE_POPUP ? global.platform.openExtensionInBrowser( ADD_SNAP_ACCOUNT_ROUTE, @@ -257,7 +261,7 @@ export const AccountListMenu = ({ onClose }) => { size={Size.SM} startIconName={IconName.Custody} onClick={() => { - dispatch(toggleAccountMenu()); + onClose(); trackEvent({ category: MetaMetricsEventCategory.Navigation, event: @@ -323,7 +327,7 @@ export const AccountListMenu = ({ onClose }) => { return ( { - dispatch(toggleAccountMenu()); + onClose(); trackEvent({ category: MetaMetricsEventCategory.Navigation, event: MetaMetricsEventName.NavAccountSwitched, @@ -339,30 +343,34 @@ export const AccountListMenu = ({ onClose }) => { closeMenu={onClose} connectedAvatar={connectedSite?.iconUrl} connectedAvatarName={connectedSite?.name} + showOptions + {...accountListItemProps} /> ); })} {/* Add / Import / Hardware button */} - - setActionMode(ACTION_MODES.MENU)} - data-testid="multichain-account-menu-popover-action-button" + {showAccountCreation ? ( + - {t('addImportAccount')} - - + setActionMode(ACTION_MODES.MENU)} + data-testid="multichain-account-menu-popover-action-button" + > + {t('addImportAccount')} + + + ) : null} ) : null} @@ -375,4 +383,12 @@ AccountListMenu.propTypes = { * Function that executes when the menu closes */ onClose: PropTypes.func.isRequired, + /** + * Represents if the button to create new accounts should display + */ + showAccountCreation: PropTypes.bool, + /** + * Props to pass to the AccountListItem, + */ + accountListItemProps: PropTypes.object, }; diff --git a/ui/components/multichain/account-list-menu/account-list-menu.test.js b/ui/components/multichain/account-list-menu/account-list-menu.test.js index 3ea7532f404a..6960501958ff 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.test.js +++ b/ui/components/multichain/account-list-menu/account-list-menu.test.js @@ -14,14 +14,9 @@ import { import { AccountListMenu } from '.'; ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) -const mockToggleAccountMenu = jest.fn(); +const mockOnClose = jest.fn(); const mockGetEnvironmentType = jest.fn(); -jest.mock('../../../store/actions.ts', () => ({ - ...jest.requireActual('../../../store/actions.ts'), - toggleAccountMenu: () => mockToggleAccountMenu, -})); - jest.mock('../../../../app/scripts/lib/util', () => ({ ...jest.requireActual('../../../../app/scripts/lib/util'), getEnvironmentType: () => mockGetEnvironmentType, @@ -114,9 +109,8 @@ describe('AccountListMenu', () => { }, }, }); - const props = { onClose: () => jest.fn() }; const { container } = renderWithProvider( - , + , mockStore, ); const searchBox = container.querySelector('input[type=search]'); @@ -202,7 +196,7 @@ describe('AccountListMenu', () => { ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) describe('addSnapAccountButton', () => { - const renderWithState = (state, props = { onClose: () => jest.fn() }) => { + const renderWithState = (state, props = { onClose: mockOnClose }) => { const store = configureStore({ ...mockState, ...{ @@ -246,7 +240,7 @@ describe('AccountListMenu', () => { fireEvent.click(addSnapAccountButton); await waitFor(() => { - expect(mockToggleAccountMenu).toHaveBeenCalled(); + expect(mockOnClose).toHaveBeenCalled(); }); }); diff --git a/ui/components/multichain/account-picker/__snapshots__/account-picker.test.js.snap b/ui/components/multichain/account-picker/__snapshots__/account-picker.test.js.snap index fac4f07c3017..7d00d5597907 100644 --- a/ui/components/multichain/account-picker/__snapshots__/account-picker.test.js.snap +++ b/ui/components/multichain/account-picker/__snapshots__/account-picker.test.js.snap @@ -7,42 +7,34 @@ exports[`AccountPicker renders properly 1`] = ` data-testid="account-menu-icon" > + +
`; diff --git a/ui/components/multichain/account-picker/account-picker.js b/ui/components/multichain/account-picker/account-picker.js index cc46c938a826..1d09823e3850 100644 --- a/ui/components/multichain/account-picker/account-picker.js +++ b/ui/components/multichain/account-picker/account-picker.js @@ -1,12 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; import { useSelector } from 'react-redux'; import { toChecksumHexAddress } from '@metamask/controller-utils'; import { - Box, AvatarAccount, AvatarAccountVariant, - Icon, IconName, Text, ButtonBase, @@ -17,11 +16,8 @@ import { BackgroundColor, BorderRadius, Display, - FlexDirection, - FontWeight, IconColor, Size, - TextAlign, TextColor, TextVariant, } from '../../../helpers/constants/design-system'; @@ -32,15 +28,20 @@ export const AccountPicker = ({ address, name, onClick, - disabled, + disabled = false, showAddress = false, + addressProps = {}, + labelProps = {}, + textProps = {}, + className = '', + ...props }) => { const useBlockie = useSelector(getUseBlockie); const shortenedAddress = shortenAddress(toChecksumHexAddress(address)); return ( - + - - - - {name} - - - + {name} {showAddress ? ( {shortenedAddress} ) : null} - + ); }; @@ -109,9 +113,29 @@ AccountPicker.propTypes = { /** * Represents if the AccountPicker should be actionable */ - disabled: PropTypes.bool.isRequired, + disabled: PropTypes.bool, /** * Represents if the account address should display */ showAddress: PropTypes.bool, + /** + * Represents if the AccountPicker should take full width + */ + block: PropTypes.bool, + /** + * Props to be added to the address element + */ + addressProps: PropTypes.object, + /** + * Props to be added to the label element + */ + labelProps: PropTypes.object, + /** + * Props to be added to the text element + */ + textProps: PropTypes.object, + /** + * Additional className to be added to the AccountPicker + */ + className: PropTypes.string, }; diff --git a/ui/components/multichain/account-picker/account-picker.stories.js b/ui/components/multichain/account-picker/account-picker.stories.js index 3aaf03de6d42..0cc58b7e9994 100644 --- a/ui/components/multichain/account-picker/account-picker.stories.js +++ b/ui/components/multichain/account-picker/account-picker.stories.js @@ -1,4 +1,9 @@ import React from 'react'; +import { + BorderColor, + BorderRadius, + TextAlign, +} from '../../../helpers/constants/design-system'; import { AccountPicker } from '.'; const CHAOS_ACCOUNT = { @@ -11,19 +16,39 @@ export default { component: AccountPicker, argTypes: { name: { - control: 'string', + control: 'text', }, address: { - control: 'string', + control: 'text', }, onClick: { action: 'onClick', }, + disabled: { + control: 'boolean', + }, + showAddress: { + control: 'boolean', + }, + block: { + control: 'boolean', + }, + addressPops: { + control: 'object', + }, + labelPros: { + control: 'object', + }, + textProp: { + control: 'object', + }, + classNam: { + control: 'text', + }, }, args: { address: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e', name: 'Account 1', - onClick: () => undefined, }, }; @@ -55,3 +80,23 @@ export const ChaosWithAddressStory = (args) => ( ); ChaosWithAddressStory.storyName = 'Chaos with Address'; ChaosWithAddressStory.args = { name: CHAOS_ACCOUNT.name, showAddress: true }; + +export const CustomAccountPicker = (args) => ( + +); diff --git a/ui/components/multichain/account-picker/account-picker.test.js b/ui/components/multichain/account-picker/account-picker.test.js index 0c02fb09b263..32e18533ec6f 100644 --- a/ui/components/multichain/account-picker/account-picker.test.js +++ b/ui/components/multichain/account-picker/account-picker.test.js @@ -50,4 +50,9 @@ describe('AccountPicker', () => { const { getByText } = render({ showAddress: true }); expect(getByText('0x0DCD5...3E7bc')).toBeInTheDocument(); }); + + it('should allow for an additional class name via className prop', () => { + const { container } = render({ className: 'test-class' }); + expect(container.querySelector('.test-class')).toBeInTheDocument(); + }); }); diff --git a/ui/components/multichain/account-picker/index.scss b/ui/components/multichain/account-picker/index.scss index baa22226efbc..30eddba35e5c 100644 --- a/ui/components/multichain/account-picker/index.scss +++ b/ui/components/multichain/account-picker/index.scss @@ -1,6 +1,6 @@ .multichain-account-picker { - &:hover, - &:focus { + &:not([disabled]):hover, + &:not([disabled]):focus { box-shadow: none; background: var(--color-background-default-hover); } diff --git a/ui/components/multichain/app-header/__snapshots__/app-header.test.js.snap b/ui/components/multichain/app-header/__snapshots__/app-header.test.js.snap index 67d265f76e1e..8944b9ce601c 100644 --- a/ui/components/multichain/app-header/__snapshots__/app-header.test.js.snap +++ b/ui/components/multichain/app-header/__snapshots__/app-header.test.js.snap @@ -216,13 +216,13 @@ exports[`App Header should match snapshot 1`] = ` > C
-

Chain 5 -

+ @@ -232,69 +232,61 @@ exports[`App Header should match snapshot 1`] = ` data-testid="account-menu-icon" > + +
{ src={currentNetwork?.rpcPrefs?.imageUrl} label={currentNetwork?.nickname} aria-label={t('networkMenu')} + labelProps={{ + display: Display.None, + }} onClick={(e) => { e.stopPropagation(); e.preventDefault(); @@ -301,6 +305,7 @@ export const AppHeader = ({ location }) => { }} disabled={disableAccountPicker} showAddress={Boolean(process.env.MULTICHAIN)} + labelProps={{ fontWeight: FontWeight.Bold }} /> ) : null} undefined, - onClose: () => undefined, }, }; @@ -20,6 +20,9 @@ export const DefaultStory = (args) => ; DefaultStory.storyName = 'Default'; export const ReceiveStory = (args) => ( - + ); ReceiveStory.storyName = 'Receive'; diff --git a/ui/components/multichain/avatar-group/__snapshots__/avatar-group.test.tsx.snap b/ui/components/multichain/avatar-group/__snapshots__/avatar-group.test.tsx.snap new file mode 100644 index 000000000000..dbba54f9e6d8 --- /dev/null +++ b/ui/components/multichain/avatar-group/__snapshots__/avatar-group.test.tsx.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AvatarGroup should render AvatarGroup component 1`] = ` +
+
+
+
+
+ AVAX logo +
+
+
+
+ OP logo +
+
+
+
+ MATIC logo +
+
+
+
+ ETH logo +
+
+
+
+

+ +1 +

+
+
+
+`; diff --git a/ui/components/multichain/avatar-group/avatar-group.stories.tsx b/ui/components/multichain/avatar-group/avatar-group.stories.tsx new file mode 100644 index 000000000000..f4397c18eb21 --- /dev/null +++ b/ui/components/multichain/avatar-group/avatar-group.stories.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { StoryFn, Meta } from '@storybook/react'; +import { AvatarGroup } from '.'; + +export default { + title: 'Components/Multichain/AvatarGroup', + component: AvatarGroup, + argTypes: { + limit: { + control: 'number', + }, + members: { + control: 'object', + }, + }, + args: { + members: [ + { symbol: 'ETH', iconUrl: './images/eth_logo.png' }, + { symbol: 'MATIC', iconUrl: './images/matic-token.png' }, + { symbol: 'OP', iconUrl: './images/optimism.svg' }, + { symbol: 'AVAX', iconUrl: './images/avax-token.png' }, + ], + limit: 4, + }, +} as Meta; + +const Template = (args) => ; + +export const DefaultStory = Template.bind({}); +DefaultStory.storyName = 'Default'; + +export const WithTag: StoryFn = (args) => ( + +); +WithTag.args = { + members: [ + { symbol: 'ETH', iconUrl: './images/eth_logo.png' }, + { symbol: 'MATIC', iconUrl: './images/matic-token.png' }, + { symbol: 'OP', iconUrl: './images/optimism.svg' }, + { symbol: 'AVAX', iconUrl: './images/avax-token.png' }, + { symbol: 'PALM', iconUrl: './images/palm.svg' }, + ], + limit: 2, +}; + +export const TokenWithOutSrc: StoryFn = (args) => ( + +); +TokenWithOutSrc.args = { + members: [ + { symbol: 'ETH', iconUrl: '' }, + { symbol: 'MATIC', iconUrl: '' }, + { symbol: 'OP', iconUrl: '' }, + { symbol: 'AVAX', iconUrl: '' }, + ], + limit: 2, +}; diff --git a/ui/components/multichain/avatar-group/avatar-group.test.tsx b/ui/components/multichain/avatar-group/avatar-group.test.tsx new file mode 100644 index 000000000000..76f6b12347da --- /dev/null +++ b/ui/components/multichain/avatar-group/avatar-group.test.tsx @@ -0,0 +1,34 @@ +/* eslint-disable jest/require-top-level-describe */ +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { AvatarGroup } from './avatar-group'; + +const members = [ + { symbol: 'ETH', iconUrl: './images/eth_logo.png' }, + { symbol: 'MATIC', iconUrl: './images/matic-token.png' }, + { symbol: 'OP', iconUrl: './images/optimism.svg' }, + { symbol: 'AVAX', iconUrl: './images/avax-token.png' }, + { symbol: 'PALM', iconUrl: './images/palm.svg' }, +]; + +describe('AvatarGroup', () => { + it('should render AvatarGroup component', () => { + const { getByTestId, container } = render( + , + ); + expect(getByTestId('avatar-group')).toBeDefined(); + expect(container).toMatchSnapshot(); + }); + + it('should render the tag +1 if members has a length greater than limit', () => { + render(); + + expect(screen.getByText('+1')).toBeDefined(); + }); + + it('should not render the tag if members has a length less than or equal to limit', () => { + const { queryByText } = render(); + expect(queryByText('+1')).not.toBeInTheDocument(); + }); +}); diff --git a/ui/components/multichain/avatar-group/avatar-group.tsx b/ui/components/multichain/avatar-group/avatar-group.tsx new file mode 100644 index 000000000000..eb1da0989da5 --- /dev/null +++ b/ui/components/multichain/avatar-group/avatar-group.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import classnames from 'classnames'; +import { + Text, + Box, + AvatarToken, + AvatarTokenSize, +} from '../../component-library'; +import { + AlignItems, + BorderColor, + BorderRadius, + Display, + TextColor, + TextVariant, +} from '../../../helpers/constants/design-system'; +import { AvatarGroupProps } from './avatar-group.types'; + +export const AvatarGroup: React.FC = ({ + className = '', + limit = 4, + members = [], + size = AvatarTokenSize.Xs, + borderColor = BorderColor.borderDefault, +}): JSX.Element => { + const membersCount = members.length; + const visibleMembers = members.slice(0, limit).reverse(); + const showTag = membersCount > limit; + let marginLeftValue = ''; + if (AvatarTokenSize.Xs) { + marginLeftValue = '-8px'; + } else if (AvatarTokenSize.Sm) { + marginLeftValue = '-12px'; + } else { + marginLeftValue = '-16px'; + } + const tagValue = `+${(membersCount - limit).toLocaleString()}`; + return ( + + + {visibleMembers.map((member, i) => ( + + + + ))} + + {showTag ? ( + + + {tagValue} + + + ) : null} + + ); +}; diff --git a/ui/components/multichain/avatar-group/avatar-group.types.tsx b/ui/components/multichain/avatar-group/avatar-group.types.tsx new file mode 100644 index 000000000000..3336469a82c5 --- /dev/null +++ b/ui/components/multichain/avatar-group/avatar-group.types.tsx @@ -0,0 +1,21 @@ +import { BorderColor } from '../../../helpers/constants/design-system'; +import { AvatarTokenSize } from '../../component-library'; +import type { StyleUtilityProps } from '../../component-library/box'; + +export interface AvatarGroupProps extends StyleUtilityProps { + /** * Additional class name for the AvatarGroup component */ + className?: string; + /** * Limit to show only a certain number of tokens and extras in Text */ + limit: number; + /** * List of Avatar Tokens */ + members: { + /** * Image of Avatar Token */ + iconUrl: string; + /** * Symbol of Avatar Token */ + symbol?: string; + }[]; + /** * Size of Avatar Tokens. For AvatarGroup we are considering AvatarTokenSize.Xs, AvatarTokenSize.Sm, AvatarTokenSize.Md */ + size?: AvatarTokenSize; + /** * Border Color of Avatar Tokens */ + borderColor?: BorderColor; +} diff --git a/ui/components/multichain/avatar-group/index.ts b/ui/components/multichain/avatar-group/index.ts new file mode 100644 index 000000000000..59d3238f30af --- /dev/null +++ b/ui/components/multichain/avatar-group/index.ts @@ -0,0 +1 @@ +export { AvatarGroup } from './avatar-group'; diff --git a/ui/components/multichain/balance-overview/__snapshots__/balance-overview.test.js.snap b/ui/components/multichain/balance-overview/__snapshots__/balance-overview.test.js.snap index 0cf4312db5fe..e839f4f988f8 100644 --- a/ui/components/multichain/balance-overview/__snapshots__/balance-overview.test.js.snap +++ b/ui/components/multichain/balance-overview/__snapshots__/balance-overview.test.js.snap @@ -13,12 +13,13 @@ exports[`Balance Overview and Portfolio for Tokens should output correct ETH tot >
- 0.001 + 0.0013 +
+
+

+ Send a token +

+
+
+
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + + + + + +
+
+ +
+
+`; diff --git a/ui/components/multichain/pages/send/components/account-picker.tsx b/ui/components/multichain/pages/send/components/account-picker.tsx new file mode 100644 index 000000000000..fa44ea90068d --- /dev/null +++ b/ui/components/multichain/pages/send/components/account-picker.tsx @@ -0,0 +1,60 @@ +import React, { useContext, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { getSelectedIdentity } from '../../../../../selectors'; +import { Label } from '../../../../component-library'; +import { AccountPicker } from '../../../account-picker'; +import { + BlockSize, + BorderColor, + Display, + JustifyContent, + TextAlign, +} from '../../../../../helpers/constants/design-system'; +import { I18nContext } from '../../../../../contexts/i18n'; +import { AccountListMenu } from '../../..'; +import { SendPageRow } from '.'; + +export const SendPageAccountPicker = () => { + const t = useContext(I18nContext); + const identity = useSelector(getSelectedIdentity); + + const [showAccountPicker, setShowAccountPicker] = useState(false); + + return ( + + + setShowAccountPicker(true)} + showAddress + borderColor={BorderColor.borderDefault} + borderWidth={1} + paddingTop={4} + paddingBottom={4} + block + justifyContent={JustifyContent.flexStart} + addressProps={{ + display: Display.Flex, + textAlign: TextAlign.Start, + }} + labelProps={{ + style: { flexGrow: 1, textAlign: 'start' }, + paddingInlineStart: 2, + }} + textProps={{ + display: Display.Flex, + width: BlockSize.Full, + }} + width={BlockSize.Full} + /> + {showAccountPicker ? ( + setShowAccountPicker(false)} + /> + ) : null} + + ); +}; diff --git a/ui/components/multichain/pages/send/components/index.ts b/ui/components/multichain/pages/send/components/index.ts new file mode 100644 index 000000000000..7c3c3ffecb85 --- /dev/null +++ b/ui/components/multichain/pages/send/components/index.ts @@ -0,0 +1,5 @@ +export { SendPageRow } from './send-page-row'; +export { SendPageAccountPicker } from './account-picker'; +export { SendPageNetworkPicker } from './network-picker'; +export { SendPageYourAccount } from './your-accounts'; +export { SendPageRecipientInput } from './recipient-input'; diff --git a/ui/components/multichain/pages/send/components/network-picker.tsx b/ui/components/multichain/pages/send/components/network-picker.tsx new file mode 100644 index 000000000000..fd2f52f985a7 --- /dev/null +++ b/ui/components/multichain/pages/send/components/network-picker.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { getCurrentNetwork } from '../../../../../selectors'; +import { toggleNetworkMenu } from '../../../../../store/actions'; +import { PickerNetwork } from '../../../../component-library'; +import { SendPageRow } from '.'; + +export const SendPageNetworkPicker = () => { + const currentNetwork = useSelector(getCurrentNetwork); + const dispatch = useDispatch(); + + return ( + + dispatch(toggleNetworkMenu())} + data-testid="send-page-network-picker" + /> + + ); +}; diff --git a/ui/components/multichain/pages/send/components/recipient-input.js b/ui/components/multichain/pages/send/components/recipient-input.js new file mode 100644 index 000000000000..30377bd6dfa1 --- /dev/null +++ b/ui/components/multichain/pages/send/components/recipient-input.js @@ -0,0 +1,69 @@ +import React, { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Label } from '../../../../component-library'; +import DomainInput from '../../../../../pages/send/send-content/add-recipient/domain-input'; +import { I18nContext } from '../../../../../contexts/i18n'; +import { + addHistoryEntry, + getIsUsingMyAccountForRecipientSearch, + getRecipient, + getRecipientUserInput, + resetRecipientInput, + updateRecipient, + updateRecipientUserInput, +} from '../../../../../ducks/send'; +import { showQrScanner } from '../../../../../store/actions'; +import { MetaMetricsContext } from '../../../../../contexts/metametrics'; +import { MetaMetricsEventCategory } from '../../../../../../shared/constants/metametrics'; +import { SendPageRow } from '.'; + +export const SendPageRecipientInput = () => { + const t = useContext(I18nContext); + const dispatch = useDispatch(); + const trackEvent = useContext(MetaMetricsContext); + + const recipient = useSelector(getRecipient); + const userInput = useSelector(getRecipientUserInput); + const isUsingMyAccountsForRecipientSearch = useSelector( + getIsUsingMyAccountForRecipientSearch, + ); + + return ( + + + dispatch(updateRecipientUserInput(address))} + onValidAddressTyped={async (address) => { + dispatch( + addHistoryEntry(`sendFlow - Valid address typed ${address}`), + ); + await dispatch(updateRecipientUserInput(address)); + dispatch(updateRecipient({ address, nickname: '' })); + }} + internalSearch={isUsingMyAccountsForRecipientSearch} + selectedAddress={recipient.address} + selectedName={recipient.nickname} + onPaste={(text) => { + dispatch( + addHistoryEntry( + `sendFlow - User pasted ${text} into address field`, + ), + ); + }} + onReset={() => dispatch(resetRecipientInput())} + scanQrCode={() => { + trackEvent({ + event: 'Used QR scanner', + category: MetaMetricsEventCategory.Transactions, + properties: { + action: 'Edit Screen', + legacy_event: true, + }, + }); + dispatch(showQrScanner()); + }} + /> + + ); +}; diff --git a/ui/components/multichain/pages/send/components/send-page-row.tsx b/ui/components/multichain/pages/send/components/send-page-row.tsx new file mode 100644 index 000000000000..af382fdadd8d --- /dev/null +++ b/ui/components/multichain/pages/send/components/send-page-row.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Box } from '../../../../component-library'; +import { + Display, + FlexDirection, +} from '../../../../../helpers/constants/design-system'; + +export const SendPageRow = ({ + children, +}: { + children: React.ReactNode | React.ReactNode[]; +}) => ( + + {children} + +); diff --git a/ui/components/multichain/pages/send/components/your-accounts.tsx b/ui/components/multichain/pages/send/components/your-accounts.tsx new file mode 100644 index 000000000000..24f8863aa2ae --- /dev/null +++ b/ui/components/multichain/pages/send/components/your-accounts.tsx @@ -0,0 +1,46 @@ +import React, { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Label } from '../../../../component-library'; +import { getMetaMaskAccountsOrdered } from '../../../../../selectors'; +import { AccountListItem } from '../../..'; +import { I18nContext } from '../../../../../contexts/i18n'; +import { + addHistoryEntry, + updateRecipient, + updateRecipientUserInput, +} from '../../../../../ducks/send'; +import { SendPageRow } from '.'; + +export const SendPageYourAccount = () => { + const t = useContext(I18nContext); + const dispatch = useDispatch(); + + // Your Accounts + const accounts = useSelector(getMetaMaskAccountsOrdered); + + return ( + + + {accounts.map((account: any) => ( + { + dispatch( + addHistoryEntry( + `sendFlow - User clicked recipient from my accounts. address: ${account.address}, nickname ${account.name}`, + ), + ); + dispatch( + updateRecipient({ + address: account.address, + nickname: account.name, + }), + ); + dispatch(updateRecipientUserInput(account.address)); + }} + /> + ))} + + ); +}; diff --git a/ui/components/multichain/pages/send/index.js b/ui/components/multichain/pages/send/index.js new file mode 100644 index 000000000000..e0718de39bdd --- /dev/null +++ b/ui/components/multichain/pages/send/index.js @@ -0,0 +1 @@ +export { SendPage } from './send'; diff --git a/ui/components/multichain/pages/send/index.scss b/ui/components/multichain/pages/send/index.scss new file mode 100644 index 000000000000..8dcb393dbc96 --- /dev/null +++ b/ui/components/multichain/pages/send/index.scss @@ -0,0 +1,3 @@ +.multichain-send-page { + width: 408px; +} diff --git a/ui/components/multichain/pages/send/send.js b/ui/components/multichain/pages/send/send.js new file mode 100644 index 000000000000..c1632a5b69b1 --- /dev/null +++ b/ui/components/multichain/pages/send/send.js @@ -0,0 +1,131 @@ +import React, { useCallback, useContext, useEffect, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory, useLocation } from 'react-router-dom'; +import { I18nContext } from '../../../../contexts/i18n'; +import { + ButtonIcon, + ButtonIconSize, + ButtonPrimary, + ButtonPrimarySize, + ButtonSecondary, + ButtonSecondarySize, + IconName, +} from '../../../component-library'; +import { Content, Footer, Header, Page } from '../page'; +import { + SEND_STAGES, + getDraftTransactionExists, + getDraftTransactionID, + getSendStage, + resetSendState, + startNewDraftTransaction, +} from '../../../../ducks/send'; +import { AssetType } from '../../../../../shared/constants/transaction'; +import { cancelTx, showQrScanner } from '../../../../store/actions'; +import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'; +import { getMostRecentOverviewPage } from '../../../../ducks/history/history'; +import { + SendPageAccountPicker, + SendPageRecipientInput, + SendPageYourAccount, + SendPageNetworkPicker, +} from './components'; + +export const SendPage = () => { + const t = useContext(I18nContext); + const dispatch = useDispatch(); + + const startedNewDraftTransaction = useRef(false); + const draftTransactionExists = useSelector(getDraftTransactionExists); + const draftTransactionID = useSelector(getDraftTransactionID); + const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); + const sendStage = useSelector(getSendStage); + + const history = useHistory(); + const location = useLocation(); + + const cleanup = useCallback(() => { + dispatch(resetSendState()); + }, [dispatch]); + + /** + * It is possible to route to this page directly, either by typing in the url + * or by clicking the browser back button after progressing to the confirm + * screen. In the case where a draft transaction does not yet exist, this + * hook is responsible for creating it. We will assume that this is a native + * asset send. + */ + useEffect(() => { + if ( + draftTransactionExists === false && + startedNewDraftTransaction.current === false + ) { + startedNewDraftTransaction.current = true; + dispatch(startNewDraftTransaction({ type: AssetType.native })); + } + }, [draftTransactionExists, dispatch]); + + useEffect(() => { + window.addEventListener('beforeunload', cleanup); + }, [cleanup]); + + useEffect(() => { + if (location.search === '?scan=true') { + dispatch(showQrScanner()); + + // Clear the queryString param after showing the modal + const [cleanUrl] = window.location.href.split('?'); + window.history.pushState({}, null, `${cleanUrl}`); + window.location.hash = '#send'; + } + }, [location, dispatch]); + + useEffect(() => { + return () => { + dispatch(resetSendState()); + window.removeEventListener('beforeunload', cleanup); + }; + }, [dispatch, cleanup]); + + const onCancel = useCallback(() => { + if (draftTransactionID) { + dispatch(cancelTx({ id: draftTransactionID })); + } + dispatch(resetSendState()); + + const nextRoute = + sendStage === SEND_STAGES.EDIT ? DEFAULT_ROUTE : mostRecentOverviewPage; + history.push(nextRoute); + }); + + return ( + +
+ } + > + {t('sendAToken')} +
+ + + + + + +
+ + {t('cancel')} + + + {t('confirm')} + +
+
+ ); +}; diff --git a/ui/components/multichain/pages/send/send.test.js b/ui/components/multichain/pages/send/send.test.js new file mode 100644 index 000000000000..74b97df8798b --- /dev/null +++ b/ui/components/multichain/pages/send/send.test.js @@ -0,0 +1,31 @@ +import React from 'react'; +import configureStore from '../../../../store/store'; +import mockState from '../../../../../test/data/mock-state.json'; +import { renderWithProvider } from '../../../../../test/jest'; +import { SendPage } from '.'; + +jest.mock('@ethersproject/providers', () => { + const originalModule = jest.requireActual('@ethersproject/providers'); + return { + ...originalModule, + Web3Provider: jest.fn().mockImplementation(() => { + return {}; + }), + }; +}); + +const render = (props = {}) => { + const store = configureStore(mockState); + return renderWithProvider(, store); +}; + +describe('SendPage', () => { + describe('render', () => { + it('renders correctly', () => { + const { container, getByTestId } = render(); + expect(container).toMatchSnapshot(); + + expect(getByTestId('send-page-network-picker')).toBeInTheDocument(); + }); + }); +}); diff --git a/ui/components/multichain/select-action-modal-item/select-action-modal-item.js b/ui/components/multichain/select-action-modal-item/select-action-modal-item.js index 9b327dfaca6c..d227e510e65e 100644 --- a/ui/components/multichain/select-action-modal-item/select-action-modal-item.js +++ b/ui/components/multichain/select-action-modal-item/select-action-modal-item.js @@ -27,6 +27,7 @@ export const SelectActionModalItem = ({ primaryText, secondaryText, disabled, + ...props }) => { if (disabled) { return null; @@ -46,6 +47,7 @@ export const SelectActionModalItem = ({ className="select-action-modal-item" data-testid="select-action-modal-item" width={BlockSize.Full} + {...props} > { ///: END:ONLY_INCLUDE_IN onClose(); }} + data-testid="select-action-modal-item-swap" /> { history.push(SEND_ROUTE); onClose(); }} + data-testid="select-action-modal-item-send" /> { ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask) diff --git a/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.js.snap b/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.js.snap index d0fe06997199..b853f7248569 100644 --- a/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.js.snap +++ b/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.js.snap @@ -49,6 +49,7 @@ exports[`TokenListItem should render correctly 1`] = `

{secondary} diff --git a/ui/components/ui/icon-button/icon-button.js b/ui/components/ui/icon-button/icon-button.js index cc95d8343cf1..9f298dae5444 100644 --- a/ui/components/ui/icon-button/icon-button.js +++ b/ui/components/ui/icon-button/icon-button.js @@ -28,10 +28,10 @@ export default function IconButton({ {renderWrapper( <>

{Icon}
- {label.length > 9 ? ( + {label.length > 10 ? ( diff --git a/ui/components/ui/icon-button/icon-button.scss b/ui/components/ui/icon-button/icon-button.scss index e80854307fc7..b7526aca1313 100644 --- a/ui/components/ui/icon-button/icon-button.scss +++ b/ui/components/ui/icon-button/icon-button.scss @@ -30,7 +30,7 @@ cursor: auto; } - &__label { + &__label-large { width: 60px; //for ellipsis keeping the width same as icon-button used here } } diff --git a/ui/components/ui/new-network-info/new-network-info.js b/ui/components/ui/new-network-info/new-network-info.js index fac443c44c00..ab58bbced971 100644 --- a/ui/components/ui/new-network-info/new-network-info.js +++ b/ui/components/ui/new-network-info/new-network-info.js @@ -9,6 +9,7 @@ import { AlignItems, Color, Display, + FlexDirection, FontWeight, TextAlign, TextVariant, @@ -16,8 +17,7 @@ import { import { IMPORT_TOKEN_ROUTE } from '../../../helpers/constants/routes'; import { getCurrentNetwork, getUseTokenDetection } from '../../../selectors'; import { setFirstTimeUsedNetwork } from '../../../store/actions'; -import { PickerNetwork, Text } from '../../component-library'; -import Box from '../box'; +import { PickerNetwork, Text, Box } from '../../component-library'; import Button from '../button'; import Popover from '../popover'; @@ -78,7 +78,11 @@ export default function NewNetworkInfo() { } > - + ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/ui/sender-to-recipient/sender-to-recipient.component.js b/ui/components/ui/sender-to-recipient/sender-to-recipient.component.js index 49cdcc8e0e32..accb8595cfd9 100644 --- a/ui/components/ui/sender-to-recipient/sender-to-recipient.component.js +++ b/ui/components/ui/sender-to-recipient/sender-to-recipient.component.js @@ -8,6 +8,7 @@ import { shortenAddress } from '../../../helpers/utils/util'; import AccountMismatchWarning from '../account-mismatch-warning/account-mismatch-warning.component'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; +import { COPY_OPTIONS } from '../../../../shared/constants/copy'; import NicknamePopovers from '../../app/modals/nickname-popovers'; import { Icon, IconName } from '../../component-library'; import { @@ -51,7 +52,7 @@ function SenderAddress({ )} onClick={() => { setAddressCopied(true); - copyToClipboard(checksummedSenderAddress); + copyToClipboard(checksummedSenderAddress, COPY_OPTIONS); if (onSenderClick) { onSenderClick(); } @@ -130,7 +131,7 @@ export function RecipientWithAddress({ onClick={() => { if (recipientIsOwnedAccount) { setAddressCopied(true); - copyToClipboard(checksummedRecipientAddress); + copyToClipboard(checksummedRecipientAddress, COPY_OPTIONS); } else { setShowNicknamePopovers(true); if (onRecipientClick) { diff --git a/ui/components/ui/toggle-button/toggle-button.component.js b/ui/components/ui/toggle-button/toggle-button.component.js index c14cdbcef2c5..b6966ada856f 100644 --- a/ui/components/ui/toggle-button/toggle-button.component.js +++ b/ui/components/ui/toggle-button/toggle-button.component.js @@ -47,7 +47,15 @@ const colors = { }; const ToggleButton = (props) => { - const { value, onToggle, offLabel, onLabel, disabled, className } = props; + const { + value, + onToggle, + offLabel, + onLabel, + disabled, + className, + dataTestId, + } = props; const modifier = value ? 'on' : 'off'; @@ -77,6 +85,9 @@ const ToggleButton = (props) => { thumbStyle={thumbStyle} thumbAnimateRange={[3, 18]} colors={colors} + passThroughInputProps={{ + 'data-testId': dataTestId, + }} />
{offLabel} @@ -111,6 +122,10 @@ ToggleButton.propTypes = { * Additional className to add to the ToggleButton */ className: PropTypes.string, + /** + * A test id for the toggle button + */ + dataTestId: PropTypes.string, }; export default ToggleButton; diff --git a/ui/components/ui/token-input/token-input.component.js b/ui/components/ui/token-input/token-input.component.js index 45eb8a8e14d8..8b79e7cdd4ae 100644 --- a/ui/components/ui/token-input/token-input.component.js +++ b/ui/components/ui/token-input/token-input.component.js @@ -33,6 +33,7 @@ export default class TokenInput extends PureComponent { symbol: PropTypes.string, }).isRequired, tokenExchangeRates: PropTypes.object, + nativeCurrency: PropTypes.string, tokens: PropTypes.array.isRequired, }; @@ -104,7 +105,9 @@ export default class TokenInput extends PureComponent { hideConversion, token, tokens, + nativeCurrency, } = this.props; + const { decimalValue } = this.state; const existingToken = tokens.find(({ address }) => @@ -127,8 +130,8 @@ export default class TokenInput extends PureComponent { currency = currentCurrency; numberOfDecimals = 2; } else { - // Display ETH - currency = EtherDenomination.ETH; + // Display Native currency + currency = nativeCurrency; numberOfDecimals = 6; } @@ -138,7 +141,6 @@ export default class TokenInput extends PureComponent { fromCurrency: EtherDenomination.ETH, fromDenomination: EtherDenomination.ETH, }); - return tokenExchangeRate ? ( { @@ -55,6 +56,32 @@ describe('TokenInput Component', () => { expect(queryByTitle('0 ETH')).toBeInTheDocument(); }); + it('should render conversionRate on polygon', () => { + const showFiatState = { + ...mockState, + metamask: { + ...mockState.metamask, + nativeCurrency: 'MATIC', + preferences: { + ...mockState.metamask.preferences, + showFiatInTestnets: true, + }, + providerConfig: { + chainId: CHAIN_IDS.POLYGON, + type: NETWORK_TYPES.MAINNET, + }, + }, + }; + const mockStore = configureMockStore()(showFiatState); + + const { queryByTitle } = renderWithProvider( + , + mockStore, + ); + + expect(queryByTitle('0 MATIC')).toBeInTheDocument(); + }); + it('should render showFiat', () => { const showFiatState = { ...mockState, diff --git a/ui/components/ui/token-input/token-input.container.js b/ui/components/ui/token-input/token-input.container.js index 98082a3db387..9778e068675b 100644 --- a/ui/components/ui/token-input/token-input.container.js +++ b/ui/components/ui/token-input/token-input.container.js @@ -1,6 +1,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { getTokenExchangeRates, getShouldShowFiat } from '../../../selectors'; +import { getNativeCurrency } from '../../../ducks/metamask/metamask'; import TokenInput from './token-input.component'; const mapStateToProps = (state) => { @@ -12,6 +13,7 @@ const mapStateToProps = (state) => { currentCurrency, tokenExchangeRates: getTokenExchangeRates(state), hideConversion: !getShouldShowFiat(state), + nativeCurrency: getNativeCurrency(state), tokens, }; }; diff --git a/ui/css/base-styles.scss b/ui/css/base-styles.scss index da70908fd1b9..26027541fe45 100644 --- a/ui/css/base-styles.scss +++ b/ui/css/base-styles.scss @@ -43,16 +43,6 @@ html { } } -.mouse-user-styles { - button:focus, - input:focus, - textarea:focus, - .unit-input__input, - .currency-display__input { - outline: none; - } -} - /* This error class is used in the following files still: /ui/pages/create-account/import-account/json.js @@ -93,19 +83,6 @@ a:hover { color: var(--color-primary-alternative); } -input.large-input, -textarea.large-input { - padding: 8px; -} - -input.large-input { - height: 36px; -} - -.allcaps { - text-transform: uppercase; -} - .input-label { padding-bottom: 10px; font-weight: 400; diff --git a/ui/css/design-system/attributes.scss b/ui/css/design-system/attributes.scss index c3b4f43edf67..de019701b1e7 100644 --- a/ui/css/design-system/attributes.scss +++ b/ui/css/design-system/attributes.scss @@ -8,6 +8,7 @@ $flex-direction: row, row-reverse, column, column-reverse; $flex-wrap: wrap, wrap-reverse, nowrap; $fractions: ( + 0: 0, 1\/2: 50%, 1\/3: 33.333333%, 2\/3: 66.666667%, diff --git a/ui/css/itcss/components/send.scss b/ui/css/itcss/components/send.scss index 49f70f9cfa3a..58a3fdbf2a97 100644 --- a/ui/css/itcss/components/send.scss +++ b/ui/css/itcss/components/send.scss @@ -70,14 +70,6 @@ padding-right: 4px; } - .large-input { - @include Paragraph; - - border: 1px solid var(--color-border-default); - border-radius: 4px; - margin: 4px 0 20px; - } - .send-screen-gas-input { border: 1px solid transparent; } diff --git a/ui/ducks/app/app.test.js b/ui/ducks/app/app.test.js index 714e2b514966..81a7619120bc 100644 --- a/ui/ducks/app/app.test.js +++ b/ui/ducks/app/app.test.js @@ -302,15 +302,6 @@ describe('App State', () => { expect(state.accountDetail.privateKey).toStrictEqual('private key'); }); - it('set mouse user state', () => { - const state = reduceApp(metamaskState, { - type: actions.SET_MOUSE_USER_STATE, - payload: true, - }); - - expect(state.isMouseUser).toStrictEqual(true); - }); - it('smart transactions - SET_SMART_TRANSACTIONS_ERROR', () => { const state = reduceApp(metamaskState, { type: actions.SET_SMART_TRANSACTIONS_ERROR, diff --git a/ui/ducks/app/app.ts b/ui/ducks/app/app.ts index 3251d9cecca7..c9dac520d0bc 100644 --- a/ui/ducks/app/app.ts +++ b/ui/ducks/app/app.ts @@ -50,7 +50,6 @@ interface AppState { scrollToBottom: boolean; warning: string | null | undefined; buyView: Record; - isMouseUser: boolean; defaultHdPaths: { trezor: string; ledger: string; @@ -126,7 +125,6 @@ const initialState: AppState = { // Used to display error text warning: null, buyView: {}, - isMouseUser: false, defaultHdPaths: { trezor: `m/44'/60'/0'/0`, ledger: `m/44'/60'/0'/0/0`, @@ -418,12 +416,6 @@ export default function reduceApp( }, }; - case actionConstants.SET_MOUSE_USER_STATE: - return { - ...appState, - isMouseUser: action.payload, - }; - case actionConstants.SET_SELECTED_NETWORK_CONFIGURATION_ID: return { ...appState, diff --git a/ui/ducks/confirm-transaction/confirm-transaction.duck.test.js b/ui/ducks/confirm-transaction/confirm-transaction.duck.test.js index 324591fecb70..1d5bf398b6e5 100644 --- a/ui/ducks/confirm-transaction/confirm-transaction.duck.test.js +++ b/ui/ducks/confirm-transaction/confirm-transaction.duck.test.js @@ -276,7 +276,7 @@ describe('Confirm Transaction Duck', () => { history: [], id: 2603411941761054, loadingDefaults: false, - metamaskNetworkId: '5', + chainId: '0x5', origin: 'faucet.metamask.io', status: TransactionStatus.unapproved, time: 1530838113716, @@ -345,7 +345,6 @@ describe('Confirm Transaction Duck', () => { metamask: { conversionRate: 468.58, currentCurrency: 'usd', - networkId: '5', selectedNetworkClientId: NetworkType.goerli, networksMetadata: { [NetworkType.goerli]: { @@ -361,7 +360,7 @@ describe('Confirm Transaction Duck', () => { history: [], id: 2603411941761054, loadingDefaults: false, - metamaskNetworkId: '5', + chainId: '0x5', origin: 'faucet.metamask.io', status: TransactionStatus.unapproved, time: 1530838113716, diff --git a/ui/ducks/domains.js b/ui/ducks/domains.js index b17ff2a914a8..6c380f392510 100644 --- a/ui/ducks/domains.js +++ b/ui/ducks/domains.js @@ -1,15 +1,14 @@ import { createSlice } from '@reduxjs/toolkit'; import log from 'loglevel'; -import networkMap from 'ethereum-ens-network-map'; +import ensNetworkMap from 'ethereum-ens-network-map'; import { isConfusing } from 'unicode-confusables'; import { isHexString } from 'ethereumjs-util'; import { Web3Provider } from '@ethersproject/providers'; import { getCurrentChainId } from '../selectors'; import { - CHAIN_ID_TO_NETWORK_ID_MAP, - NETWORK_IDS, - NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP, + CHAIN_IDS, + CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP, } from '../../shared/constants/network'; import { CONFUSING_ENS_ERROR, @@ -37,7 +36,7 @@ const initialState = { resolution: null, error: null, warning: null, - network: null, + chainId: null, domainType: null, domainName: null, }; @@ -57,7 +56,7 @@ const slice = createSlice({ state.resolution = null; state.error = null; state.warning = null; - const { address, error, network, domainType, domainName } = + const { address, error, chainId, domainType, domainName } = action.payload; state.domainType = domainType; if (state.domainType === ENS) { @@ -67,7 +66,7 @@ const slice = createSlice({ error.message === 'ENS name not defined.' ) { state.error = - network === NETWORK_IDS.MAINNET + chainId === CHAIN_IDS.MAINNET ? ENS_NO_ADDRESS_FOR_NAME : ENS_NOT_FOUND_ON_NETWORK; } else if (error.message === 'Illegal character for ENS.') { @@ -97,14 +96,14 @@ const slice = createSlice({ state.error = null; state.resolution = null; state.warning = null; - state.network = action.payload; + state.chainId = action.payload; }, disableDomainLookup: (state) => { state.stage = 'NO_NETWORK_SUPPORT'; state.error = null; state.warning = null; state.resolution = null; - state.network = null; + state.chainId = null; }, ensNotSupported: (state) => { state.resolution = null; @@ -119,7 +118,7 @@ const slice = createSlice({ }, extraReducers: (builder) => { builder.addCase(CHAIN_CHANGED, (state, action) => { - if (action.payload !== state.currentChainId) { + if (action.payload !== state.chainId) { state.stage = 'UNINITIALIZED'; web3Provider = null; } @@ -143,17 +142,17 @@ export function initializeDomainSlice() { return (dispatch, getState) => { const state = getState(); const chainId = getCurrentChainId(state); - const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId]; - const networkName = NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP[network]; - const ensAddress = networkMap[network]; + const networkName = CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP[chainId]; + const chainIdInt = parseInt(chainId, 16); + const ensAddress = ensNetworkMap[chainIdInt.toString()]; const networkIsSupported = Boolean(ensAddress); if (networkIsSupported) { web3Provider = new Web3Provider(global.ethereumProvider, { - chainId: parseInt(network, 10), + chainId: chainIdInt, name: networkName, ensAddress, }); - dispatch(enableDomainLookup(network)); + dispatch(enableDomainLookup(chainId)); } else { web3Provider = null; dispatch(disableDomainLookup()); @@ -188,14 +187,12 @@ export function lookupEnsName(domainName) { error = err; } const chainId = getCurrentChainId(state); - const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId]; await dispatch( domainLookup({ address, error, chainId, - network, domainType: ENS, domainName: trimmedDomainName, }), diff --git a/ui/ducks/metamask/metamask.test.js b/ui/ducks/metamask/metamask.test.js index 1f43f9fea15c..b71a749d1f1b 100644 --- a/ui/ducks/metamask/metamask.test.js +++ b/ui/ducks/metamask/metamask.test.js @@ -42,7 +42,6 @@ describe('MetaMask Reducers', () => { conversionRate: 1200.88200327, nativeCurrency: 'ETH', useCurrencyRateCheck: true, - networkId: '5', selectedNetworkClientId: NetworkType.goerli, networksMetadata: { [NetworkType.goerli]: { @@ -96,13 +95,13 @@ describe('MetaMask Reducers', () => { time: 1487363153561, status: TransactionStatus.unapproved, gasMultiplier: 1, - metamaskNetworkId: '5', + chainId: '0x5', txParams: { from: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', to: '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761', value: '0xde0b6b3a7640000', metamaskId: 4768706228115573, - metamaskNetworkId: '5', + chainId: '0x5', gas: '0x5209', }, txFee: '17e0186e60800', diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 58d45c780367..2cf64fbfe326 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -964,6 +964,7 @@ const slice = createSlice({ slice.caseReducers.updateAmountToMax(state); } else if (initialAssetSet === false) { slice.caseReducers.updateSendAmount(state, { payload: '0x0' }); + slice.caseReducers.updateUserInputHexData(state, { payload: '' }); } // validate send state slice.caseReducers.validateSendState(state); diff --git a/ui/ducks/send/send.test.js b/ui/ducks/send/send.test.js index a7cf6c22c6b6..90db016fcda5 100644 --- a/ui/ducks/send/send.test.js +++ b/ui/ducks/send/send.test.js @@ -22,7 +22,7 @@ import { TransactionEnvelopeType, } from '../../../shared/constants/transaction'; import * as Actions from '../../store/actions'; -import { setBackgroundConnection } from '../../../test/jest'; +import { setBackgroundConnection } from '../../store/background-connection'; import { generateERC20TransferData, generateERC721TransferData, @@ -481,6 +481,33 @@ describe('Send Slice', () => { ); }); + it('should update hex data if its not the initial asset set', () => { + const updateAssetState = getInitialSendStateWithExistingTxState({ + asset: { + type: 'old type', + balance: 'old balance', + }, + userInputHexData: '0xTestHexData', + }); + + const action = { + type: 'send/updateAsset', + payload: { + asset: { + type: 'new type', + balance: 'new balance', + }, + initialAssetSet: false, + }, + }; + + const result = sendReducer(updateAssetState, action); + + const draftTransaction = getTestUUIDTx(result); + + expect(draftTransaction.userInputHexData).toStrictEqual(''); + }); + it('should nullify old contract address error when asset types is not TOKEN', () => { const recipientErrorState = getInitialSendStateWithExistingTxState({ recipient: { diff --git a/ui/helpers/constants/design-system.ts b/ui/helpers/constants/design-system.ts index 881d4bea9232..7fcc7acc44b0 100644 --- a/ui/helpers/constants/design-system.ts +++ b/ui/helpers/constants/design-system.ts @@ -375,6 +375,7 @@ export const FRACTIONS = { }; export enum BlockSize { + Zero = '0', Half = '1/2', OneThird = '1/3', TwoThirds = '2/3', diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts index a415542d15aa..2a331fa8fe85 100644 --- a/ui/helpers/constants/routes.ts +++ b/ui/helpers/constants/routes.ts @@ -14,8 +14,6 @@ const NETWORKS_FORM_ROUTE = '/settings/networks/form'; const ADD_NETWORK_ROUTE = '/settings/networks/add-network'; const ADD_POPULAR_CUSTOM_NETWORK = '/settings/networks/add-popular-custom-network'; -const SNAPS_LIST_ROUTE = '/settings/snaps-list'; -const SNAPS_VIEW_ROUTE = '/settings/snaps-view'; const CONTACT_LIST_ROUTE = '/settings/contact-list'; const CONTACT_EDIT_ROUTE = '/settings/contact-list/edit-contact'; const CONTACT_ADD_ROUTE = '/settings/contact-list/add-contact'; @@ -47,6 +45,8 @@ const CONNECT_SNAP_INSTALL_ROUTE = '/snap-install'; const CONNECT_SNAP_UPDATE_ROUTE = '/snap-update'; const CONNECT_SNAP_RESULT_ROUTE = '/snap-install-result'; const NOTIFICATIONS_ROUTE = '/notifications'; +const SNAPS_ROUTE = '/snaps'; +const SNAPS_VIEW_ROUTE = '/snaps/view'; ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) const ADD_SNAP_ACCOUNT_ROUTE = '/add-snap-account'; @@ -225,8 +225,6 @@ export { SECURITY_ROUTE, GENERAL_ROUTE, ABOUT_US_ROUTE, - SNAPS_LIST_ROUTE, - SNAPS_VIEW_ROUTE, CONTACT_LIST_ROUTE, CONTACT_EDIT_ROUTE, CONTACT_ADD_ROUTE, @@ -250,6 +248,8 @@ export { CONNECT_SNAP_UPDATE_ROUTE, CONNECT_SNAP_RESULT_ROUTE, NOTIFICATIONS_ROUTE, + SNAPS_ROUTE, + SNAPS_VIEW_ROUTE, ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) ADD_SNAP_ACCOUNT_ROUTE, diff --git a/ui/helpers/constants/settings.js b/ui/helpers/constants/settings.js index 23af83fbd845..177f5cc30426 100644 --- a/ui/helpers/constants/settings.js +++ b/ui/helpers/constants/settings.js @@ -8,9 +8,6 @@ import { NETWORKS_ROUTE, CONTACT_LIST_ROUTE, EXPERIMENTAL_ROUTE, - ///: BEGIN:ONLY_INCLUDE_IN(snaps) - SNAPS_LIST_ROUTE, - ///: END:ONLY_INCLUDE_IN } from './routes'; export const SETTINGS_CONSTANTS = [ @@ -133,15 +130,6 @@ export const SETTINGS_CONSTANTS = [ route: CONTACT_LIST_ROUTE, iconName: IconName.Book, }, - ///: BEGIN:ONLY_INCLUDE_IN(snaps) - { - tabMessage: (t) => t('snaps'), - sectionMessage: (t) => t('snaps'), - descriptionMessage: (t) => t('snaps'), - route: SNAPS_LIST_ROUTE, - iconName: IconName.Snaps, - }, - ///: END:ONLY_INCLUDE_IN { tabMessage: (t) => t('securityAndPrivacy'), sectionMessage: (t) => t('revealSeedWords'), diff --git a/ui/helpers/utils/optimism/buildUnserializedTransaction.js b/ui/helpers/utils/optimism/buildUnserializedTransaction.js index 07badbbf2726..15f08b021cb9 100644 --- a/ui/helpers/utils/optimism/buildUnserializedTransaction.js +++ b/ui/helpers/utils/optimism/buildUnserializedTransaction.js @@ -19,7 +19,6 @@ function buildTransactionCommon(txMeta) { // fine for our use case. return Common.custom({ chainId: new BN(stripHexPrefix(txMeta.chainId), 16), - networkId: new BN(txMeta.metamaskNetworkId, 10), // Optimism only supports type-0 transactions; it does not support any of // the newer EIPs since EIP-155. Source: // diff --git a/ui/helpers/utils/token-util.js b/ui/helpers/utils/token-util.js index e34bd98885e3..db0de5e69855 100644 --- a/ui/helpers/utils/token-util.js +++ b/ui/helpers/utils/token-util.js @@ -242,7 +242,7 @@ export async function getAssetDetails( isEqualCaseInsensitive(tokenAddress, address) && _tokenId === tokenId, ); - if (existingNft) { + if (existingNft && (existingNft.name || existingNft.symbol)) { return { toAddress, ...existingNft, diff --git a/ui/helpers/utils/token-util.test.js b/ui/helpers/utils/token-util.test.js new file mode 100644 index 000000000000..76bc356db527 --- /dev/null +++ b/ui/helpers/utils/token-util.test.js @@ -0,0 +1,161 @@ +import { TokenStandard } from '../../../shared/constants/transaction'; +import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils'; +import { getTokenStandardAndDetails } from '../../store/actions'; +import { getAssetDetails } from './token-util'; + +jest.mock('../../../shared/modules/transaction.utils', () => ({ + parseStandardTokenTransactionData: jest.fn(), +})); + +jest.mock('../../store/actions', () => ({ + getTokenStandardAndDetails: jest.fn(), +})); + +describe('getAssetDetails', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return asset details for an erc721 token transaction', async () => { + const erc721Params = { + tokenAddress: '0xAddrEssToken', + currentUserAddress: '0xAddrEss', + transactionData: '0xTransactionData', + existingNfts: [ + { + address: '0xAddrEss', + name: null, + standard: 'ERC721', + tokenId: '1', + tokenURI: 'tokenURI', + }, + ], + }; + parseStandardTokenTransactionData.mockReturnValue({ + args: { id: 1, to: '0xtoAddRess' }, + }); + getTokenStandardAndDetails.mockReturnValue({ + name: 'myToken', + symbol: 'MTK', + standard: TokenStandard.ERC721, + }); + const result = await getAssetDetails( + erc721Params.currentUserAddress, + erc721Params.tokenAddress, + erc721Params.transactionData, + erc721Params.existingNfts, + ); + + // should be called if name is null + expect(getTokenStandardAndDetails).toHaveBeenCalled(); + expect(result.name).toStrictEqual('myToken'); + expect(result.symbol).toStrictEqual('MTK'); + expect(result.standard).toStrictEqual(TokenStandard.ERC721); + }); + + it('should return asset details for an erc721 token transaction without calling api if name is not null', async () => { + const erc721ParamsWithName = { + tokenAddress: '0xAddrEssToken', + currentUserAddress: '0xAddrEss', + transactionData: '0xTransactionData', + existingNfts: [ + { + address: '0xAddrEss', + name: 'myToken', + symbol: 'MTK', + standard: 'ERC721', + tokenId: '1', + tokenURI: 'tokenURI', + }, + ], + }; + parseStandardTokenTransactionData.mockReturnValue({ + args: { id: 1, to: '0xtoAddRess' }, + }); + getTokenStandardAndDetails.mockReturnValue({ + name: 'myToken', + symbol: 'MTK', + standard: TokenStandard.ERC721, + }); + const result = await getAssetDetails( + erc721ParamsWithName.currentUserAddress, + erc721ParamsWithName.tokenAddress, + erc721ParamsWithName.transactionData, + erc721ParamsWithName.existingNfts, + ); + + // should not be called if name is not null + expect(getTokenStandardAndDetails).not.toHaveBeenCalled(); + expect(result.name).toStrictEqual('myToken'); + expect(result.symbol).toStrictEqual('MTK'); + expect(result.standard).toStrictEqual(TokenStandard.ERC721); + }); + + it('should return the correct asset details for an erc721 token transaction without calling api if symbol is not null', async () => { + const erc721ParamsWithName = { + tokenAddress: '0xAddrEssToken', + currentUserAddress: '0xAddrEss', + transactionData: '0xTransactionData', + existingNfts: [ + { + address: '0xAddrEss', + name: null, + symbol: 'MTK', + standard: 'ERC721', + tokenId: '1', + tokenURI: 'tokenURI', + }, + ], + }; + parseStandardTokenTransactionData.mockReturnValue({ + args: { id: 1, to: '0xtoAddRess' }, + }); + getTokenStandardAndDetails.mockReturnValue({ + name: 'myToken', + symbol: 'MTK', + standard: TokenStandard.ERC721, + }); + const result = await getAssetDetails( + erc721ParamsWithName.currentUserAddress, + erc721ParamsWithName.tokenAddress, + erc721ParamsWithName.transactionData, + erc721ParamsWithName.existingNfts, + ); + + // should not be called if name is not null + expect(getTokenStandardAndDetails).not.toHaveBeenCalled(); + expect(result.name).toStrictEqual(null); + expect(result.symbol).toStrictEqual('MTK'); + expect(result.standard).toStrictEqual(TokenStandard.ERC721); + }); + + it('should return the correct asset details for an erc20 token transaction', async () => { + const erc20Params = { + tokenAddress: '0xAddrEssToken', + currentUserAddress: '0xAccouNtAddress', + transactionData: '0xTransactionData', + }; + parseStandardTokenTransactionData.mockReturnValue({ + args: { to: '0xtoAddRess' }, + }); + getTokenStandardAndDetails.mockReturnValue({ + name: 'myERC20Token', + symbol: 'MTK', + standard: TokenStandard.ERC20, + }); + const result = await getAssetDetails( + erc20Params.currentUserAddress, + erc20Params.tokenAddress, + erc20Params.transactionData, + erc20Params.existingNfts, + ); + + expect(getTokenStandardAndDetails).toHaveBeenCalled(); + expect(result.name).toStrictEqual('myERC20Token'); + expect(result.symbol).toStrictEqual('MTK'); + }); +}); diff --git a/ui/helpers/utils/tx-helper.test.js b/ui/helpers/utils/tx-helper.test.js index 579ffbf0d6e4..0dd330ed550d 100644 --- a/ui/helpers/utils/tx-helper.test.js +++ b/ui/helpers/utils/tx-helper.test.js @@ -1,20 +1,19 @@ -import { CHAIN_IDS, NETWORK_IDS } from '../../../shared/constants/network'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import txHelper from './tx-helper'; describe('txHelper', () => { it('always shows the oldest tx first', () => { - const metamaskNetworkId = NETWORK_IDS.MAINNET; const chainId = CHAIN_IDS.MAINNET; const mockUnapprovedTxs = { - a: { metamaskNetworkId, time: 3 }, - b: { metamaskNetworkId, time: 6 }, - c: { metamaskNetworkId, time: 2 }, + a: { chainId, time: 3 }, + b: { chainId, time: 6 }, + c: { chainId, time: 2 }, }; const mockUnapprovedMsgs = { - d: { metamaskNetworkId, time: 4 }, - e: { metamaskNetworkId, time: 1 }, - f: { metamaskNetworkId, time: 5 }, + d: { chainId, time: 4 }, + e: { chainId, time: 1 }, + f: { chainId, time: 5 }, }; const sorted = txHelper( @@ -24,7 +23,6 @@ describe('txHelper', () => { null, null, null, - metamaskNetworkId, chainId, ); diff --git a/ui/helpers/utils/tx-helper.ts b/ui/helpers/utils/tx-helper.ts index 1ce48a75e5c6..c9700a32f91e 100644 --- a/ui/helpers/utils/tx-helper.ts +++ b/ui/helpers/utils/tx-helper.ts @@ -1,5 +1,4 @@ import log from 'loglevel'; -import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils'; import { valuesFor } from './util'; export default function txHelper( @@ -9,7 +8,6 @@ export default function txHelper( decryptMsgs: Record | null, encryptionPublicKeyMsgs: Record | null, typedMessages: Record | null, - networkId?: string | null, chainId?: string, ): Record { log.debug('tx-helper called with params:'); @@ -20,14 +18,11 @@ export default function txHelper( decryptMsgs, encryptionPublicKeyMsgs, typedMessages, - networkId, chainId, }); - const txValues = networkId - ? valuesFor(unapprovedTxs).filter((txMeta) => - transactionMatchesNetwork(txMeta, chainId, networkId), - ) + const txValues = chainId + ? valuesFor(unapprovedTxs).filter((txMeta) => txMeta.chainId === chainId) : valuesFor(unapprovedTxs); const msgValues = valuesFor(unapprovedMsgs); diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index 693a47ee038e..34c738ac1ead 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -9,7 +9,7 @@ import * as lodash from 'lodash'; import bowser from 'bowser'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { getSnapPrefix } from '@metamask/snaps-utils'; -import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods'; +import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods'; // eslint-disable-next-line import/no-duplicates import { isObject } from '@metamask/utils'; ///: END:ONLY_INCLUDE_IN @@ -37,6 +37,7 @@ import { // formatData :: ( date: ) -> String import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { hexToDecimal } from '../../../shared/modules/conversion.utils'; +import { SNAPS_VIEW_ROUTE } from '../constants/routes'; export function formatDate(date, format = "M/d/y 'at' T") { if (!date) { @@ -604,6 +605,10 @@ export const getSnapName = (snapId, subjectMetadata) => { return subjectMetadata?.name ?? removeSnapIdPrefix(snapId); }; +export const getSnapRoute = (snapId) => { + return `${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snapId)}`; +}; + export const getDedupedSnaps = (request, permissions) => { const permission = request?.permissions?.[WALLET_SNAP_PERMISSION_KEY]; const requestedSnaps = permission?.caveats[0].value; diff --git a/ui/hooks/gasFeeInput/test-utils.js b/ui/hooks/gasFeeInput/test-utils.js index 86e00a822447..8d5225a12ccd 100644 --- a/ui/hooks/gasFeeInput/test-utils.js +++ b/ui/hooks/gasFeeInput/test-utils.js @@ -8,7 +8,6 @@ import { import { checkNetworkAndAccountSupports1559, getCurrentCurrency, - getSelectedAccount, getShouldShowFiat, getPreferences, txDataSelector, @@ -122,7 +121,7 @@ export const generateUseSelectorRouter = }, }; } - if (selector === getSelectedAccount) { + if (selector.toString().includes('getTargetAccount')) { return { balance: '0x440aa47cc2556', }; diff --git a/ui/hooks/gasFeeInput/useGasFeeErrors.js b/ui/hooks/gasFeeInput/useGasFeeErrors.js index b48f5cef0fcf..3fdedd035b2f 100644 --- a/ui/hooks/gasFeeInput/useGasFeeErrors.js +++ b/ui/hooks/gasFeeInput/useGasFeeErrors.js @@ -3,12 +3,14 @@ import { shallowEqual, useSelector } from 'react-redux'; import { GasEstimateTypes, GAS_LIMITS } from '../../../shared/constants/gas'; import { checkNetworkAndAccountSupports1559, - getSelectedAccount, + getTargetAccount, } from '../../selectors'; import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; import { bnGreaterThan, bnLessThan } from '../../helpers/utils/util'; import { GAS_FORM_ERRORS } from '../../helpers/constants/gas'; import { Numeric } from '../../../shared/modules/Numeric'; +import { PENDING_STATUS_HASH } from '../../helpers/constants/transactions'; +import { TransactionType } from '../../../shared/constants/transaction'; const HIGH_FEE_WARNING_MULTIPLIER = 1.5; @@ -267,13 +269,19 @@ export function useGasFeeErrors({ [gasErrors, gasWarnings], ); - const { balance: ethBalance } = useSelector(getSelectedAccount, shallowEqual); - const balanceError = hasBalanceError( - minimumCostInHexWei, - transaction, - ethBalance, + const account = useSelector( + (state) => getTargetAccount(state, transaction?.txParams?.from), + shallowEqual, ); + // Balance check is only relevant for outgoing + pending transactions + const balanceError = + account !== undefined && + transaction?.type !== TransactionType.incoming && + transaction?.status in PENDING_STATUS_HASH + ? hasBalanceError(minimumCostInHexWei, transaction, account.balance) + : false; + return { gasErrors: errorsAndWarnings, hasGasErrors, diff --git a/ui/hooks/gasFeeInput/useGasFeeErrors.test.js b/ui/hooks/gasFeeInput/useGasFeeErrors.test.js index 48cedcaada06..feb407b818a1 100644 --- a/ui/hooks/gasFeeInput/useGasFeeErrors.test.js +++ b/ui/hooks/gasFeeInput/useGasFeeErrors.test.js @@ -2,6 +2,11 @@ import { renderHook } from '@testing-library/react-hooks'; import { GAS_FORM_ERRORS } from '../../helpers/constants/gas'; +import { + TransactionStatus, + TransactionType, +} from '../../../shared/constants/transaction'; + import { useGasFeeErrors } from './useGasFeeErrors'; import { @@ -24,10 +29,20 @@ jest.mock('react-redux', () => { }; }); +const mockTransaction = { + status: TransactionStatus.unapproved, + type: TransactionType.simpleSend, + txParams: { + from: '0x000000000000000000000000000000000000dead', + type: '0x2', + value: '100', + }, +}; + const renderUseGasFeeErrorsHook = (props) => { return renderHook(() => useGasFeeErrors({ - transaction: { txParams: { type: '0x2', value: '100' } }, + transaction: mockTransaction, gasLimit: '21000', gasPrice: '10', maxPriorityFeePerGas: '10', @@ -273,7 +288,13 @@ describe('useGasFeeErrors', () => { it('is true if balance is less than transaction value', () => { configureLegacy(); const { result } = renderUseGasFeeErrorsHook({ - transaction: { txParams: { type: '0x2', value: '0x440aa47cc2556' } }, + transaction: { + ...mockTransaction, + txParams: { + ...mockTransaction.txParams, + value: '0x440aa47cc2556', + }, + }, ...LEGACY_GAS_ESTIMATE_RETURN_VALUE, }); expect(result.current.balanceError).toBe(true); diff --git a/ui/hooks/gasFeeInput/useGasFeeInputs.test.js b/ui/hooks/gasFeeInput/useGasFeeInputs.test.js index 35205583ee8d..a6bbfd660d9a 100644 --- a/ui/hooks/gasFeeInput/useGasFeeInputs.test.js +++ b/ui/hooks/gasFeeInput/useGasFeeInputs.test.js @@ -1,6 +1,10 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { useSelector } from 'react-redux'; -import { TransactionEnvelopeType } from '../../../shared/constants/transaction'; +import { + TransactionEnvelopeType, + TransactionStatus, + TransactionType, +} from '../../../shared/constants/transaction'; import { GasRecommendations, EditGasModes, @@ -39,6 +43,16 @@ jest.mock('react-redux', () => { }; }); +const mockTransaction = { + status: TransactionStatus.unapproved, + type: TransactionType.simpleSend, + txParams: { + from: '0x000000000000000000000000000000000000dead', + type: '0x2', + value: '100', + }, +}; + describe('useGasFeeInputs', () => { beforeEach(() => { jest.clearAllMocks(); @@ -141,7 +155,9 @@ describe('useGasFeeInputs', () => { }); it('should return false', () => { - const { result } = renderHook(() => useGasFeeInputs()); + const { result } = renderHook(() => + useGasFeeInputs(undefined, mockTransaction), + ); expect(result.current.balanceError).toBe(false); }); }); @@ -157,8 +173,12 @@ describe('useGasFeeInputs', () => { it('should return true', () => { const { result } = renderHook(() => useGasFeeInputs(null, { + ...mockTransaction, userFeeLevel: GasRecommendations.medium, - txParams: { gas: '0x5208' }, + txParams: { + ...mockTransaction.txParams, + gas: '0x5208', + }, }), ); expect(result.current.balanceError).toBe(true); diff --git a/ui/hooks/useAccountTotalFiatBalance.js b/ui/hooks/useAccountTotalFiatBalance.js index ee0e8173a629..d0c55b8a8a5a 100644 --- a/ui/hooks/useAccountTotalFiatBalance.js +++ b/ui/hooks/useAccountTotalFiatBalance.js @@ -5,13 +5,18 @@ import { getCurrentCurrency, getMetaMaskCachedBalances, getTokenExchangeRates, + getNativeCurrencyImage, + getTokenList, } from '../selectors'; import { getValueFromWeiHex, getWeiHexFromDecimalValue, sumDecimals, } from '../../shared/modules/conversion.utils'; -import { getConversionRate } from '../ducks/metamask/metamask'; +import { + getConversionRate, + getNativeCurrency, +} from '../ducks/metamask/metamask'; import { formatCurrency } from '../helpers/utils/confirm-tx.util'; import { getTokenFiatAmount } from '../helpers/utils/token-util'; import { isEqualCaseInsensitive } from '../../shared/modules/string-utils'; @@ -39,8 +44,13 @@ export const useAccountTotalFiatBalance = ( numberOfDecimals: 2, }); - const allTokens = useSelector(getAllTokens); - const tokens = allTokens?.[currentChainId]?.[address] ?? []; + const detectedTokens = useSelector(getAllTokens); + const tokens = detectedTokens?.[currentChainId]?.[address] ?? []; + // This selector returns all the tokens, we need it to get the image of token + const allTokenList = useSelector(getTokenList); + const allTokenListValues = Object.values(allTokenList); + const primaryTokenImage = useSelector(getNativeCurrencyImage); + const nativeCurrency = useSelector(getNativeCurrency); const { loading, tokensWithBalances } = useTokenTracker({ tokens, @@ -72,6 +82,53 @@ export const useAccountTotalFiatBalance = ( return totalFiatValue; }); + // Create an object with native token info. NOTE: Native token info is fetched from a separate controller + const nativeTokenValues = {}; + nativeTokenValues.iconUrl = primaryTokenImage; + nativeTokenValues.symbol = nativeCurrency; + nativeTokenValues.fiatBalance = nativeFiat; + + // To match the list of detected tokens with the entire token list to find the image for tokens + const findMatchingTokens = (array1, array2) => { + const result = []; + + array2.forEach((token2) => { + const matchingToken = array1.find( + (token1) => token1.symbol === token2.symbol, + ); + + if (matchingToken) { + result.push({ + ...matchingToken, + balance: token2.balance, + string: token2.string, + balanceError: token2.balanceError, + }); + } + }); + + return result; + }; + + const matchingTokens = findMatchingTokens( + allTokenListValues, + tokensWithBalances, + ); + + // Combine native token, detected token with image in an array + const allTokensWithFiatValues = [ + nativeTokenValues, + ...matchingTokens.map((item, index) => ({ + ...item, + fiatBalance: tokenFiatBalances[index], + })), + ]; + + // Order of the tokens in this array is in decreasing order based on their fiatBalance + const orderedTokenList = allTokensWithFiatValues.sort( + (a, b) => parseFloat(b.fiatBalance) - parseFloat(a.fiatBalance), + ); + // Total native and token fiat balance as a string (ex: "8.90") const totalFiatBalance = sumDecimals( nativeFiat, @@ -82,18 +139,25 @@ export const useAccountTotalFiatBalance = ( const formattedFiat = formatCurrency(totalFiatBalance, currentCurrency); // WEI Number which can be used with UserPreferencedCurrencyDisplay component - const totalWeiBalance = getWeiHexFromDecimalValue({ + let totalWeiBalance = getWeiHexFromDecimalValue({ value: totalFiatBalance, fromCurrency: currentCurrency, conversionRate, invertConversionRate: true, }); + // If we have a totalFiatBalance of "0" and conversionRate of "0", + // getWeiHexFromDecimalValue responds with "NaN" + if (totalWeiBalance === 'NaN') { + totalWeiBalance = '0x0'; + } + return { formattedFiat, totalWeiBalance, totalFiatBalance, tokensWithBalances, loading, + orderedTokenList, }; }; diff --git a/ui/hooks/useAccountTotalFiatBalance.test.js b/ui/hooks/useAccountTotalFiatBalance.test.js index f45c2a7ebf45..756224f8be74 100644 --- a/ui/hooks/useAccountTotalFiatBalance.test.js +++ b/ui/hooks/useAccountTotalFiatBalance.test.js @@ -57,7 +57,7 @@ const renderUseAccountTotalFiatBalance = (address) => { providerConfig: { chainId: CHAIN_IDS.MAINNET, }, - allTokens: { + detectedTokens: { '0x1': { '0x0836f5ed6b62baf60706fe3adc0ff0fd1df833da': [ { @@ -119,6 +119,40 @@ describe('useAccountTotalFiatBalance', () => { }, ], loading: false, + orderedTokenList: [ + { + fiatBalance: '1.85', + iconUrl: './images/eth_logo.png', + symbol: 'ETH', + }, + { + address: '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', + aggregators: [ + 'airswapLight', + 'bancor', + 'cmc', + 'coinGecko', + 'kleros', + 'oneInch', + 'paraswap', + 'pmm', + 'totle', + 'zapper', + 'zerion', + 'zeroEx', + ], + balance: '1409247882142934', + balanceError: null, + decimals: 18, + fiatBalance: '0.05', + iconUrl: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e/logo.png', + name: 'yearn.finance', + occurrences: 12, + string: '0.001409247882142934', + symbol: 'YFI', + }, + ], }); }); }); diff --git a/ui/hooks/useCopyToClipboard.js b/ui/hooks/useCopyToClipboard.js index 2aead9976c06..d658189c726b 100644 --- a/ui/hooks/useCopyToClipboard.js +++ b/ui/hooks/useCopyToClipboard.js @@ -1,6 +1,7 @@ import { useState, useCallback } from 'react'; import copyToClipboard from 'copy-to-clipboard'; import { MINUTE } from '../../shared/constants/time'; +import { COPY_OPTIONS } from '../../shared/constants/copy'; import { useTimeout } from './useTimeout'; /** @@ -15,7 +16,7 @@ export function useCopyToClipboard(delay = DEFAULT_DELAY) { const [copied, setCopied] = useState(false); const startTimeout = useTimeout( () => { - copyToClipboard(' '); + copyToClipboard(' ', COPY_OPTIONS); setCopied(false); }, delay, @@ -26,7 +27,7 @@ export function useCopyToClipboard(delay = DEFAULT_DELAY) { (text) => { setCopied(true); startTimeout(); - copyToClipboard(text); + copyToClipboard(text, COPY_OPTIONS); }, [startTimeout], ); diff --git a/ui/hooks/useTokenDisplayValue.js b/ui/hooks/useTokenDisplayValue.js index bdeae9c0e210..eb9b7458ed2d 100644 --- a/ui/hooks/useTokenDisplayValue.js +++ b/ui/hooks/useTokenDisplayValue.js @@ -43,7 +43,8 @@ export function useTokenDisplayValue( // and a token object has been provided token && // and the provided token object contains a defined decimal value we need to calculate amount - token.decimals && + token.decimals !== null && + token.decimals !== undefined && // and we are able to parse the token detail we to calculate amount from the raw data tokenValue, ); diff --git a/ui/hooks/useTokenDisplayValue.test.js b/ui/hooks/useTokenDisplayValue.test.js index 76834c1ea65b..1b66f14ce666 100644 --- a/ui/hooks/useTokenDisplayValue.test.js +++ b/ui/hooks/useTokenDisplayValue.test.js @@ -115,6 +115,17 @@ const tests = [ tokenValue: '25500000', displayValue: '25.5', }, + { + token: { + symbol: 'MTK', + decimals: 0, + }, + tokenData: { + args: 'decoded-params11', + }, + tokenValue: '25', + displayValue: '25', + }, ]; describe('useTokenDisplayValue', () => { diff --git a/ui/hooks/useTransactionEventFragment.js b/ui/hooks/useTransactionEventFragment.js index 1a10a7157584..f304e5ebdeec 100644 --- a/ui/hooks/useTransactionEventFragment.js +++ b/ui/hooks/useTransactionEventFragment.js @@ -7,7 +7,6 @@ import { updateEventFragment, } from '../store/actions'; import { selectMatchingFragment } from '../selectors'; -import { TransactionMetaMetricsEvent } from '../../shared/constants/transaction'; export const useTransactionEventFragment = () => { const { transaction } = useGasFeeContext(); @@ -24,10 +23,7 @@ export const useTransactionEventFragment = () => { return; } if (!fragment) { - await createTransactionEventFragment( - transaction.id, - TransactionMetaMetricsEvent.approved, - ); + await createTransactionEventFragment(transaction.id); } updateEventFragment(`transaction-added-${transaction.id}`, params); }, diff --git a/ui/hooks/useTransactionInsights.js b/ui/hooks/useTransactionInsights.js index 94557ab0a49e..835296bb0a57 100644 --- a/ui/hooks/useTransactionInsights.js +++ b/ui/hooks/useTransactionInsights.js @@ -1,8 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../shared/constants/network'; -import { stripHexPrefix } from '../../shared/modules/hexstring-utils'; import { TransactionType } from '../../shared/constants/transaction'; import { getInsightSnaps } from '../selectors'; import { Tab } from '../components/ui/tabs'; @@ -40,8 +38,7 @@ const useTransactionInsights = ({ txData }) => { ); const { txParams, chainId, origin } = txData; - const networkId = CHAIN_ID_TO_NETWORK_ID_MAP[chainId]; - const caip2ChainId = `eip155:${networkId ?? stripHexPrefix(chainId)}`; + const caip2ChainId = `eip155:${parseInt(chainId, 16).toString()}`; if (insightSnaps.length === 1) { return ( diff --git a/ui/index.js b/ui/index.js index aa3dea6f393c..87adcdf471fb 100644 --- a/ui/index.js +++ b/ui/index.js @@ -10,6 +10,7 @@ import { AlertTypes } from '../shared/constants/alerts'; import { maskObject } from '../shared/modules/object.utils'; import { SENTRY_UI_STATE } from '../app/scripts/lib/setupSentry'; import { ENVIRONMENT_TYPE_POPUP } from '../shared/constants/app'; +import { COPY_OPTIONS } from '../shared/constants/copy'; import switchDirection from '../shared/lib/switch-direction'; import { setupLocale } from '../shared/lib/error-utils'; import * as actions from './store/actions'; @@ -26,7 +27,7 @@ import { } from './ducks/metamask/metamask'; import Root from './pages'; import txHelper from './helpers/utils/tx-helper'; -import { _setBackgroundConnection } from './store/action-queue'; +import { setBackgroundConnection } from './store/background-connection'; log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn', false); @@ -38,7 +39,7 @@ let reduxStore; * @param backgroundConnection - connection object to background */ export const updateBackgroundConnection = (backgroundConnection) => { - _setBackgroundConnection(backgroundConnection); + setBackgroundConnection(backgroundConnection); backgroundConnection.onNotification((data) => { if (data.method === 'sendUpdate') { reduxStore.dispatch(actions.updateMetamaskState(data.params[0])); @@ -163,7 +164,6 @@ async function startApp(metamaskState, backgroundConnection, opts) { metamaskState.unapprovedDecryptMsgs, metamaskState.unapprovedEncryptionPublicKeyMsgs, metamaskState.unapprovedTypedMessages, - metamaskState.networkId, metamaskState.providerConfig.chainId, ); const numberOfUnapprovedTx = unapprovedTxsAll.length; @@ -274,7 +274,7 @@ window.logState = function (toClipboard) { if (err) { console.error(err.message); } else if (toClipboard) { - copyToClipboard(result); + copyToClipboard(result, COPY_OPTIONS); console.log('State log copied'); } else { console.log(result); diff --git a/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js b/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js index 21cb9ad1cac6..a6a0c9dfdfef 100644 --- a/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js +++ b/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js @@ -240,6 +240,7 @@ const ConfirmAddSuggestedNFT = () => { return ( copyToClipboard(toAddress)} + onClick={() => copyToClipboard(toAddress, COPY_OPTIONS)} color={IconColor.iconDefault} iconName={ this.state.copied ? IconName.CopySuccess : IconName.Copy @@ -420,7 +421,7 @@ export default class ConfirmApproveContent extends Component { { - copyToClipboard(tokenAddress); + copyToClipboard(tokenAddress, COPY_OPTIONS); }} title={tokenAddress} > diff --git a/ui/pages/confirm-decrypt-message/confirm-decrypt-message.component.js b/ui/pages/confirm-decrypt-message/confirm-decrypt-message.component.js index 620aa41fc9fa..99bcfaaf2042 100644 --- a/ui/pages/confirm-decrypt-message/confirm-decrypt-message.component.js +++ b/ui/pages/confirm-decrypt-message/confirm-decrypt-message.component.js @@ -17,6 +17,7 @@ import { Icon, IconName } from '../../components/component-library'; import { IconColor } from '../../helpers/constants/design-system'; import { formatCurrency } from '../../helpers/utils/confirm-tx.util'; import { getValueFromWeiHex } from '../../../shared/modules/conversion.utils'; +import { COPY_OPTIONS } from '../../../shared/constants/copy'; export default class ConfirmDecryptMessage extends Component { static contextTypes = { @@ -50,7 +51,7 @@ export default class ConfirmDecryptMessage extends Component { }; copyMessage = () => { - copyToClipboard(this.state.rawMessage); + copyToClipboard(this.state.rawMessage, COPY_OPTIONS); this.context.trackEvent({ category: MetaMetricsEventCategory.Messages, event: 'Copy', diff --git a/ui/pages/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap b/ui/pages/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap index 90014163cd6d..0c9c2d78ecda 100644 --- a/ui/pages/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap +++ b/ui/pages/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap @@ -323,7 +323,9 @@ exports[`ConfirmSendEther should render correct information for for confirm send class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-warning-default" style="mask-image: url('./images/icons/danger.svg');" /> -
+

@@ -338,7 +340,9 @@ exports[`ConfirmSendEther should render correct information for for confirm send class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-warning-default" style="mask-image: url('./images/icons/danger.svg');" /> -

+

diff --git a/ui/pages/confirm-send-ether/confirm-send-ether.stories.js b/ui/pages/confirm-send-ether/confirm-send-ether.stories.js index 0c6d26b8537d..3dcbe4b8aef9 100644 --- a/ui/pages/confirm-send-ether/confirm-send-ether.stories.js +++ b/ui/pages/confirm-send-ether/confirm-send-ether.stories.js @@ -9,7 +9,6 @@ const sendEther = { id: 9597986287241458, time: 1681203297082, status: 'unapproved', - metamaskNetworkId: '5', originalGasEstimate: '0x5208', userEditedGasLimit: false, chainId: '0x5', diff --git a/ui/pages/confirm-send-ether/confirm-send-ether.test.js b/ui/pages/confirm-send-ether/confirm-send-ether.test.js index 446dbc7ed0ee..701ec7bd11fd 100644 --- a/ui/pages/confirm-send-ether/confirm-send-ether.test.js +++ b/ui/pages/confirm-send-ether/confirm-send-ether.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { renderWithProvider } from '../../../test/lib/render-helpers'; -import { setBackgroundConnection } from '../../../test/jest'; +import { setBackgroundConnection } from '../../store/background-connection'; import mockState from '../../../test/data/mock-state.json'; import configureStore from '../../store/store'; import ConfirmSendEther from './confirm-send-ether'; @@ -19,7 +19,6 @@ const sendEther = { id: 9597986287241458, time: 1681203297082, status: 'unapproved', - metamaskNetworkId: '5', originalGasEstimate: '0x5208', userEditedGasLimit: false, chainId: '0x5', diff --git a/ui/pages/confirm-signature-request/index.js b/ui/pages/confirm-signature-request/index.js index a7c9c9af0430..b06ee2bfbdb6 100644 --- a/ui/pages/confirm-signature-request/index.js +++ b/ui/pages/confirm-signature-request/index.js @@ -60,7 +60,6 @@ const ConfirmTxScreen = ({ match }) => { unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, - networkId, blockGasLimit, } = useSelector((state) => state.metamask); const unapprovedTxs = useSelector(getUnapprovedTransactions); @@ -81,7 +80,8 @@ const ConfirmTxScreen = ({ match }) => { {}, {}, {}, - networkId, + {}, + {}, chainId, ); if (unconfTxList.length === 0 && !sendTo && unapprovedMessagesTotal === 0) { @@ -90,7 +90,6 @@ const ConfirmTxScreen = ({ match }) => { }, [ chainId, navigateToMostRecentOverviewPage, - networkId, sendTo, unapprovedMessagesTotal, unapprovedTxs, @@ -117,7 +116,8 @@ const ConfirmTxScreen = ({ match }) => { {}, {}, {}, - networkId, + {}, + {}, chainId, ); const prevTxData = prevUnconfTxList[prevIndex] || {}; @@ -130,7 +130,8 @@ const ConfirmTxScreen = ({ match }) => { {}, {}, {}, - networkId, + {}, + {}, chainId, ); @@ -165,8 +166,9 @@ const ConfirmTxScreen = ({ match }) => { unapprovedTxs || {}, unapprovedMsgs, unapprovedPersonalMsgs, + {}, + {}, unapprovedTypedMessages, - networkId, chainId, ); @@ -180,7 +182,6 @@ const ConfirmTxScreen = ({ match }) => { chainId, index, match, - networkId, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTxs, diff --git a/ui/pages/confirm-transaction-base/__snapshots__/confirm-transaction-base.test.js.snap b/ui/pages/confirm-transaction-base/__snapshots__/confirm-transaction-base.test.js.snap index 344a28ff4714..28f7c6d7c711 100644 --- a/ui/pages/confirm-transaction-base/__snapshots__/confirm-transaction-base.test.js.snap +++ b/ui/pages/confirm-transaction-base/__snapshots__/confirm-transaction-base.test.js.snap @@ -487,7 +487,9 @@ exports[`Confirm Transaction Base should match snapshot 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-error-default" style="mask-image: url('./images/icons/danger.svg');" /> -

+

diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js index 053732c80cd3..1deae6f575e8 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -62,7 +62,6 @@ import { import { parseStandardTokenTransactionData, - transactionMatchesNetwork, txParamsAreDappSuggested, } from '../../../shared/modules/transaction.utils'; import { @@ -123,8 +122,7 @@ const mapStateToProps = (state, ownProps) => { const gasLoadingAnimationIsShowing = getGasLoadingAnimationIsShowing(state); const isBuyableChain = getIsBuyableChain(state); const { confirmTransaction, metamask } = state; - const { conversionRate, identities, addressBook, networkId, nextNonce } = - metamask; + const { conversionRate, identities, addressBook, nextNonce } = metamask; const unapprovedTxs = getUnapprovedTransactions(state); const { chainId } = getProviderConfig(state); const { tokenData, txData, tokenProps, nonce } = confirmTransaction; @@ -183,9 +181,7 @@ const mapStateToProps = (state, ownProps) => { } = transactionFeeSelector(state, transaction); const currentNetworkUnapprovedTxs = Object.keys(unapprovedTxs) - .filter((key) => - transactionMatchesNetwork(unapprovedTxs[key], chainId, networkId), - ) + .filter((key) => unapprovedTxs[key].chainId === chainId) .reduce((acc, key) => ({ ...acc, [key]: unapprovedTxs[key] }), {}); const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length; diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.test.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.test.js index fee7d33bb7dc..2c1a2db9c54a 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.test.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.test.js @@ -6,7 +6,7 @@ import { fireEvent } from '@testing-library/react'; import { NetworkType } from '@metamask/controller-utils'; import { NetworkStatus } from '@metamask/network-controller'; import { renderWithProvider } from '../../../test/lib/render-helpers'; -import { setBackgroundConnection } from '../../../test/jest'; +import { setBackgroundConnection } from '../../store/background-connection'; import { INITIAL_SEND_STATE_FOR_EXISTING_DRAFT } from '../../../test/jest/mocks'; import { GasEstimateTypes } from '../../../shared/constants/gas'; import { KeyringType } from '../../../shared/constants/keyring'; @@ -33,8 +33,6 @@ setBackgroundConnection({ getNextNonce: jest.fn(), }); -const mockNetworkId = '5'; - const mockTxParamsFromAddress = '0x123456789'; const mockTxParamsToAddress = '0x85c1685cfceaa5c0bdb1609fc536e9a8387dd65e'; @@ -73,7 +71,7 @@ const baseStore = { transactions: [ { id: 1, - metamaskNetworkId: mockNetworkId, + chainId: '0x5', txParams: { ...mockTxParams }, status: 'unapproved', }, @@ -91,7 +89,6 @@ const baseStore = { accounts: ['0x0'], }, ], - networkId: mockNetworkId, selectedNetworkClientId: NetworkType.mainnet, networksMetadata: { [NetworkType.mainnet]: { @@ -139,7 +136,6 @@ const baseStore = { confirmTransaction: { txData: { id: 1, - metamaskNetworkId: mockNetworkId, txParams: { ...mockTxParams }, time: 1675012496170, status: TransactionStatus.unapproved, diff --git a/ui/pages/confirm-transaction/confirm-transaction.test.js b/ui/pages/confirm-transaction/confirm-transaction.test.js index 3aae253acf9e..3b549e81b8a2 100644 --- a/ui/pages/confirm-transaction/confirm-transaction.test.js +++ b/ui/pages/confirm-transaction/confirm-transaction.test.js @@ -8,7 +8,7 @@ import * as ConfirmTransactionDucks from '../../ducks/confirm-transaction/confir import * as Actions from '../../store/actions'; import _mockState from '../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../test/lib/render-helpers'; -import { setBackgroundConnection } from '../../../test/jest'; +import { setBackgroundConnection } from '../../store/background-connection'; import { CONFIRM_TRANSACTION_ROUTE, diff --git a/ui/pages/confirm-transaction/confirm-transaction.transaction.test.js b/ui/pages/confirm-transaction/confirm-transaction.transaction.test.js index 83eb5356e356..b913e6391a11 100644 --- a/ui/pages/confirm-transaction/confirm-transaction.transaction.test.js +++ b/ui/pages/confirm-transaction/confirm-transaction.transaction.test.js @@ -3,7 +3,7 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { renderWithProvider } from '../../../test/lib/render-helpers'; -import { setBackgroundConnection } from '../../../test/jest'; +import { setBackgroundConnection } from '../../store/background-connection'; import mockState from '../../../test/data/mock-state.json'; import { CONFIRM_SEND_ETHER_PATH, diff --git a/ui/pages/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap b/ui/pages/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap index 6081dc41ab4e..7115a4fca692 100644 --- a/ui/pages/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap +++ b/ui/pages/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap @@ -173,7 +173,9 @@ exports[`remove-snap-account confirmation should match snapshot 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-warning-default" style="mask-image: url('./images/icons/danger.svg');" /> -

+

diff --git a/ui/pages/confirmation/templates/switch-ethereum-chain.test.js b/ui/pages/confirmation/templates/switch-ethereum-chain.test.js index b7762412c279..30e56094c7ff 100644 --- a/ui/pages/confirmation/templates/switch-ethereum-chain.test.js +++ b/ui/pages/confirmation/templates/switch-ethereum-chain.test.js @@ -88,6 +88,7 @@ describe('switch-ethereum-chain confirmation', () => { { id: 1, status: 'unapproved', + chainId: '0x9999', }, ], }, diff --git a/ui/pages/home/home.container.js b/ui/pages/home/home.container.js index c3f4ded65042..f71763f79656 100644 --- a/ui/pages/home/home.container.js +++ b/ui/pages/home/home.container.js @@ -135,8 +135,7 @@ const mapStateToProps = (state) => { selectedAddress, firstPermissionsRequestId, totalUnapprovedCount, - hasApprovalFlows: - Array.isArray(getApprovalFlows) && getApprovalFlows(state).length > 0, + hasApprovalFlows: getApprovalFlows(state)?.length > 0, connectedStatusPopoverHasBeenShown, defaultHomeActiveTabName, firstTimeFlowType, diff --git a/ui/pages/institutional/onboarding-successful/onboarding-successful.test.js b/ui/pages/institutional/onboarding-successful/onboarding-successful.test.js index 304781a6881f..a6ed0b970cc5 100644 --- a/ui/pages/institutional/onboarding-successful/onboarding-successful.test.js +++ b/ui/pages/institutional/onboarding-successful/onboarding-successful.test.js @@ -1,10 +1,8 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { - renderWithProvider, - setBackgroundConnection, -} from '../../../../test/jest'; +import { renderWithProvider } from '../../../../test/jest'; +import { setBackgroundConnection } from '../../../store/background-connection'; import OnboardingSuccessful from './onboarding-successful'; const mockHistoryPush = jest.fn(); diff --git a/ui/pages/keychains/__snapshots__/reveal-seed.test.js.snap b/ui/pages/keychains/__snapshots__/reveal-seed.test.js.snap index 92fce9dedec0..7158c9531c62 100644 --- a/ui/pages/keychains/__snapshots__/reveal-seed.test.js.snap +++ b/ui/pages/keychains/__snapshots__/reveal-seed.test.js.snap @@ -60,7 +60,9 @@ exports[`Reveal Seed Page should match snapshot 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-error-default" style="mask-image: url('./images/icons/danger.svg');" /> -

+

@@ -89,7 +91,6 @@ exports[`Reveal Seed Page should match snapshot 1`] = ` class="box mm-text-field mm-text-field--size-lg mm-text-field--focused mm-text-field--truncate box--display-inline-flex box--flex-direction-row box--align-items-center box--width-full box--background-color-background-default box--rounded-sm box--border-width-1 box--border-style-solid" > diff --git a/ui/pages/notifications/notification.test.js b/ui/pages/notifications/notification.test.js index 4734731c870d..2dd860c81dec 100644 --- a/ui/pages/notifications/notification.test.js +++ b/ui/pages/notifications/notification.test.js @@ -19,27 +19,24 @@ describe('Notifications', () => { notifications: { test: { id: 'test', - origin: 'test', + origin: 'npm:@metamask/notifications-example-snap', createdDate: 1652967897732, readDate: null, message: 'foo', }, test2: { id: 'test2', - origin: 'test', + origin: 'npm:@metamask/notifications-example-snap', createdDate: 1652967897732, readDate: null, message: 'bar', }, }, - snaps: { - test: { - enabled: true, - id: 'test', - manifest: { - proposedName: 'Notification Example Snap', - description: 'A notification example snap.', - }, + subjectMetadata: { + 'npm:@metamask/notifications-example-snap': { + name: 'Notifications Example Snap', + version: '1.2.3', + subjectType: 'snap', }, }, }, @@ -76,29 +73,37 @@ describe('Notifications', () => { }); describe('NotificationItem', () => { - const render = (props) => renderWithProvider(); + const render = (params, props) => { + const store = configureStore({ + ...params, + }); + + return renderWithProvider(, store); + }; + it('can render notification item', () => { + const mockStore = { + metamask: { + subjectMetadata: { + 'npm:@metamask/notifications-example-snap': { + name: 'Notifications Example Snap', + version: '1.2.3', + subjectType: 'snap', + }, + }, + }, + }; const props = { notification: { id: 'test', - origin: 'test', + origin: 'npm:@metamask/notifications-example-snap', createdDate: 1652967897732, readDate: null, message: 'Hello, http://localhost:8086!', }, - snaps: [ - { - id: 'test', - tabMessage: () => 'test snap name', - descriptionMessage: () => 'test description', - sectionMessage: () => 'test section Message', - route: '/test', - icon: 'test', - }, - ], onItemClick: jest.fn(), }; - const { getByText } = render(props); + const { getByText } = render(mockStore, props); expect(getByText(props.notification.message)).toBeDefined(); }); diff --git a/ui/pages/notifications/notifications.js b/ui/pages/notifications/notifications.js index 013f51d94dbb..d603976b3355 100644 --- a/ui/pages/notifications/notifications.js +++ b/ui/pages/notifications/notifications.js @@ -3,10 +3,14 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { formatDate } from '../../helpers/utils/util'; +import { + formatDate, + getSnapName, + getSnapRoute, +} from '../../helpers/utils/util'; import { getNotifications, - getSnapsRouteObjects, + getTargetSubjectMetadata, getUnreadNotifications, } from '../../selectors'; import { DEFAULT_ROUTE } from '../../helpers/constants/routes'; @@ -23,18 +27,19 @@ import { } from '../../components/component-library'; import { Color } from '../../helpers/constants/design-system'; -export function NotificationItem({ notification, snaps, onItemClick }) { +export function NotificationItem({ notification, onItemClick }) { const { message, origin, createdDate, readDate } = notification; const history = useHistory(); const t = useI18nContext(); + const targetSubjectMetadata = useSelector((state) => + getTargetSubjectMetadata(state, origin), + ); - const snap = snaps.find(({ id: snapId }) => { - return snapId === origin; - }); + const snapName = getSnapName(origin, targetSubjectMetadata); const handleNameClick = (e) => { e.stopPropagation(); - history.push(snap.route); + history.push(getSnapRoute(origin)); }; const handleItemClick = () => onItemClick(notification); @@ -53,7 +58,7 @@ export function NotificationItem({ notification, snaps, onItemClick }) { {t('notificationsInfos', [ formatDate(createdDate, "LLLL d',' yyyy 'at' t"), , ])}

@@ -67,7 +72,6 @@ export default function Notifications() { const dispatch = useDispatch(); const t = useI18nContext(); const notifications = useSelector(getNotifications); - const snapsRouteObject = useSelector(getSnapsRouteObjects); const unreadNotifications = useSelector(getUnreadNotifications); const markAllAsRead = () => { @@ -119,7 +123,6 @@ export default function Notifications() { notifications.map((notification, id) => ( @@ -142,6 +145,5 @@ NotificationItem.propTypes = { createdDate: PropTypes.number.isRequired, readDate: PropTypes.number, }), - snaps: PropTypes.array.isRequired, onItemClick: PropTypes.func.isRequired, }; diff --git a/ui/pages/onboarding-flow/creation-successful/creation-successful.test.js b/ui/pages/onboarding-flow/creation-successful/creation-successful.test.js index dd7e5b4555fc..b5700908af79 100644 --- a/ui/pages/onboarding-flow/creation-successful/creation-successful.test.js +++ b/ui/pages/onboarding-flow/creation-successful/creation-successful.test.js @@ -6,10 +6,8 @@ import { ONBOARDING_PRIVACY_SETTINGS_ROUTE, ONBOARDING_PIN_EXTENSION_ROUTE, } from '../../../helpers/constants/routes'; -import { - renderWithProvider, - setBackgroundConnection, -} from '../../../../test/jest'; +import { setBackgroundConnection } from '../../../store/background-connection'; +import { renderWithProvider } from '../../../../test/jest'; import CreationSuccessful from './creation-successful'; const mockHistoryPush = jest.fn(); diff --git a/ui/pages/onboarding-flow/pin-extension/pin-extension.test.js b/ui/pages/onboarding-flow/pin-extension/pin-extension.test.js index 7a6988c5ba90..673ad9aa8a2c 100644 --- a/ui/pages/onboarding-flow/pin-extension/pin-extension.test.js +++ b/ui/pages/onboarding-flow/pin-extension/pin-extension.test.js @@ -3,10 +3,8 @@ import { fireEvent } from '@testing-library/react'; import reactRouterDom from 'react-router-dom'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { - renderWithProvider, - setBackgroundConnection, -} from '../../../../test/jest'; +import { setBackgroundConnection } from '../../../store/background-connection'; +import { renderWithProvider } from '../../../../test/jest'; import PinExtension from './pin-extension'; const completeOnboardingStub = jest diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js index 9eb8386e4694..cb5863123785 100644 --- a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js +++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js @@ -2,10 +2,8 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { - renderWithProvider, - setBackgroundConnection, -} from '../../../../test/jest'; +import { setBackgroundConnection } from '../../../store/background-connection'; +import { renderWithProvider } from '../../../../test/jest'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import PrivacySettings from './privacy-settings'; diff --git a/ui/pages/pages.scss b/ui/pages/pages.scss index 96f1f29c8861..d8defb715b50 100644 --- a/ui/pages/pages.scss +++ b/ui/pages/pages.scss @@ -28,6 +28,8 @@ @import 'permissions-connect/index'; @import 'send/send'; @import 'settings/index'; +@import 'snaps/snaps-list/index'; +@import 'snaps/snap-view/index'; @import 'swaps/index'; @import 'token-allowance/index'; @import 'token-details/index'; diff --git a/ui/pages/permissions-connect/permissions-connect.container.js b/ui/pages/permissions-connect/permissions-connect.container.js index f9054410e1d6..f9dd8e4aed7f 100644 --- a/ui/pages/permissions-connect/permissions-connect.container.js +++ b/ui/pages/permissions-connect/permissions-connect.container.js @@ -1,6 +1,6 @@ import { SubjectType } from '@metamask/permission-controller'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) -import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods'; +import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods'; ///: END:ONLY_INCLUDE_IN import { connect } from 'react-redux'; import PropTypes from 'prop-types'; diff --git a/ui/pages/permissions-connect/snaps/snap-install/snap-install.js b/ui/pages/permissions-connect/snaps/snap-install/snap-install.js index 8ed4dc77dcf9..8846c7f4f427 100644 --- a/ui/pages/permissions-connect/snaps/snap-install/snap-install.js +++ b/ui/pages/permissions-connect/snaps/snap-install/snap-install.js @@ -158,7 +158,7 @@ export default function SnapInstall({ paddingBottom={2} textAlign="center" > - {t('snapInstall')} + {t('installRequest')} - {t('snapUpdate')} + {t('updateRequest')} )} {isLoading && ( diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 1a5c525d5661..c84931d87244 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -44,6 +44,8 @@ import OnboardingAppHeader from '../onboarding-flow/onboarding-app-header/onboar import TokenDetailsPage from '../token-details'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import Notifications from '../notifications'; +import SnapList from '../snaps/snaps-list'; +import SnapView from '../snaps/snap-view'; ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) import AddSnapAccountPage from '../keyring-snaps/add-snap-account'; @@ -91,6 +93,8 @@ import { ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(snaps) NOTIFICATIONS_ROUTE, + SNAPS_ROUTE, + SNAPS_VIEW_ROUTE, ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) ADD_SNAP_ACCOUNT_ROUTE, @@ -124,6 +128,8 @@ import { ToggleIpfsModal } from '../../components/app/nft-default-image/toggle-i import KeyringSnapRemovalResult from '../../components/app/modals/keyring-snap-removal-modal'; ///: END:ONLY_INCLUDE_IN +import { SendPage } from '../../components/multichain/pages/send'; + export default class Routes extends Component { static propTypes = { currentCurrency: PropTypes.string, @@ -139,8 +145,6 @@ export default class Routes extends Component { history: PropTypes.object, location: PropTypes.object, lockMetaMask: PropTypes.func, - isMouseUser: PropTypes.bool, - setMouseUserState: PropTypes.func, providerId: PropTypes.string, providerType: PropTypes.string, autoLockTimeLimit: PropTypes.number, @@ -277,13 +281,23 @@ export default class Routes extends Component { ///: END:ONLY_INCLUDE_IN } + { + ///: BEGIN:ONLY_INCLUDE_IN(snaps) + + ///: END:ONLY_INCLUDE_IN + } + { + ///: BEGIN:ONLY_INCLUDE_IN(snaps) + + ///: END:ONLY_INCLUDE_IN + } setMouseUserState(true)} - onKeyDown={(e) => { - if (e.keyCode === 9) { - setMouseUserState(false); - } - }} > {shouldShowNetworkDeprecationWarning && } {shouldShowNetworkInfo && } diff --git a/ui/pages/routes/routes.component.test.js b/ui/pages/routes/routes.component.test.js index d44ec27f81ac..21908b727c38 100644 --- a/ui/pages/routes/routes.component.test.js +++ b/ui/pages/routes/routes.component.test.js @@ -33,10 +33,6 @@ jest.mock('../../store/actions', () => ({ addPollingTokenToAppState: jest.fn(), showNetworkDropdown: () => mockShowNetworkDropdown, hideNetworkDropdown: () => mockHideNetworkDropdown, - setMouseUserState: jest.fn().mockImplementation((payload) => ({ - type: 'SET_MOUSE_USER_STATE', - payload, - })), })); jest.mock('react-router-dom', () => ({ diff --git a/ui/pages/routes/routes.container.js b/ui/pages/routes/routes.container.js index 6516f0429db0..eba59f6b058f 100644 --- a/ui/pages/routes/routes.container.js +++ b/ui/pages/routes/routes.container.js @@ -19,7 +19,6 @@ import { hideIpfsModal, setCurrentCurrency, setLastActiveTime, - setMouseUserState, toggleAccountMenu, toggleNetworkMenu, hideImportTokensModal, @@ -54,7 +53,6 @@ function mapStateToProps(state) { isUnlocked: getIsUnlocked(state), isNetworkLoading: isNetworkLoading(state), currentCurrency: state.metamask.currentCurrency, - isMouseUser: state.appState.isMouseUser, autoLockTimeLimit, browserEnvironmentOs: state.metamask.browserEnvironment?.os, browserEnvironmentContainter: state.metamask.browserEnvironment?.browser, @@ -88,8 +86,6 @@ function mapDispatchToProps(dispatch) { return { lockMetaMask: () => dispatch(lockMetamask(false)), setCurrentCurrencyToUSD: () => dispatch(setCurrentCurrency('usd')), - setMouseUserState: (isMouseUser) => - dispatch(setMouseUserState(isMouseUser)), setLastActiveTime: () => dispatch(setLastActiveTime()), pageChanged: (path) => dispatch(pageChanged(path)), prepareToLeaveSwaps: () => dispatch(prepareToLeaveSwaps()), diff --git a/ui/pages/send/send.test.js b/ui/pages/send/send.test.js index 794f3aa44d96..9ab156f630d9 100644 --- a/ui/pages/send/send.test.js +++ b/ui/pages/send/send.test.js @@ -5,15 +5,13 @@ import { useLocation } from 'react-router-dom'; import { NetworkType } from '@metamask/controller-utils'; import { SEND_STAGES, startNewDraftTransaction } from '../../ducks/send'; import { domainInitialState } from '../../ducks/domains'; +import { setBackgroundConnection } from '../../store/background-connection'; import { CHAIN_IDS, GOERLI_DISPLAY_NAME, NETWORK_TYPES, } from '../../../shared/constants/network'; -import { - renderWithProvider, - setBackgroundConnection, -} from '../../../test/jest'; +import { renderWithProvider } from '../../../test/jest'; import { GasEstimateTypes } from '../../../shared/constants/gas'; import { KeyringType } from '../../../shared/constants/keyring'; import { INITIAL_SEND_STATE_FOR_EXISTING_DRAFT } from '../../../test/jest/mocks'; @@ -230,7 +228,6 @@ describe('Send Page', () => { id: 3111025347726181, time: 1620723786838, status: 'unapproved', - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: false, txParams: { @@ -276,7 +273,6 @@ describe('Send Page', () => { id: 3111025347726181, time: 1620723786838, status: 'unapproved', - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: false, txParams: { diff --git a/ui/pages/settings/experimental-tab/__snapshots__/experimental-tab.test.js.snap b/ui/pages/settings/experimental-tab/__snapshots__/experimental-tab.test.js.snap index 06308c7587d1..66abfe3154e5 100644 --- a/ui/pages/settings/experimental-tab/__snapshots__/experimental-tab.test.js.snap +++ b/ui/pages/settings/experimental-tab/__snapshots__/experimental-tab.test.js.snap @@ -60,20 +60,7 @@ exports[`ExperimentalTab with desktop enabled renders ExperimentalTab component
- - - Privacy preserving - no data is shared with third parties. Available on Ethereum Mainnet. - - Terms of use - - - - + Privacy preserving - no data is shared with third parties. Available on Ethereum Mainnet.
{ }, ); expect(getAllByRole('link', { name: 'Terms of use' })[0]).toHaveAttribute( - 'href', - 'https://blockaid.io/legal/metamask-ppom-privacy-policy/', - ); - expect(getAllByRole('link', { name: 'Terms of use' })[1]).toHaveAttribute( 'href', 'https://opensea.io/securityproviderterms', ); @@ -97,4 +93,21 @@ describe('ExperimentalTab', () => { expect(setTransactionSecurityCheckEnabled).toHaveBeenCalledWith(true); expect(setSecurityAlertsEnabled).toHaveBeenCalledWith(false); }); + + it('should enable add account snap', async () => { + const setAddSnapAccountEnabled = jest.fn(); + const { getByTestId } = render( + { desktopEnabled: true }, + { + setAddSnapAccountEnabled, + }, + ); + + const toggle = getByTestId('add-snap-account-toggle'); + fireEvent.click(toggle); + + await waitFor(() => { + expect(setAddSnapAccountEnabled).toHaveBeenCalledWith(true); + }); + }); }); diff --git a/ui/pages/settings/index.scss b/ui/pages/settings/index.scss index 27f27c7f3dea..86a5d82a5be0 100644 --- a/ui/pages/settings/index.scss +++ b/ui/pages/settings/index.scss @@ -3,8 +3,6 @@ @import 'networks-tab/index'; @import 'settings-tab/index'; @import 'contact-list-tab/index'; -@import 'snaps/snaps-list-tab/index'; -@import 'snaps/view-snap/index'; .settings-page { position: relative; diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index 7c52b3c8e7cc..ce6603ddd2cd 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -306,11 +306,11 @@ exports[`Security Tab should match snapshot 1`] = ` This relies on different third-party APIs for each network, which expose your Ethereum address and your IP address.

Custom Mainnet RPC

-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
- {isSnapViewPage ? null : this.renderSubHeader()} + {this.renderSubHeader()} {this.renderContent()}
@@ -216,12 +206,9 @@ class SettingsPage extends PureComponent { renderTitle() { const { t } = this.context; - const { isPopup, pathnameI18nKey, addressName, isSnapViewPage } = - this.props; + const { isPopup, pathnameI18nKey, addressName } = this.props; let titleText; - if (isSnapViewPage) { - titleText = t('snaps'); - } else if (isPopup && addressName) { + if (isPopup && addressName) { titleText = t('details'); } else if (pathnameI18nKey && isPopup) { titleText = t(pathnameI18nKey); @@ -320,15 +307,6 @@ class SettingsPage extends PureComponent { icon: , key: CONTACT_LIST_ROUTE, }, - ///: BEGIN:ONLY_INCLUDE_IN(snaps) - { - content: t('snaps'), - icon: ( - - ), - key: SNAPS_LIST_ROUTE, - }, - ///: END:ONLY_INCLUDE_IN { content: t('securityAndPrivacy'), icon: , @@ -421,16 +399,6 @@ class SettingsPage extends PureComponent { path={`${CONTACT_VIEW_ROUTE}/:id`} component={ContactListTab} /> - { - ///: BEGIN:ONLY_INCLUDE_IN(snaps) - - ///: END:ONLY_INCLUDE_IN - } - { - ///: BEGIN:ONLY_INCLUDE_IN(snaps) - - ///: END:ONLY_INCLUDE_IN - } ( { @@ -59,7 +55,6 @@ const mapStateToProps = (state, ownProps) => { const pathNameTail = pathname.match(/[^/]+$/u)[0]; const isAddressEntryPage = pathNameTail.includes('0x'); - const isSnapViewPage = Boolean(pathname.match(SNAPS_VIEW_ROUTE)); const isAddContactPage = Boolean(pathname.match(CONTACT_ADD_ROUTE)); const isEditContactPage = Boolean(pathname.match(CONTACT_EDIT_ROUTE)); const isNetworksFormPage = @@ -80,8 +75,6 @@ const mapStateToProps = (state, ownProps) => { backRoute = CONTACT_LIST_ROUTE; } else if (isNetworksFormPage) { backRoute = NETWORKS_ROUTE; - } else if (isSnapViewPage) { - backRoute = SNAPS_LIST_ROUTE; } else if (isAddPopularCustomNetwork) { backRoute = NETWORKS_ROUTE; } @@ -107,7 +100,6 @@ const mapStateToProps = (state, ownProps) => { initialBreadCrumbRoute, isAddressEntryPage, isPopup, - isSnapViewPage, mostRecentOverviewPage: getMostRecentOverviewPage(state), pathnameI18nKey, }; diff --git a/ui/pages/settings/settings.stories.js b/ui/pages/settings/settings.stories.js index 81c33993032d..f33cd3bba41f 100644 --- a/ui/pages/settings/settings.stories.js +++ b/ui/pages/settings/settings.stories.js @@ -1,22 +1,20 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { withRouter, MemoryRouter } from 'react-router-dom'; +import { MemoryRouter, withRouter } from 'react-router-dom'; import { ABOUT_US_ROUTE, ADVANCED_ROUTE, ALERTS_ROUTE, - CONTACT_LIST_ROUTE, CONTACT_ADD_ROUTE, CONTACT_EDIT_ROUTE, + CONTACT_LIST_ROUTE, CONTACT_VIEW_ROUTE, GENERAL_ROUTE, NETWORKS_FORM_ROUTE, NETWORKS_ROUTE, SECURITY_ROUTE, SETTINGS_ROUTE, - SNAPS_LIST_ROUTE, - SNAPS_VIEW_ROUTE, } from '../../helpers/constants/routes'; import SettingsPage from './settings.component'; @@ -44,8 +42,6 @@ const ROUTES_TO_I18N_KEYS = { [NETWORKS_FORM_ROUTE]: 'networks', [NETWORKS_ROUTE]: 'networks', [SECURITY_ROUTE]: 'securityAndPrivacy', - [SNAPS_LIST_ROUTE]: 'snaps', - [SNAPS_VIEW_ROUTE]: 'snaps', }; global.platform = { @@ -59,9 +55,6 @@ const Settings = ({ history }) => { ? '/settings/general' : location.pathname; const pathnameI18nKey = ROUTES_TO_I18N_KEYS[pathname]; - const isSnapViewPage = Boolean(pathname.match(SNAPS_VIEW_ROUTE)); - const backRoute = isSnapViewPage ? SNAPS_LIST_ROUTE : SETTINGS_ROUTE; - return (
{ mostRecentOverviewPage={pathname} history={history} pathnameI18nKey={pathnameI18nKey} - backRoute={backRoute} - isSnapViewPage={isSnapViewPage} + backRoute={SETTINGS_ROUTE} />
); diff --git a/ui/pages/settings/snaps/snaps-list-tab/index.js b/ui/pages/settings/snaps/snaps-list-tab/index.js deleted file mode 100644 index 70e07b6868f5..000000000000 --- a/ui/pages/settings/snaps/snaps-list-tab/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './snap-list-tab'; diff --git a/ui/pages/settings/snaps/snaps-list-tab/index.scss b/ui/pages/settings/snaps/snaps-list-tab/index.scss deleted file mode 100644 index 4d26bd065fab..000000000000 --- a/ui/pages/settings/snaps/snaps-list-tab/index.scss +++ /dev/null @@ -1,22 +0,0 @@ -.snap-list-tab { - &__wrapper { - max-width: 475px; - } - - .snap-list-tab__container--no-snaps_inner { - max-width: 164px; - align-self: flex-end; - } - - .snap-list-tab__no-snaps_icon { - font-size: 48px; - } - - &__container--no-snaps_banner-tip { - max-width: 390px; - } - - &__container--snaps-info-content { - max-width: 475px; - } -} diff --git a/ui/pages/settings/snaps/snaps-list-tab/snap-list-tab.js b/ui/pages/settings/snaps/snaps-list-tab/snap-list-tab.js deleted file mode 100644 index 8b5f0b8451f7..000000000000 --- a/ui/pages/settings/snaps/snaps-list-tab/snap-list-tab.js +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useRef, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import SnapSettingsCard from '../../../../components/app/snaps/snap-settings-card'; -import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { - JustifyContent, - AlignItems, - IconColor, - Color, - TextAlign, - FlexDirection, - Size, - Display, - BlockSize, - FlexWrap, - TextVariant, -} from '../../../../helpers/constants/design-system'; -import { SNAPS_VIEW_ROUTE } from '../../../../helpers/constants/routes'; -import { getSnapsList } from '../../../../selectors'; -import { handleSettingsRefs } from '../../../../helpers/utils/settings-search'; -import { - Box, - BannerTip, - BannerTipLogoType, - ButtonLink, - Icon, - IconName, - IconSize, - Text, -} from '../../../../components/component-library'; - -const SnapListTab = () => { - const t = useI18nContext(); - const history = useHistory(); - const settingsRef = useRef(); - const onClick = (snap) => { - history.push(`${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snap.id)}`); - }; - - useEffect(() => { - handleSettingsRefs(t, t('snaps'), settingsRef); - }, [settingsRef, t]); - - const snapsList = useSelector((state) => getSnapsList(state)); - - return ( - - {snapsList.length > 0 && ( -
-
- {snapsList.map((snap) => { - return ( - { - onClick(snap); - }} - snapId={snap.id} - /> - ); - })} -
-
- )} - {snapsList.length <= 5 && ( - - {snapsList.length < 1 && ( - - - - {t('noSnaps')} - - - )} - - - - 0 - ? 'https://snaps.metamask.io/' - : 'https://metamask.io/snaps/' - } - target="_blank" - endIconName={IconName.Export} - > - {`${t('discoverSnaps')}`} - - - - - )} -
- ); -}; - -export default SnapListTab; diff --git a/ui/pages/settings/snaps/view-snap/index.js b/ui/pages/settings/snaps/view-snap/index.js deleted file mode 100644 index 520826cbbb80..000000000000 --- a/ui/pages/settings/snaps/view-snap/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './view-snap'; diff --git a/ui/pages/settings/snaps/view-snap/index.scss b/ui/pages/settings/snaps/view-snap/index.scss deleted file mode 100644 index c074c154c1fa..000000000000 --- a/ui/pages/settings/snaps/view-snap/index.scss +++ /dev/null @@ -1,74 +0,0 @@ -.view-snap { - max-width: 475px; - - &__description { - &__wrapper { - position: relative; - overflow: hidden; - max-height: 5.5rem; - - @include screen-md-min { - max-height: 6rem; - } - - &.open { - max-height: none; - } - } - - &__more-button { - position: absolute; - bottom: 0; - right: 0; - width: unset; - padding: 0; - padding-left: 32px; - background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, var(--color-background-default) 33%); - } - } - - - &__permissions { - .permission-cell { - margin: 0; - } - } - - .connected-sites-list { - &__content-row { - border-top: none; - padding: 0 0 8px 0; - - & &-link-button { - padding: 0; - padding-inline-start: 0; - color: var(--color-error-default); - font-size: 14px; - } - - a { - font-size: 14px; - color: var(--color-error-default); - } - - a:hover { - color: var(--color-error-alternative); - } - } - - &__subject-info { - a.btn-link { - font-size: 14px; - color: var(--color-error-default); - } - } - - &__subject-icon { - flex-shrink: 0; - } - - &__subject-name { - font-size: 14px; - } - } -} diff --git a/ui/pages/settings/snaps/view-snap/view-snap.js b/ui/pages/settings/snaps/view-snap/view-snap.js deleted file mode 100644 index 9f4e39f1cb90..000000000000 --- a/ui/pages/settings/snaps/view-snap/view-snap.js +++ /dev/null @@ -1,292 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; -import { useDispatch, useSelector } from 'react-redux'; -import { - SnapCaveatType, - WALLET_SNAP_PERMISSION_KEY, -} from '@metamask/rpc-methods'; -import classnames from 'classnames'; -import Button from '../../../../components/ui/button'; -import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { - Color, - FLEX_WRAP, - TextColor, - TextVariant, -} from '../../../../helpers/constants/design-system'; -import SnapAuthorshipExpanded from '../../../../components/app/snaps/snap-authorship-expanded'; -import Box from '../../../../components/ui/box'; -import SnapRemoveWarning from '../../../../components/app/snaps/snap-remove-warning'; -import ConnectedSitesList from '../../../../components/app/connected-sites-list'; -///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) -import KeyringSnapRemovalWarning from '../../../../components/app/snaps/keyring-snap-removal-warning'; -///: END:ONLY_INCLUDE_IN -import { SNAPS_LIST_ROUTE } from '../../../../helpers/constants/routes'; -import { - removeSnap, - removePermissionsFor, - updateCaveat, - ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) - showKeyringSnapRemovalModal, - getSnapAccountsById, - ///: END:ONLY_INCLUDE_IN -} from '../../../../store/actions'; -import { - getSnaps, - getSubjectsWithSnapPermission, - getPermissions, - getPermissionSubjects, - getTargetSubjectMetadata, - ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) - getMemoizedMetaMaskIdentities, - ///: END:ONLY_INCLUDE_IN -} from '../../../../selectors'; -import { getSnapName } from '../../../../helpers/utils/util'; -import { Text } from '../../../../components/component-library'; -import SnapPermissionsList from '../../../../components/app/snaps/snap-permissions-list'; -import { SnapDelineator } from '../../../../components/app/snaps/snap-delineator'; -import { DelineatorType } from '../../../../helpers/constants/snaps'; -///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) -import { KeyringSnapRemovalResultStatus } from './constants'; -///: END:ONLY_INCLUDE_IN - -function ViewSnap() { - const t = useI18nContext(); - const history = useHistory(); - const location = useLocation(); - const descriptionRef = useRef(null); - const { pathname } = location; - // The snap ID is in URI-encoded form in the last path segment of the URL. - const decodedSnapId = decodeURIComponent(pathname.match(/[^/]+$/u)[0]); - const snaps = useSelector(getSnaps); - const snap = Object.entries(snaps) - .map(([_, snapState]) => snapState) - .find((snapState) => snapState.id === decodedSnapId); - - const [isShowingRemoveWarning, setIsShowingRemoveWarning] = useState(false); - const [isDescriptionOpen, setIsDescriptionOpen] = useState(false); - const [isOverflowing, setIsOverflowing] = useState(false); - // eslint-disable-next-line no-unused-vars -- Main build does not use setIsRemovingKeyringSnap - const [isRemovingKeyringSnap, setIsRemovingKeyringSnap] = useState(false); - - // eslint-disable-next-line no-unused-vars -- Main build does not use setKeyringAccounts - const [keyringAccounts, setKeyringAccounts] = useState([]); - ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) - const identities = useSelector(getMemoizedMetaMaskIdentities); - ///: END:ONLY_INCLUDE_IN - - useEffect(() => { - if (!snap) { - history.push(SNAPS_LIST_ROUTE); - } - }, [history, snap]); - - useEffect(() => { - setIsOverflowing( - descriptionRef.current && - descriptionRef.current.offsetHeight < - descriptionRef.current.scrollHeight, - ); - }, [descriptionRef]); - - const connectedSubjects = useSelector((state) => - getSubjectsWithSnapPermission(state, snap?.id), - ); - const permissions = useSelector( - (state) => snap && getPermissions(state, snap.id), - ); - const subjects = useSelector((state) => getPermissionSubjects(state)); - const targetSubjectMetadata = useSelector((state) => - getTargetSubjectMetadata(state, snap?.id), - ); - - let isKeyringSnap = false; - ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) - isKeyringSnap = Boolean(subjects[snap?.id]?.permissions?.snap_manageAccounts); - - useEffect(() => { - if (isKeyringSnap) { - (async () => { - const addresses = await getSnapAccountsById(snap.id); - const snapIdentities = Object.values(identities).filter((identity) => - addresses.includes(identity.address.toLowerCase()), - ); - setKeyringAccounts(snapIdentities); - })(); - } - }, [snap?.id, identities, isKeyringSnap]); - - ///: END:ONLY_INCLUDE_IN - - const dispatch = useDispatch(); - - const onDisconnect = (connectedOrigin, snapId) => { - const caveatValue = - subjects[connectedOrigin].permissions[WALLET_SNAP_PERMISSION_KEY] - .caveats[0].value; - const newCaveatValue = { ...caveatValue }; - delete newCaveatValue[snapId]; - if (Object.keys(newCaveatValue).length > 0) { - dispatch( - updateCaveat( - connectedOrigin, - WALLET_SNAP_PERMISSION_KEY, - SnapCaveatType.SnapIds, - newCaveatValue, - ), - ); - } else { - dispatch( - removePermissionsFor({ - [connectedOrigin]: [WALLET_SNAP_PERMISSION_KEY], - }), - ); - } - }; - - if (!snap) { - return null; - } - - const snapName = getSnapName(snap.id, targetSubjectMetadata); - - const shouldDisplayMoreButton = isOverflowing && !isDescriptionOpen; - const handleMoreClick = () => { - setIsDescriptionOpen(true); - }; - - return ( - - - - - - {snap?.manifest.description} - {shouldDisplayMoreButton && ( - - )} - - - - - {t('permissions')} - - - - - {t('connectedSites')} - - { - onDisconnect(origin, snap.id); - }} - /> - - - - {t('removeSnap')} - - - {t('removeSnapDescription')} - - - - setIsShowingRemoveWarning(false)} - onSubmit={async () => { - await dispatch(removeSnap(snap.id)); - }} - snapName={snapName} - /> - { - ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) - <> - setIsShowingRemoveWarning(false)} - onClose={() => setIsShowingRemoveWarning(false)} - onBack={() => setIsShowingRemoveWarning(false)} - onSubmit={async () => { - try { - setIsRemovingKeyringSnap(true); - await dispatch(removeSnap(snap.id)); - setIsShowingRemoveWarning(false); - dispatch( - showKeyringSnapRemovalModal({ - snapName, - result: KeyringSnapRemovalResultStatus.Success, - }), - ); - } catch { - setIsShowingRemoveWarning(false); - dispatch( - showKeyringSnapRemovalModal({ - snapName, - result: KeyringSnapRemovalResultStatus.Failed, - }), - ); - } finally { - setIsRemovingKeyringSnap(false); - } - }} - isOpen={ - isShowingRemoveWarning && - isKeyringSnap && - keyringAccounts.length > 0 - } - /> - - ///: END:ONLY_INCLUDE_IN - } - - - - ); -} - -export default ViewSnap; diff --git a/ui/pages/settings/snaps/view-snap/constants.ts b/ui/pages/snaps/snap-view/constants.ts similarity index 100% rename from ui/pages/settings/snaps/view-snap/constants.ts rename to ui/pages/snaps/snap-view/constants.ts diff --git a/ui/pages/snaps/snap-view/index.js b/ui/pages/snaps/snap-view/index.js new file mode 100644 index 000000000000..f53f5a884688 --- /dev/null +++ b/ui/pages/snaps/snap-view/index.js @@ -0,0 +1 @@ +export { default } from './snap-view'; diff --git a/ui/pages/snaps/snap-view/index.scss b/ui/pages/snaps/snap-view/index.scss new file mode 100644 index 000000000000..15070446d446 --- /dev/null +++ b/ui/pages/snaps/snap-view/index.scss @@ -0,0 +1,111 @@ +.snap-view { + $height-screen-sm-max: 100%; + $width-screen-sm-min: 85vw; + $width-screen-md-min: 80vw; + $width-screen-lg-min: 62vw; + + width: 100%; + flex-flow: column nowrap; + + @include screen-sm-max { + height: $height-screen-sm-max; + } + + @include screen-sm-min { + width: $width-screen-sm-min; + } + + @include screen-md-min { + width: $width-screen-md-min; + } + + @include screen-lg-min { + width: $width-screen-lg-min; + } + + &__content { + padding: 32px; + + @include screen-sm-max { + padding: 16px; + } + + &__description { + &__wrapper { + position: relative; + overflow: hidden; + max-height: 5.5rem; + + @include screen-md-min { + max-height: 6rem; + } + + &.open { + max-height: none; + } + } + + &__more-button { + position: absolute; + bottom: 0; + right: 0; + width: unset; + padding: 0; + padding-left: 32px; + background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, var(--color-background-default) 33%); + } + } + + &__permissions { + .permission-cell { + margin: 0; + } + } + + .connected-sites-list { + &__content-row { + border-top: none; + padding: 0 0 8px 0; + + & &-link-button { + padding: 0; + padding-inline-start: 0; + color: var(--color-error-default); + font-size: 14px; + } + + a { + font-size: 14px; + color: var(--color-error-default); + } + + a:hover { + color: var(--color-error-alternative); + } + } + + &__subject-info { + a.btn-link { + font-size: 14px; + color: var(--color-error-default); + } + } + + &__subject-icon { + flex-shrink: 0; + } + + &__subject-name { + font-size: 14px; + } + } + + &__remove-button, + &__remove-button:hover { + .mm-box--color-error-default { + color: inherit; + } + } + } +} + diff --git a/ui/pages/snaps/snap-view/snap-view.js b/ui/pages/snaps/snap-view/snap-view.js new file mode 100644 index 000000000000..2d5422f39e64 --- /dev/null +++ b/ui/pages/snaps/snap-view/snap-view.js @@ -0,0 +1,340 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { + SnapCaveatType, + WALLET_SNAP_PERMISSION_KEY, +} from '@metamask/snaps-rpc-methods'; +import classnames from 'classnames'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + BackgroundColor, + BlockSize, + Color, + Display, + FlexWrap, + JustifyContent, + TextColor, + TextVariant, +} from '../../../helpers/constants/design-system'; +import SnapAuthorshipExpanded from '../../../components/app/snaps/snap-authorship-expanded'; +import SnapRemoveWarning from '../../../components/app/snaps/snap-remove-warning'; +import ConnectedSitesList from '../../../components/app/connected-sites-list'; +///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) +import KeyringSnapRemovalWarning from '../../../components/app/snaps/keyring-snap-removal-warning'; +///: END:ONLY_INCLUDE_IN +import { SNAPS_ROUTE } from '../../../helpers/constants/routes'; +import { + removeSnap, + removePermissionsFor, + updateCaveat, + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + showKeyringSnapRemovalModal, + getSnapAccountsById, + ///: END:ONLY_INCLUDE_IN +} from '../../../store/actions'; +import { + getSnaps, + getSubjectsWithSnapPermission, + getPermissions, + getPermissionSubjects, + getTargetSubjectMetadata, + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + getMemoizedMetaMaskIdentities, + ///: END:ONLY_INCLUDE_IN +} from '../../../selectors'; +import { getSnapName } from '../../../helpers/utils/util'; +import { + Box, + Button, + ButtonIcon, + ButtonSize, + ButtonVariant, + Text, +} from '../../../components/component-library'; +import SnapPermissionsList from '../../../components/app/snaps/snap-permissions-list'; +import { SnapDelineator } from '../../../components/app/snaps/snap-delineator'; +import { DelineatorType } from '../../../helpers/constants/snaps'; +import { + Content, + Header, + Page, +} from '../../../components/multichain/pages/page'; +///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) +import { KeyringSnapRemovalResultStatus } from './constants'; +///: END:ONLY_INCLUDE_IN + +function SnapView() { + const t = useI18nContext(); + const history = useHistory(); + const location = useLocation(); + const descriptionRef = useRef(null); + const { pathname } = location; + // The snap ID is in URI-encoded form in the last path segment of the URL. + const decodedSnapId = decodeURIComponent(pathname.match(/[^/]+$/u)[0]); + const snaps = useSelector(getSnaps); + const snap = Object.entries(snaps) + .map(([_, snapState]) => snapState) + .find((snapState) => snapState.id === decodedSnapId); + + const [isShowingRemoveWarning, setIsShowingRemoveWarning] = useState(false); + const [isDescriptionOpen, setIsDescriptionOpen] = useState(false); + const [isOverflowing, setIsOverflowing] = useState(false); + // eslint-disable-next-line no-unused-vars -- Main build does not use setIsRemovingKeyringSnap + const [isRemovingKeyringSnap, setIsRemovingKeyringSnap] = useState(false); + + // eslint-disable-next-line no-unused-vars -- Main build does not use setKeyringAccounts + const [keyringAccounts, setKeyringAccounts] = useState([]); + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + const identities = useSelector(getMemoizedMetaMaskIdentities); + ///: END:ONLY_INCLUDE_IN + + useEffect(() => { + if (!snap) { + history.push(SNAPS_ROUTE); + } + }, [history, snap]); + + useEffect(() => { + setIsOverflowing( + descriptionRef.current && + descriptionRef.current.offsetHeight < + descriptionRef.current.scrollHeight, + ); + }, [descriptionRef]); + + const connectedSubjects = useSelector((state) => + getSubjectsWithSnapPermission(state, snap?.id), + ); + const permissions = useSelector( + (state) => snap && getPermissions(state, snap.id), + ); + const subjects = useSelector((state) => getPermissionSubjects(state)); + const targetSubjectMetadata = useSelector((state) => + getTargetSubjectMetadata(state, snap?.id), + ); + + let isKeyringSnap = false; + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + isKeyringSnap = Boolean(subjects[snap?.id]?.permissions?.snap_manageAccounts); + + useEffect(() => { + if (isKeyringSnap) { + (async () => { + const addresses = await getSnapAccountsById(snap.id); + const snapIdentities = Object.values(identities).filter((identity) => + addresses.includes(identity.address.toLowerCase()), + ); + setKeyringAccounts(snapIdentities); + })(); + } + }, [snap?.id, identities, isKeyringSnap]); + + ///: END:ONLY_INCLUDE_IN + + const dispatch = useDispatch(); + + const onDisconnect = (connectedOrigin, snapId) => { + const caveatValue = + subjects[connectedOrigin].permissions[WALLET_SNAP_PERMISSION_KEY] + .caveats[0].value; + const newCaveatValue = { ...caveatValue }; + delete newCaveatValue[snapId]; + if (Object.keys(newCaveatValue).length > 0) { + dispatch( + updateCaveat( + connectedOrigin, + WALLET_SNAP_PERMISSION_KEY, + SnapCaveatType.SnapIds, + newCaveatValue, + ), + ); + } else { + dispatch( + removePermissionsFor({ + [connectedOrigin]: [WALLET_SNAP_PERMISSION_KEY], + }), + ); + } + }; + + if (!snap) { + return null; + } + + const snapName = getSnapName(snap.id, targetSubjectMetadata); + + const shouldDisplayMoreButton = isOverflowing && !isDescriptionOpen; + const handleMoreClick = () => { + setIsDescriptionOpen(true); + }; + + return ( +
+ +
history.push(SNAPS_ROUTE)} + /> + } + > + {snapName} +
+ + + + + + + {snap?.manifest.description} + {shouldDisplayMoreButton && ( + + )} + + + + + {t('permissions')} + + + + + {t('connectedSites')} + + { + onDisconnect(origin, snap.id); + }} + /> + + + + {t('removeSnap')} + + + {t('removeSnapDescription')} + + + + setIsShowingRemoveWarning(false)} + onSubmit={async () => { + await dispatch(removeSnap(snap.id)); + }} + snapName={snapName} + /> + { + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + <> + setIsShowingRemoveWarning(false)} + onClose={() => setIsShowingRemoveWarning(false)} + onBack={() => setIsShowingRemoveWarning(false)} + onSubmit={async () => { + try { + setIsRemovingKeyringSnap(true); + await dispatch(removeSnap(snap.id)); + setIsShowingRemoveWarning(false); + dispatch( + showKeyringSnapRemovalModal({ + snapName, + result: KeyringSnapRemovalResultStatus.Success, + }), + ); + } catch { + setIsShowingRemoveWarning(false); + dispatch( + showKeyringSnapRemovalModal({ + snapName, + result: KeyringSnapRemovalResultStatus.Failed, + }), + ); + } finally { + setIsRemovingKeyringSnap(false); + } + }} + isOpen={ + isShowingRemoveWarning && + isKeyringSnap && + keyringAccounts.length > 0 + } + /> + + ///: END:ONLY_INCLUDE_IN + } + + + + +
+
+ ); +} + +export default SnapView; diff --git a/ui/pages/settings/snaps/view-snap/view-snap.test.js b/ui/pages/snaps/snap-view/snap-view.test.js similarity index 78% rename from ui/pages/settings/snaps/view-snap/view-snap.test.js rename to ui/pages/snaps/snap-view/snap-view.test.js index 47ff4447cf81..fe068a71a537 100644 --- a/ui/pages/settings/snaps/view-snap/view-snap.test.js +++ b/ui/pages/snaps/snap-view/snap-view.test.js @@ -2,11 +2,11 @@ import * as React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { waitFor, screen } from '@testing-library/react'; -import { renderWithProvider } from '../../../../../test/lib/render-helpers'; -import mockState from '../../../../../test/data/mock-state.json'; -import ViewSnap from './view-snap'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import mockState from '../../../../test/data/mock-state.json'; +import SnapView from './snap-view'; -jest.mock('../../../../store/actions.ts', () => { +jest.mock('../../../store/actions.ts', () => { return { disableSnap: jest.fn(), enableSnap: jest.fn(), @@ -26,7 +26,7 @@ jest.mock('react-router-dom', () => { return { ...original, useLocation: jest.fn(() => ({ - pathname: `/settings/snaps-view/${encodeURIComponent( + pathname: `/snaps/view/${encodeURIComponent( 'npm:@metamask/test-snap-bip44', )}`, })), @@ -35,15 +35,13 @@ jest.mock('react-router-dom', () => { const mockStore = configureMockStore([thunk])(mockState); -describe('ViewSnap', () => { +describe('SnapView', () => { it('should properly display Snap View elements', async () => { - const { getByText, container, getByRole } = renderWithProvider( - , - mockStore, - ); + const { getByText, container, getByTestId, getAllByText } = + renderWithProvider(, mockStore); // Snap name & Snap authorship component - expect(getByText('BIP-44 Test Snap')).toBeDefined(); + expect(getAllByText('BIP-44 Test Snap')).toHaveLength(2); expect( container.getElementsByClassName('snaps-authorship-expanded')?.length, ).toBe(1); @@ -81,8 +79,8 @@ describe('ViewSnap', () => { ), ).toBeDefined(); expect(getByText('Remove BIP-44 Test Snap')).toBeDefined(); - expect(getByRole('button')).toHaveClass( - 'button btn--rounded btn-danger view-snap__remove-button', + expect(getByTestId('remove-snap-button')).toHaveClass( + 'snap-view__content__remove-button', ); }); }); diff --git a/ui/pages/snaps/snaps-list/index.js b/ui/pages/snaps/snaps-list/index.js new file mode 100644 index 000000000000..c2f473f29951 --- /dev/null +++ b/ui/pages/snaps/snaps-list/index.js @@ -0,0 +1 @@ +export { default } from './snap-list'; diff --git a/ui/pages/snaps/snaps-list/index.scss b/ui/pages/snaps/snaps-list/index.scss new file mode 100644 index 000000000000..87e0e2a7778a --- /dev/null +++ b/ui/pages/snaps/snaps-list/index.scss @@ -0,0 +1,52 @@ +.snaps { + $height-screen-sm-max: 100%; + $width-screen-sm-min: 85vw; + $width-screen-md-min: 80vw; + $width-screen-lg-min: 62vw; + + width: 100%; + flex-flow: column nowrap; + + @include screen-sm-max { + height: $height-screen-sm-max; + } + + @include screen-sm-min { + width: $width-screen-sm-min; + } + + @include screen-md-min { + width: $width-screen-md-min; + } + + @include screen-lg-min { + width: $width-screen-lg-min; + } + + &__content { + padding: 0; + + &__list { + &__wrapper { + @include screen-md-min { + margin-top: 24px; + margin-left: 24px; + margin-right: 24px; + } + } + + &__container--no-snaps_inner { + max-width: 164px; + align-self: flex-end; + } + + &__no-snaps_icon { + font-size: 48px; + } + + &__container--no-snaps_banner-tip { + max-width: 390px; + } + } + } +} diff --git a/ui/pages/snaps/snaps-list/snap-list.js b/ui/pages/snaps/snaps-list/snap-list.js new file mode 100644 index 000000000000..58e409dbdc62 --- /dev/null +++ b/ui/pages/snaps/snaps-list/snap-list.js @@ -0,0 +1,175 @@ +import React, { useRef, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import SnapListItem from '../../../components/app/snaps/snap-list-item'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + JustifyContent, + AlignItems, + IconColor, + Color, + TextAlign, + FlexDirection, + Size, + Display, + BlockSize, + FlexWrap, + TextVariant, + BackgroundColor, +} from '../../../helpers/constants/design-system'; +import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; +import { getSnapsList } from '../../../selectors'; +import { handleSettingsRefs } from '../../../helpers/utils/settings-search'; +import { + Box, + BannerTip, + BannerTipLogoType, + ButtonLink, + Icon, + IconName, + IconSize, + Text, + ButtonIcon, +} from '../../../components/component-library'; +import { + Content, + Header, + Page, +} from '../../../components/multichain/pages/page'; +import { getSnapRoute } from '../../../helpers/utils/util'; + +const SnapList = () => { + const t = useI18nContext(); + const history = useHistory(); + const settingsRef = useRef(); + const onClick = (snap) => { + history.push(getSnapRoute(snap.id)); + }; + + useEffect(() => { + handleSettingsRefs(t, t('snaps'), settingsRef); + }, [settingsRef, t]); + + const snapsList = useSelector((state) => getSnapsList(state)); + + return ( +
+ +
history.push(DEFAULT_ROUTE)} + /> + } + > + {t('snaps')} +
+ + + {snapsList.length > 0 && ( +
+
+ {snapsList.map((snap) => { + return ( + { + onClick(snap); + }} + snapId={snap.id} + /> + ); + })} +
+
+ )} + {snapsList.length <= 5 && ( + + {snapsList.length < 1 && ( + + + + {t('noSnaps')} + + + )} + + + + 0 + ? 'https://snaps.metamask.io/' + : 'https://metamask.io/snaps/' + } + target="_blank" + endIconName={IconName.Export} + > + {`${t('discoverSnaps')}`} + + + + + )} +
+
+
+
+ ); +}; + +export default SnapList; diff --git a/ui/pages/settings/snaps/snaps-list-tab/snap-list-tab.stories.js b/ui/pages/snaps/snaps-list/snap-list.stories.js similarity index 82% rename from ui/pages/settings/snaps/snaps-list-tab/snap-list-tab.stories.js rename to ui/pages/snaps/snaps-list/snap-list.stories.js index d1a27e2a588e..b506f0bcbf88 100644 --- a/ui/pages/settings/snaps/snaps-list-tab/snap-list-tab.stories.js +++ b/ui/pages/snaps/snaps-list/snap-list.stories.js @@ -1,14 +1,14 @@ import React, { useState } from 'react'; import { Provider } from 'react-redux'; -import configureStore from '../../../../store/store'; -import testData from '../../../../../.storybook/test-data'; -import SnapListTab from './snap-list-tab'; +import configureStore from '../../../store/store'; +import testData from '../../../../.storybook/test-data'; +import SnapList from './snap-list'; // Using Test Data For Redux const store = configureStore(testData); export default { - title: 'Pages/Settings/SnapListTab', + title: 'Pages/Settings/SnapList', decorators: [(story) => {story()}], argTypes: { @@ -27,7 +27,7 @@ export const DefaultStory = (args) => { return (
- { return ( diff --git a/ui/pages/swaps/prepare-swap-page/prepare-swap-page.test.js b/ui/pages/swaps/prepare-swap-page/prepare-swap-page.test.js index 9f0a499f7bbd..d02113bfc93d 100644 --- a/ui/pages/swaps/prepare-swap-page/prepare-swap-page.test.js +++ b/ui/pages/swaps/prepare-swap-page/prepare-swap-page.test.js @@ -2,10 +2,10 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; +import { setBackgroundConnection } from '../../../store/background-connection'; import { renderWithProvider, createSwapsMockStore, - setBackgroundConnection, fireEvent, } from '../../../../test/jest'; import { diff --git a/ui/pages/swaps/prepare-swap-page/review-quote.test.js b/ui/pages/swaps/prepare-swap-page/review-quote.test.js index e02cb3d0b34d..59970d761a73 100644 --- a/ui/pages/swaps/prepare-swap-page/review-quote.test.js +++ b/ui/pages/swaps/prepare-swap-page/review-quote.test.js @@ -3,10 +3,10 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { NetworkType } from '@metamask/controller-utils'; +import { setBackgroundConnection } from '../../../store/background-connection'; import { renderWithProvider, createSwapsMockStore, - setBackgroundConnection, MOCKS, } from '../../../../test/jest'; import ReviewQuote from './review-quote'; diff --git a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.test.js b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.test.js index 57a8212e45ca..9efddfed245d 100644 --- a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.test.js +++ b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.test.js @@ -1,11 +1,10 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; - +import { setBackgroundConnection } from '../../../store/background-connection'; import { renderWithProvider, createSwapsMockStore, - setBackgroundConnection, fireEvent, } from '../../../../test/jest'; import { CHAIN_IDS } from '../../../../shared/constants/network'; diff --git a/ui/pages/swaps/view-quote/view-quote.test.js b/ui/pages/swaps/view-quote/view-quote.test.js index 4cf0d55eed3f..efd203187cd7 100644 --- a/ui/pages/swaps/view-quote/view-quote.test.js +++ b/ui/pages/swaps/view-quote/view-quote.test.js @@ -4,10 +4,10 @@ import thunk from 'redux-thunk'; import { NetworkType } from '@metamask/controller-utils'; import { NetworkStatus } from '@metamask/network-controller'; +import { setBackgroundConnection } from '../../../store/background-connection'; import { renderWithProvider, createSwapsMockStore, - setBackgroundConnection, MOCKS, } from '../../../../test/jest'; import ViewQuote from '.'; diff --git a/ui/pages/token-allowance/token-allowance.stories-to-do.js b/ui/pages/token-allowance/token-allowance.stories-to-do.js index c03a1db991cc..fa9b52724817 100644 --- a/ui/pages/token-allowance/token-allowance.stories-to-do.js +++ b/ui/pages/token-allowance/token-allowance.stories-to-do.js @@ -98,7 +98,6 @@ export default { id: 3049568294499567, time: 1664449552289, status: 'unapproved', - metamaskNetworkId: '3', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x3', @@ -124,7 +123,6 @@ export default { id: 3049568294499567, time: 1664449552289, status: 'unapproved', - metamaskNetworkId: '3', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x3', diff --git a/ui/pages/token-allowance/token-allowance.test.js b/ui/pages/token-allowance/token-allowance.test.js index d9f9021e6b5e..1929e7b5a714 100644 --- a/ui/pages/token-allowance/token-allowance.test.js +++ b/ui/pages/token-allowance/token-allowance.test.js @@ -163,7 +163,6 @@ describe('TokenAllowancePage', () => { id: 3049568294499567, time: 1664449552289, status: 'unapproved', - metamaskNetworkId: '3', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x3', diff --git a/ui/pages/token-details/token-details-page.js b/ui/pages/token-details/token-details-page.js index 398f05edf34a..de1e9ef52b8a 100644 --- a/ui/pages/token-details/token-details-page.js +++ b/ui/pages/token-details/token-details-page.js @@ -37,7 +37,6 @@ export default function TokenDetailsPage() { const t = useContext(I18nContext); const tokens = useSelector(getTokens); const tokenList = useSelector(getTokenList); - const { address: tokenAddress } = useParams(); const tokenMetadata = tokenList[tokenAddress.toLowerCase()]; const aggregators = tokenMetadata?.aggregators?.join(', '); @@ -46,8 +45,14 @@ export default function TokenDetailsPage() { isEqualCaseInsensitive(address, tokenAddress), ); - const { tokensWithBalances } = useTokenTracker({ tokens: [token] }); + // When the user did not import the token + // the token variable will be undefined. + // In that case we want to call useTokenTracker with [] instead of [undefined] + const { tokensWithBalances } = useTokenTracker({ + tokens: token ? [token] : [], + }); const tokenBalance = tokensWithBalances[0]?.string; + const tokenCurrencyBalance = useTokenFiatAmount( token?.address, tokenBalance, diff --git a/ui/selectors/confirm-transaction.js b/ui/selectors/confirm-transaction.js index cdd7eed36f1b..20fdb546f148 100644 --- a/ui/selectors/confirm-transaction.js +++ b/ui/selectors/confirm-transaction.js @@ -6,7 +6,6 @@ import { addFiat, addEth, } from '../helpers/utils/confirm-tx.util'; -import { transactionMatchesNetwork } from '../../shared/modules/transaction.utils'; import { getGasEstimateType, getGasFeeEstimates, @@ -29,7 +28,7 @@ import { sumHexes, } from '../../shared/modules/conversion.utils'; import { getAveragePriceEstimateInHexWEI } from './custom-gas'; -import { getCurrentChainId, deprecatedGetCurrentNetworkId } from './selectors'; +import { getCurrentChainId } from './selectors'; import { checkNetworkAndAccountSupports1559, getUnapprovedTransactions, @@ -53,7 +52,6 @@ export const unconfirmedTransactionsListSelector = createSelector( unapprovedDecryptMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector, unapprovedTypedMessagesSelector, - deprecatedGetCurrentNetworkId, getCurrentChainId, ( unapprovedTxs = {}, @@ -62,7 +60,6 @@ export const unconfirmedTransactionsListSelector = createSelector( unapprovedDecryptMsgs = {}, unapprovedEncryptionPublicKeyMsgs = {}, unapprovedTypedMessages = {}, - network, chainId, ) => txHelper( @@ -72,7 +69,6 @@ export const unconfirmedTransactionsListSelector = createSelector( unapprovedDecryptMsgs, unapprovedEncryptionPublicKeyMsgs, unapprovedTypedMessages, - network, chainId, ) || [], ); @@ -84,7 +80,6 @@ export const unconfirmedTransactionsHashSelector = createSelector( unapprovedDecryptMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector, unapprovedTypedMessagesSelector, - deprecatedGetCurrentNetworkId, getCurrentChainId, ( unapprovedTxs = {}, @@ -93,16 +88,13 @@ export const unconfirmedTransactionsHashSelector = createSelector( unapprovedDecryptMsgs = {}, unapprovedEncryptionPublicKeyMsgs = {}, unapprovedTypedMessages = {}, - network, chainId, ) => { const filteredUnapprovedTxs = Object.keys(unapprovedTxs).reduce( (acc, address) => { const transactions = { ...acc }; - if ( - transactionMatchesNetwork(unapprovedTxs[address], chainId, network) - ) { + if (unapprovedTxs[address].chainId === chainId) { transactions[address] = unapprovedTxs[address]; } diff --git a/ui/selectors/nonce-sorted-transactions-selector.test.js b/ui/selectors/nonce-sorted-transactions-selector.test.js index 81594efa090e..9b320c679cc5 100644 --- a/ui/selectors/nonce-sorted-transactions-selector.test.js +++ b/ui/selectors/nonce-sorted-transactions-selector.test.js @@ -24,12 +24,14 @@ const INCOMING_TX = { from: RECIPIENTS.ONE, to: SENDERS.ONE, }, + chainId: CHAIN_IDS.MAINNET, }; const SIGNING_REQUEST = { type: TransactionType.sign, id: '0-signing', status: TransactionStatus.unapproved, + chainId: CHAIN_IDS.MAINNET, }; const SIMPLE_SEND_TX = { @@ -39,6 +41,7 @@ const SIMPLE_SEND_TX = { to: RECIPIENTS.ONE, }, type: TransactionType.simpleSend, + chainId: CHAIN_IDS.MAINNET, }; const TOKEN_SEND_TX = { @@ -50,12 +53,14 @@ const TOKEN_SEND_TX = { data: '0xdata', }, type: TransactionType.tokenMethodTransfer, + chainId: CHAIN_IDS.MAINNET, }; const RETRY_TX = { ...SIMPLE_SEND_TX, id: '0-retry', type: TransactionType.retry, + chainId: CHAIN_IDS.MAINNET, }; const CANCEL_TX = { @@ -66,6 +71,7 @@ const CANCEL_TX = { to: SENDERS.ONE, }, type: TransactionType.cancel, + chainId: CHAIN_IDS.MAINNET, }; const getStateTree = ({ diff --git a/ui/selectors/permissions.js b/ui/selectors/permissions.js index c14b50bbf4a8..4b5cdd078aa5 100644 --- a/ui/selectors/permissions.js +++ b/ui/selectors/permissions.js @@ -1,6 +1,6 @@ import { ApprovalType } from '@metamask/controller-utils'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) -import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods'; +import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods'; ///: END:ONLY_INCLUDE_IN import { CaveatTypes } from '../../shared/constants/permissions'; import { getApprovalRequestsByType } from './approvals'; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 7de1e62faf17..047eaae50bba 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -50,10 +50,7 @@ import { ALLOWED_DEV_SWAPS_CHAIN_IDS, } from '../../shared/constants/swaps'; -import { - ALLOWED_BRIDGE_CHAIN_IDS, - ALLOWED_BRIDGE_TOKEN_ADDRESSES, -} from '../../shared/constants/bridge'; +import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../shared/constants/bridge'; import { shortenAddress, @@ -101,7 +98,6 @@ import { } from './transactions'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) // eslint-disable-next-line import/order -import { SNAPS_VIEW_ROUTE } from '../helpers/constants/routes'; import { getPermissionSubjects } from './permissions'; ///: END:ONLY_INCLUDE_IN import { createDeepEqualSelector } from './util'; @@ -284,20 +280,6 @@ export function getAccountTypeForKeyring(keyring) { } } -/** - * get the currently selected networkId which will be 'loading' when the - * network changes. The network id should not be used in most cases, - * instead use chainId in most situations. There are a limited number of - * use cases to use this method still, such as when comparing transaction - * metadata that predates the switch to using chainId. - * - * @deprecated - use getCurrentChainId instead - * @param {object} state - redux state object - */ -export function deprecatedGetCurrentNetworkId(state) { - return state.metamask.networkId ?? 'loading'; -} - /** * Get MetaMask accounts, including account name and balance. */ @@ -375,14 +357,7 @@ export function getMetaMaskAccountBalances(state) { export function getMetaMaskCachedBalances(state) { const chainId = getCurrentChainId(state); - // Fallback to fetching cached balances from network id - // this can eventually be removed - const network = deprecatedGetCurrentNetworkId(state); - - return ( - state.metamask.cachedBalances[chainId] ?? - state.metamask.cachedBalances[network] - ); + return state.metamask.cachedBalances[chainId]; } /** @@ -848,15 +823,6 @@ export function getIsBridgeChain(state) { return ALLOWED_BRIDGE_CHAIN_IDS.includes(chainId); } -export const getIsBridgeToken = (tokenAddress) => (state) => { - const chainId = getCurrentChainId(state); - const isBridgeChain = getIsBridgeChain(state); - return ( - isBridgeChain && - ALLOWED_BRIDGE_TOKEN_ADDRESSES[chainId].includes(tokenAddress.toLowerCase()) - ); -}; - export function getIsBuyableChain(state) { const chainId = getCurrentChainId(state); return Object.keys(BUYABLE_CHAINS_MAP).includes(chainId); @@ -986,19 +952,6 @@ export const getNotifySnaps = createDeepEqualSelector( }, ); -export const getSnapsRouteObjects = createSelector(getSnaps, (snaps) => { - return Object.values(snaps).map((snap) => { - return { - id: snap.id, - tabMessage: () => snap.manifest.proposedName, - descriptionMessage: () => snap.manifest.description, - sectionMessage: () => snap.manifest.description, - route: `${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snap.id)}`, - icon: 'fa fa-flask', - }; - }); -}); - /** * @typedef {object} Notification * @property {string} id - A unique identifier for the notification diff --git a/ui/selectors/selectors.test.js b/ui/selectors/selectors.test.js index a057667a1b1a..0c5bfdb53447 100644 --- a/ui/selectors/selectors.test.js +++ b/ui/selectors/selectors.test.js @@ -728,25 +728,6 @@ describe('Selectors', () => { expect(isFantomSupported).toBeFalsy(); }); - it('#getIsBridgeToken', () => { - mockState.metamask.providerConfig.chainId = '0xa'; - const isOptimismTokenSupported = selectors.getIsBridgeToken( - '0x94B008aa00579c1307b0ef2c499ad98a8ce58e58', - )(mockState); - expect(isOptimismTokenSupported).toBeTruthy(); - - const isOptimismUnknownTokenSupported = selectors.getIsBridgeToken( - '0x94B008aa00579c1307b0ef2c499ad98a8ce58e60', - )(mockState); - expect(isOptimismUnknownTokenSupported).toBeFalsy(); - - mockState.metamask.providerConfig.chainId = '0xfa'; - const isFantomTokenSupported = selectors.getIsBridgeToken( - '0x94B008aa00579c1307b0ef2c499ad98a8ce58e58', - )(mockState); - expect(isFantomTokenSupported).toBeFalsy(); - }); - it('returns proper values for snaps privacy warning shown status', () => { mockState.metamask.snapsInstallPrivacyWarningShown = false; expect(selectors.getSnapsInstallPrivacyWarningShown(mockState)).toBe(false); diff --git a/ui/selectors/transactions.js b/ui/selectors/transactions.js index a1827f2c0443..58d8feb43f3a 100644 --- a/ui/selectors/transactions.js +++ b/ui/selectors/transactions.js @@ -10,14 +10,9 @@ import { TransactionType, SmartTransactionStatus, } from '../../shared/constants/transaction'; -import { transactionMatchesNetwork } from '../../shared/modules/transaction.utils'; import { hexToDecimal } from '../../shared/modules/conversion.utils'; import { getProviderConfig } from '../ducks/metamask/metamask'; -import { - getCurrentChainId, - deprecatedGetCurrentNetworkId, - getSelectedAddress, -} from './selectors'; +import { getCurrentChainId, getSelectedAddress } from './selectors'; import { hasPendingApprovals, getApprovalRequestsByType } from './approvals'; import { createDeepEqualSelector } from './util'; @@ -30,7 +25,7 @@ export const unapprovedMsgsSelector = (state) => state.metamask.unapprovedMsgs; export const getCurrentNetworkTransactions = createDeepEqualSelector( (state) => { - const { transactions, networkId } = state.metamask ?? {}; + const { transactions } = state.metamask ?? {}; if (!transactions?.length) { return []; @@ -38,8 +33,8 @@ export const getCurrentNetworkTransactions = createDeepEqualSelector( const { chainId } = getProviderConfig(state); - return transactions.filter((transaction) => - transactionMatchesNetwork(transaction, chainId, networkId), + return transactions.filter( + (transaction) => transaction.chainId === chainId, ); }, (transactions) => transactions, @@ -119,7 +114,6 @@ export const unapprovedMessagesSelector = createSelector( unapprovedDecryptMsgsSelector, unapprovedEncryptionPublicKeyMsgsSelector, unapprovedTypedMessagesSelector, - deprecatedGetCurrentNetworkId, getCurrentChainId, ( unapprovedMsgs = {}, @@ -127,7 +121,6 @@ export const unapprovedMessagesSelector = createSelector( unapprovedDecryptMsgs = {}, unapprovedEncryptionPublicKeyMsgs = {}, unapprovedTypedMessages = {}, - network, chainId, ) => txHelper( @@ -137,7 +130,6 @@ export const unapprovedMessagesSelector = createSelector( unapprovedDecryptMsgs, unapprovedEncryptionPublicKeyMsgs, unapprovedTypedMessages, - network, chainId, ) || [], ); @@ -584,9 +576,7 @@ const hasUnapprovedTransactionsInCurrentNetwork = (state) => { const chainId = getCurrentChainId(state); const filteredUnapprovedTxInCurrentNetwork = unapprovedTxRequests.filter( - ({ id }) => - unapprovedTxs[id] && - transactionMatchesNetwork(unapprovedTxs[id], chainId), + ({ id }) => unapprovedTxs[id] && unapprovedTxs[id].chainId === chainId, ); return filteredUnapprovedTxInCurrentNetwork.length > 0; diff --git a/ui/selectors/transactions.test.js b/ui/selectors/transactions.test.js index fb6bb629793e..87ef1b7bf950 100644 --- a/ui/selectors/transactions.test.js +++ b/ui/selectors/transactions.test.js @@ -154,6 +154,7 @@ describe('Transaction Selectors', () => { const tx1 = { id: 0, time: 0, + chainId: CHAIN_IDS.MAINNET, txParams: { from: '0xAddress', to: '0xRecipient', @@ -164,6 +165,7 @@ describe('Transaction Selectors', () => { const tx2 = { id: 1, time: 1, + chainId: CHAIN_IDS.MAINNET, txParams: { from: '0xAddress', to: '0xRecipient', @@ -212,6 +214,7 @@ describe('Transaction Selectors', () => { const submittedTx = { id: 0, time: 0, + chainId: CHAIN_IDS.MAINNET, txParams: { from: '0xAddress', to: '0xRecipient', @@ -223,6 +226,7 @@ describe('Transaction Selectors', () => { const unapprovedTx = { id: 1, time: 1, + chainId: CHAIN_IDS.MAINNET, txParams: { from: '0xAddress', to: '0xRecipient', @@ -234,6 +238,7 @@ describe('Transaction Selectors', () => { const approvedTx = { id: 2, time: 2, + chainId: CHAIN_IDS.MAINNET, txParams: { from: '0xAddress', to: '0xRecipient', @@ -245,6 +250,7 @@ describe('Transaction Selectors', () => { const confirmedTx = { id: 3, time: 3, + chainId: CHAIN_IDS.MAINNET, txParams: { from: '0xAddress', to: '0xRecipient', @@ -324,11 +330,11 @@ describe('Transaction Selectors', () => { }); describe('hasTransactionPendingApprovals', () => { - const mockNetworkId = 'mockNetworkId'; + const mockChainId = 'mockChainId'; const mockedState = { metamask: { providerConfig: { - chainId: mockNetworkId, + chainId: mockChainId, }, pendingApprovalCount: 2, pendingApprovals: { @@ -352,7 +358,7 @@ describe('Transaction Selectors', () => { transactions: [ { id: '2', - chainId: mockNetworkId, + chainId: mockChainId, status: TransactionStatus.unapproved, }, ], @@ -365,7 +371,7 @@ describe('Transaction Selectors', () => { }); it('should return false if there is a pending transaction on different network', () => { - mockedState.metamask.transactions[0].chainId = 'differentNetworkId'; + mockedState.metamask.transactions[0].chainId = 'differentChainId'; const result = hasTransactionPendingApprovals(mockedState); expect(result).toBe(false); }); diff --git a/ui/store/action-queue/index.test.js b/ui/store/action-queue/index.test.js deleted file mode 100644 index d93aa28a84f7..000000000000 --- a/ui/store/action-queue/index.test.js +++ /dev/null @@ -1,365 +0,0 @@ -import sinon from 'sinon'; - -import { - dropQueue, - callBackgroundMethod, - submitRequestToBackground, - _setBackgroundConnection, -} from '.'; - -// This file tests only MV3 queue scenario -// MV2 tests are already covered by '../actions.test.js' - -jest.mock('../../../shared/modules/mv3.utils', () => { - return { - isManifestV3: () => true, - }; -}); - -describe('ActionQueue', () => { - afterEach(() => { - sinon.restore(); - dropQueue(true); - }); - - describe('dropQueue', () => { - it('rejects all pending actions by default', async () => { - const background = { - connectionStream: { - readable: false, - }, - backgroundFunction: sinon.stub().yields(), - }; - - _setBackgroundConnection(background); - const result = submitRequestToBackground('backgroundFunction'); - dropQueue(); - - await expect(result).rejects.toThrow( - 'Background operation cancelled while waiting for connection.', - ); - expect(background.backgroundFunction.called).toStrictEqual(false); - }); - }); - - describe('submitRequestToBackground', () => { - it('calls promisified background method if the stream is connected', async () => { - const background = { - connectionStream: { - readable: true, - }, - backgroundFunction1: sinon.stub().yields(), - }; - - _setBackgroundConnection(background); - submitRequestToBackground('backgroundFunction1'); - expect(background.backgroundFunction1.called).toStrictEqual(true); - }); - - it('does not calls promisified background method if the stream is not connected', async () => { - const background = { - connectionStream: { - readable: false, - }, - backgroundFunction2: sinon.stub().yields(), - }; - - _setBackgroundConnection(background); - submitRequestToBackground('backgroundFunction2'); - expect(background.backgroundFunction2.called).toStrictEqual(false); - }); - - it('calls promisified background method on stream reconnection', async () => { - const background = { - connectionStream: { - readable: false, - }, - backgroundFunction3: sinon.stub().yields(), - trackMetaMetricsEvent: sinon.stub().yields(), - }; - _setBackgroundConnection(background); - const requestPromise = submitRequestToBackground('backgroundFunction3'); - - background.connectionStream = { - readable: true, - }; - _setBackgroundConnection(background); - await requestPromise; - expect(background.backgroundFunction3.calledOnce).toStrictEqual(true); - }); - - it('resolves if backgroundFunction resolves', async () => { - const background = { - connectionStream: { - readable: true, - }, - backgroundFunction4: (cb) => { - return cb(null, 'test'); - }, - }; - _setBackgroundConnection(background); - await expect( - submitRequestToBackground('backgroundFunction4'), - ).resolves.toStrictEqual('test'); - }); - - it('rejects if backgroundFunction throws exception', async () => { - expect.assertions(1); - const background = { - connectionStream: { - readable: true, - }, - backgroundFunction: () => { - throw Error('test'); - }, - }; - _setBackgroundConnection(background); - await expect( - submitRequestToBackground('backgroundFunction'), - ).rejects.toThrow('test'); - }); - - it('calls methods in parallel when connection available', async () => { - const trace = {}; - const background = { - connectionStream: { - readable: true, - }, - first: (cb) => { - setTimeout(() => { - trace.firstDone = Date.now(); - cb(null, 'first'); - }, 5); - }, - second: (cb) => { - trace.secondStarted = Date.now(); - setTimeout(() => cb(null, 'second'), 10); - }, - trackMetaMetricsEvent: sinon.stub().yields(), - }; - _setBackgroundConnection(background); - const scheduled = Promise.all([ - submitRequestToBackground('first'), - submitRequestToBackground('second'), - ]); - await scheduled; - expect(trace.firstDone).toBeGreaterThan(trace.secondStarted); - }); - - it('processes the queue sequentially when connection is restored', async () => { - const trace = {}; - const background = { - connectionStream: { - readable: false, - }, - first: (cb) => { - setTimeout(() => { - trace.firstDone = Date.now(); - cb(null, 'first'); - }, 5); - }, - second: (cb) => { - trace.secondStarted = Date.now(); - setTimeout(() => cb(null, 'second'), 10); - }, - trackMetaMetricsEvent: sinon.stub().yields(), - }; - _setBackgroundConnection(background); - const scheduled = Promise.all([ - submitRequestToBackground('first'), - submitRequestToBackground('second'), - ]); - background.connectionStream.readable = true; - _setBackgroundConnection(background); - await scheduled; - expect(trace.firstDone).toBeLessThanOrEqual(trace.secondStarted); - }); - - it('ensures actions in queue will not repeat once finished', async () => { - const trace = { calls: 0 }; - const background = { - connectionStream: { - readable: false, - }, - first: (cb) => { - trace.calls += 1; - setTimeout(() => { - trace.firstDone = Date.now(); - cb(null, 'first'); - }, 5); - }, - second: (cb) => { - trace.calls += 1; - trace.secondStarted = Date.now(); - setTimeout(() => cb(null, 'second'), 10); - }, - trackMetaMetricsEvent: sinon.stub().yields(), - }; - _setBackgroundConnection(background); - const scheduled = Promise.all([ - submitRequestToBackground('first'), - submitRequestToBackground('second'), - ]); - background.connectionStream.readable = true; - _setBackgroundConnection(background); - await scheduled; - _setBackgroundConnection(background); // once all actions finished, this triggers draining the queue again - expect(trace.firstDone).toBeLessThanOrEqual(trace.secondStarted); - expect(trace.calls).toStrictEqual(2); - }); - - it('stops processng the queue if connection is lost', async () => { - const trace = {}; - const background = { - connectionStream: { - readable: false, - }, - first: (cb) => { - setTimeout(() => { - trace.firstDone = true; - background.connectionStream.readable = false; - cb(Error('lost connection')); - }, 5); - }, - second: sinon.stub().yields(), - trackMetaMetricsEvent: sinon.stub().yields(), - }; - _setBackgroundConnection(background); - const scheduled = Promise.race([ - submitRequestToBackground('first').catch(() => ({})), - submitRequestToBackground('second'), - ]); - background.connectionStream.readable = true; - _setBackgroundConnection(background); - await scheduled; - await Promise.resolve('one more tick'); // One asynchronous tick to avoid depending on implementation details - expect(trace.firstDone).toStrictEqual(true); - expect(background.second.called).toStrictEqual(false); - }); - - // Failing test for a race condition related to how items are removed from queue - it('avoids race conditions', async () => { - const trace = { first: 0, second: 0 }; - const flowControl = {}; - const background = { - connectionStream: { - readable: false, - }, - first: (cb) => { - trace.first += 1; - setTimeout(() => { - flowControl.triggerRaceCondition(); - cb(null, 'first'); - }, 5); - }, - second: (cb) => { - trace.second += 1; - setTimeout(() => cb(null, 'second'), 10); - }, - third: sinon.stub().yields(), - trackMetaMetricsEvent: sinon.stub().yields(), - }; - flowControl.triggerRaceCondition = () => { - flowControl.waitFor = submitRequestToBackground('third'); - }; - _setBackgroundConnection(background); - const scheduled = Promise.all([ - submitRequestToBackground('first'), - submitRequestToBackground('second'), - ]); - background.connectionStream.readable = true; - _setBackgroundConnection(background); - await scheduled; - await flowControl.waitFor; - expect(trace.first).toStrictEqual(1); - expect(trace.second).toStrictEqual(1); - expect(background.third.calledOnce).toStrictEqual(true); - }); - }); - - describe('callBackgroundMethod', () => { - afterEach(() => { - sinon.restore(); - }); - - it('calls background method if the stream is connected', async () => { - const background = { - connectionStream: { - readable: true, - }, - backgroundFunction: sinon.stub().yields(), - }; - - _setBackgroundConnection(background); - callBackgroundMethod('backgroundFunction', [], () => ({})); - expect(background.backgroundFunction.called).toStrictEqual(true); - }); - - it('does not call background method if the stream is not connected', async () => { - const background = { - connectionStream: { - readable: false, - }, - backgroundFunction: sinon.stub(), - }; - - _setBackgroundConnection(background); - callBackgroundMethod('backgroundFunction', [], () => ({})); - expect(background.backgroundFunction.called).toStrictEqual(false); - }); - - it('calls background method on stream reconnection', async () => { - const background = { - connectionStream: { - readable: false, - }, - backgroundFunction: sinon.stub().yields(), - }; - _setBackgroundConnection(background); - callBackgroundMethod('backgroundFunction', [], () => ({})); - expect(background.backgroundFunction.called).toStrictEqual(false); - - background.connectionStream = { - readable: true, - }; - _setBackgroundConnection(background); - expect(background.backgroundFunction.calledOnce).toStrictEqual(true); - }); - - it('resolves if backgroundFunction called resolves', async () => { - const background = { - connectionStream: { - readable: true, - }, - backgroundFunction: (cb) => { - return cb(null, 'successViaCallback'); - }, - }; - _setBackgroundConnection(background); - const value = await new Promise((resolve) => { - callBackgroundMethod('backgroundFunction', [], (_err, result) => { - resolve(result); - }); - }); - expect(value).toStrictEqual('successViaCallback'); - }); - it('rejects if backgroundFunction called rejects', async () => { - const errorViaCallback = Error('errorViaCallback'); - const background = { - connectionStream: { - readable: true, - }, - backgroundFunction: (cb) => { - return cb(errorViaCallback); - }, - }; - _setBackgroundConnection(background); - const value = await new Promise((resolve) => { - callBackgroundMethod('backgroundFunction', [], (err) => { - resolve(err); - }); - }); - expect(value).toStrictEqual(errorViaCallback); - }); - }); -}); diff --git a/ui/store/action-queue/index.ts b/ui/store/action-queue/index.ts deleted file mode 100644 index f4d8aa5594dd..000000000000 --- a/ui/store/action-queue/index.ts +++ /dev/null @@ -1,250 +0,0 @@ -import pify from 'pify'; -import { - MetaMetricsEventCategory, - MetaMetricsEventName, -} from '../../../shared/constants/metametrics'; -import { isManifestV3 } from '../../../shared/modules/mv3.utils'; -import { trackMetaMetricsEvent } from '../actions'; - -// // A simplified pify maybe? -// function pify(apiObject) { -// return Object.keys(apiObject).reduce((promisifiedAPI, key) => { -// if (apiObject[key].apply) { // depending on our browser support we might use a nicer check for functions here -// promisifiedAPI[key] = function (...args) { -// return new Promise((resolve, reject) => { -// return apiObject[key]( -// ...args, -// (err, result) => { -// if (err) { -// reject(err); -// } else { -// resolve(result); -// } -// }, -// ); -// }); -// }; -// } -// return promisifiedAPI; -// }, {}); -// } - -let background: - | ({ - connectionStream: { readable: boolean }; - DisconnectError: typeof Error; - } & Record any>) - | null = null; -let promisifiedBackground: Record< - string, - (...args: any[]) => Promise -> | null = null; - -interface BackgroundAction { - actionId: number; - request: { method: string; args: any }; - resolve: (result: any) => any; - reject: (err: Error) => void; -} - -const actionRetryQueue: BackgroundAction[] = []; - -export const generateActionId = () => Date.now() + Math.random(); - -function failQueue() { - actionRetryQueue.forEach(({ reject }) => - reject( - Error('Background operation cancelled while waiting for connection.'), - ), - ); -} - -/** - * Drops the entire actions queue. Rejects all actions in the queue unless silently==true - * Does not affect the single action that is currently being processed. - * - * @param [silently] - */ -export function dropQueue(silently: boolean): void { - if (!silently) { - failQueue(); - } - actionRetryQueue.length = 0; -} - -// add action to queue -const executeActionOrAddToRetryQueue = (item: BackgroundAction): void => { - if (actionRetryQueue.some((act) => act.actionId === item.actionId)) { - return; - } - - if (background?.connectionStream.readable) { - executeAction({ - action: item, - disconnectSideeffect: () => actionRetryQueue.push(item), - }); - } else { - actionRetryQueue.push(item); - } -}; - -/** - * Promise-style call to background method - * In MV2: invokes promisifiedBackground method directly. - * In MV3: action is added to retry queue, along with resolve handler to be executed on completion, - * the queue is then immediately processed if background connection is available. - * On completion (successful or error) the action is removed from the retry queue. - * - * @param method - name of the background method - * @param [args] - arguments to that method, if any - * @param [actionId] - if an action with the === same id is submitted, it'll be ignored if already in queue waiting for a retry. - * @returns - */ -export function submitRequestToBackground( - method: string, - args?: any[], - actionId = generateActionId(), // current date is not guaranteed to be unique -): Promise { - if (isManifestV3) { - return new Promise((resolve, reject) => { - executeActionOrAddToRetryQueue({ - actionId, - request: { method, args: args ?? [] }, - resolve, - reject, - }); - }); - } - return promisifiedBackground?.[method]( - ...(args ?? []), - ) as unknown as Promise; -} - -type CallbackMethod = (error?: unknown, result?: R) => void; - -/** - * [Deprecated] Callback-style call to background method - * In MV2: invokes promisifiedBackground method directly. - * In MV3: action is added to retry queue, along with resolve handler to be executed on completion, - * the queue is then immediately processed if background connection is available. - * On completion (successful or error) the action is removed from the retry queue. - * - * @deprecated Use async `submitRequestToBackground` function instead. - * @param method - name of the background method - * @param [args] - arguments to that method, if any - * @param callback - Node style (error, result) callback for finishing the operation - * @param [actionId] - if an action with the === same id is submitted, it'll be ignored if already in queue. - */ -export const callBackgroundMethod = ( - method: string, - args: any[], - callback: CallbackMethod, - actionId = generateActionId(), // current date is not guaranteed to be unique -) => { - if (isManifestV3) { - const resolve = (value: R) => callback(undefined, value); - const reject = (err: unknown) => callback(err, undefined); - executeActionOrAddToRetryQueue({ - actionId, - request: { method, args: args ?? [] }, - resolve, - reject, - }); - } else { - background?.[method](...args, callback); - } -}; - -async function executeAction({ - action, - disconnectSideeffect, -}: { - action: BackgroundAction; - disconnectSideeffect: (action: BackgroundAction) => void; -}) { - const { - request: { method, args }, - resolve, - reject, - } = action; - try { - resolve(await promisifiedBackground?.[method](...args)); - } catch (err: any) { - if ( - background?.DisconnectError && // necessary to not break compatibility with background stubs or non-default implementations - err instanceof background.DisconnectError - ) { - disconnectSideeffect(action); - } else { - reject(err); - } - } -} - -let processingQueue = false; - -// Clears list of pending action in actionRetryQueue -// The results of background calls are wired up to the original promises that's been returned -// The first method on the queue gets called synchronously to make testing and reasoning about -// a single request to an open connection easier. -async function processActionRetryQueue() { - if (processingQueue) { - return; - } - processingQueue = true; - try { - if (actionRetryQueue.length > 0) { - const metametricsPayload = { - category: MetaMetricsEventCategory.ServiceWorkers, - event: MetaMetricsEventName.ServiceWorkerRestarted, - properties: { - service_worker_action_queue_methods: actionRetryQueue.map( - (action) => action.request.method, - ), - }, - }; - - trackMetaMetricsEvent(metametricsPayload); - } - - while ( - background?.connectionStream.readable && - actionRetryQueue.length > 0 - ) { - // If background disconnects and fails the action, the next one will not be taken off the queue. - // Retrying an action that failed because of connection loss while it was processing is not supported. - const item = actionRetryQueue.shift(); - await executeAction({ - action: item as BackgroundAction, - disconnectSideeffect: () => - actionRetryQueue.unshift(item as BackgroundAction), - }); - } - } catch (e) { - // error in the queue mechanism itself, the action was malformed - console.error(e); - } - processingQueue = false; -} - -/** - * Sets/replaces the background connection reference - * Under MV3 it also triggers queue processing if the new background is connected - * - * @param backgroundConnection - */ -export async function _setBackgroundConnection( - backgroundConnection: typeof background, -) { - background = backgroundConnection; - promisifiedBackground = pify(background as Record); - if (isManifestV3) { - if (processingQueue) { - console.warn( - '_setBackgroundConnection called while a queue was processing and not disconnected yet', - ); - } - // Process all actions collected while connection stream was not available. - processActionRetryQueue(); - } -} diff --git a/ui/store/action-queue/queue.integration.test.js b/ui/store/action-queue/queue.integration.test.js deleted file mode 100644 index a90c0233059a..000000000000 --- a/ui/store/action-queue/queue.integration.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import sinon from 'sinon'; -import PortStream from 'extension-port-stream'; -import { setupMultiplex } from '../../../app/scripts/lib/stream-utils'; -import metaRPCClientFactory from '../../../app/scripts/lib/metaRPCClientFactory'; - -import { - dropQueue, - submitRequestToBackground, - _setBackgroundConnection, -} from '.'; - -jest.mock('../../../shared/modules/mv3.utils', () => { - return { - isManifestV3: () => true, - }; -}); - -const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - -describe('queue integration test', () => { - afterEach(() => { - dropQueue(true); - }); - it('schedules a retry if background method failed because of a disconnect', async () => { - let disconnectListener; - const extensionPort = { - onMessage: { - addListener: sinon.stub(), - }, - onDisconnect: { - addListener(cb) { - disconnectListener = cb; - }, - }, - postMessage: sinon.stub().callsFake(() => { - disconnectListener(); - }), - }; - - const connectionStream = new PortStream(extensionPort); - const mx = setupMultiplex(connectionStream); - const multiplexStream1 = mx.createStream('controller'); - const background = metaRPCClientFactory(multiplexStream1); - - _setBackgroundConnection(background); - - // disconnect will happen on the attempt to send the message - const finished = submitRequestToBackground('backgroundFunction').catch( - (error) => { - // disconnect error should not get propagated, we retry. - // eslint-disable-next-line jest/no-conditional-expect - expect(error).not.toBeInstanceOf(background.DisconnectError); - // eslint-disable-next-line jest/no-conditional-expect - expect(error.message).toContain('cancelled'); - }, - ); - // We want to make sure we disconnect in the middle of processing, so we have to wait for the control flow to reach postMessage - // undetermined number of asynchronous jumps withing the stream implementation leaves no other option - await wait(3); - // we drop the queue because we're expecting the action to have been returned to the queue and this is the simplest way to check that - dropQueue(); - - expect(extensionPort.postMessage.calledOnce).toStrictEqual(true); - await finished; - }); -}); diff --git a/ui/store/actionConstants.ts b/ui/store/actionConstants.ts index c38ae2a010b4..bc8ee676f104 100644 --- a/ui/store/actionConstants.ts +++ b/ui/store/actionConstants.ts @@ -72,8 +72,6 @@ export const SET_CURRENT_LOCALE = 'SET_CURRENT_LOCALE'; export const COMPLETE_ONBOARDING = 'COMPLETE_ONBOARDING'; export const ONBOARDED_IN_THIS_UI_SESSION = 'ONBOARDED_IN_THIS_UI_SESSION'; -export const SET_MOUSE_USER_STATE = 'SET_MOUSE_USER_STATE'; - // Ledger export const SET_WEBHID_CONNECTED_STATUS = 'SET_WEBHID_CONNECTED_STATUS'; diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index 56882e5059e4..eb5adfdf6244 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -9,7 +9,7 @@ import { GAS_LIMITS } from '../../shared/constants/gas'; import { ORIGIN_METAMASK } from '../../shared/constants/app'; import { MetaMetricsNetworkEventSource } from '../../shared/constants/metametrics'; import * as actions from './actions'; -import { _setBackgroundConnection } from './action-queue'; +import { setBackgroundConnection } from './background-connection'; const middleware = [thunk]; const defaultState = { @@ -39,7 +39,7 @@ const baseMockState = defaultState.metamask; describe('Actions', () => { let background; - const currentNetworkId = '5'; + const currentChainId = '0x5'; beforeEach(async () => { background = sinon.createStubInstance(MetaMaskController, { @@ -63,7 +63,7 @@ describe('Actions', () => { cb(), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -88,7 +88,7 @@ describe('Actions', () => { background.submitPassword.callsFake((_, cb) => cb(new Error('error'))); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -118,7 +118,7 @@ describe('Actions', () => { background.unMarkPasswordForgotten.callsFake((cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch( actions.createNewVaultAndRestore('password', 'test'), @@ -132,7 +132,7 @@ describe('Actions', () => { background.createNewVaultAndRestore.callsFake((_, __, cb) => cb()); background.unMarkPasswordForgotten.callsFake((cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -158,7 +158,7 @@ describe('Actions', () => { cb(new Error('error')), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -189,7 +189,7 @@ describe('Actions', () => { cb(null, Array.from(Buffer.from('test').values())), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch(actions.requestRevealSeedWords()); expect(verifyPassword.callCount).toStrictEqual(1); @@ -204,7 +204,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -252,7 +252,7 @@ describe('Actions', () => { const removeAccount = background.removeAccount.callsFake((_, cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ 'SHOW_LOADING_INDICATION', @@ -279,7 +279,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -307,7 +307,7 @@ describe('Actions', () => { const resetAccount = background.resetAccount.callsFake((cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -327,7 +327,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -356,7 +356,7 @@ describe('Actions', () => { cb(); }); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch( actions.importNewAccount( @@ -375,7 +375,7 @@ describe('Actions', () => { cb(new Error('error')), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { @@ -405,7 +405,7 @@ describe('Actions', () => { }), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch(actions.addNewAccount(1)); expect(addNewAccount.callCount).toStrictEqual(1); @@ -418,7 +418,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -448,7 +448,7 @@ describe('Actions', () => { }, ); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch( actions.checkHardwareStatus( @@ -466,7 +466,7 @@ describe('Actions', () => { cb(new Error('error')), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -492,7 +492,7 @@ describe('Actions', () => { const forgetDevice = background.forgetDevice.callsFake((_, cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch(actions.forgetDevice(HardwareDeviceNames.ledger)); expect(forgetDevice.callCount).toStrictEqual(1); @@ -503,7 +503,7 @@ describe('Actions', () => { background.forgetDevice.callsFake((_, cb) => cb(new Error('error'))); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -533,7 +533,7 @@ describe('Actions', () => { background.establishLedgerTransportPreference.callsFake((cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch( actions.connectHardware( @@ -554,7 +554,7 @@ describe('Actions', () => { background.establishLedgerTransportPreference.callsFake((cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { @@ -585,7 +585,7 @@ describe('Actions', () => { (_, __, ___, ____, cb) => cb(), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch( actions.unlockHardwareWalletAccounts( @@ -605,7 +605,7 @@ describe('Actions', () => { cb(new Error('error')), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -629,7 +629,7 @@ describe('Actions', () => { it('calls setCurrentCurrency', async () => { const store = mockStore(); background.setCurrentCurrency = sinon.stub().callsFake((_, cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch(actions.setCurrentCurrency('jpy')); expect(background.setCurrentCurrency.callCount).toStrictEqual(1); @@ -640,7 +640,7 @@ describe('Actions', () => { background.setCurrentCurrency = sinon .stub() .callsFake((_, cb) => cb(new Error('error'))); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -665,7 +665,7 @@ describe('Actions', () => { const txData = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, }; @@ -683,7 +683,7 @@ describe('Actions', () => { getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.updateTransaction(txData)); @@ -711,7 +711,7 @@ describe('Actions', () => { ), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -748,7 +748,7 @@ describe('Actions', () => { const backgroundSetLocked = background.setLocked.callsFake((cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch(actions.lockMetamask()); expect(backgroundSetLocked.callCount).toStrictEqual(1); @@ -761,7 +761,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -790,7 +790,7 @@ describe('Actions', () => { setSelectedAddress: setSelectedAddressSpy, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch( actions.setSelectedAddress( @@ -811,7 +811,7 @@ describe('Actions', () => { setSelectedAddress: setSelectedAddressSpy, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -841,7 +841,7 @@ describe('Actions', () => { setSelectedAddress: setSelectedAddressSpy, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.setSelectedAccount()); expect(setSelectedAddressSpy.callCount).toStrictEqual(1); @@ -861,7 +861,7 @@ describe('Actions', () => { setSelectedAddress: setSelectedAddressSpy, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -891,7 +891,7 @@ describe('Actions', () => { getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch( actions.addToken({ @@ -922,7 +922,7 @@ describe('Actions', () => { getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -961,7 +961,7 @@ describe('Actions', () => { getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch( actions.ignoreTokens({ tokensToIgnore: '0x0000001' }), @@ -977,7 +977,7 @@ describe('Actions', () => { getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -1011,7 +1011,7 @@ describe('Actions', () => { setProviderType: setProviderTypeStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.setProviderType()); expect(setProviderTypeStub.callCount).toStrictEqual(1); @@ -1026,7 +1026,7 @@ describe('Actions', () => { .callsFake((_, cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { @@ -1053,7 +1053,7 @@ describe('Actions', () => { background.getApi.returns({ setActiveNetwork: setCurrentNetworkStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.setActiveNetwork('networkConfigurationId')); expect( @@ -1071,7 +1071,7 @@ describe('Actions', () => { background.getApi.returns({ setActiveNetwork: setCurrentNetworkStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { @@ -1105,7 +1105,7 @@ describe('Actions', () => { removeNetworkConfiguration: removeNetworkConfigurationStub, upsertNetworkConfiguration: upsertNetworkConfigurationStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const networkConfiguration = { rpcUrl: 'newRpc', @@ -1152,7 +1152,7 @@ describe('Actions', () => { upsertNetworkConfiguration: upsertNetworkConfigurationStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'DISPLAY_WARNING', payload: 'Had a problem removing network!' }, @@ -1208,7 +1208,7 @@ describe('Actions', () => { background.getApi.returns({ upsertNetworkConfiguration: upsertNetworkConfigurationStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const networkConfiguration = { rpcUrl: 'newRpc', @@ -1265,7 +1265,7 @@ describe('Actions', () => { background.getApi.returns({ requestUserApproval: requestUserApprovalStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const networkConfiguration = { rpcUrl: 'newRpc', @@ -1308,7 +1308,7 @@ describe('Actions', () => { background.getApi.returns({ removeNetworkConfiguration: removeNetworkConfigurationStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch( actions.removeNetworkConfiguration('testNetworkConfigurationId'), @@ -1373,7 +1373,7 @@ describe('Actions', () => { setAddressBook: setAddressBookStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.addToAddressBook('0x0000')); expect(setAddressBookStub.callCount).toStrictEqual(1); @@ -1402,7 +1402,7 @@ describe('Actions', () => { exportAccount: exportAccountStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -1434,7 +1434,7 @@ describe('Actions', () => { verifyPassword: verifyPasswordStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -1463,7 +1463,7 @@ describe('Actions', () => { exportAccount: exportAccountStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -1496,7 +1496,7 @@ describe('Actions', () => { setAccountLabel: setAccountLabelStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch( actions.setAccountLabel( @@ -1516,7 +1516,7 @@ describe('Actions', () => { .callsFake((_, __, cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -1551,7 +1551,7 @@ describe('Actions', () => { setFeatureFlag: setFeatureFlagStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.setFeatureFlag()); expect(setFeatureFlagStub.callCount).toStrictEqual(1); @@ -1566,7 +1566,7 @@ describe('Actions', () => { .callsFake((_, __, cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -1595,7 +1595,7 @@ describe('Actions', () => { completeOnboarding: completeOnboardingStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.setCompletedOnboarding()); expect(completeOnboardingStub.callCount).toStrictEqual(1); @@ -1610,7 +1610,7 @@ describe('Actions', () => { .callsFake((cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -1641,7 +1641,7 @@ describe('Actions', () => { setParticipateInMetaMetrics: setParticipateInMetaMetricsStub, }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.setParticipateInMetaMetrics(true)); expect(setParticipateInMetaMetricsStub).toHaveBeenCalledWith( @@ -1660,7 +1660,7 @@ describe('Actions', () => { it('calls setUseBlockie in background', async () => { const store = mockStore(); const setUseBlockieStub = sinon.stub().callsFake((_, cb) => cb()); - _setBackgroundConnection({ setUseBlockie: setUseBlockieStub }); + setBackgroundConnection({ setUseBlockie: setUseBlockieStub }); await store.dispatch(actions.setUseBlockie()); expect(setUseBlockieStub.callCount).toStrictEqual(1); @@ -1672,7 +1672,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection({ setUseBlockie: setUseBlockieStub }); + setBackgroundConnection({ setUseBlockie: setUseBlockieStub }); const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', payload: undefined }, @@ -1693,7 +1693,7 @@ describe('Actions', () => { it('calls setUsePhishDetect in background', () => { const store = mockStore(); const setUsePhishDetectStub = sinon.stub().callsFake((_, cb) => cb()); - _setBackgroundConnection({ + setBackgroundConnection({ setUsePhishDetect: setUsePhishDetectStub, }); @@ -1707,7 +1707,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection({ + setBackgroundConnection({ setUsePhishDetect: setUsePhishDetectStub, }); @@ -1732,7 +1732,7 @@ describe('Actions', () => { const setUseMultiAccountBalanceCheckerStub = sinon .stub() .callsFake((_, cb) => cb()); - _setBackgroundConnection({ + setBackgroundConnection({ setUseMultiAccountBalanceChecker: setUseMultiAccountBalanceCheckerStub, }); @@ -1748,7 +1748,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection({ + setBackgroundConnection({ setUseMultiAccountBalanceChecker: setUseMultiAccountBalanceCheckerStub, }); @@ -1771,7 +1771,7 @@ describe('Actions', () => { it('calls setUse4ByteResolution in background', async () => { const store = mockStore(); const setUse4ByteResolutionStub = sinon.stub().callsFake((_, cb) => cb()); - _setBackgroundConnection({ + setBackgroundConnection({ setUse4ByteResolution: setUse4ByteResolutionStub, }); @@ -1785,7 +1785,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection({ + setBackgroundConnection({ setUse4ByteResolution: setUse4ByteResolutionStub, }); @@ -1810,7 +1810,7 @@ describe('Actions', () => { const setUseSafeChainsListValidationStub = sinon .stub() .callsFake((_, cb) => cb()); - _setBackgroundConnection({ + setBackgroundConnection({ setUseSafeChainsListValidation: setUseSafeChainsListValidationStub, }); @@ -1826,7 +1826,7 @@ describe('Actions', () => { cb(new Error('error')); }); - _setBackgroundConnection({ + setBackgroundConnection({ setUseSafeChainsListValidation: setUseSafeChainsListValidationStub, }); @@ -1855,7 +1855,7 @@ describe('Actions', () => { it('calls expected actions', async () => { const store = mockStore(); const setCurrentLocaleStub = sinon.stub().callsFake((_, cb) => cb()); - _setBackgroundConnection({ + setBackgroundConnection({ setCurrentLocale: setCurrentLocaleStub, }); @@ -1878,7 +1878,7 @@ describe('Actions', () => { const setCurrentLocaleStub = sinon .stub() .callsFake((_, cb) => cb(new Error('error'))); - _setBackgroundConnection({ + setBackgroundConnection({ setCurrentLocale: setCurrentLocaleStub, }); @@ -1904,7 +1904,7 @@ describe('Actions', () => { background.markPasswordForgotten.callsFake((cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); await store.dispatch(actions.markPasswordForgotten()); @@ -1918,7 +1918,7 @@ describe('Actions', () => { cb(new Error('error')), ); - _setBackgroundConnection(background); + setBackgroundConnection(background); const expectedActions = [ { type: 'HIDE_LOADING_INDICATION' }, @@ -1942,7 +1942,7 @@ describe('Actions', () => { background.unMarkPasswordForgotten.callsFake((cb) => cb()); - _setBackgroundConnection(background); + setBackgroundConnection(background); store.dispatch(actions.unMarkPasswordForgotten()); @@ -1999,7 +1999,7 @@ describe('Actions', () => { ), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const txId = 1457634084250832; @@ -2028,7 +2028,7 @@ describe('Actions', () => { ), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); await store.dispatch(actions.setDesktopEnabled(true)); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 057027c2f15a..fbe791bc5e0b 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -87,7 +87,6 @@ import { decimalToHex } from '../../shared/modules/conversion.utils'; import { TxGasFees, PriorityLevels } from '../../shared/constants/gas'; import { TransactionMeta, - TransactionMetaMetricsEvent, TransactionType, } from '../../shared/constants/transaction'; import { NetworkType, RPCDefinition } from '../../shared/constants/network'; @@ -107,7 +106,7 @@ import { generateActionId, callBackgroundMethod, submitRequestToBackground, -} from './action-queue'; +} from './background-connection'; import { MetaMaskReduxDispatch, MetaMaskReduxState, @@ -980,7 +979,6 @@ export function addTransactionAndRouteToConfirmationPage( const transactionMeta = await submitRequestToBackground( 'addTransaction', [txParams, { ...options, actionId, origin: ORIGIN_METAMASK }], - actionId, ); dispatch(showConfTxPage()); @@ -1032,7 +1030,6 @@ export async function addTransactionAndWaitForPublish( actionId, }, ], - actionId, ); } @@ -2089,7 +2086,6 @@ export function createCancelTransaction( resolve(newState); } }, - actionId, ); }) .then((newState) => dispatch(updateMetamaskState(newState))) @@ -2125,7 +2121,6 @@ export function createSpeedUpTransaction( resolve(newState); } }, - actionId, ); }) .then((newState) => dispatch(updateMetamaskState(newState))) @@ -2855,15 +2850,6 @@ export function completeOnboarding() { }; } -export function setMouseUserState( - isMouseUser: boolean, -): PayloadAction { - return { - type: actionConstants.SET_MOUSE_USER_STATE, - payload: isMouseUser, - }; -} - export async function forceUpdateMetamaskState( dispatch: MetaMaskReduxDispatch, ) { @@ -4139,13 +4125,13 @@ export function createEventFragment( export function createTransactionEventFragment( transactionId: string, - event: TransactionMetaMetricsEvent, ): Promise { const actionId = generateActionId(); return submitRequestToBackground('createTransactionEventFragment', [ - transactionId, - event, - actionId, + { + transactionId, + actionId, + }, ]); } diff --git a/ui/store/background-connection.ts b/ui/store/background-connection.ts new file mode 100644 index 000000000000..c6c0767ad21d --- /dev/null +++ b/ui/store/background-connection.ts @@ -0,0 +1,61 @@ +import pify from 'pify'; + +let background: + | ({ + connectionStream: { readable: boolean }; + DisconnectError: typeof Error; + } & Record any>) + | null = null; +let promisifiedBackground: Record< + string, + (...args: any[]) => Promise +> | null = null; + +export const generateActionId = () => Date.now() + Math.random(); + +/** + * Promise-style call to background method invokes promisifiedBackground method directly. + * + * @param method - name of the background method + * @param [args] - arguments to that method, if any + * @returns + */ +export function submitRequestToBackground( + method: string, + args?: any[], +): Promise { + return promisifiedBackground?.[method]( + ...(args ?? []), + ) as unknown as Promise; +} + +type CallbackMethod = (error?: unknown, result?: R) => void; + +/** + * [Deprecated] Callback-style call to background method + * invokes promisifiedBackground method directly. + * + * @param method - name of the background method + * @param [args] - arguments to that method, if any + * @param callback - Node style (error, result) callback for finishing the operation + */ +export const callBackgroundMethod = ( + method: string, + args: any[], + callback: CallbackMethod, +) => { + background?.[method](...args, callback); +}; + +/** + * Sets/replaces the background connection reference + * Under MV3 it also triggers queue processing if the new background is connected + * + * @param backgroundConnection + */ +export async function setBackgroundConnection( + backgroundConnection: typeof background, +) { + background = backgroundConnection; + promisifiedBackground = pify(background as Record); +} diff --git a/ui/store/institutional/institution-actions.test.js b/ui/store/institutional/institution-actions.test.js index 2295791bd9ec..6026719214d2 100644 --- a/ui/store/institutional/institution-actions.test.js +++ b/ui/store/institutional/institution-actions.test.js @@ -2,7 +2,7 @@ import sinon from 'sinon'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import MetaMaskController from '../../../app/scripts/metamask-controller'; -import { _setBackgroundConnection } from '../action-queue'; +import { setBackgroundConnection } from '../background-connection'; import { showInteractiveReplacementTokenModal, showCustodyConfirmLink, @@ -43,6 +43,7 @@ const defaultState = { { id: 0, time: 0, + chainId: '0x1', txParams: { from: '0xAddress', to: '0xRecipient', @@ -53,6 +54,7 @@ const defaultState = { { id: 1, time: 1, + chainId: '0x1', txParams: { from: '0xAddress', to: '0xRecipient', @@ -107,7 +109,7 @@ describe('#InstitutionActions', () => { .callsFake((_, __, cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { @@ -130,7 +132,7 @@ describe('#InstitutionActions', () => { .callsFake((_, __, cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const expectedActions = [ { @@ -199,7 +201,7 @@ describe('#updateCustodyState', () => { .callsFake((_, __, cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const newState = { providerConfig: { @@ -223,7 +225,7 @@ describe('#updateCustodyState', () => { .callsFake((_, __, cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const newState = { providerConfig: { @@ -236,6 +238,7 @@ describe('#updateCustodyState', () => { { id: 0, time: 0, + chainId: '0x1', txParams: { from: '0xAddress', to: '0xRecipient', @@ -246,6 +249,7 @@ describe('#updateCustodyState', () => { { id: 1, time: 1, + chainId: '0x1', txParams: { from: '0xAddress', to: '0xRecipient', @@ -276,7 +280,7 @@ describe('#updateCustodyState', () => { .callsFake((_, __, cb) => cb(new Error('error'))), }); - _setBackgroundConnection(background.getApi()); + setBackgroundConnection(background.getApi()); const newState = { providerConfig: { @@ -289,6 +293,7 @@ describe('#updateCustodyState', () => { { id: 0, time: 0, + chainId: '0x1', txParams: { from: '0xAddress', to: '0xRecipient', @@ -299,6 +304,7 @@ describe('#updateCustodyState', () => { { id: 1, time: 1, + chainId: '0x1', txParams: { from: '0xAddress', to: '0xRecipient', diff --git a/ui/store/institutional/institution-background.test.js b/ui/store/institutional/institution-background.test.js index 9f041b0ecf8f..b1c207144d7c 100644 --- a/ui/store/institutional/institution-background.test.js +++ b/ui/store/institutional/institution-background.test.js @@ -3,7 +3,7 @@ import { showLoadingIndication, forceUpdateMetamaskState, } from '../actions'; -import { submitRequestToBackground } from '../action-queue'; +import { submitRequestToBackground } from '../background-connection'; import { mmiActionsFactory, showInteractiveReplacementTokenBanner, @@ -18,7 +18,7 @@ jest.mock('../actions', () => ({ forceUpdateMetamaskState: jest.fn(), })); -jest.mock('../action-queue', () => ({ +jest.mock('../background-connection', () => ({ submitRequestToBackground: jest.fn(), })); diff --git a/ui/store/institutional/institution-background.ts b/ui/store/institutional/institution-background.ts index 40a5696d3a1b..2721d72e9add 100644 --- a/ui/store/institutional/institution-background.ts +++ b/ui/store/institutional/institution-background.ts @@ -10,7 +10,7 @@ import { import { callBackgroundMethod, submitRequestToBackground, -} from '../action-queue'; +} from '../background-connection'; import { MetaMaskReduxDispatch, MetaMaskReduxState } from '../store'; import { isErrorWithMessage } from '../../../shared/modules/error'; diff --git a/ui/store/store.ts b/ui/store/store.ts index 739271df9b8b..d0785a8ed909 100644 --- a/ui/store/store.ts +++ b/ui/store/store.ts @@ -65,7 +65,6 @@ interface TemporaryBackgroundState { unapprovedMsgs: MessagesIndexedById; unapprovedPersonalMsgs: MessagesIndexedById; unapprovedTypedMessages: MessagesIndexedById; - networkId: string | null; networksMetadata: { [NetworkClientId: string]: { EIPS: { [eip: string]: boolean }; diff --git a/yarn.lock b/yarn.lock index 786fae6e82ca..572578903dd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1678,7 +1678,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.0, @babel/types@npm:^7.13.0, @babel/types@npm:^7.18.7, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.0, @babel/types@npm:^7.13.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.23.0 resolution: "@babel/types@npm:7.23.0" dependencies: @@ -2722,6 +2722,27 @@ __metadata: languageName: node linkType: hard +"@figspec/components@npm:^1.0.1": + version: 1.0.2 + resolution: "@figspec/components@npm:1.0.2" + dependencies: + lit: "npm:^2.1.3" + checksum: 5f76666278a23ea38518bc3b4488ba740916c0f87d71d872e595265c1f17553949ac2d8b6bcc87ec7000704cc6fe7062fc57eaf0573c31a51341ed1e5f7a07a0 + languageName: node + linkType: hard + +"@figspec/react@npm:^1.0.0": + version: 1.0.3 + resolution: "@figspec/react@npm:1.0.3" + dependencies: + "@figspec/components": "npm:^1.0.1" + "@lit-labs/react": "npm:^1.0.2" + peerDependencies: + react: ^16.14.0 || ^17.0.0 || ^18.0.0 + checksum: 8cfc1be1d8b6aa089fb4a7461a46bc96cdd4ad40ee49b0dea46507221d6036a81644b732ed5be0e8047ef6436e776036bcf0a72db724f0f9358837270f4278cf + languageName: node + linkType: hard + "@floating-ui/core@npm:^1.4.2": version: 1.5.0 resolution: "@floating-ui/core@npm:1.5.0" @@ -3612,6 +3633,29 @@ __metadata: languageName: node linkType: hard +"@lit-labs/react@npm:^1.0.2": + version: 1.2.1 + resolution: "@lit-labs/react@npm:1.2.1" + checksum: 2515f503ca1578e2576bf993b3b605a6f3c935df4e98d7b45e77ea85d2fd66dd3ce37306e80d15a706999d8655d0acff35baf6d70070d50155914754698416ff + languageName: node + linkType: hard + +"@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0": + version: 1.1.2 + resolution: "@lit-labs/ssr-dom-shim@npm:1.1.2" + checksum: a930f7de57b952dc21317a5754aa0411e000bb4991053cde771c111b7792c4a4cdc896922f0353c832215bed71400431c5ab5a6252c8f4f70bb9ce0b37fe4752 + languageName: node + linkType: hard + +"@lit/reactive-element@npm:^1.3.0, @lit/reactive-element@npm:^1.6.0": + version: 1.6.3 + resolution: "@lit/reactive-element@npm:1.6.3" + dependencies: + "@lit-labs/ssr-dom-shim": "npm:^1.0.0" + checksum: 664c899bb0b144590dc4faf83b358b1504810eac107778c3aeb384affc65a7ef4eda754944bcc34a57237db03dff145332406345ac24da19ca37cf4b3cb343d3 + languageName: node + linkType: hard + "@material-ui/core@npm:^4.11.0": version: 4.11.0 resolution: "@material-ui/core@npm:4.11.0" @@ -3908,62 +3952,27 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@npm:13.0.0": - version: 13.0.0 - resolution: "@metamask/assets-controllers@npm:13.0.0" - dependencies: - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/contracts": "npm:^5.7.0" - "@ethersproject/providers": "npm:^5.7.0" - "@metamask/abi-utils": "npm:^2.0.2" - "@metamask/approval-controller": "npm:^3.5.1" - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/contract-metadata": "npm:^2.3.1" - "@metamask/controller-utils": "npm:^5.0.0" - "@metamask/eth-query": "npm:^3.0.1" - "@metamask/metamask-eth-abis": "npm:3.0.0" - "@metamask/network-controller": "npm:^13.0.0" - "@metamask/preferences-controller": "npm:^4.4.1" - "@metamask/rpc-errors": "npm:^5.1.1" - "@metamask/utils": "npm:^6.2.0" - "@types/uuid": "npm:^8.3.0" - abort-controller: "npm:^3.0.0" - async-mutex: "npm:^0.2.6" - ethereumjs-util: "npm:^7.0.10" - immer: "npm:^9.0.6" - multiformats: "npm:^9.5.2" - single-call-balance-checker-abi: "npm:^1.0.0" - uuid: "npm:^8.3.2" - peerDependencies: - "@metamask/approval-controller": ^3.5.1 - "@metamask/network-controller": ^13.0.0 - "@metamask/preferences-controller": ^4.4.1 - checksum: cfb770ac88fa31d2435fe22c58046fc0356884c8981202d8b0b55a3e13387c4f807878941873be204a61a5777fc298ae013382e4948113628d232fbd68c31237 - languageName: node - linkType: hard - -"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A13.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-13.0.0-91c45127c5.patch": - version: 13.0.0 - resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A13.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-13.0.0-91c45127c5.patch::version=13.0.0&hash=5544c9" +"@metamask/assets-controllers@npm:^16.0.0": + version: 16.0.0 + resolution: "@metamask/assets-controllers@npm:16.0.0" dependencies: "@ethersproject/address": "npm:^5.7.0" "@ethersproject/bignumber": "npm:^5.7.0" "@ethersproject/contracts": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.0" "@metamask/abi-utils": "npm:^2.0.2" - "@metamask/approval-controller": "npm:^3.5.1" - "@metamask/base-controller": "npm:^3.2.1" + "@metamask/approval-controller": "npm:^4.0.1" + "@metamask/base-controller": "npm:^3.2.3" "@metamask/contract-metadata": "npm:^2.3.1" - "@metamask/controller-utils": "npm:^5.0.0" + "@metamask/controller-utils": "npm:^5.0.2" "@metamask/eth-query": "npm:^3.0.1" "@metamask/metamask-eth-abis": "npm:3.0.0" - "@metamask/network-controller": "npm:^13.0.0" - "@metamask/preferences-controller": "npm:^4.4.1" - "@metamask/rpc-errors": "npm:^5.1.1" - "@metamask/utils": "npm:^6.2.0" + "@metamask/network-controller": "npm:^15.0.0" + "@metamask/polling-controller": "npm:^0.2.0" + "@metamask/preferences-controller": "npm:^4.4.3" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/utils": "npm:^8.1.0" "@types/uuid": "npm:^8.3.0" - abort-controller: "npm:^3.0.0" async-mutex: "npm:^0.2.6" ethereumjs-util: "npm:^7.0.10" immer: "npm:^9.0.6" @@ -3971,10 +3980,10 @@ __metadata: single-call-balance-checker-abi: "npm:^1.0.0" uuid: "npm:^8.3.2" peerDependencies: - "@metamask/approval-controller": ^3.5.1 - "@metamask/network-controller": ^13.0.0 - "@metamask/preferences-controller": ^4.4.1 - checksum: e473c050ad5ed733668d9278d8f4806eea0a528b434d7f5a49983505cf49bf75cc6de07f65b339d61661de2d7bbb5dbce203a43ad22278b76b9baa7b830126c0 + "@metamask/approval-controller": ^4.0.1 + "@metamask/network-controller": ^15.0.0 + "@metamask/preferences-controller": ^4.4.3 + checksum: 3aeb86a5ad56bdd49f77a1b7c0639d966f57db1a528834554ff6e1dedd8bec76fbb43f5dda1fd04bfa2e2eb02f0641eef040e0cb43bc8cce769d699e818fdf73 languageName: node linkType: hard @@ -4016,7 +4025,7 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@npm:^4.0.0, @metamask/controller-utils@npm:^4.1.0, @metamask/controller-utils@npm:^4.2.0, @metamask/controller-utils@npm:^4.3.0, @metamask/controller-utils@npm:^4.3.2": +"@metamask/controller-utils@npm:^4.0.0, @metamask/controller-utils@npm:^4.1.0, @metamask/controller-utils@npm:^4.2.0, @metamask/controller-utils@npm:^4.3.0": version: 4.3.2 resolution: "@metamask/controller-utils@npm:4.3.2" dependencies: @@ -4157,6 +4166,19 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-json-rpc-infura@npm:^9.0.0": + version: 9.0.0 + resolution: "@metamask/eth-json-rpc-infura@npm:9.0.0" + dependencies: + "@metamask/eth-json-rpc-provider": "npm:^2.1.0" + "@metamask/json-rpc-engine": "npm:^7.1.1" + "@metamask/rpc-errors": "npm:^6.0.0" + "@metamask/utils": "npm:^8.1.0" + node-fetch: "npm:^2.6.7" + checksum: 4272905d6849f79e6ceaa04c0c984f8a835b93829b1e872115289270d3fe2ed47e5f2b82f2485d3c89ad781dd8141b2277f1c1ba830ae4089c9fc97b3e6e7627 + languageName: node + linkType: hard + "@metamask/eth-json-rpc-middleware@npm:^11.0.0, @metamask/eth-json-rpc-middleware@npm:^11.0.2": version: 11.0.2 resolution: "@metamask/eth-json-rpc-middleware@npm:11.0.2" @@ -4174,6 +4196,23 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-json-rpc-middleware@npm:^12.0.0": + version: 12.0.0 + resolution: "@metamask/eth-json-rpc-middleware@npm:12.0.0" + dependencies: + "@metamask/eth-json-rpc-provider": "npm:^2.1.0" + "@metamask/eth-sig-util": "npm:^7.0.0" + "@metamask/json-rpc-engine": "npm:^7.1.1" + "@metamask/rpc-errors": "npm:^6.0.0" + "@metamask/utils": "npm:^8.1.0" + eth-block-tracker: "npm:^8.0.0" + klona: "npm:^2.0.6" + pify: "npm:^5.0.0" + safe-stable-stringify: "npm:^2.4.3" + checksum: 55da1e56604d7b872adf925c0d6f0c782a65ea9a7c23593264e53881a7dc10c866e8a3bfdcbef341d657062d1bcff08c7c5a9e3b8158dd659fa102ff0e45bfc3 + languageName: node + linkType: hard + "@metamask/eth-json-rpc-provider@npm:^1.0.0": version: 1.0.0 resolution: "@metamask/eth-json-rpc-provider@npm:1.0.0" @@ -4184,6 +4223,17 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-json-rpc-provider@npm:^2.1.0": + version: 2.2.0 + resolution: "@metamask/eth-json-rpc-provider@npm:2.2.0" + dependencies: + "@metamask/json-rpc-engine": "npm:^7.1.0" + "@metamask/safe-event-emitter": "npm:^3.0.0" + "@metamask/utils": "npm:^8.1.0" + checksum: f31c0952b4d2cefebbd83cd84b2d77783253febe302d2f43e8502c3bc474b04fb85d6d4da221779ff9b4400499cb6ad32b26eadc31a7845df53cd66a8d4c4d82 + languageName: node + linkType: hard + "@metamask/eth-keyring-controller@npm:13.0.1": version: 13.0.1 resolution: "@metamask/eth-keyring-controller@npm:13.0.1" @@ -4394,7 +4444,7 @@ __metadata: languageName: node linkType: hard -"@metamask/json-rpc-engine@npm:^7.1.1": +"@metamask/json-rpc-engine@npm:^7.1.0, @metamask/json-rpc-engine@npm:^7.1.1": version: 7.1.1 resolution: "@metamask/json-rpc-engine@npm:7.1.1" dependencies: @@ -4455,14 +4505,14 @@ __metadata: languageName: node linkType: hard -"@metamask/logging-controller@npm:^1.0.1": - version: 1.0.1 - resolution: "@metamask/logging-controller@npm:1.0.1" +"@metamask/logging-controller@npm:^1.0.1, @metamask/logging-controller@npm:^1.0.3": + version: 1.0.4 + resolution: "@metamask/logging-controller@npm:1.0.4" dependencies: - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/controller-utils": "npm:^4.3.2" + "@metamask/base-controller": "npm:^3.2.3" + "@metamask/controller-utils": "npm:^5.0.2" uuid: "npm:^8.3.2" - checksum: ce70fab9e5345e08c5a361a6ee2ca4095a4ad25939728617b762d0465045f3efb3f87d5b98f2bebe504e2b7d5072b18a2164c90604ee5a00cf5bff51ac1ae205 + checksum: 8b119a11e302ca43b0d7b90e4462426a53aee48bafdac308cab70a64f06e639209c9fb72868ed026d1730938a239ce2bdf6702e3c1cc5ec3e58ea8ed6bd30adf languageName: node linkType: hard @@ -4476,7 +4526,7 @@ __metadata: languageName: node linkType: hard -"@metamask/message-manager@npm:^7.3.0, @metamask/message-manager@npm:^7.3.2, @metamask/message-manager@npm:^7.3.5": +"@metamask/message-manager@npm:^7.3.0, @metamask/message-manager@npm:^7.3.5": version: 7.3.5 resolution: "@metamask/message-manager@npm:7.3.5" dependencies: @@ -4534,12 +4584,12 @@ __metadata: languageName: node linkType: hard -"@metamask/network-controller@npm:^12.1.2, @metamask/network-controller@npm:^12.2.0": - version: 12.2.0 - resolution: "@metamask/network-controller@npm:12.2.0" +"@metamask/network-controller@npm:^13.0.0": + version: 13.0.1 + resolution: "@metamask/network-controller@npm:13.0.1" dependencies: - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/controller-utils": "npm:^4.3.2" + "@metamask/base-controller": "npm:^3.2.2" + "@metamask/controller-utils": "npm:^5.0.1" "@metamask/eth-json-rpc-infura": "npm:^8.1.1" "@metamask/eth-json-rpc-middleware": "npm:^11.0.2" "@metamask/eth-json-rpc-provider": "npm:^1.0.0" @@ -4552,29 +4602,51 @@ __metadata: immer: "npm:^9.0.6" json-rpc-engine: "npm:^6.1.0" uuid: "npm:^8.3.2" - checksum: e5497f00c8a6ed5f00a9a5dccc6ff98cc15cc83ec31493e1387f654e038e244dff6b7954422f753d07461e2d4ef9c9dde2337a6cc4c013e3fae045592278da04 + checksum: 68bb0073799ef1568b88db44c7dd0189c0a2043af7ab3c285b23d33b0950bddcf20f239089b8a5b327e8317bb6aace9ea94a34c46535967acb2097a3c005b6ff languageName: node linkType: hard -"@metamask/network-controller@npm:^13.0.0": - version: 13.0.0 - resolution: "@metamask/network-controller@npm:13.0.0" +"@metamask/network-controller@npm:^14.0.0": + version: 14.0.0 + resolution: "@metamask/network-controller@npm:14.0.0" dependencies: - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/controller-utils": "npm:^5.0.0" + "@metamask/base-controller": "npm:^3.2.3" + "@metamask/controller-utils": "npm:^5.0.2" "@metamask/eth-json-rpc-infura": "npm:^8.1.1" "@metamask/eth-json-rpc-middleware": "npm:^11.0.2" "@metamask/eth-json-rpc-provider": "npm:^1.0.0" "@metamask/eth-query": "npm:^3.0.1" "@metamask/swappable-obj-proxy": "npm:^2.1.0" - "@metamask/utils": "npm:^6.2.0" + "@metamask/utils": "npm:^8.1.0" async-mutex: "npm:^0.2.6" eth-block-tracker: "npm:^7.0.1" eth-rpc-errors: "npm:^4.0.2" immer: "npm:^9.0.6" json-rpc-engine: "npm:^6.1.0" uuid: "npm:^8.3.2" - checksum: 42a5fb970ece23219f4a70fb999baacce356a469062f694474b25dd9ce69ec64c88d2c32effc88b915efacfd91bc7b04f0682af6bf4127e504d43caa873b751d + checksum: bcfb6b1b0b42e3cc92e1ea1aa46d3621fbf7e40a4e05f06de9e141c4276342acb77ebd928ee2363d7e81aa74e59e7d76d124c677d3fd0192324c2362a4c0545f + languageName: node + linkType: hard + +"@metamask/network-controller@npm:^15.0.0": + version: 15.0.0 + resolution: "@metamask/network-controller@npm:15.0.0" + dependencies: + "@metamask/base-controller": "npm:^3.2.3" + "@metamask/controller-utils": "npm:^5.0.2" + "@metamask/eth-json-rpc-infura": "npm:^9.0.0" + "@metamask/eth-json-rpc-middleware": "npm:^12.0.0" + "@metamask/eth-json-rpc-provider": "npm:^2.1.0" + "@metamask/eth-query": "npm:^3.0.1" + "@metamask/json-rpc-engine": "npm:^7.1.1" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/swappable-obj-proxy": "npm:^2.1.0" + "@metamask/utils": "npm:^8.1.0" + async-mutex: "npm:^0.2.6" + eth-block-tracker: "npm:^8.0.0" + immer: "npm:^9.0.6" + uuid: "npm:^8.3.2" + checksum: 8bd51c09b9aee747adbb6d117acfd6035b08c2c72b642bc02b383a96ff2563064de38357970faab44bd80cf804afcf2798dd9d15028789353dbc5ad5c2f84c92 languageName: node linkType: hard @@ -4632,7 +4704,7 @@ __metadata: languageName: node linkType: hard -"@metamask/permission-controller@npm:^4.0.0, @metamask/permission-controller@npm:^4.1.2": +"@metamask/permission-controller@npm:^4.1.2": version: 4.1.2 resolution: "@metamask/permission-controller@npm:4.1.2" dependencies: @@ -4652,6 +4724,26 @@ __metadata: languageName: node linkType: hard +"@metamask/permission-controller@npm:^5.0.0": + version: 5.0.0 + resolution: "@metamask/permission-controller@npm:5.0.0" + dependencies: + "@metamask/approval-controller": "npm:^4.0.0" + "@metamask/base-controller": "npm:^3.2.3" + "@metamask/controller-utils": "npm:^5.0.2" + "@metamask/json-rpc-engine": "npm:^7.1.1" + "@metamask/rpc-errors": "npm:^6.0.0" + "@metamask/utils": "npm:^8.1.0" + "@types/deep-freeze-strict": "npm:^1.1.0" + deep-freeze-strict: "npm:^1.1.1" + immer: "npm:^9.0.6" + nanoid: "npm:^3.1.31" + peerDependencies: + "@metamask/approval-controller": ^4.0.0 + checksum: c15dff37f2263e1b84054172a5f68239f14da36cd83b01a6c71a9a4d46e4473cd3b02d86e5a8f88e9979976cbc362deaa7a41dca5a19d4c5ee83431fb61e5f3c + languageName: node + linkType: hard + "@metamask/phishing-controller@npm:^6.0.0": version: 6.0.0 resolution: "@metamask/phishing-controller@npm:6.0.0" @@ -4682,6 +4774,23 @@ __metadata: languageName: node linkType: hard +"@metamask/polling-controller@npm:^0.2.0": + version: 0.2.0 + resolution: "@metamask/polling-controller@npm:0.2.0" + dependencies: + "@metamask/base-controller": "npm:^3.2.3" + "@metamask/controller-utils": "npm:^5.0.2" + "@metamask/network-controller": "npm:^15.0.0" + "@metamask/utils": "npm:^8.1.0" + "@types/uuid": "npm:^8.3.0" + fast-json-stable-stringify: "npm:^2.1.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/network-controller": ^15.0.0 + checksum: 978bd41b57a0664fa25af7e3d0a6e580a48c15324e24d9dc2ee9b4dfda19feb1504c9dc327aed654bec999c8a8647826ebbe3ec69e4a7825133cbc3f86ffcb39 + languageName: node + linkType: hard + "@metamask/post-message-stream@npm:^6.0.0, @metamask/post-message-stream@npm:^6.2.0": version: 6.2.0 resolution: "@metamask/post-message-stream@npm:6.2.0" @@ -4715,7 +4824,7 @@ __metadata: languageName: node linkType: hard -"@metamask/preferences-controller@npm:^4.4.1, @metamask/preferences-controller@npm:^4.4.3": +"@metamask/preferences-controller@npm:^4.4.3": version: 4.4.3 resolution: "@metamask/preferences-controller@npm:4.4.3" dependencies: @@ -4725,25 +4834,6 @@ __metadata: languageName: node linkType: hard -"@metamask/providers@npm:^11.1.1": - version: 11.1.2 - resolution: "@metamask/providers@npm:11.1.2" - dependencies: - "@metamask/object-multiplex": "npm:^1.1.0" - "@metamask/safe-event-emitter": "npm:^3.0.0" - detect-browser: "npm:^5.2.0" - eth-rpc-errors: "npm:^4.0.2" - extension-port-stream: "npm:^2.1.1" - fast-deep-equal: "npm:^3.1.3" - is-stream: "npm:^2.0.0" - json-rpc-engine: "npm:^6.1.0" - json-rpc-middleware-stream: "npm:^4.2.1" - pump: "npm:^3.0.0" - webextension-polyfill: "npm:^0.10.0" - checksum: 702399f2e21f3ce952c8a09d238ac777f33ef7fdc653793df1a833634eead27194602b9ef5ab44a4d519116b10815dc615ea7c304743eeeddae498d0973aad38 - languageName: node - linkType: hard - "@metamask/providers@npm:^13.0.0, @metamask/providers@npm:^13.1.0": version: 13.1.0 resolution: "@metamask/providers@npm:13.1.0" @@ -4774,23 +4864,13 @@ __metadata: languageName: node linkType: hard -"@metamask/rpc-errors@npm:^5.1.1": - version: 5.1.1 - resolution: "@metamask/rpc-errors@npm:5.1.1" - dependencies: - "@metamask/utils": "npm:^5.0.0" - fast-safe-stringify: "npm:^2.0.6" - checksum: 8167b9cbb2167317d58f9ac492bd639249a4855287f18614e230c83643a76ac8eb80d5d732c5f0fb6b9c33a330ba1dcda21fddec94607d61dfbf3181661ec09d - languageName: node - linkType: hard - -"@metamask/rpc-errors@npm:^6.0.0": - version: 6.0.0 - resolution: "@metamask/rpc-errors@npm:6.0.0" +"@metamask/rpc-errors@npm:^6.0.0, @metamask/rpc-errors@npm:^6.1.0": + version: 6.1.0 + resolution: "@metamask/rpc-errors@npm:6.1.0" dependencies: - "@metamask/utils": "npm:^8.0.0" + "@metamask/utils": "npm:^8.1.0" fast-safe-stringify: "npm:^2.0.6" - checksum: f907a01d061fe9354fa88a1891adeb393505f7a679342fcbc46ec81385558ac1791b442c6b68f5df61765d7927b54b988f562b5dd2bfa09150d25d39298e3eaa + checksum: 04054aed47fede31c755cf4504fdd0b7d226adeeeb221affc8801236a726d29bfe400a4fd7487ca65bd790653cf9dba1a4f7b6c5932fd2c65f8e8fc56e399bf7 languageName: node linkType: hard @@ -4835,54 +4915,58 @@ __metadata: languageName: node linkType: hard -"@metamask/selected-network-controller@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/selected-network-controller@npm:1.0.0" +"@metamask/selected-network-controller@npm:^2.0.0": + version: 2.0.0 + resolution: "@metamask/selected-network-controller@npm:2.0.0" dependencies: "@metamask/base-controller": "npm:^3.2.1" - "@metamask/network-controller": "npm:^12.1.2" + "@metamask/network-controller": "npm:^13.0.0" json-rpc-engine: "npm:^6.1.0" peerDependencies: - "@metamask/network-controller": ^12.1.2 - checksum: 4a004ccf426603a111e9c5d490f4a86ce4cb0143a9ff53c215e031df3d237cef0ef4fcde6c484f3ae0c5ce1a4b8d60f1faf4b1ceaf63675587480e704c26a903 + "@metamask/network-controller": ^13.0.0 + checksum: dc4519b743f1ce9d47d4dd8d80a90131f248e55f6ebdd99e623356ec4b2d4ab6a4c0d6830083bcd96110f4d52f343358764e4c7bf9de2f65e35e03ca3a16557f languageName: node linkType: hard -"@metamask/signature-controller@npm:6.0.0": - version: 6.0.0 - resolution: "@metamask/signature-controller@npm:6.0.0" +"@metamask/signature-controller@npm:6.1.2": + version: 6.1.2 + resolution: "@metamask/signature-controller@npm:6.1.2" dependencies: - "@metamask/approval-controller": "npm:^3.5.1" - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/controller-utils": "npm:^4.3.2" - "@metamask/message-manager": "npm:^7.3.2" - "@metamask/utils": "npm:^6.2.0" + "@metamask/approval-controller": "npm:^4.0.0" + "@metamask/base-controller": "npm:^3.2.3" + "@metamask/controller-utils": "npm:^5.0.2" + "@metamask/logging-controller": "npm:^1.0.3" + "@metamask/message-manager": "npm:^7.3.5" + "@metamask/utils": "npm:^8.1.0" eth-rpc-errors: "npm:^4.0.2" ethereumjs-util: "npm:^7.0.10" immer: "npm:^9.0.6" lodash: "npm:^4.17.21" peerDependencies: - "@metamask/approval-controller": ^3.5.1 - checksum: 4d2b6e47d721905e8e0fde981a492f3bcb498893b7892a0a649389ef957cbdcbe00238144c37dad189c5da4121606ffe0f1c061f8bc473c073ce84e4fbe24965 + "@metamask/approval-controller": ^4.0.0 + "@metamask/logging-controller": ^1.0.3 + checksum: c90440b9926510db0b397761bb4bdd245c1f2d11719ace069abb1198a51ec56f211304f77a914a1478a71bf7311c06fe38ed7eb03ca48125d6ed7c79fe94fb23 languageName: node linkType: hard -"@metamask/signature-controller@patch:@metamask/signature-controller@npm%3A6.0.0#~/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch": - version: 6.0.0 - resolution: "@metamask/signature-controller@patch:@metamask/signature-controller@npm%3A6.0.0#~/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch::version=6.0.0&hash=ae5433" +"@metamask/signature-controller@patch:@metamask/signature-controller@npm%3A6.1.2#~/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch": + version: 6.1.2 + resolution: "@metamask/signature-controller@patch:@metamask/signature-controller@npm%3A6.1.2#~/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch::version=6.1.2&hash=fd2be3" dependencies: - "@metamask/approval-controller": "npm:^3.5.1" - "@metamask/base-controller": "npm:^3.2.1" - "@metamask/controller-utils": "npm:^4.3.2" - "@metamask/message-manager": "npm:^7.3.2" - "@metamask/utils": "npm:^6.2.0" + "@metamask/approval-controller": "npm:^4.0.0" + "@metamask/base-controller": "npm:^3.2.3" + "@metamask/controller-utils": "npm:^5.0.2" + "@metamask/logging-controller": "npm:^1.0.3" + "@metamask/message-manager": "npm:^7.3.5" + "@metamask/utils": "npm:^8.1.0" eth-rpc-errors: "npm:^4.0.2" ethereumjs-util: "npm:^7.0.10" immer: "npm:^9.0.6" lodash: "npm:^4.17.21" peerDependencies: - "@metamask/approval-controller": ^3.5.1 - checksum: f178e2aacfe031930eb68018571e7531bb91b742a2d4beacb51d5ff8d35e609d0dd601c7cd8ae734c9f8a1f7c41d3ca4d59b3fd48261fb691223f0e6b85ba7d9 + "@metamask/approval-controller": ^4.0.0 + "@metamask/logging-controller": ^1.0.3 + checksum: 0a8ca6302925fc235e274c4fda16c85d78362b3ae63ce30f10d64c3cfc5daa5b3532e05864e840f9b0d90c1c66b1b4f973f1b9202f13f511428d8ac8d2e23054 languageName: node linkType: hard @@ -4910,91 +4994,94 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/snaps-controllers@npm:3.0.0" +"@metamask/snaps-controllers@npm:^3.0.0, @metamask/snaps-controllers@npm:^3.1.1": + version: 3.1.1 + resolution: "@metamask/snaps-controllers@npm:3.1.1" dependencies: - "@metamask/approval-controller": "npm:^3.5.0" + "@metamask/approval-controller": "npm:^4.0.0" "@metamask/base-controller": "npm:^3.2.0" + "@metamask/json-rpc-engine": "npm:^7.1.1" "@metamask/object-multiplex": "npm:^1.2.0" - "@metamask/permission-controller": "npm:^4.1.2" + "@metamask/permission-controller": "npm:^5.0.0" "@metamask/post-message-stream": "npm:^7.0.0" - "@metamask/rpc-methods": "npm:^3.0.0" - "@metamask/snaps-execution-environments": "npm:^3.0.0" - "@metamask/snaps-registry": "npm:^2.0.0" - "@metamask/snaps-utils": "npm:^3.0.0" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/snaps-registry": "npm:^2.1.0" + "@metamask/snaps-rpc-methods": "npm:^3.1.0" + "@metamask/snaps-utils": "npm:^3.1.0" "@metamask/utils": "npm:^8.1.0" "@xstate/fsm": "npm:^2.0.0" concat-stream: "npm:^2.0.0" - eth-rpc-errors: "npm:^4.0.3" + get-npm-tarball-url: "npm:^2.0.3" gunzip-maybe: "npm:^1.4.2" immer: "npm:^9.0.6" - json-rpc-engine: "npm:^6.1.0" - json-rpc-middleware-stream: "npm:^4.2.0" + json-rpc-middleware-stream: "npm:^5.0.0" nanoid: "npm:^3.1.31" readable-web-to-node-stream: "npm:^3.0.2" - tar-stream: "npm:^2.2.0" - checksum: 80a724a41dd17b4df4c8fb75eb987f03f2c787837707f3341fdafd78b678112198fc14c4e01397dca91a1c2c479cc4dea1cb4a7d0da53aaca628fd64530bdc22 + tar-stream: "npm:^3.1.6" + peerDependencies: + "@metamask/snaps-execution-environments": ^3.1.0 + peerDependenciesMeta: + "@metamask/snaps-execution-environments": + optional: true + checksum: 34a116e22aa89540d25d7dc85340a8827046d2c68f853ed84fcdcd97ff0f716aff4e2a57916dad01bb431877ea46a0dd619ff3db8b29a804f2306aeadf2f9aba languageName: node linkType: hard -"@metamask/snaps-execution-environments@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/snaps-execution-environments@npm:3.0.0" +"@metamask/snaps-registry@npm:^2.1.0": + version: 2.1.0 + resolution: "@metamask/snaps-registry@npm:2.1.0" dependencies: - "@metamask/object-multiplex": "npm:^1.2.0" - "@metamask/post-message-stream": "npm:^7.0.0" - "@metamask/providers": "npm:^11.1.1" - "@metamask/rpc-methods": "npm:^3.0.0" - "@metamask/snaps-utils": "npm:^3.0.0" "@metamask/utils": "npm:^8.1.0" - eth-rpc-errors: "npm:^4.0.3" - json-rpc-engine: "npm:^6.1.0" - nanoid: "npm:^3.1.31" + "@noble/secp256k1": "npm:^1.7.1" superstruct: "npm:^1.0.3" - checksum: 151c4b9da478ba48b48301bb50fe553efa981b215885bd7d59365b266c0e333a95142b4abb7084d9aae7f085bfbbe2bca5edfb3b546e9615fee33b7edb0487a2 + checksum: aec0613491acd06a2ca7e90025bfdaa9b1d61cc86d4342afdc213451f6e238d7994092c3e2a1900f637b37f3bafd1982937ae5b86753274f92025577c0c58ce3 languageName: node linkType: hard -"@metamask/snaps-registry@npm:^2.0.0": - version: 2.0.0 - resolution: "@metamask/snaps-registry@npm:2.0.0" +"@metamask/snaps-rpc-methods@npm:^3.1.0": + version: 3.1.0 + resolution: "@metamask/snaps-rpc-methods@npm:3.1.0" dependencies: + "@metamask/key-tree": "npm:^9.0.0" + "@metamask/permission-controller": "npm:^5.0.0" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/snaps-ui": "npm:^3.0.1" + "@metamask/snaps-utils": "npm:^3.1.0" "@metamask/utils": "npm:^8.1.0" - "@noble/secp256k1": "npm:^1.7.1" + "@noble/hashes": "npm:^1.3.1" superstruct: "npm:^1.0.3" - checksum: 99f8777708b6db3b2c12b66bd3d85f44c9e20ec78f04bd814a50c414fdb936a073d590926a494623691dd552592299eb1c3e8bb66382da46ec87c55694f46534 + checksum: 3be7042cef4a3392f2be075f8763ae3c4b46151dba43c1cc12d22a68fdd56694f9dea92abaeb044abe077c3fc513374e2e620a9c398b99afa9403a48a31856d2 languageName: node linkType: hard -"@metamask/snaps-ui@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/snaps-ui@npm:3.0.0" +"@metamask/snaps-ui@npm:^3.0.0, @metamask/snaps-ui@npm:^3.0.1": + version: 3.0.1 + resolution: "@metamask/snaps-ui@npm:3.0.1" dependencies: "@metamask/utils": "npm:^8.1.0" is-svg: "npm:^4.4.0" superstruct: "npm:^1.0.3" - checksum: 4d5364bbd3ae80389ac03a570b51a40ed9f93b18ad539c3609c7c2b6753ce0b7c7a64b98ad99fc5d1e623fbd88a4c3993811a89683a5f7769951e0f5d479de81 + checksum: c18d2d5b97c15a4af408879a9b29d62c9801f6f96815b26cd082905e6133413878b552b68610aa33cab9f530c3b0c5e0172d81c0e3f2270c5c9f9286d6014461 languageName: node linkType: hard -"@metamask/snaps-utils@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/snaps-utils@npm:3.0.0" +"@metamask/snaps-utils@npm:^3.0.0, @metamask/snaps-utils@npm:^3.1.0": + version: 3.1.0 + resolution: "@metamask/snaps-utils@npm:3.1.0" dependencies: - "@babel/core": "npm:^7.20.12" - "@babel/types": "npm:^7.18.7" + "@babel/core": "npm:^7.23.2" + "@babel/types": "npm:^7.23.0" "@metamask/base-controller": "npm:^3.2.0" "@metamask/key-tree": "npm:^9.0.0" - "@metamask/permission-controller": "npm:^4.1.2" - "@metamask/snaps-registry": "npm:^2.0.0" - "@metamask/snaps-ui": "npm:^3.0.0" + "@metamask/permission-controller": "npm:^5.0.0" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/snaps-registry": "npm:^2.1.0" + "@metamask/snaps-ui": "npm:^3.0.1" "@metamask/utils": "npm:^8.1.0" "@noble/hashes": "npm:^1.3.1" "@scure/base": "npm:^1.1.1" chalk: "npm:^4.1.2" cron-parser: "npm:^4.5.0" - eth-rpc-errors: "npm:^4.0.3" fast-deep-equal: "npm:^3.1.3" fast-json-stable-stringify: "npm:^2.1.0" is-svg: "npm:^4.4.0" @@ -5003,7 +5090,7 @@ __metadata: ses: "npm:^0.18.8" superstruct: "npm:^1.0.3" validate-npm-package-name: "npm:^5.0.0" - checksum: 576946daf63177a3c7d780847aff56ea87f2ef0f441ea2114976562838bf4a9ffb6e374e5a3587268df936b5f524300f7a5ef08ad3ff8ebee2fd228e999a827e + checksum: 1308b1475b457008075f8019706904d06b8c4ee6fc72cc05b0d1e94bdce2addb88611af31fa426144449696bfae3c1b3acacf6cfcb653f78cc8041ac093c1783 languageName: node linkType: hard @@ -5014,10 +5101,10 @@ __metadata: languageName: node linkType: hard -"@metamask/test-dapp@npm:^7.1.0": - version: 7.1.0 - resolution: "@metamask/test-dapp@npm:7.1.0" - checksum: 94a01886a4254dafdf719086da6ded59fb1fef701d32d9c32673251fd0d664b133dd0c73664184f2b9c566caedcc9550d845cdfe7976cef993b6549c1d3b8881 +"@metamask/test-dapp@npm:^7.2.0": + version: 7.2.0 + resolution: "@metamask/test-dapp@npm:7.2.0" + checksum: 415ea793d2d75f51679d2944bd14412fa98cdb11022b127eb2bbb09b9f39204ad7ee61c7c63a29a8e6ce5605ead7471432c114c73bb1aaabe962f55a18cd9678 languageName: node linkType: hard @@ -5395,43 +5482,6 @@ __metadata: languageName: node linkType: hard -"@oozcitak/dom@npm:1.15.10": - version: 1.15.10 - resolution: "@oozcitak/dom@npm:1.15.10" - dependencies: - "@oozcitak/infra": "npm:1.0.8" - "@oozcitak/url": "npm:1.0.4" - "@oozcitak/util": "npm:8.3.8" - checksum: cc1ecbde9a1b72b34c4500db16188b831b4d1e6acb16659b31716d26a210c18e9eaabcf990dea56cbd0c308023e7a014c888f7529bf69697af83bf72438aa10d - languageName: node - linkType: hard - -"@oozcitak/infra@npm:1.0.8": - version: 1.0.8 - resolution: "@oozcitak/infra@npm:1.0.8" - dependencies: - "@oozcitak/util": "npm:8.3.8" - checksum: 568833490051825435fd99865d58b258ee7aca1a5d640f5002ec792224a3f2e4808e8ca7cdefa7469dbde6e9ab0e98d2eae388fdbb84ffd871a3e8fff34a564c - languageName: node - linkType: hard - -"@oozcitak/url@npm:1.0.4": - version: 1.0.4 - resolution: "@oozcitak/url@npm:1.0.4" - dependencies: - "@oozcitak/infra": "npm:1.0.8" - "@oozcitak/util": "npm:8.3.8" - checksum: f81890a2a21429295ee99e1e5b386ce07621a99d3f10551ea2456eeb338065b6820f80c771921ec4d5e382358f478e1c67d4194892d04f98f84c0449bd6c3c74 - languageName: node - linkType: hard - -"@oozcitak/util@npm:8.3.8": - version: 8.3.8 - resolution: "@oozcitak/util@npm:8.3.8" - checksum: 844c0e55d13803aff8e0be368c32e5e5b4a96d93b5a359d7899bb657ca14410a67a838070b1d2a554fe743f83f75d0906d836891c0c5d6585b8a5909f96a9e93 - languageName: node - linkType: hard - "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -6541,6 +6591,29 @@ __metadata: languageName: node linkType: hard +"@storybook/addon-designs@npm:^7.0.5": + version: 7.0.5 + resolution: "@storybook/addon-designs@npm:7.0.5" + dependencies: + "@figspec/react": "npm:^1.0.0" + peerDependencies: + "@storybook/addon-docs": ^7.0.0 + "@storybook/addons": ^7.0.0 + "@storybook/components": ^7.0.0 + "@storybook/manager-api": ^7.0.0 + "@storybook/preview-api": ^7.0.0 + "@storybook/theming": ^7.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + checksum: bb093a697698dd6d7c3ffee4a776a69c472b31bf9b18d83ed1b38fea19978e39694963a7546c4e1edd9af1e52fac3b01c38d64840443c15dbb419df7f3945779 + languageName: node + linkType: hard + "@storybook/addon-docs@npm:7.4.6": version: 7.4.6 resolution: "@storybook/addon-docs@npm:7.4.6" @@ -8698,6 +8771,13 @@ __metadata: languageName: node linkType: hard +"@types/mocha@npm:^10.0.3": + version: 10.0.3 + resolution: "@types/mocha@npm:10.0.3" + checksum: 31d44b6a45e20dba3f349a62b0f6fc23b054155e7b7e558e1be76b1bc5e91a6902062fdd7b9167beeed7b0083d351bd2b94352a677bf1a03b4d42c767497213f + languageName: node + linkType: hard + "@types/ms@npm:*": version: 0.7.31 resolution: "@types/ms@npm:0.7.31" @@ -8923,6 +9003,15 @@ __metadata: languageName: node linkType: hard +"@types/selenium-webdriver@npm:^4.1.19": + version: 4.1.19 + resolution: "@types/selenium-webdriver@npm:4.1.19" + dependencies: + "@types/ws": "npm:*" + checksum: 95e47c7028d1f78bef3c59ea66ba5922d6bf003f8c078366b5f74e94952c94fd3636b9e08ce160f1e0882a20087ef8da0edc4a0d0c7caaeb01c05494682cf6e0 + languageName: node + linkType: hard + "@types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4": version: 7.5.0 resolution: "@types/semver@npm:7.5.0" @@ -8989,6 +9078,13 @@ __metadata: languageName: node linkType: hard +"@types/trusted-types@npm:^2.0.2": + version: 2.0.5 + resolution: "@types/trusted-types@npm:2.0.5" + checksum: e138a70a702e31b49ac73cc33852d892367224be6e096c445194d76327cb46f54f971ae311e34371f649a2d5ac9204afee345bb22f32cfc515eb21c3f12f66b7 + languageName: node + linkType: hard + "@types/undertaker-registry@npm:*": version: 1.0.1 resolution: "@types/undertaker-registry@npm:1.0.1" @@ -9390,13 +9486,6 @@ __metadata: languageName: node linkType: hard -"@ungap/promise-all-settled@npm:1.1.2": - version: 1.1.2 - resolution: "@ungap/promise-all-settled@npm:1.1.2" - checksum: ee8fe811becd830f5e276ec63469ec66c22503eb140064580e712c9fccadfd54157c462188640ba6765d5c21f829e7120eb37afb5ead512684b9a1ab86d2db66 - languageName: node - linkType: hard - "@vue/compiler-core@npm:3.1.4": version: 3.1.4 resolution: "@vue/compiler-core@npm:3.1.4" @@ -10925,6 +11014,13 @@ __metadata: languageName: node linkType: hard +"b4a@npm:^1.6.4": + version: 1.6.4 + resolution: "b4a@npm:1.6.4" + checksum: 223158e626a7e024a8d945ce85e7d8871c0689c0375c5b0df5880eedcb5683a12eeb3557591ff5ccd515f3ee8d1664e370c6ff7917fa257405571b81b946604a + languageName: node + linkType: hard + "babel-core@npm:^7.0.0-bridge.0": version: 7.0.0-bridge.0 resolution: "babel-core@npm:7.0.0-bridge.0" @@ -12594,6 +12690,13 @@ __metadata: languageName: node linkType: hard +"charenc@npm:0.0.2": + version: 0.0.2 + resolution: "charenc@npm:0.0.2" + checksum: 81dcadbe57e861d527faf6dd3855dc857395a1c4d6781f4847288ab23cffb7b3ee80d57c15bba7252ffe3e5e8019db767757ee7975663ad2ca0939bb8fcaf2e5 + languageName: node + linkType: hard + "cheerio-select@npm:^2.1.0": version: 2.1.0 resolution: "cheerio-select@npm:2.1.0" @@ -12688,9 +12791,9 @@ __metadata: languageName: node linkType: hard -"chromedriver@npm:^116.0.0": - version: 116.0.0 - resolution: "chromedriver@npm:116.0.0" +"chromedriver@npm:^118.0.1": + version: 118.0.1 + resolution: "chromedriver@npm:118.0.1" dependencies: "@testim/chrome-version": "npm:^1.1.3" axios: "npm:^1.4.0" @@ -12701,7 +12804,7 @@ __metadata: tcp-port-used: "npm:^1.0.1" bin: chromedriver: bin/chromedriver - checksum: 0f3254efed54e4746c28606a61485e9f91d143f6c92158c904b729871bca14ff50738323f18eac9b30fbcef48c1510a9d7700ee38a53a54bbd24c06330b3bb2d + checksum: 019f42716e3f00d655afe02d57ca408504c384c73d8cd1cc10b2fa5abc66d72e9a236a88592c55bcb497f935ce2826ff9d5236b791566178b997375eeefc7a03 languageName: node linkType: hard @@ -13227,13 +13330,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:~9.4.0": - version: 9.4.1 - resolution: "commander@npm:9.4.1" - checksum: 9d0d1d7e816545cf5ebf25e303533e45af2f941731063587d04917ac9fb6c81f59690aa8bda60d9b88d8aac018fdef6735ed953e72fdab08bb8b778bd4e0ef95 - languageName: node - linkType: hard - "comment-parser@npm:1.3.1": version: 1.3.1 resolution: "comment-parser@npm:1.3.1" @@ -13776,6 +13872,13 @@ __metadata: languageName: node linkType: hard +"crypt@npm:0.0.2": + version: 0.0.2 + resolution: "crypt@npm:0.0.2" + checksum: 2c72768de3d28278c7c9ffd81a298b26f87ecdfe94415084f339e6632f089b43fe039f2c93f612bcb5ffe447238373d93b2e8c90894cba6cfb0ac7a74616f8b9 + languageName: node + linkType: hard + "crypto-addr-codec@npm:^0.1.7": version: 0.1.7 resolution: "crypto-addr-codec@npm:0.1.7" @@ -14147,7 +14250,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -14168,18 +14271,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:4.3.3": - version: 4.3.3 - resolution: "debug@npm:4.3.3" - dependencies: - ms: "npm:2.1.2" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 723a9570dcd15d146ea4992f0dca12467d1b00f534abb42473df166d36826fcae8bab045aef59ac2f407b47a23266110bc0e646df8ac82f7800c11384f82050e - languageName: node - linkType: hard - "debug@npm:~3.1.0": version: 3.1.0 resolution: "debug@npm:3.1.0" @@ -16485,6 +16576,19 @@ __metadata: languageName: node linkType: hard +"eth-block-tracker@npm:^8.0.0": + version: 8.1.0 + resolution: "eth-block-tracker@npm:8.1.0" + dependencies: + "@metamask/eth-json-rpc-provider": "npm:^2.1.0" + "@metamask/safe-event-emitter": "npm:^3.0.0" + "@metamask/utils": "npm:^8.1.0" + json-rpc-random-id: "npm:^1.0.1" + pify: "npm:^5.0.0" + checksum: 94d81a0f5ed62bb7fd70b99a3a6172f416a574dc9fcaa96c9bdedb4d98c52b257ee505957d4a5b248ce73220b96083420acdd2dd702a0330d016018a59bd0b2e + languageName: node + linkType: hard + "eth-eip712-util-browser@npm:^0.0.3": version: 0.0.3 resolution: "eth-eip712-util-browser@npm:0.0.3" @@ -17404,7 +17508,14 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.4, fast-glob@npm:^3.2.9, fast-glob@npm:~3.2.11": +"fast-fifo@npm:^1.1.0, fast-fifo@npm:^1.2.0": + version: 1.3.2 + resolution: "fast-fifo@npm:1.3.2" + checksum: 6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.4, fast-glob@npm:^3.2.9": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" dependencies: @@ -19161,13 +19272,6 @@ __metadata: languageName: node linkType: hard -"growl@npm:1.10.5": - version: 1.10.5 - resolution: "growl@npm:1.10.5" - checksum: 1391a9add951964de566adc0aee8b0e2b2321e768c1fdccb7a8e156d6a6cd7ea72782883ba8c2c307baf524e3059519423b72e585eba5e7a5f6e83a1e2359b0d - languageName: node - linkType: hard - "gud@npm:^1.0.0": version: 1.0.0 resolution: "gud@npm:1.0.0" @@ -20508,7 +20612,7 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^1.0.2, is-buffer@npm:^1.1.0, is-buffer@npm:^1.1.5": +"is-buffer@npm:^1.0.2, is-buffer@npm:^1.1.0, is-buffer@npm:^1.1.5, is-buffer@npm:~1.1.6": version: 1.1.6 resolution: "is-buffer@npm:1.1.6" checksum: f63da109e74bbe8947036ed529d43e4ae0c5fcd0909921dce4917ad3ea212c6a87c29f525ba1d17c0858c18331cf1046d4fc69ef59ed26896b25c8288a627133 @@ -22486,18 +22590,6 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:3.14.0": - version: 3.14.0 - resolution: "js-yaml@npm:3.14.0" - dependencies: - argparse: "npm:^1.0.7" - esprima: "npm:^4.0.0" - bin: - js-yaml: bin/js-yaml.js - checksum: 88c96664ed27db929fa046f4e6d1f1cbcab4372379c77af68e8d56ada9ff59566528c053f83260c4e23bd348fc430814ed3637e4b809e1d32b795c74b5d15638 - languageName: node - linkType: hard - "js-yaml@npm:4.1.0, js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" @@ -22759,7 +22851,7 @@ __metadata: languageName: node linkType: hard -"json-rpc-middleware-stream@npm:^4.2.0, json-rpc-middleware-stream@npm:^4.2.1": +"json-rpc-middleware-stream@npm:^4.2.1": version: 4.2.1 resolution: "json-rpc-middleware-stream@npm:4.2.1" dependencies: @@ -22769,6 +22861,18 @@ __metadata: languageName: node linkType: hard +"json-rpc-middleware-stream@npm:^5.0.0": + version: 5.0.1 + resolution: "json-rpc-middleware-stream@npm:5.0.1" + dependencies: + "@metamask/json-rpc-engine": "npm:^7.1.1" + "@metamask/safe-event-emitter": "npm:^3.0.0" + "@metamask/utils": "npm:^8.1.0" + readable-stream: "npm:^3.6.2" + checksum: b5e9b2ae21cc93586f1f4d8c6543634406575bf9cb6e909a4b5d47359b44519f37192a0262279291e5cde0876a67928d26d7e420d9e2aaf7992083e2c1f97a37 + languageName: node + linkType: hard + "json-rpc-random-id@npm:^1.0.0, json-rpc-random-id@npm:^1.0.1": version: 1.0.1 resolution: "json-rpc-random-id@npm:1.0.1" @@ -23034,20 +23138,6 @@ __metadata: languageName: node linkType: hard -"junit-report-merger@npm:^4.0.0": - version: 4.0.0 - resolution: "junit-report-merger@npm:4.0.0" - dependencies: - commander: "npm:~9.4.0" - fast-glob: "npm:~3.2.11" - xmlbuilder2: "npm:~3.0.2" - bin: - jrm: cli.js - junit-report-merger: cli.js - checksum: fa5b9ce3a77f7c5b863069fc3eeafc14e850c08c1a8bc96bcc2e86ea3440bc826a28df794612d8373c1be1d7c17e269e5fac03c211b6bb24a6324c76fc182de0 - languageName: node - linkType: hard - "just-debounce@npm:^1.0.0": version: 1.0.0 resolution: "just-debounce@npm:1.0.0" @@ -23191,10 +23281,10 @@ __metadata: languageName: node linkType: hard -"klona@npm:^2.0.4": - version: 2.0.4 - resolution: "klona@npm:2.0.4" - checksum: 7767d79f5b2ce4833321b3af6edd257d5a9f3f4c296c80862c131ce8aa0caf5933071b69452b8e52ea5924d846b42eb0e3d215e3eb093bfdfaae21e3ac8b7116 +"klona@npm:^2.0.4, klona@npm:^2.0.6": + version: 2.0.6 + resolution: "klona@npm:2.0.6" + checksum: ed7e2c9af58cb646e758e60b75dec24bf72466066290f78c515a2bae23a06fa280f11ff3210c43b94a18744954aa5358f9d46583d5e4c36da073ecc3606355c4 languageName: node linkType: hard @@ -23553,6 +23643,37 @@ __metadata: languageName: node linkType: hard +"lit-element@npm:^3.3.0": + version: 3.3.3 + resolution: "lit-element@npm:3.3.3" + dependencies: + "@lit-labs/ssr-dom-shim": "npm:^1.1.0" + "@lit/reactive-element": "npm:^1.3.0" + lit-html: "npm:^2.8.0" + checksum: 7968e7f3ce3994911f27c4c54acc956488c91d8af81677cce3d6f0c2eaea45cceb79b064077159392238d6e43d46015a950269db9914fea8913566aacb17eaa1 + languageName: node + linkType: hard + +"lit-html@npm:^2.8.0": + version: 2.8.0 + resolution: "lit-html@npm:2.8.0" + dependencies: + "@types/trusted-types": "npm:^2.0.2" + checksum: 3503e55e2927c2ff94773cf041fc4128f92291869c9192f36eacb7f95132d11f6b329e5b910ab60a4456349cd2e6d23b33d83291b24d557bcd6b904d6314ac1a + languageName: node + linkType: hard + +"lit@npm:^2.1.3": + version: 2.8.0 + resolution: "lit@npm:2.8.0" + dependencies: + "@lit/reactive-element": "npm:^1.6.0" + lit-element: "npm:^3.3.0" + lit-html: "npm:^2.8.0" + checksum: aa64c1136b855ba328d41157dba67657d480345aeec3c1dd829abeb67719d759c9ff2ade9903f9cfb4f9d012b16087034aaa5b33f1182e70c615765562e3251b + languageName: node + linkType: hard + "livereload-js@npm:^2.3.0": version: 2.4.0 resolution: "livereload-js@npm:2.4.0" @@ -24222,6 +24343,17 @@ __metadata: languageName: node linkType: hard +"md5@npm:^2.3.0": + version: 2.3.0 + resolution: "md5@npm:2.3.0" + dependencies: + charenc: "npm:0.0.2" + crypt: "npm:0.0.2" + is-buffer: "npm:~1.1.6" + checksum: 88dce9fb8df1a084c2385726dcc18c7f54e0b64c261b5def7cdfe4928c4ee1cd68695c34108b4fab7ecceb05838c938aa411c6143df9fdc0026c4ddb4e4e72fa + languageName: node + linkType: hard + "mdast-util-compact@npm:^2.0.0": version: 2.0.1 resolution: "mdast-util-compact@npm:2.0.1" @@ -24612,12 +24744,12 @@ __metadata: "@metamask/address-book-controller": "npm:^3.0.0" "@metamask/announcement-controller": "npm:^4.0.0" "@metamask/approval-controller": "npm:^3.4.0" - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A13.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-13.0.0-91c45127c5.patch" + "@metamask/assets-controllers": "npm:^16.0.0" "@metamask/auto-changelog": "npm:^2.1.0" "@metamask/base-controller": "npm:^3.2.0" "@metamask/browser-passworder": "npm:^4.1.0" "@metamask/contract-metadata": "npm:^2.3.1" - "@metamask/controller-utils": "npm:^4.2.0" + "@metamask/controller-utils": "npm:^5.0.0" "@metamask/design-tokens": "npm:^1.12.0" "@metamask/desktop": "npm:^0.3.0" "@metamask/eslint-config": "npm:^9.0.0" @@ -24644,27 +24776,27 @@ __metadata: "@metamask/message-manager": "npm:^7.3.0" "@metamask/metamask-eth-abis": "npm:^3.0.0" "@metamask/name-controller": "npm:^3.0.0" - "@metamask/network-controller": "npm:^12.2.0" + "@metamask/network-controller": "npm:^14.0.0" "@metamask/notification-controller": "npm:^3.0.0" "@metamask/obs-store": "npm:^8.1.0" - "@metamask/permission-controller": "npm:^4.0.0" + "@metamask/permission-controller": "npm:^5.0.0" "@metamask/phishing-controller": "npm:^6.0.0" "@metamask/phishing-warning": "npm:^2.1.0" "@metamask/post-message-stream": "npm:^6.2.0" "@metamask/ppom-validator": "npm:^0.8.0" "@metamask/providers": "npm:^13.1.0" "@metamask/rate-limit-controller": "npm:^3.0.0" - "@metamask/rpc-methods": "npm:^3.0.0" "@metamask/safe-event-emitter": "npm:^2.0.0" "@metamask/scure-bip39": "npm:^2.0.3" - "@metamask/selected-network-controller": "npm:^1.0.0" - "@metamask/signature-controller": "npm:^6.0.0" + "@metamask/selected-network-controller": "npm:^2.0.0" + "@metamask/signature-controller": "npm:^6.1.2" "@metamask/slip44": "npm:^3.1.0" "@metamask/smart-transactions-controller": "npm:^4.0.0" - "@metamask/snaps-controllers": "npm:^3.0.0" - "@metamask/snaps-ui": "npm:^3.0.0" - "@metamask/snaps-utils": "npm:^3.0.0" - "@metamask/test-dapp": "npm:^7.1.0" + "@metamask/snaps-controllers": "npm:^3.1.1" + "@metamask/snaps-rpc-methods": "npm:^3.1.0" + "@metamask/snaps-ui": "npm:^3.0.1" + "@metamask/snaps-utils": "npm:^3.1.0" + "@metamask/test-dapp": "npm:^7.2.0" "@metamask/utils": "npm:^5.0.0" "@ngraveio/bc-ur": "npm:^1.1.6" "@popperjs/core": "npm:^2.4.0" @@ -24677,6 +24809,7 @@ __metadata: "@sentry/utils": "npm:^7.53.0" "@storybook/addon-a11y": "npm:^7.4.6" "@storybook/addon-actions": "npm:^7.4.6" + "@storybook/addon-designs": "npm:^7.0.5" "@storybook/addon-essentials": "npm:^7.4.6" "@storybook/addon-knobs": "npm:^7.0.2" "@storybook/addon-mdx-gfm": "npm:^7.4.6" @@ -24708,6 +24841,7 @@ __metadata: "@types/gulp-sourcemaps": "npm:^0.0.35" "@types/jest": "npm:^29.1.2" "@types/madge": "npm:^5.0.0" + "@types/mocha": "npm:^10.0.3" "@types/node": "npm:^17.0.21" "@types/pify": "npm:^5.0.1" "@types/pump": "npm:^1.1.1" @@ -24717,6 +24851,7 @@ __metadata: "@types/react-router-dom": "npm:^5.3.3" "@types/remote-redux-devtools": "npm:^0.5.5" "@types/sass": "npm:^1.43.1" + "@types/selenium-webdriver": "npm:^4.1.19" "@types/sinon": "npm:^10.0.13" "@types/w3c-web-hid": "npm:^1.0.3" "@types/watchify": "npm:^3.11.1" @@ -24741,7 +24876,7 @@ __metadata: browserify: "npm:^17.0.0" chalk: "npm:^4.1.2" chokidar: "npm:^3.5.3" - chromedriver: "npm:^116.0.0" + chromedriver: "npm:^118.0.1" classnames: "npm:^2.2.6" concurrently: "npm:^7.6.0" copy-to-clipboard: "npm:^3.3.3" @@ -24824,7 +24959,6 @@ __metadata: jsdom: "npm:^16.7.0" json-rpc-engine: "npm:^6.1.0" json-rpc-middleware-stream: "npm:^4.2.1" - junit-report-merger: "npm:^4.0.0" koa: "npm:^2.7.0" labeled-stream-splicer: "npm:^2.0.2" lavamoat: "npm:^7.1.2" @@ -24837,7 +24971,8 @@ __metadata: loose-envify: "npm:^1.4.0" luxon: "npm:^3.2.1" madge: "npm:^6.1.0" - mocha: "npm:^9.2.2" + mocha: "npm:^10.2.0" + mocha-junit-reporter: "npm:^2.2.1" mockttp: "npm:^2.6.0" nanoid: "npm:^2.1.6" nock: "npm:^13.2.9" @@ -25464,16 +25599,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:4.2.1": - version: 4.2.1 - resolution: "minimatch@npm:4.2.1" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 27e49fb720116face9588c29634404edc0c6677e5448ba01b4ec6179002461cc4fabc842497a0537edc5aa87bc93e65cfb0fe6dc32b850563429a64836dd1d54 - languageName: node - linkType: hard - -"minimatch@npm:^5.0.1": +"minimatch@npm:5.0.1, minimatch@npm:^5.0.1": version: 5.0.1 resolution: "minimatch@npm:5.0.1" dependencies: @@ -25690,38 +25816,59 @@ __metadata: languageName: node linkType: hard -"mocha@npm:^9.2.2": - version: 9.2.2 - resolution: "mocha@npm:9.2.2" +"mkdirp@npm:^3.0.0": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba + languageName: node + linkType: hard + +"mocha-junit-reporter@npm:^2.2.1": + version: 2.2.1 + resolution: "mocha-junit-reporter@npm:2.2.1" + dependencies: + debug: "npm:^4.3.4" + md5: "npm:^2.3.0" + mkdirp: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + xml: "npm:^1.0.1" + peerDependencies: + mocha: ">=2.2.5" + checksum: f2e9db2daf4b5de8824ed557a7d6b534683558fadf843de6b4f96385c6edad239ee6194e81f5983809d7223b45f57c7af0337442552cab47103e9e92d5ee4cb3 + languageName: node + linkType: hard + +"mocha@npm:^10.2.0": + version: 10.2.0 + resolution: "mocha@npm:10.2.0" dependencies: - "@ungap/promise-all-settled": "npm:1.1.2" ansi-colors: "npm:4.1.1" browser-stdout: "npm:1.3.1" chokidar: "npm:3.5.3" - debug: "npm:4.3.3" + debug: "npm:4.3.4" diff: "npm:5.0.0" escape-string-regexp: "npm:4.0.0" find-up: "npm:5.0.0" glob: "npm:7.2.0" - growl: "npm:1.10.5" he: "npm:1.2.0" js-yaml: "npm:4.1.0" log-symbols: "npm:4.1.0" - minimatch: "npm:4.2.1" + minimatch: "npm:5.0.1" ms: "npm:2.1.3" - nanoid: "npm:3.3.1" + nanoid: "npm:3.3.3" serialize-javascript: "npm:6.0.0" strip-json-comments: "npm:3.1.1" supports-color: "npm:8.1.1" - which: "npm:2.0.2" - workerpool: "npm:6.2.0" + workerpool: "npm:6.2.1" yargs: "npm:16.2.0" yargs-parser: "npm:20.2.4" yargs-unparser: "npm:2.0.0" bin: _mocha: bin/_mocha - mocha: bin/mocha - checksum: 8ee58bff8694ad4013fc0fbb5670c5ec6d8404c601df2d4ae798fad01dd03b5f9395347cf59167006b315a14813a6f839290d60dcbdee8ef4246afe43609d2dc + mocha: bin/mocha.js + checksum: f7362898ae65e8fe716cfe62fd014b432d100c9611aaf5abe85ed14efcbfdd82f3bdf32c44bccf00c9059a264c7e8d93a69dd5b830652109052a92beffb7ea35 languageName: node linkType: hard @@ -26072,12 +26219,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:3.3.1": - version: 3.3.1 - resolution: "nanoid@npm:3.3.1" +"nanoid@npm:3.3.3": + version: 3.3.3 + resolution: "nanoid@npm:3.3.3" bin: nanoid: bin/nanoid.cjs - checksum: 306f2cb9e4dcfb94738b09de9dc63839a37db33626f66b24dbcc8f66d4b91784645794a7c4f250d629e4d66f5385164c6748c58ac5b7c95217e9e048590efbe4 + checksum: c703ed58a234b68245a8a4826dd25c1453a9017d34fa28bc58e7aa8247de87d854582fa2209d7aee04084cff9ce150be8fd30300abe567dc615d4e8e735f2d99 languageName: node linkType: hard @@ -28878,6 +29025,13 @@ __metadata: languageName: node linkType: hard +"queue-tick@npm:^1.0.1": + version: 1.0.1 + resolution: "queue-tick@npm:1.0.1" + checksum: f447926c513b64a857906f017a3b350f7d11277e3c8d2a21a42b7998fa1a613d7a829091e12d142bb668905c8f68d8103416c7197856efb0c72fa835b8e254b5 + languageName: node + linkType: hard + "queue@npm:6.0.2": version: 6.0.2 resolution: "queue@npm:6.0.2" @@ -29655,7 +29809,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:2 || 3, readable-stream@npm:3, readable-stream@npm:3.6.2, readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:2 || 3, readable-stream@npm:3, readable-stream@npm:3.6.2, readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -30844,7 +30998,7 @@ __metadata: languageName: node linkType: hard -"safe-stable-stringify@npm:^2.3.1, safe-stable-stringify@npm:^2.3.2": +"safe-stable-stringify@npm:^2.3.1, safe-stable-stringify@npm:^2.3.2, safe-stable-stringify@npm:^2.4.3": version: 2.4.3 resolution: "safe-stable-stringify@npm:2.4.3" checksum: a6c192bbefe47770a11072b51b500ed29be7b1c15095371c1ee1dc13e45ce48ee3c80330214c56764d006c485b88bd0b24940d868948170dddc16eed312582d8 @@ -32169,6 +32323,16 @@ __metadata: languageName: node linkType: hard +"streamx@npm:^2.15.0": + version: 2.15.1 + resolution: "streamx@npm:2.15.1" + dependencies: + fast-fifo: "npm:^1.1.0" + queue-tick: "npm:^1.0.1" + checksum: 5c5143d832b4d4c2cba09d3e77dcc099f62bfc44bffac38e7b196cdd7f17dcd46bc2012c614fad934c0d706712c2e9455e485435810504cf748906b2f1746837 + languageName: node + linkType: hard + "string-hash@npm:^1.1.1": version: 1.1.3 resolution: "string-hash@npm:1.1.3" @@ -32903,7 +33067,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.1.4, tar-stream@npm:^2.2.0": +"tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -32916,6 +33080,17 @@ __metadata: languageName: node linkType: hard +"tar-stream@npm:^3.1.6": + version: 3.1.6 + resolution: "tar-stream@npm:3.1.6" + dependencies: + b4a: "npm:^1.6.4" + fast-fifo: "npm:^1.2.0" + streamx: "npm:^2.15.0" + checksum: 2c32e0d57de778ae415357bfb126a512a384e9bfb8e234920455ad65282181a3765515bbd80392ab8e7e630158376ec7de46b18ab86a33d256a7dcc43b0648b7 + languageName: node + linkType: hard + "tar@npm:^4": version: 4.4.19 resolution: "tar@npm:4.4.19" @@ -35354,25 +35529,25 @@ __metadata: languageName: node linkType: hard -"which@npm:2.0.2, which@npm:^2.0.1, which@npm:^2.0.2": - version: 2.0.2 - resolution: "which@npm:2.0.2" +"which@npm:^1.2.12, which@npm:^1.2.14, which@npm:^1.2.9, which@npm:^1.3.1": + version: 1.3.1 + resolution: "which@npm:1.3.1" dependencies: isexe: "npm:^2.0.0" bin: - node-which: ./bin/node-which - checksum: 4782f8a1d6b8fc12c65e968fea49f59752bf6302dc43036c3bf87da718a80710f61a062516e9764c70008b487929a73546125570acea95c5b5dcc8ac3052c70f + which: ./bin/which + checksum: 549dcf1752f3ee7fbb64f5af2eead4b9a2f482108b7de3e85c781d6c26d8cf6a52d37cfbe0642a155fa6470483fe892661a859c03157f24c669cf115f3bbab5e languageName: node linkType: hard -"which@npm:^1.2.12, which@npm:^1.2.14, which@npm:^1.2.9, which@npm:^1.3.1": - version: 1.3.1 - resolution: "which@npm:1.3.1" +"which@npm:^2.0.1, which@npm:^2.0.2": + version: 2.0.2 + resolution: "which@npm:2.0.2" dependencies: isexe: "npm:^2.0.0" bin: - which: ./bin/which - checksum: 549dcf1752f3ee7fbb64f5af2eead4b9a2f482108b7de3e85c781d6c26d8cf6a52d37cfbe0642a155fa6470483fe892661a859c03157f24c669cf115f3bbab5e + node-which: ./bin/node-which + checksum: 4782f8a1d6b8fc12c65e968fea49f59752bf6302dc43036c3bf87da718a80710f61a062516e9764c70008b487929a73546125570acea95c5b5dcc8ac3052c70f languageName: node linkType: hard @@ -35437,10 +35612,10 @@ __metadata: languageName: node linkType: hard -"workerpool@npm:6.2.0": - version: 6.2.0 - resolution: "workerpool@npm:6.2.0" - checksum: c7dce6eae02098d70fe9924503bd95688564a1316cbb96fe55600f7ede0e66f1f2fea4d18aaec71fcee32373d17eda0bf87ac4dac8e5823e90ca1524aac90bdc +"workerpool@npm:6.2.1": + version: 6.2.1 + resolution: "workerpool@npm:6.2.1" + checksum: 3e637f76320cab92eaeffa4fbefb351db02e20aa29245d8ee05fa7c088780ef7b4446bfafff2668a22fd94b7d9d97c7020117036ac77a76370ecea278b9a9b91 languageName: node linkType: hard @@ -35635,19 +35810,6 @@ __metadata: languageName: node linkType: hard -"xmlbuilder2@npm:~3.0.2": - version: 3.0.2 - resolution: "xmlbuilder2@npm:3.0.2" - dependencies: - "@oozcitak/dom": "npm:1.15.10" - "@oozcitak/infra": "npm:1.0.8" - "@oozcitak/util": "npm:8.3.8" - "@types/node": "npm:*" - js-yaml: "npm:3.14.0" - checksum: 9b0c6317c797748bb1cdd17d645bb00d15b0c12d735fc6e19479f5677bc973460c83562fceae4039d427b4f005bcabea285c42386d5da20f14439be234b9ea13 - languageName: node - linkType: hard - "xmlchars@npm:^2.2.0": version: 2.2.0 resolution: "xmlchars@npm:2.2.0"