diff --git a/.circleci/config.yml b/.circleci/config.yml index e26983022..496486a16 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,11 +30,44 @@ rc_branch_only: &rc_branch_only only: - /^Version-v(\d+)[.](\d+)[.](\d+)/ -rc_or_master_branch_only: &rc_or_master_branch_only - filters: - branches: - only: - - /^Version-v(\d+)[.](\d+)[.](\d+)|master/ +aliases: + # Shallow Git Clone + - &shallow-git-clone + name: Shallow Git Clone + command: | + #!/bin/bash + set -e + set -u + set -o pipefail + + # This Shallow Git Clone code is adapted from what the standard CircleCI `checkout` command does for the case of an external PR (link to example below): + # https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/49817/workflows/dc195ea6-ac06-4de1-9edf-4c949427b5fb/jobs/1430976/parallel-runs/0/steps/0-101 + ### git clone --no-checkout "$CIRCLE_REPOSITORY_URL" . + ### git fetch --force origin +refs/pull/18748/head:refs/remotes/origin/pull/18748 + ### git checkout --force -B "$CIRCLE_BRANCH" "$CIRCLE_SHA1" + ### git --no-pager log --no-color -n 1 --format='HEAD is now at %h %s' + + # Set up SSH access + # This SSH key is the current github.com SSH key as of June 2023, but it will need to be changed whenever github changes their key (probably every few years) + GITHUB_SSH_KEY="AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl" + mkdir -p ~/.ssh + echo github.com ssh-ed25519 $GITHUB_SSH_KEY >> ~/.ssh/known_hosts + + # Take a different clone path depending on if it's a tag, a PR from an external repo, or the normal case + if [ -n "${CIRCLE_TAG-}" ]; then + # tag + git clone --depth 1 --no-checkout "$CIRCLE_REPOSITORY_URL" . + git fetch --depth 1 --force origin "+refs/tags/${CIRCLE_TAG}:refs/tags/${CIRCLE_TAG}" + git checkout --force -q "$CIRCLE_TAG" "$CIRCLE_SHA1" + elif [[ "$CIRCLE_BRANCH" =~ ^pull\/* ]]; then + # pull request + git clone --depth 1 --no-checkout "$CIRCLE_REPOSITORY_URL" . + git fetch --depth 1 --force origin "${CIRCLE_BRANCH}/head:remotes/origin/${CIRCLE_BRANCH}" + git checkout --force -B "$CIRCLE_BRANCH" "$CIRCLE_SHA1" + else + # normal case + git clone --depth 1 "$CIRCLE_REPOSITORY_URL" --branch "$CIRCLE_BRANCH" . + fi workflows: test_and_release: @@ -57,15 +90,12 @@ workflows: requires: - prep-deps - validate-lavamoat-allow-scripts: - <<: *rc_or_master_branch_only requires: - prep-deps - validate-lavamoat-policy-build: - <<: *rc_or_master_branch_only requires: - prep-deps - validate-lavamoat-policy-webapp: - <<: *rc_or_master_branch_only matrix: parameters: build-type: [main, beta, flask, mmi, desktop] @@ -250,7 +280,7 @@ jobs: trigger-beta-build: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - when: @@ -279,7 +309,7 @@ jobs: create_release_pull_request: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -298,7 +328,7 @@ jobs: prep-deps: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - restore_cache: keys: # First try to get the specific cache for the checksum of the yarn.lock file. @@ -348,7 +378,7 @@ jobs: validate-lavamoat-allow-scripts: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -361,7 +391,7 @@ jobs: validate-lavamoat-policy-build: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -377,7 +407,7 @@ jobs: build-type: type: string steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -390,7 +420,7 @@ jobs: prep-build: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - when: @@ -424,7 +454,7 @@ jobs: prep-build-desktop: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -448,7 +478,7 @@ jobs: prep-build-flask: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - when: @@ -488,7 +518,7 @@ jobs: prep-build-test-flask: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -509,7 +539,7 @@ jobs: prep-build-test-mv3: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -530,7 +560,7 @@ jobs: prep-build-test: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -551,7 +581,7 @@ jobs: prep-build-storybook: executor: node-browsers-large steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -565,7 +595,7 @@ jobs: prep-build-ts-migration-dashboard: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -579,7 +609,7 @@ jobs: test-yarn-dedupe: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -589,7 +619,7 @@ jobs: test-lint: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -602,7 +632,7 @@ jobs: test-storybook: executor: node-browsers-large steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -624,7 +654,7 @@ jobs: test-lint-lockfile: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -634,7 +664,7 @@ jobs: test-lint-changelog: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - when: @@ -660,7 +690,7 @@ jobs: test-deps-audit: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -670,7 +700,7 @@ jobs: test-deps-depcheck: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -681,7 +711,7 @@ jobs: executor: node-browsers parallelism: 8 steps: - - checkout + - run: *shallow-git-clone - run: name: Re-Install Chrome command: ./.circleci/scripts/chrome-install.sh @@ -718,7 +748,7 @@ jobs: executor: node-browsers parallelism: 8 steps: - - checkout + - run: *shallow-git-clone - run: name: Re-Install Chrome command: ./.circleci/scripts/chrome-install.sh @@ -746,7 +776,7 @@ jobs: executor: node-browsers parallelism: 4 steps: - - checkout + - run: *shallow-git-clone - run: name: Install Firefox command: ./.circleci/scripts/firefox-install.sh @@ -783,7 +813,7 @@ jobs: executor: node-browsers parallelism: 4 steps: - - checkout + - run: *shallow-git-clone - run: name: Re-Install Chrome command: ./.circleci/scripts/chrome-install.sh @@ -820,7 +850,7 @@ jobs: executor: node-browsers-medium-plus parallelism: 8 steps: - - checkout + - run: *shallow-git-clone - run: name: Install Firefox command: ./.circleci/scripts/firefox-install.sh @@ -856,7 +886,7 @@ jobs: benchmark: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - run: name: Re-Install Chrome command: ./.circleci/scripts/chrome-install.sh @@ -882,7 +912,7 @@ jobs: user-actions-benchmark: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - run: name: Re-Install Chrome command: ./.circleci/scripts/chrome-install.sh @@ -908,7 +938,7 @@ jobs: stats-module-load-init: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - run: name: Re-Install Chrome command: ./.circleci/scripts/chrome-install.sh @@ -1005,7 +1035,7 @@ jobs: job-publish-release: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1025,7 +1055,7 @@ jobs: - add_ssh_keys: fingerprints: - '3d:49:29:f4:b2:e8:ea:af:d1:32:eb:2a:fc:15:85:d8' - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1040,7 +1070,7 @@ jobs: - add_ssh_keys: fingerprints: - '8b:21:e3:20:7c:c9:db:82:74:2d:86:d6:11:a7:2f:49' - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1054,7 +1084,7 @@ jobs: test-unit-mocha: executor: node-browsers-medium-plus steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1069,7 +1099,7 @@ jobs: test-unit-jest-development: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1086,7 +1116,7 @@ jobs: executor: node-browsers-medium-plus parallelism: 12 steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1102,7 +1132,7 @@ jobs: upload-and-validate-coverage: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - codecov/upload @@ -1117,7 +1147,7 @@ jobs: test-unit-global: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1127,7 +1157,7 @@ jobs: validate-source-maps: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1137,7 +1167,7 @@ jobs: validate-source-maps-beta: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1148,7 +1178,7 @@ jobs: validate-source-maps-desktop: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1164,7 +1194,7 @@ jobs: validate-source-maps-flask: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1180,7 +1210,7 @@ jobs: test-mozilla-lint: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1190,7 +1220,7 @@ jobs: test-mozilla-lint-desktop: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: @@ -1206,7 +1236,7 @@ jobs: test-mozilla-lint-flask: executor: node-browsers steps: - - checkout + - run: *shallow-git-clone - attach_workspace: at: . - run: diff --git a/.github/scripts/label-prs.ts b/.github/scripts/label-prs.ts deleted file mode 100644 index 8d05779d7..000000000 --- a/.github/scripts/label-prs.ts +++ /dev/null @@ -1,177 +0,0 @@ -import * as core from '@actions/core'; -import { context, getOctokit } from '@actions/github'; -import { GitHub } from '@actions/github/lib/utils'; - -main().catch((error: Error): void => { - console.error(error); - process.exit(1); -}); - -async function main(): Promise { - const token = process.env.GITHUB_TOKEN; - - if (!token) { - core.setFailed('GITHUB_TOKEN not found'); - process.exit(1); - } - - const octokit = getOctokit(token); - - const headRef = context.payload.pull_request?.head.ref || ''; - - let issueNumber = await getIssueNumberFromPullRequestBody(); - if (issueNumber === "") { - bailIfIsBranchNameInvalid(headRef); - bailIfIsNotFeatureBranch(headRef); - issueNumber = getIssueNumberFromBranchName(headRef); - } - - if (Number(issueNumber) === 0) { - process.exit(0); - } - - await updateLabels(octokit, issueNumber); -} - -async function getIssueNumberFromPullRequestBody(): Promise { - console.log("Checking if the PR's body references an issue..."); - - let ISSUE_LINK_IN_PR_DESCRIPTION_REGEX = - /(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s#\d+/gi; - - const prBody = await getPullRequestBody(); - - let matches = prBody.match(ISSUE_LINK_IN_PR_DESCRIPTION_REGEX); - if (!matches || matches?.length === 0) { - console.log( - 'No direct link can be drawn between the PR and an issue from the PR body because no issue number was referenced.', - ); - return ""; - } - - if (matches?.length > 1) { - console.log( - 'No direct link can be drawn between the PR and an issue from the PR body because more than one issue number was referenced.', - ); - return ""; - } - - const ISSUE_NUMBER_REGEX = /\d+/; - const issueNumber = matches[0].match(ISSUE_NUMBER_REGEX)?.[0] || ''; - - console.log(`Found issue number ${issueNumber} in PR body.`); - - return issueNumber; -} - -async function getPullRequestBody(): Promise { - if (context.eventName !== 'pull_request') { - console.log('This action should only run on pull_request events.'); - process.exit(1); - } - - const prBody = context.payload.pull_request?.body || ''; - return prBody; -} - -function bailIfIsBranchNameInvalid(branchName: string): void { - const BRANCH_REGEX = - /^(main|develop|(ci|chore|docs|feat|feature|fix|perf|refactor|revert|style)\/\d*(?:[-](?![-])\w*)*|Version-v\d+\.\d+\.\d+)$/; - const isValidBranchName = new RegExp(BRANCH_REGEX).test(branchName); - - if (!isValidBranchName) { - console.log('This branch name does not follow the convention.'); - console.log( - 'Here are some example branch names that are accepted: "fix/123-description", "feat/123-longer-description", "feature/123", "main", "develop", "Version-v10.24.2".', - ); - console.log( - 'No issue could be linked to this PR, so no labels were copied', - ); - - process.exit(0); - } -} - -function bailIfIsNotFeatureBranch(branchName: string): void { - if ( - branchName === 'main' || - branchName === 'develop' || - branchName.startsWith('Version-v') - ) { - console.log(`${branchName} is not a feature branch.`); - console.log( - 'No issue could be linked to this PR, so no labels were copied', - ); - process.exit(0); - } -} - -async function updateLabels(octokit: InstanceType, issueNumber: string): Promise { - interface ILabel { - name: string; - }; - - const owner = context.repo.owner; - const repo = context.repo.repo; - - const issue = await octokit.rest.issues.get({ - owner: owner, - repo: repo, - issue_number: Number(issueNumber), - }); - - const getNameFromLabel = (label: ILabel): string => label.name - - const issueLabels = issue.data.labels.map(label => getNameFromLabel(label as ILabel)); - - const prNumber = context.payload.number; - - const pr = await octokit.rest.issues.get({ - owner: owner, - repo: repo, - issue_number: prNumber, - }); - - const startingPRLabels = pr.data.labels.map(label => getNameFromLabel(label as ILabel)); - - const dedupedFinalPRLabels = [ - ...new Set([...startingPRLabels, ...issueLabels]), - ]; - - const hasIssueAdditionalLabels = !sortedArrayEqual( - startingPRLabels, - dedupedFinalPRLabels, - ); - if (hasIssueAdditionalLabels) { - await octokit.rest.issues.update({ - owner, - repo, - issue_number: prNumber, - labels: dedupedFinalPRLabels, - }); - } -} - -function getIssueNumberFromBranchName(branchName: string): string { - console.log('Checking if the branch name references an issue...'); - - let issueNumber: string; - if (branchName.split('/').length > 1) { - issueNumber = branchName.split('/')[1].split('-')[0]; - } else { - issueNumber = branchName.split('-')[0]; - } - - console.log(`Found issue number ${issueNumber} in branch name.`); - - return issueNumber; -} - -function sortedArrayEqual(array1: string[], array2: string[]): boolean { - const lengthsAreEqual = array1.length === array2.length; - const everyElementMatchesByIndex = array1.every( - (value: string, index: number): boolean => value === array2[index], - ); - - return lengthsAreEqual && everyElementMatchesByIndex; -} diff --git a/.github/workflows/add-release-label.yml b/.github/workflows/add-release-label.yml index 24d8f717a..8a9bf087f 100644 --- a/.github/workflows/add-release-label.yml +++ b/.github/workflows/add-release-label.yml @@ -21,6 +21,7 @@ jobs: uses: actions/setup-node@v3 with: node-version-file: '.nvmrc' + cache: yarn - name: Install dependencies run: yarn --immutable diff --git a/.github/workflows/fitness-functions.yml b/.github/workflows/fitness-functions.yml index 6ca53a53a..87e0bff2c 100644 --- a/.github/workflows/fitness-functions.yml +++ b/.github/workflows/fitness-functions.yml @@ -18,6 +18,7 @@ jobs: uses: actions/setup-node@v3 with: node-version-file: '.nvmrc' + cache: yarn - name: Install dependencies run: yarn --immutable diff --git a/.github/workflows/label-prs.yml b/.github/workflows/label-prs.yml deleted file mode 100644 index 37e92725d..000000000 --- a/.github/workflows/label-prs.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Label PR - -on: - pull_request: - types: [assigned, opened, edited, synchronize, reopened] - -jobs: - label-pr: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: '16' - - - name: Install Yarn - run: npm install -g yarn - - - name: Install dependencies - run: yarn \ No newline at end of file diff --git a/.github/workflows/remove-labels-after-pr-closed.yml b/.github/workflows/remove-labels-after-pr-closed.yml index 17845f6fe..20a8d1630 100644 --- a/.github/workflows/remove-labels-after-pr-closed.yml +++ b/.github/workflows/remove-labels-after-pr-closed.yml @@ -9,6 +9,9 @@ on: jobs: cleanup: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write steps: - name: Remove labels diff --git a/.github/workflows/update-lavamoat-policies.yml b/.github/workflows/update-lavamoat-policies.yml index ad43fb486..3d26c2033 100644 --- a/.github/workflows/update-lavamoat-policies.yml +++ b/.github/workflows/update-lavamoat-policies.yml @@ -20,6 +20,28 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} + react-to-comment: + name: React to the comment + runs-on: ubuntu-latest + needs: is-fork-pull-request + # Early exit if this is a fork, since later steps are skipped for forks + if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: React to the comment + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" \ + -f content='+1' + env: + COMMENT_ID: ${{ github.event.comment.id }} + GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} + REPO: ${{ github.repository }} + prepare: name: Prepare dependencies runs-on: ubuntu-latest @@ -27,7 +49,13 @@ jobs: # Early exit if this is a fork, since later steps are skipped for forks if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} steps: - - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v3 + - name: Checkout pull request + run: gh pr checkout "${PR_NUMBER}" + env: + GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} - name: Use Node.js uses: actions/setup-node@v3 with: @@ -44,6 +72,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + - name: Checkout pull request + run: gh pr checkout "${PR_NUMBER}" + env: + GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} - name: Setup Node.js uses: actions/setup-node@v3 with: @@ -72,6 +105,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + - name: Checkout pull request + run: gh pr checkout "${PR_NUMBER}" + env: + GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} - name: Setup Node.js uses: actions/setup-node@v3 with: @@ -99,7 +137,8 @@ jobs: # Ensure forks don't get access to the LavaMoat update token if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} steps: - - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v3 with: # Use PAT to ensure that the commit later can trigger status check workflows token: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} @@ -113,6 +152,7 @@ jobs: with: path: lavamoat/build-system key: cache-build-${{ github.run_id }}-${{ github.run_attempt }} + fail-on-cache-miss: true # One restore step per build type: [main, beta, flask, mmi, desktop] # Ensure this is synchronized with the list above in the "update-lavamoat-webapp-policy" job # and with the build type list in `builds.yml` @@ -121,27 +161,31 @@ jobs: with: path: lavamoat/browserify/main key: cache-main-${{ github.run_id }}-${{ github.run_attempt }} + fail-on-cache-miss: true - name: Restore beta application policy uses: actions/cache/restore@v3 with: path: lavamoat/browserify/beta key: cache-beta-${{ github.run_id }}-${{ github.run_attempt }} + fail-on-cache-miss: true - name: Restore flask application policy uses: actions/cache/restore@v3 with: path: lavamoat/browserify/flask key: cache-flask-${{ github.run_id }}-${{ github.run_attempt }} + fail-on-cache-miss: true - name: Restore mmi application policy uses: actions/cache/restore@v3 with: path: lavamoat/browserify/mmi key: cache-mmi-${{ github.run_id }}-${{ github.run_attempt }} + fail-on-cache-miss: true - name: Restore desktop application policy uses: actions/cache/restore@v3 with: path: lavamoat/browserify/desktop key: cache-desktop-${{ github.run_id }}-${{ github.run_attempt }} - + fail-on-cache-miss: true - name: Check whether there are policy changes id: policy-changes run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index e628cdcbb..1b0039a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,7 +158,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Additional incoming transactions support ([#14219](https://github.com/MetaMask/metamask-extension/pull/14219)) ### Changed -- UX: Loaclize the avatar-favicon description text ([#18132](https://github.com/MetaMask/metamask-extension/pull/18132)) +- UX: Localize the avatar-favicon description text ([#18132](https://github.com/MetaMask/metamask-extension/pull/18132)) - 17921 Update TransactionAlerts with BannerAlert ([#17940](https://github.com/MetaMask/metamask-extension/pull/17940)) - Part of 17670: Replace Typography with Text confirm-approve-content.component.js and home.component.js ([#18049](https://github.com/MetaMask/metamask-extension/pull/18049)) - UX: Icon: Update buy icon ([#18123](https://github.com/MetaMask/metamask-extension/pull/18123)) diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index d384c22c6..60a718372 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -202,9 +202,6 @@ "delete": { "message": "ሰርዝ" }, - "deleteAccount": { - "message": "መለያን ሰርዝ" - }, "deleteNetwork": { "message": "አውታረ መረብ ይሰረዝ?" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index ed5d2fe23..cb243675e 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -215,9 +215,6 @@ "delete": { "message": "حذف" }, - "deleteAccount": { - "message": "حذف الحساب" - }, "deleteNetwork": { "message": "هل تريد حذف الشبكة؟" }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index abc75bec3..cfa823034 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Изтриване" }, - "deleteAccount": { - "message": "Изтриване на акаунт" - }, "deleteNetwork": { "message": "Да се изтрие ли мрежата?" }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index ec197129c..fa8fa73c7 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -208,9 +208,6 @@ "delete": { "message": "মুছুন" }, - "deleteAccount": { - "message": "অ্যাকাউন্ট মুছুন" - }, "deleteNetwork": { "message": "নেটওয়ার্ক মুছবেন?" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index 1e8b324b4..9e5c4dcc0 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -208,9 +208,6 @@ "delete": { "message": "Suprimeix" }, - "deleteAccount": { - "message": "Elimina el compte" - }, "deleteNetwork": { "message": "Esborrar Xarxa?" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 7ebd54ecf..abe777d47 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Slet" }, - "deleteAccount": { - "message": "Slet konto" - }, "deleteNetwork": { "message": "Slet Netværk?" }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 248f25f40..3da5e212e 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Löschen" }, - "deleteAccount": { - "message": "Konto löschen" - }, "deleteNetwork": { "message": "Netzwerk löschen?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "Die ID eines NFTs ist eine eindeutige Kennung, da keine zwei NFTs gleich sind. Auch diese Nummer finden Sie in OpenSea unter „Details“. Notieren Sie diese oder kopieren Sie sie in Ihre Zwischenablage." }, - "importNFTs": { - "message": "NFTs importieren" - }, "importSelectedTokens": { "message": "Ausgewählte Token importieren?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Laut unseren Aufzeichnungen stimmt der angegebene RPC-URL-Wert nicht mit einem bekannten Provider für diese Chain-ID überein." }, - "missingNFT": { - "message": "Sie sehen Ihr NFT nicht?" - }, "missingSetting": { "message": "Sie können eine Einstellung nicht finden?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Kein Umrechnungskurs verfügbar" }, - "noNFTs": { - "message": "Noch keine NFTs" - }, "noSnaps": { "message": "Keine Snaps installiert" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 9361b3725..7bbe75c6b 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Διαγραφή" }, - "deleteAccount": { - "message": "Διαγραφή Λογαριασμού" - }, "deleteNetwork": { "message": "Διαγραφή Δικτύου;" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "Ένα αναγνωριστικό του συλλεκτικού είναι ένα μοναδικό αναγνωριστικό δεδομένου ότι δεν υπάρχουν δύο ίδια NFT. Και πάλι, στο OpenSea αυτός ο αριθμός βρίσκεται στην ενότητα \"Λεπτομέρειες\". Σημειώστε τον ή αντιγράψτε τον στο πρόχειρο σας." }, - "importNFTs": { - "message": "Εισαγωγή NFT" - }, "importSelectedTokens": { "message": "Εισαγωγή επιλεγμένων token;" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Σύμφωνα με τις καταχωρήσεις μας, η τιμή RPC URL που υποβλήθηκε δεν ταιριάζει με κάποιον γνωστό πάροχο για αυτό το αναγνωριστικό αλυσίδας." }, - "missingNFT": { - "message": "Δεν βλέπετε το NFT σας;" - }, "missingSetting": { "message": "Δεν μπορείτε να βρείτε μια ρύθμιση;" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Δεν Υπάρχει Διαθέσιμη Ισοτιμία Μετατροπής" }, - "noNFTs": { - "message": "Δεν υπάρχουν NFT ακόμα" - }, "noSnaps": { "message": "Δεν εγκαταστάθηκαν Snaps" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index b0ef001ba..3405e0712 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1101,8 +1101,8 @@ "delete": { "message": "Delete" }, - "deleteAccount": { - "message": "Delete account" + "deleteContact": { + "message": "Delete contact" }, "deleteNetwork": { "message": "Delete network?" @@ -1884,9 +1884,6 @@ "importNFTTokenIdToolTip": { "message": "An NFT's ID is a unique identifier since no two NFTs are alike. Again, on OpenSea this number is under 'Details'. Make a note of it, or copy it onto your clipboard." }, - "importNFTs": { - "message": "Import NFTs" - }, "importSelectedTokens": { "message": "Import selected tokens?" }, @@ -2273,9 +2270,6 @@ "mismatchedRpcUrl": { "message": "According to our records the submitted RPC URL value does not match a known provider for this chain ID." }, - "missingNFT": { - "message": "Don't see your NFT?" - }, "missingSetting": { "message": "Can't find a setting?" }, @@ -5162,6 +5156,9 @@ "viewOnOpensea": { "message": "View on Opensea" }, + "viewPortfolioDashboard": { + "message": "View Portfolio Dashboard" + }, "viewinCustodianApp": { "message": "View in custodian app" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 387497661..bc3dfb601 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Eliminar" }, - "deleteAccount": { - "message": "Eliminar cuenta" - }, "deleteNetwork": { "message": "¿Eliminar red?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "La ID de un NFT es un identificador único, ya que no hay dos NFT iguales. Nuevamente, en OpenSea, este número se encuentra en 'Detalles'. Tome nota de ello o cópielo en su portapapeles." }, - "importNFTs": { - "message": "AGREGAR NFT" - }, "importSelectedTokens": { "message": "¿Agregar los tokens seleccionados?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Según nuestros registros, el valor de la URL de RPC enviado no coincide con un proveedor conocido para este ID de cadena." }, - "missingNFT": { - "message": "¿No ve su NFT?" - }, "missingSetting": { "message": "¿No puede encontrar un ajuste?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "No hay tasa de conversión disponible" }, - "noNFTs": { - "message": "No hay ningún NFT aún" - }, "noSnaps": { "message": "No hay complementos instalados" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 6562f505a..7628f9c7a 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -581,9 +581,6 @@ "delete": { "message": "Eliminar" }, - "deleteAccount": { - "message": "Eliminar cuenta" - }, "deleteNetwork": { "message": "¿Eliminar red?" }, @@ -1073,9 +1070,6 @@ "importMyWallet": { "message": "Importar Mi cartera" }, - "importNFTs": { - "message": "Importar NFT" - }, "importTokenQuestion": { "message": "¿Desea importar el token?" }, @@ -1333,9 +1327,6 @@ "message": "verifique los detalles de la red", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." }, - "missingNFT": { - "message": "¿No ve su NFT?" - }, "mustSelectOne": { "message": "Debe seleccionar al menos 1 token." }, @@ -1468,9 +1459,6 @@ "noConversionRateAvailable": { "message": "No hay tasa de conversión disponible" }, - "noNFTs": { - "message": "Aún no hay NFT" - }, "noTransactions": { "message": "No tiene transacciones" }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index 63eb35697..cc92f0f26 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Kustuta" }, - "deleteAccount": { - "message": "Kustuta konto" - }, "deleteNetwork": { "message": "Võrk kustutada?" }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index ed7a8934e..c94f0efef 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "حذف" }, - "deleteAccount": { - "message": "حذف حساب" - }, "deleteNetwork": { "message": "شبکه حذف شود؟" }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index fdeed0452..788eda6b4 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Poista" }, - "deleteAccount": { - "message": "Poista tili" - }, "deleteNetwork": { "message": "Poistetaanko verkko?" }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index 940f78da6..462856836 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -187,9 +187,6 @@ "delete": { "message": "I-delete" }, - "deleteAccount": { - "message": "I-delete ang Account" - }, "deleteNetwork": { "message": "I-delete ang Network?" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index ea020f961..aafeeead8 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Supprimer" }, - "deleteAccount": { - "message": "Supprimer le compte" - }, "deleteNetwork": { "message": "Supprimer le réseau ?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "L’ID d’un NFT est un identifiant unique puisqu’il n’y a pas deux NFT identiques. Encore une fois, sur OpenSea, ce numéro se trouve dans la section « Détails ». Prenez-en note ou copiez-le dans votre presse-papiers." }, - "importNFTs": { - "message": "Importer des NFT" - }, "importSelectedTokens": { "message": "Voulez-vous importer les jetons sélectionnés ?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Selon nos informations, la valeur de l’URL RPC soumise ne correspond pas à un fournisseur connu pour cet ID de chaîne." }, - "missingNFT": { - "message": "Vous ne voyez pas votre NFT ?" - }, "missingSetting": { "message": "Vous ne trouvez pas un paramètre ?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Aucun taux de conversion disponible" }, - "noNFTs": { - "message": "Aucun NFT pour le moment" - }, "noSnaps": { "message": "Aucun Snap installé" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index b2977d3b1..ad633a3e5 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "מחיקה" }, - "deleteAccount": { - "message": "מחק חשבון" - }, "deleteNetwork": { "message": "למחוק את הרשת?" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 5a6757562..2fcc7b43c 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "हटाएँ" }, - "deleteAccount": { - "message": "अकाउंट हटाएं" - }, "deleteNetwork": { "message": "नेटवर्क हटाएं?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "किसी एनएफटी की आईडी एक विशिष्ट पहचानकर्ता है क्योंकि कोई भी दो एनएफटी एक जैसे नहीं होते हैं। फिर से, OpenSea पर यह संख्या 'डिटेल्स' के नीचे होगी। इसे नोट कर लें या अपने क्लिपबोर्ड पर कॉपी कर लें।" }, - "importNFTs": { - "message": "NFT आयात करें" - }, "importSelectedTokens": { "message": "चयनित टोकन आयात करें?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "हमारे रिकॉर्ड के अनुसार, सबमिट किया गया RPC-URL मान इस चेन आईडी के लिए किसी ज्ञात प्रदाता से मेल नहीं खाता।" }, - "missingNFT": { - "message": "अपना NFT नहीं देख रहे हैं?" - }, "missingSetting": { "message": "सेटिंग नहीं मिल पाया?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "कोई भी रूपांतरण दर उपलब्ध नहीं है" }, - "noNFTs": { - "message": "अभी तक कोई NFT नहीं" - }, "noSnaps": { "message": "कोई स्नैप इंस्टाल नहीं किया गया" }, diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index 7557d6c2c..aa2eeb937 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Izbriši" }, - "deleteAccount": { - "message": "Izbriši račun" - }, "deleteNetwork": { "message": "Izbrisati mrežu?" }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index b1247e74e..09489674a 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Törlés" }, - "deleteAccount": { - "message": "Fiók törlése" - }, "deleteNetwork": { "message": "Törli a hálózatot?" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index da9df8643..d03338321 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Hapus" }, - "deleteAccount": { - "message": "Hapus akun" - }, "deleteNetwork": { "message": "Hapus jaringan?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "ID NFT merupakan pengenal unik karena tidak ada dua NFT yang sama. Sekali lagi, angka ini berada di bawah 'Detail' pada OpenSea. Catat atau salin ke papan klip." }, - "importNFTs": { - "message": "Impor NFT" - }, "importSelectedTokens": { "message": "Impor token yang dipilih?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Menurut catatan kami, nilai URL RPC yang dikirimkan tidak sesuai dengan penyedia yang dikenal untuk ID rantai ini." }, - "missingNFT": { - "message": "Tidak melihat NFT Anda?" - }, "missingSetting": { "message": "Tidak dapat menemukan pengaturan?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Nilai konversi tidak tersedia" }, - "noNFTs": { - "message": "Belum ada NFT" - }, "noSnaps": { "message": "Belum ada Snap yang diinstal" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index d18255efd..4109f6831 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -716,9 +716,6 @@ "delete": { "message": "Elimina" }, - "deleteAccount": { - "message": "Cancella account" - }, "deleteNetwork": { "message": "Cancella la rete?" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index fee446d3e..18cd136db 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "削除" }, - "deleteAccount": { - "message": "アカウントを削除" - }, "deleteNetwork": { "message": "ネットワークを削除しますか?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "NFT の ID は一意の識別子で、同じ NFT は 2 つとして存在しません。前述の通り、OpenSea ではこの番号は「詳細」に表示されます。この ID を書き留めるか、クリップボードにコピーしてください。" }, - "importNFTs": { - "message": "NFTをインポート" - }, "importSelectedTokens": { "message": "選択したトークンをインポートしますか?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "弊社の記録によると、送信された RPC URL の値がこのチェーン ID の既知のプロバイダーと一致しません。" }, - "missingNFT": { - "message": "NFTが見当たりませんか?" - }, "missingSetting": { "message": "設定が見つかりませんか?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "利用可能な換算レートがありません" }, - "noNFTs": { - "message": "NFTはまだありません" - }, "noSnaps": { "message": "スナップがインストールされていません" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 3f56db08b..7458ec831 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "ಅಳಿಸಿ" }, - "deleteAccount": { - "message": "ಖಾತೆಯನ್ನು ಅಳಿಸಿ" - }, "deleteNetwork": { "message": "ನೆಟ್‌ವರ್ಕ್ ಅಳಿಸುವುದೇ?" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 46e74e3da..2926560e3 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "삭제" }, - "deleteAccount": { - "message": "계정 삭제" - }, "deleteNetwork": { "message": "네트워크를 삭제할까요?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "NFT의 ID는 고유한 식별자이므로 동일한 NFT는 존재하지 않습니다. 다시 말하지만, OpenSea에서 이 번호는 '세부 정보(Details)'에서 찾아볼 수 있습니다. 이를 기록하거나 클립보드에 복사해 두세요." }, - "importNFTs": { - "message": "NFT 가져오기" - }, "importSelectedTokens": { "message": "선택한 토큰을 불러올까요?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "기록에 따르면 제출한 RPC URL 값이 이 체인 ID의 알려진 공급업체와 일치하지 않습니다." }, - "missingNFT": { - "message": "NFT가 보이지 않나요?" - }, "missingSetting": { "message": "설정을 찾으세요?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "사용 가능한 환율 없음" }, - "noNFTs": { - "message": "아직 NFT가 없음" - }, "noSnaps": { "message": "설치된 스냅이 없습니다" }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index 33963ab1e..ed49b566b 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Ištrinti" }, - "deleteAccount": { - "message": "Šalinti paskyrą" - }, "deleteNetwork": { "message": "Panaikinti tinklą?" }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index ebd3e5e6f..231a48fc9 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Dzēst" }, - "deleteAccount": { - "message": "Dzēst kontu" - }, "deleteNetwork": { "message": "Dzēst tīklu?" }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index 434b53b9c..c5e44b81d 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Padam" }, - "deleteAccount": { - "message": "Hapus Akaun" - }, "deleteNetwork": { "message": "Padamkan Rangkaian?" }, diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index ab159f73b..261ab1846 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -208,9 +208,6 @@ "delete": { "message": "Slett" }, - "deleteAccount": { - "message": "Slett konto " - }, "deleteNetwork": { "message": "Slette nettverk? " }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index 8b0947625..f69ae1563 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -406,9 +406,6 @@ "delete": { "message": "I-delete" }, - "deleteAccount": { - "message": "I-delete ang Account" - }, "deleteNetwork": { "message": "I-delete ang Network?" }, diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index fe8593329..e42eea2ae 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Usuń" }, - "deleteAccount": { - "message": "Usuń konto" - }, "deleteNetwork": { "message": "Usunąć sieć?" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 1c26903ed..39997d6ff 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Excluir" }, - "deleteAccount": { - "message": "Excluir conta" - }, "deleteNetwork": { "message": "Excluir rede?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "O ID de um NFT é um identificador único, pois não há dois NFTs iguais. Novamente, na OpenSea, esse número se encontra em \"Detalhes\". Anote-o ou copie-o para sua área de transferência." }, - "importNFTs": { - "message": "Importar NFTs" - }, "importSelectedTokens": { "message": "Importar tokens selecionados?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "De acordo com os nossos registros, o valor da URL da RPC enviado não corresponde a um provedor conhecido da ID desta cadeia." }, - "missingNFT": { - "message": "Não está vendo o seu NFT?" - }, "missingSetting": { "message": "Não consegue encontrar uma configuração?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Não há uma taxa de conversão disponível" }, - "noNFTs": { - "message": "Nenhum NFT até agora" - }, "noSnaps": { "message": "Nenhum snap instalado" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index c299dbfed..cb2c55d69 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -581,9 +581,6 @@ "delete": { "message": "Excluir" }, - "deleteAccount": { - "message": "Excluir conta" - }, "deleteNetwork": { "message": "Excluir rede?" }, @@ -1073,9 +1070,6 @@ "importMyWallet": { "message": "Importar minha carteira" }, - "importNFTs": { - "message": "Importar NFTs" - }, "importTokenQuestion": { "message": "Importar token?" }, @@ -1333,9 +1327,6 @@ "message": "verifique os detalhes da rede", "description": "Serves as link text for the 'mismatchedChain' key. This text will be embedded inside the translation for that key." }, - "missingNFT": { - "message": "Não está vendo o seu NFT?" - }, "mustSelectOne": { "message": "Selecione pelo menos 1 token." }, @@ -1468,9 +1459,6 @@ "noConversionRateAvailable": { "message": "Não há uma taxa de conversão disponível" }, - "noNFTs": { - "message": "Ainda não há nenhum NFT" - }, "noTransactions": { "message": "Você não tem transações" }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index 3133c4cd5..1fa0e9d71 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Șterge" }, - "deleteAccount": { - "message": "Ștergeți cont" - }, "deleteNetwork": { "message": "Ștergeți rețeaua?" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 8fedcfa31..1921b112b 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Удалить" }, - "deleteAccount": { - "message": "Удалить счет" - }, "deleteNetwork": { "message": "Удалить сеть?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "Идентификатор коллекционного актива является уникальным идентификатором, поскольку нет двух одинаковых NFT. Опять же, в OpenSea этот номер находится в разделе «Подробности». Запишите его или скопируйте в буфер обмена." }, - "importNFTs": { - "message": "Импорт NFT" - }, "importSelectedTokens": { "message": "Импортировать выбранные токены?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Согласно нашим записям, отправленное значение URL-адреса RPC не соответствует известному поставщику для этого идентификатора блокчейна." }, - "missingNFT": { - "message": "Не видите свои NFT?" - }, "missingSetting": { "message": "Не удается найти настройку?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Нет доступного обменного курса" }, - "noNFTs": { - "message": "Пока нет NFT-токенов" - }, "noSnaps": { "message": "Снапы не установлены" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 5e431ef39..bed99df90 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -205,9 +205,6 @@ "delete": { "message": "Odstrániť" }, - "deleteAccount": { - "message": "Zmazať účet" - }, "deleteNetwork": { "message": "Odstrániť sieť?" }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index 2dffd7a7b..b4e5faf7e 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Izbriši" }, - "deleteAccount": { - "message": "Izbriši račun" - }, "deleteNetwork": { "message": "Izbrišem to omrežje?" }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index 910853ce5..999f01d20 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -208,9 +208,6 @@ "delete": { "message": "Избриши" }, - "deleteAccount": { - "message": "Obriši nalog" - }, "deleteNetwork": { "message": "Da li želite da obrišete mrežu?" }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 70e1678ed..283a0f3ad 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -205,9 +205,6 @@ "delete": { "message": "Radera" }, - "deleteAccount": { - "message": "Radera konto" - }, "deleteNetwork": { "message": "Radera nätverk?" }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index 4b8ae6d38..7949db571 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -205,9 +205,6 @@ "delete": { "message": "Futa" }, - "deleteAccount": { - "message": "Futa Akaunti" - }, "deleteNetwork": { "message": "Futa Mtandao?" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 9318c44cf..2ca598754 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "I-delete" }, - "deleteAccount": { - "message": "I-delete ang Account" - }, "deleteNetwork": { "message": "I-delete ang network?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "Ang ID ng NFT ay isang natatanging pagkakakilanlan dahil walang dalawang NFT ang magkatulad. Muli, sa OpenSea ang numerong ito ay nasa ilalim ng 'Mga Detalye'. Itala ito, o kopyahin ito sa iyong clipboard." }, - "importNFTs": { - "message": "I-import ang mga NFT" - }, "importSelectedTokens": { "message": "I-import ang mga napiling token?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Ayon sa aming mga talaan, ang isinumiteng RPC URL value ay hindi tumutugma sa isang kilalang provider para sa chain ID na ito." }, - "missingNFT": { - "message": "Hindi makita ang NFT mo?" - }, "missingSetting": { "message": "Hindi makahanap ng setting?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Hindi available ang rate ng conversion" }, - "noNFTs": { - "message": "Wala pang mga NFT" - }, "noSnaps": { "message": "Walang mga Snap na naka-install" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 0e5bab809..9c9ee1ec8 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Sil" }, - "deleteAccount": { - "message": "Hesabı Sil" - }, "deleteNetwork": { "message": "Ağı Sil?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "Hiçbir iki NFT kimliği birbiriyle aynı olmadığı için her NFT kimliği benzersiz bir tanımlayıcıdır. Yine, OpenSea'de bu sayı 'Detaylar' kısmının altında yer alır. Not alın veya panonuza kopyalayın." }, - "importNFTs": { - "message": "NFS'leri İçe Aktar" - }, "importSelectedTokens": { "message": "Seçilen tokenleri içe aktar?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Kayıtlarımıza göre, sunulan RPC URL adresi değeri bu zincir kimliğinin bilinen bir sağlayıcısı ile uyumlu değil." }, - "missingNFT": { - "message": "NFT'nizi görmüyor musunuz?" - }, "missingSetting": { "message": "Bir ayarı bulamıyor musun?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Dönüşüm oranı mevcut değil" }, - "noNFTs": { - "message": "Henüz NFT yok" - }, "noSnaps": { "message": "Hiç Snap yüklü değil" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index 0f03dbc5f..e7b83ffc6 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -211,9 +211,6 @@ "delete": { "message": "Видалити" }, - "deleteAccount": { - "message": "Видалити обліковий запис" - }, "deleteNetwork": { "message": "Видалити мережу?" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 89b4aff06..7c8851b15 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "Xóa" }, - "deleteAccount": { - "message": "Xóa tài khoản" - }, "deleteNetwork": { "message": "Xóa mạng?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "ID của NFT là một mã nhận dạng duy nhất vì không có hai NFT nào giống hệt nhau. Một lần nữa, trên OpenSea, mã số này nằm bên dưới mục 'Chi tiết'. Hãy ghi chú lại hoặc sao chép vào bộ nhớ đệm." }, - "importNFTs": { - "message": "Nhập NFT" - }, "importSelectedTokens": { "message": "Nhập các token đã chọn?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "Theo hồ sơ của chúng tôi, giá trị RPC URL đã gửi không khớp với một nhà cung cấp đã biết cho ID chuỗi này." }, - "missingNFT": { - "message": "Không thấy NFT của mình?" - }, "missingSetting": { "message": "Không tìm thấy thiết lập?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "Không có sẵn tỷ lệ quy đổi nào" }, - "noNFTs": { - "message": "Chưa có NFT" - }, "noSnaps": { "message": "Chưa cài đặt Snap nào" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 43ea94627..9090ca86a 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -911,9 +911,6 @@ "delete": { "message": "删除" }, - "deleteAccount": { - "message": "删除账户" - }, "deleteNetwork": { "message": "删除网络?" }, @@ -1602,9 +1599,6 @@ "importNFTTokenIdToolTip": { "message": "NFT的ID是唯一标识符,因为所有NFT都是独一无二的。同样,在OpenSea上,此数字位于“详情”下方。记下它,或将它复制到剪贴板上。" }, - "importNFTs": { - "message": "添加收藏品" - }, "importSelectedTokens": { "message": "要导入所选代币吗?" }, @@ -1956,9 +1950,6 @@ "mismatchedRpcUrl": { "message": "根据我们的记录,所提交的RPC URL值与此链ID的已知提供者不匹配。" }, - "missingNFT": { - "message": "找不到您的 NFT?" - }, "missingSetting": { "message": "找不到设置吗?" }, @@ -2165,9 +2156,6 @@ "noConversionRateAvailable": { "message": "无可用汇率" }, - "noNFTs": { - "message": "尚无 NFT" - }, "noSnaps": { "message": "没有安装Snap" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 914c3f3fe..27260c920 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -408,9 +408,6 @@ "delete": { "message": "刪除" }, - "deleteAccount": { - "message": "刪除帳戶" - }, "deleteNetwork": { "message": "刪除網路?" }, diff --git a/app/build-types/mmi/images/icons/stake.svg b/app/build-types/mmi/images/icons/stake.svg index e6b1ce7c6..54c6762e3 100644 --- a/app/build-types/mmi/images/icons/stake.svg +++ b/app/build-types/mmi/images/icons/stake.svg @@ -1,14 +1,3 @@ - - - - - - - - - - \ No newline at end of file + + + diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 73bce1672..e72a2a8fe 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -485,9 +485,11 @@ const onMessageSetUpExtensionStreams = (msg) => { /** * This listener destroys the extension streams when the extension port is disconnected, * so that streams may be re-established later when the extension port is reconnected. + * + * @param {Error} [err] - Stream connection error */ -const onDisconnectDestroyStreams = () => { - const err = checkForLastError(); +const onDisconnectDestroyStreams = (err) => { + const lastErr = err || checkForLastError(); extensionPort.onDisconnect.removeListener(onDisconnectDestroyStreams); @@ -501,8 +503,8 @@ const onDisconnectDestroyStreams = () => { * may cause issues. We suspect that this is a chromium bug as this event should only be called * once the port and connections are ready. Delay time is arbitrary. */ - if (err) { - console.warn(`${err} Resetting the streams.`); + if (lastErr) { + console.warn(`${lastErr} Resetting the streams.`); setTimeout(setupExtensionStreams, 1000); } }; @@ -630,6 +632,18 @@ const start = () => { injectScript(inpageBundle); } initStreams(); + + // https://bugs.chromium.org/p/chromium/issues/detail?id=1457040 + // Temporary workaround for chromium bug that breaks the content script <=> background connection + // for prerendered pages. This resets potentially broken extension streams if a page transitions + // from the prerendered state to the active state. + if (document.prerendering) { + document.addEventListener('prerenderingchange', () => { + onDisconnectDestroyStreams( + new Error('Prerendered page has become active.'), + ); + }); + } } }; diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index 689472829..8923487b5 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -1,6 +1,7 @@ import EventEmitter from 'events'; import log from 'loglevel'; import { captureException } from '@sentry/browser'; +import { isEqual } from 'lodash'; import { PersonalMessageManager, TypedMessageManager, @@ -14,11 +15,17 @@ import { REFRESH_TOKEN_CHANGE_EVENT, INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, } from '@metamask-institutional/sdk'; +import { + handleMmiPortfolio, + setDashboardCookie, +} from '@metamask-institutional/portfolio-dashboard'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import { BUILD_QUOTE_ROUTE, CONNECT_HARDWARE_ROUTE, } from '../../../ui/helpers/constants/routes'; +import { previousValueComparator } from '../lib/util'; import { getPermissionBackgroundApiMethods } from './permissions'; export default class MMIController extends EventEmitter { @@ -69,6 +76,17 @@ export default class MMIController extends EventEmitter { this.transactionUpdateController.subscribeToEvents(); }); } + + this.preferencesController.store.subscribe( + previousValueComparator(async (prevState, currState) => { + const { identities: prevIdentities } = prevState; + const { identities: currIdentities } = currState; + if (isEqual(prevIdentities, currIdentities)) { + return; + } + await this.prepareMmiPortfolio(); + }, this.preferencesController.store.getState()), + ); } // End of constructor async persistKeyringsAfterRefreshTokenChange() { @@ -542,6 +560,44 @@ export default class MMIController extends EventEmitter { }); } + async handleMmiDashboardData() { + await this.appStateController.getUnlockPromise(true); + const keyringAccounts = await this.keyringController.getAccounts(); + const { identities } = this.preferencesController.store.getState(); + const { metaMetricsId } = this.metaMetricsController.store.getState(); + const getAccountDetails = (address) => + this.custodyController.getAccountDetails(address); + const extensionId = this.extension.runtime.id; + const networks = [ + ...this.preferencesController.getRpcMethodPreferences(), + { chainId: CHAIN_IDS.MAINNET }, + { chainId: CHAIN_IDS.GOERLI }, + ]; + + return handleMmiPortfolio({ + keyringAccounts, + identities, + metaMetricsId, + networks, + getAccountDetails, + extensionId, + }); + } + + async prepareMmiPortfolio() { + if (!process.env.IN_TEST) { + try { + const mmiDashboardData = await this.handleMmiDashboardData(); + const cookieSetUrls = + this.mmiConfigurationController.store.mmiConfiguration?.portfolio + ?.cookieSetUrls; + setDashboardCookie(mmiDashboardData, cookieSetUrls); + } catch (error) { + console.error(error); + } + } + } + async setAccountAndNetwork(origin, address, chainId) { await this.appStateController.getUnlockPromise(true); const selectedAddress = this.preferencesController.getSelectedAddress(); diff --git a/app/scripts/controllers/permissions/snaps/snap-permissions.test.js b/app/scripts/controllers/permissions/snaps/snap-permissions.test.js index 334e12df5..08a21f1eb 100644 --- a/app/scripts/controllers/permissions/snaps/snap-permissions.test.js +++ b/app/scripts/controllers/permissions/snaps/snap-permissions.test.js @@ -8,14 +8,6 @@ import { buildSnapRestrictedMethodSpecifications, } from './snap-permissions'; -// Temporarily replace the snaps packages with the Flask versions. -jest.mock('@metamask/snaps-controllers', () => - jest.requireActual('@metamask/snaps-controllers-flask'), -); -jest.mock('@metamask/rpc-methods', () => - jest.requireActual('@metamask/rpc-methods-flask'), -); - describe('buildSnapRestrictedMethodSpecifications', () => { it('creates valid permission specification objects', () => { const hooks = { diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 0386d4d94..85b2ccc43 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,8 +1,5 @@ import { ObservableStore } from '@metamask/obs-store'; import { normalize as normalizeAddress } from 'eth-sig-util'; -///: BEGIN:ONLY_INCLUDE_IN(build-mmi) -import { setDashboardCookie } from '@metamask-institutional/portfolio-dashboard'; -///: END:ONLY_INCLUDE_IN import { IPFS_DEFAULT_GATEWAY_URL } from '../../../shared/constants/network'; import { LedgerTransportTypes } from '../../../shared/constants/hardware-wallets'; import { ThemeType } from '../../../shared/constants/preferences'; @@ -81,14 +78,6 @@ export default class PreferencesController { this.store.setMaxListeners(13); this.tokenListController = opts.tokenListController; - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - this.handleMmiDashboardData = opts.handleMmiDashboardData; - - if (!process.env.IN_TEST) { - this.mmiConfigurationStore = opts.mmiConfigurationStore.getState(); - } - ///: END:ONLY_INCLUDE_IN - this._subscribeToInfuraAvailability(); global.setPreference = (key, value) => { @@ -261,10 +250,6 @@ export default class PreferencesController { return ids; }, {}); - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - this.prepareMmiPortfolio(); - ///: END:ONLY_INCLUDE_IN - this.store.updateState({ identities }); } @@ -290,10 +275,6 @@ export default class PreferencesController { this.setSelectedAddress(selected); } - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - this.prepareMmiPortfolio(); - ///: END:ONLY_INCLUDE_IN - return address; } @@ -350,10 +331,6 @@ export default class PreferencesController { this.store.updateState({ identities, lostIdentities }); this.addAddresses(addresses); - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - this.prepareMmiPortfolio(); - ///: END:ONLY_INCLUDE_IN - // If the selected account is no longer valid, // select an arbitrary other account: let selected = this.getSelectedAddress(); @@ -534,21 +511,6 @@ export default class PreferencesController { return this.store.getState().disabledRpcMethodPreferences; } - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - async prepareMmiPortfolio() { - if (!process.env.IN_TEST) { - try { - const mmiDashboardData = await this.handleMmiDashboardData(); - const cookieSetUrls = - this.mmiConfigurationStore.mmiConfiguration?.portfolio?.cookieSetUrls; - setDashboardCookie(mmiDashboardData, cookieSetUrls); - } catch (error) { - console.error(error); - } - } - } - ///: END:ONLY_INCLUDE_IN - // // PRIVATE METHODS // diff --git a/app/scripts/disable-console.js b/app/scripts/disable-console.js index ff6fa46ed..fba61b383 100644 --- a/app/scripts/disable-console.js +++ b/app/scripts/disable-console.js @@ -6,6 +6,7 @@ if ( ) { console.log = noop; console.info = noop; + console.warn = noop; } function noop() { diff --git a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js index 3b1530767..e0f1b87f4 100644 --- a/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js +++ b/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js @@ -1,8 +1,10 @@ -///: BEGIN:ONLY_INCLUDE_IN(snaps) -import { permittedMethods as permittedSnapMethods } from '@metamask/rpc-methods'; -///: END:ONLY_INCLUDE_IN import { permissionRpcMethods } from '@metamask/permission-controller'; -import { selectHooks } from '@metamask/rpc-methods/dist/utils'; +import { + selectHooks, + ///: BEGIN:ONLY_INCLUDE_IN(snaps) + permittedMethods as permittedSnapMethods, + ///: END:ONLY_INCLUDE_IN +} from '@metamask/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/rpc-method-middleware/handlers/watch-asset.js b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js index 8954a15f5..2f7fb5d7a 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js @@ -1,3 +1,5 @@ +import { ERC1155, ERC721 } from '@metamask/controller-utils'; +import { ethErrors } from 'eth-rpc-errors'; import { MESSAGE_TYPE } from '../../../../../shared/constants/app'; const watchAsset = { @@ -39,6 +41,21 @@ async function watchAssetHandler( params: { options: asset, type }, origin, } = req; + + const { tokenId } = asset; + + if ( + [ERC721, ERC1155].includes(type) && + tokenId !== undefined && + typeof tokenId !== 'string' + ) { + return end( + ethErrors.rpc.invalidParams({ + message: `Expected parameter 'tokenId' to be type 'string'. Received type '${typeof tokenId}'`, + }), + ); + } + await handleWatchAssetRequest(asset, type, origin); res.result = true; return end(); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.test.js b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.test.js new file mode 100644 index 000000000..5af3ad1bd --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.test.js @@ -0,0 +1,99 @@ +import { ERC20, ERC721 } from '@metamask/controller-utils'; +import { ethErrors } from 'eth-rpc-errors'; +import watchAssetHandler from './watch-asset'; + +describe('watchAssetHandler', () => { + let mockEnd; + let mockHandleWatchAssetRequest; + + beforeEach(() => { + mockEnd = jest.fn(); + mockHandleWatchAssetRequest = jest.fn(); + }); + + it('should handle valid input for type ERC721 correctly', async () => { + const req = { + params: { + options: { + address: '0x1234', + tokenId: 'testTokenId', + }, + type: ERC721, + }, + origin: 'testOrigin', + }; + + const res = { + result: false, + }; + + await watchAssetHandler.implementation(req, res, null, mockEnd, { + handleWatchAssetRequest: mockHandleWatchAssetRequest, + }); + + expect(mockHandleWatchAssetRequest).toHaveBeenCalledWith( + req.params.options, + req.params.type, + req.origin, + ); + expect(res.result).toStrictEqual(true); + expect(mockEnd).toHaveBeenCalledWith(); + }); + + it('should handle valid input for type ERC20 correctly', async () => { + const req = { + params: { + options: { + address: '0x1234', + symbol: 'TEST', + decimals: 18, + }, + type: ERC20, + }, + origin: 'testOrigin', + }; + + const res = { + result: false, + }; + + await watchAssetHandler.implementation(req, res, null, mockEnd, { + handleWatchAssetRequest: mockHandleWatchAssetRequest, + }); + + expect(mockHandleWatchAssetRequest).toHaveBeenCalledWith( + req.params.options, + req.params.type, + req.origin, + ); + expect(res.result).toStrictEqual(true); + expect(mockEnd).toHaveBeenCalledWith(); + }); + + it('should throw when type is ERC721 and tokenId type is invalid', async () => { + const req = { + params: { + options: { + address: '0x1234', + tokenId: 222, + }, + type: ERC721, + }, + origin: 'testOrigin', + }; + + const res = { + result: false, + }; + + await watchAssetHandler.implementation(req, res, null, mockEnd, { + handleWatchAssetRequest: mockHandleWatchAssetRequest, + }); + + expect(mockEnd).toHaveBeenCalledWith( + ethErrors.rpc.invalidParams({ + message: `Expected parameter 'tokenId' to be type 'string'. Received type 'number'`, + }), + ); + }); +}); diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index 85dd5fe6d..c1ea740f8 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -42,26 +42,11 @@ const createLoggerMiddlewareMock = () => (req, res, next) => { next(); }; -// Temporarily replace the snaps packages with the Flask versions. -const proxyPermissions = proxyquire('./controllers/permissions', { - './snaps/snap-permissions': proxyquire( - './controllers/permissions/snaps/snap-permissions', - { - // eslint-disable-next-line node/global-require - '@metamask/snaps-controllers': require('@metamask/snaps-controllers-flask'), - // eslint-disable-next-line node/global-require - '@metamask/rpc-methods': require('@metamask/rpc-methods-flask'), - }, - ), -}); - const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'; const MetaMaskController = proxyquire('./metamask-controller', { './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, - // Temporarily replace the snaps packages with the Flask versions. - './controllers/permissions': proxyPermissions, }).default; describe('MetaMaskController', function () { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0ccdb519d..b71dbbe7d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -71,7 +71,6 @@ import { } from '@metamask-institutional/custody-keyring'; import { InstitutionalFeaturesController } from '@metamask-institutional/institutional-features'; import { CustodyController } from '@metamask-institutional/custody-controller'; -import { handleMmiPortfolio } from '@metamask-institutional/portfolio-dashboard'; import { TransactionUpdateController } from '@metamask-institutional/transaction-update'; ///: END:ONLY_INCLUDE_IN import { SignatureController } from '@metamask/signature-controller'; @@ -121,6 +120,9 @@ import { ///: END:ONLY_INCLUDE_IN } from '../../shared/constants/permissions'; import { UI_NOTIFICATIONS } from '../../shared/notifications'; +///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +import { UI_INSTITUTIONAL_NOTIFICATIONS } from '../../shared/notifications/institutional'; +///: END:ONLY_INCLUDE_IN import { MILLISECOND, SECOND } from '../../shared/constants/time'; import { ORIGIN_METAMASK, @@ -379,10 +381,6 @@ export default class MetamaskController extends EventEmitter { ), tokenListController: this.tokenListController, provider: this.provider, - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - handleMmiDashboardData: this.handleMmiDashboardData.bind(this), - mmiConfigurationStore: this.mmiConfigurationController.store, - ///: END:ONLY_INCLUDE_IN }); this.preferencesController.store.subscribe(async ({ currentLocale }) => { @@ -568,12 +566,8 @@ export default class MetamaskController extends EventEmitter { ), getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind(this), - legacyAPIEndpoint: `${gasApiBaseUrl}/networks//gasPrices`, EIP1559APIEndpoint: `${gasApiBaseUrl}/networks//suggestedGasFees`, - getCurrentNetworkLegacyGasAPICompatibility: () => { - const { chainId } = this.networkController.state.providerConfig; - return process.env.IN_TEST || chainId === CHAIN_IDS.MAINNET; - }, + getCurrentNetworkLegacyGasAPICompatibility: () => false, getChainId: () => this.networkController.state.providerConfig.chainId, }); @@ -622,9 +616,16 @@ export default class MetamaskController extends EventEmitter { const announcementMessenger = this.controllerMessenger.getRestricted({ name: 'AnnouncementController', }); + + let allAnnouncements = UI_NOTIFICATIONS; + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + allAnnouncements = UI_INSTITUTIONAL_NOTIFICATIONS; + ///: END:ONLY_INCLUDE_IN + this.announcementController = new AnnouncementController({ messenger: announcementMessenger, - allAnnouncements: UI_NOTIFICATIONS, + allAnnouncements, state: initState.AnnouncementController, }); @@ -3912,7 +3913,8 @@ export default class MetamaskController extends EventEmitter { ), handleMmiCheckIfTokenIsPresent: this.mmiController.handleMmiCheckIfTokenIsPresent.bind(this), - handleMmiDashboardData: this.handleMmiDashboardData.bind(this), + handleMmiDashboardData: + this.mmiController.handleMmiDashboardData.bind(this), handleMmiOpenSwaps: this.mmiController.handleMmiOpenSwaps.bind(this), handleMmiSetAccountAndNetwork: this.mmiController.setAccountAndNetwork.bind(this), @@ -3971,37 +3973,6 @@ export default class MetamaskController extends EventEmitter { return engine; } - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - /** - * This method is needed in preferences controller - * so it needs to be here and not in our controller because - * preferences controllers is initiated first - */ - async handleMmiDashboardData() { - await this.appStateController.getUnlockPromise(true); - const keyringAccounts = await this.keyringController.getAccounts(); - const { identities } = this.preferencesController.store.getState(); - const { metaMetricsId } = this.metaMetricsController.store.getState(); - const getAccountDetails = (address) => - this.custodyController.getAccountDetails(address); - const extensionId = this.extension.runtime.id; - const networks = [ - ...this.preferencesController.getRpcMethodPreferences(), - { chainId: CHAIN_IDS.MAINNET }, - { chainId: CHAIN_IDS.GOERLI }, - ]; - - return handleMmiPortfolio({ - keyringAccounts, - identities, - metaMetricsId, - networks, - getAccountDetails, - extensionId, - }); - } - ///: END:ONLY_INCLUDE_IN - /** * TODO:LegacyProvider: Delete * A method for providing our public config info over a stream. diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 8f013c48c..32d7b3c2f 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -74,19 +74,6 @@ function MockEthContract() { }; } -// Temporarily replace the snaps packages with the Flask versions. -const proxyPermissions = proxyquire('./controllers/permissions', { - './snaps/snap-permissions': proxyquire( - './controllers/permissions/snaps/snap-permissions', - { - // eslint-disable-next-line node/global-require - '@metamask/snaps-controllers': require('@metamask/snaps-controllers-flask'), - // eslint-disable-next-line node/global-require - '@metamask/rpc-methods': require('@metamask/rpc-methods-flask'), - }, - ), -}); - // TODO, Feb 24, 2023: // ethjs-contract is being added to proxyquire, but we might want to discontinue proxyquire // this is for expediency as we resolve a bug for v10.26.0. The proper solution here would have @@ -95,14 +82,10 @@ const proxyPermissions = proxyquire('./controllers/permissions', { const MetaMaskController = proxyquire('./metamask-controller', { './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, 'ethjs-contract': MockEthContract, - // Temporarily replace the snaps packages with the Flask versions. - './controllers/permissions': proxyPermissions, }).default; const MetaMaskControllerMV3 = proxyquire('./metamask-controller', { '../../shared/modules/mv3.utils': { isManifestV3: true }, - // Temporarily replace the snaps packages with the Flask versions. - './controllers/permissions': proxyPermissions, }).default; const currentNetworkId = '5'; diff --git a/app/scripts/use-snow.js b/app/scripts/use-snow.js index 11b01d78d..868db4e36 100644 --- a/app/scripts/use-snow.js +++ b/app/scripts/use-snow.js @@ -1,8 +1,21 @@ +/* +NOTICE: +This Snow + LavaMoat scuttling integration is currently being used +with an experimental API (https://github.com/LavaMoat/LavaMoat/pull/462). +Changing this code must be done cautiously to avoid breaking the app! +*/ + // eslint-disable-next-line import/unambiguous (function () { const log = console.log.bind(console); - const msg = 'SNOW INTERCEPTED NEW WINDOW CREATION IN METAMASK APP: '; - window.top.SNOW((win) => { - log(msg, win, win?.frameElement); + const msg = + 'Snow detected a new realm creation attempt in MetaMask. Performing scuttling on new realm.'; + Object.defineProperty(window.top, 'SCUTTLER', { + value: (realm, scuttle) => { + window.top.SNOW((win) => { + log(msg, win); + scuttle(win); + }, realm); + }, }); })(); diff --git a/builds.yml b/builds.yml index 3ca68bd59..ca31ff44d 100644 --- a/builds.yml +++ b/builds.yml @@ -51,7 +51,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.1-flask.1/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.35.2-flask.1/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 @@ -69,7 +69,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.1-flask.1/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.35.2-flask.1/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 diff --git a/development/build/index.js b/development/build/index.js index 33051f5a3..84ee91040 100755 --- a/development/build/index.js +++ b/development/build/index.js @@ -147,8 +147,11 @@ async function defineAndRunBuildTasks() { // build lavamoat runtime file await lavapack.buildRuntime({ - scuttleGlobalThis: applyLavaMoat && shouldScuttle, - scuttleGlobalThisExceptions, + scuttleGlobalThis: { + enabled: applyLavaMoat && shouldScuttle, + scuttlerName: 'SCUTTLER', + exceptions: scuttleGlobalThisExceptions, + }, }); } diff --git a/development/build/manifest.js b/development/build/manifest.js index da0683e3b..b3079f806 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -135,9 +135,12 @@ function createManifestTasks({ .trim() .substring(0, 8); - manifest.name = `MetaMask ${capitalize( - buildType, - )}${mv3Str}${lavamoatStr}${snowStr}`; + const buildName = + buildType === 'mmi' + ? `MetaMask Institutional ${mv3Str}${lavamoatStr}${snowStr}` + : `MetaMask ${capitalize(buildType)}${mv3Str}${lavamoatStr}${snowStr}`; + + manifest.name = buildName; manifest.description = `${environment} build from git id: ${gitRevisionStr}`; } diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 494ce3924..06df25223 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -186,8 +186,14 @@ "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { @@ -214,7 +220,7 @@ "crypto": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { @@ -222,7 +228,19 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethersproject/abi": { @@ -1712,9 +1730,15 @@ }, "@metamask/rpc-methods": { "packages": { + "@metamask/browser-passworder": true, "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/utils": true, + "@metamask/permission-controller": true, + "@metamask/rpc-methods>@metamask/utils": true, + "@metamask/rpc-methods>nanoid": true, + "@metamask/snaps-ui": true, + "@metamask/snaps-utils": true, + "eth-rpc-errors": true, "superstruct": true } }, @@ -1723,6 +1747,18 @@ "crypto.getRandomValues": true } }, + "@metamask/rpc-methods>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -1847,6 +1883,81 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-ui": { + "packages": { + "@metamask/snaps-ui>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-ui>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "TextDecoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils>@metamask/utils": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>cron-parser": { + "packages": { + "browserify>browser-resolve": true, + "luxon": true + } + }, + "@metamask/snaps-utils>rfdc": { + "packages": { + "browserify>buffer": true + } + }, + "@metamask/snaps-utils>validate-npm-package-name": { + "packages": { + "@metamask/snaps-utils>validate-npm-package-name>builtins": true + } + }, + "@metamask/snaps-utils>validate-npm-package-name>builtins": { + "packages": { + "browserify>process": true, + "semver": true + } + }, "@metamask/subject-metadata-controller": { "packages": { "@metamask/subject-metadata-controller>@metamask/base-controller": true @@ -1948,85 +2059,110 @@ }, "@sentry/browser": { "globals": { + "TextDecoder": true, + "TextEncoder": true, "XMLHttpRequest": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "indexedDB.open": true, "setTimeout": true }, + "packages": { + "@sentry/browser>@sentry-internal/tracing": true, + "@sentry/browser>@sentry/core": true, + "@sentry/browser>@sentry/replay": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry-internal/tracing": { + "globals": { + "Headers": true, + "PerformanceObserver": true, + "Request": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "performance.getEntriesByType": true, + "removeEventListener": true + }, "packages": { "@sentry/browser>@sentry/core": true, - "@sentry/browser>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, "@sentry/browser>@sentry/core": { "globals": { + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, "clearInterval": true, - "setInterval": true + "clearTimeout": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal": true, - "@sentry/browser>@sentry/core>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, - "@sentry/browser>@sentry/core>@sentry/hub": { + "@sentry/browser>@sentry/replay": { "globals": { - "clearInterval": true, - "setInterval": true + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSSupportsRule": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLCanvasElement": true, + "HTMLElement.prototype": true, + "HTMLFormElement": true, + "HTMLImageElement": true, + "HTMLInputElement.prototype": true, + "HTMLOptionElement.prototype": true, + "HTMLSelectElement.prototype": true, + "HTMLTextAreaElement.prototype": true, + "Headers": true, + "ImageData": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.prototype.contains": true, + "PerformanceObserver": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "Zone": true, + "__SENTRY_DEBUG__": true, + "__rrMutationObserver": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "pageXOffset": true, + "pageYOffset": true, + "requestAnimationFrame": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub>tslib": true, - "@sentry/types": true, - "@sentry/utils": true - } - }, - "@sentry/browser>@sentry/core>@sentry/hub>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal": { - "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>tslib": { - "globals": { - "define": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true, + "browserify>process": true } }, "@sentry/integrations": { "globals": { - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setTimeout": true + "Request": true, + "__SENTRY_DEBUG__": true, + "console.log": true }, "packages": { - "@sentry/integrations>tslib": true, - "@sentry/types": true, "@sentry/utils": true, "localforage": true } }, - "@sentry/integrations>tslib": { - "globals": { - "define": true - } - }, "@sentry/utils": { "globals": { "CustomEvent": true, @@ -2038,22 +2174,22 @@ "Headers": true, "Request": true, "Response": true, + "TextEncoder": true, + "URL": true, "XMLHttpRequest.prototype": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, "clearTimeout": true, "console.error": true, "document": true, - "setTimeout": true + "new": true, + "setTimeout": true, + "target": true }, "packages": { - "@sentry/utils>tslib": true, "browserify>process": true } }, - "@sentry/utils>tslib": { - "globals": { - "define": true - } - }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -4490,6 +4626,12 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "superstruct": { + "globals": { + "console.warn": true, + "define": true + } + }, "uuid": { "globals": { "crypto": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index fafcfcc40..99706027e 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -186,8 +186,14 @@ "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { @@ -214,7 +220,7 @@ "crypto": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { @@ -222,7 +228,19 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethersproject/abi": { @@ -1883,6 +1901,7 @@ "document.createElement": true }, "packages": { + "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/rpc-methods-flask>@metamask/utils": true, @@ -2082,6 +2101,7 @@ "document.createElement": true }, "packages": { + "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/snaps-controllers-flask>@metamask/utils": true, @@ -2293,6 +2313,7 @@ "document.createElement": true }, "packages": { + "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/snaps-utils-flask>@metamask/utils": true, @@ -2474,85 +2495,110 @@ }, "@sentry/browser": { "globals": { + "TextDecoder": true, + "TextEncoder": true, "XMLHttpRequest": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "indexedDB.open": true, "setTimeout": true }, + "packages": { + "@sentry/browser>@sentry-internal/tracing": true, + "@sentry/browser>@sentry/core": true, + "@sentry/browser>@sentry/replay": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry-internal/tracing": { + "globals": { + "Headers": true, + "PerformanceObserver": true, + "Request": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "performance.getEntriesByType": true, + "removeEventListener": true + }, "packages": { "@sentry/browser>@sentry/core": true, - "@sentry/browser>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, "@sentry/browser>@sentry/core": { "globals": { + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, "clearInterval": true, - "setInterval": true + "clearTimeout": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal": true, - "@sentry/browser>@sentry/core>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, - "@sentry/browser>@sentry/core>@sentry/hub": { + "@sentry/browser>@sentry/replay": { "globals": { - "clearInterval": true, - "setInterval": true + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSSupportsRule": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLCanvasElement": true, + "HTMLElement.prototype": true, + "HTMLFormElement": true, + "HTMLImageElement": true, + "HTMLInputElement.prototype": true, + "HTMLOptionElement.prototype": true, + "HTMLSelectElement.prototype": true, + "HTMLTextAreaElement.prototype": true, + "Headers": true, + "ImageData": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.prototype.contains": true, + "PerformanceObserver": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "Zone": true, + "__SENTRY_DEBUG__": true, + "__rrMutationObserver": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "pageXOffset": true, + "pageYOffset": true, + "requestAnimationFrame": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub>tslib": true, - "@sentry/types": true, - "@sentry/utils": true - } - }, - "@sentry/browser>@sentry/core>@sentry/hub>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal": { - "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>tslib": { - "globals": { - "define": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true, + "browserify>process": true } }, "@sentry/integrations": { "globals": { - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setTimeout": true + "Request": true, + "__SENTRY_DEBUG__": true, + "console.log": true }, "packages": { - "@sentry/integrations>tslib": true, - "@sentry/types": true, "@sentry/utils": true, "localforage": true } }, - "@sentry/integrations>tslib": { - "globals": { - "define": true - } - }, "@sentry/utils": { "globals": { "CustomEvent": true, @@ -2564,22 +2610,22 @@ "Headers": true, "Request": true, "Response": true, + "TextEncoder": true, + "URL": true, "XMLHttpRequest.prototype": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, "clearTimeout": true, "console.error": true, "document": true, - "setTimeout": true + "new": true, + "setTimeout": true, + "target": true }, "packages": { - "@sentry/utils>tslib": true, "browserify>process": true } }, - "@sentry/utils>tslib": { - "globals": { - "define": true - } - }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -5148,6 +5194,12 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "superstruct": { + "globals": { + "console.warn": true, + "define": true + } + }, "terser>source-map-support>buffer-from": { "packages": { "browserify>buffer": true diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index fafcfcc40..99706027e 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -186,8 +186,14 @@ "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { @@ -214,7 +220,7 @@ "crypto": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { @@ -222,7 +228,19 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethersproject/abi": { @@ -1883,6 +1901,7 @@ "document.createElement": true }, "packages": { + "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/rpc-methods-flask>@metamask/utils": true, @@ -2082,6 +2101,7 @@ "document.createElement": true }, "packages": { + "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/snaps-controllers-flask>@metamask/utils": true, @@ -2293,6 +2313,7 @@ "document.createElement": true }, "packages": { + "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, "@metamask/snaps-utils-flask>@metamask/utils": true, @@ -2474,85 +2495,110 @@ }, "@sentry/browser": { "globals": { + "TextDecoder": true, + "TextEncoder": true, "XMLHttpRequest": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "indexedDB.open": true, "setTimeout": true }, + "packages": { + "@sentry/browser>@sentry-internal/tracing": true, + "@sentry/browser>@sentry/core": true, + "@sentry/browser>@sentry/replay": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry-internal/tracing": { + "globals": { + "Headers": true, + "PerformanceObserver": true, + "Request": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "performance.getEntriesByType": true, + "removeEventListener": true + }, "packages": { "@sentry/browser>@sentry/core": true, - "@sentry/browser>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, "@sentry/browser>@sentry/core": { "globals": { + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, "clearInterval": true, - "setInterval": true + "clearTimeout": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal": true, - "@sentry/browser>@sentry/core>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, - "@sentry/browser>@sentry/core>@sentry/hub": { + "@sentry/browser>@sentry/replay": { "globals": { - "clearInterval": true, - "setInterval": true + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSSupportsRule": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLCanvasElement": true, + "HTMLElement.prototype": true, + "HTMLFormElement": true, + "HTMLImageElement": true, + "HTMLInputElement.prototype": true, + "HTMLOptionElement.prototype": true, + "HTMLSelectElement.prototype": true, + "HTMLTextAreaElement.prototype": true, + "Headers": true, + "ImageData": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.prototype.contains": true, + "PerformanceObserver": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "Zone": true, + "__SENTRY_DEBUG__": true, + "__rrMutationObserver": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "pageXOffset": true, + "pageYOffset": true, + "requestAnimationFrame": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub>tslib": true, - "@sentry/types": true, - "@sentry/utils": true - } - }, - "@sentry/browser>@sentry/core>@sentry/hub>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal": { - "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>tslib": { - "globals": { - "define": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true, + "browserify>process": true } }, "@sentry/integrations": { "globals": { - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setTimeout": true + "Request": true, + "__SENTRY_DEBUG__": true, + "console.log": true }, "packages": { - "@sentry/integrations>tslib": true, - "@sentry/types": true, "@sentry/utils": true, "localforage": true } }, - "@sentry/integrations>tslib": { - "globals": { - "define": true - } - }, "@sentry/utils": { "globals": { "CustomEvent": true, @@ -2564,22 +2610,22 @@ "Headers": true, "Request": true, "Response": true, + "TextEncoder": true, + "URL": true, "XMLHttpRequest.prototype": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, "clearTimeout": true, "console.error": true, "document": true, - "setTimeout": true + "new": true, + "setTimeout": true, + "target": true }, "packages": { - "@sentry/utils>tslib": true, "browserify>process": true } }, - "@sentry/utils>tslib": { - "globals": { - "define": true - } - }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -5148,6 +5194,12 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "superstruct": { + "globals": { + "console.warn": true, + "define": true + } + }, "terser>source-map-support>buffer-from": { "packages": { "browserify>buffer": true diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 494ce3924..06df25223 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -186,8 +186,14 @@ "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { @@ -214,7 +220,7 @@ "crypto": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { @@ -222,7 +228,19 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethersproject/abi": { @@ -1712,9 +1730,15 @@ }, "@metamask/rpc-methods": { "packages": { + "@metamask/browser-passworder": true, "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/utils": true, + "@metamask/permission-controller": true, + "@metamask/rpc-methods>@metamask/utils": true, + "@metamask/rpc-methods>nanoid": true, + "@metamask/snaps-ui": true, + "@metamask/snaps-utils": true, + "eth-rpc-errors": true, "superstruct": true } }, @@ -1723,6 +1747,18 @@ "crypto.getRandomValues": true } }, + "@metamask/rpc-methods>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -1847,6 +1883,81 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-ui": { + "packages": { + "@metamask/snaps-ui>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-ui>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "TextDecoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils>@metamask/utils": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>cron-parser": { + "packages": { + "browserify>browser-resolve": true, + "luxon": true + } + }, + "@metamask/snaps-utils>rfdc": { + "packages": { + "browserify>buffer": true + } + }, + "@metamask/snaps-utils>validate-npm-package-name": { + "packages": { + "@metamask/snaps-utils>validate-npm-package-name>builtins": true + } + }, + "@metamask/snaps-utils>validate-npm-package-name>builtins": { + "packages": { + "browserify>process": true, + "semver": true + } + }, "@metamask/subject-metadata-controller": { "packages": { "@metamask/subject-metadata-controller>@metamask/base-controller": true @@ -1948,85 +2059,110 @@ }, "@sentry/browser": { "globals": { + "TextDecoder": true, + "TextEncoder": true, "XMLHttpRequest": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "indexedDB.open": true, "setTimeout": true }, + "packages": { + "@sentry/browser>@sentry-internal/tracing": true, + "@sentry/browser>@sentry/core": true, + "@sentry/browser>@sentry/replay": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry-internal/tracing": { + "globals": { + "Headers": true, + "PerformanceObserver": true, + "Request": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "performance.getEntriesByType": true, + "removeEventListener": true + }, "packages": { "@sentry/browser>@sentry/core": true, - "@sentry/browser>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, "@sentry/browser>@sentry/core": { "globals": { + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, "clearInterval": true, - "setInterval": true + "clearTimeout": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal": true, - "@sentry/browser>@sentry/core>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, - "@sentry/browser>@sentry/core>@sentry/hub": { + "@sentry/browser>@sentry/replay": { "globals": { - "clearInterval": true, - "setInterval": true + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSSupportsRule": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLCanvasElement": true, + "HTMLElement.prototype": true, + "HTMLFormElement": true, + "HTMLImageElement": true, + "HTMLInputElement.prototype": true, + "HTMLOptionElement.prototype": true, + "HTMLSelectElement.prototype": true, + "HTMLTextAreaElement.prototype": true, + "Headers": true, + "ImageData": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.prototype.contains": true, + "PerformanceObserver": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "Zone": true, + "__SENTRY_DEBUG__": true, + "__rrMutationObserver": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "pageXOffset": true, + "pageYOffset": true, + "requestAnimationFrame": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub>tslib": true, - "@sentry/types": true, - "@sentry/utils": true - } - }, - "@sentry/browser>@sentry/core>@sentry/hub>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal": { - "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>tslib": { - "globals": { - "define": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true, + "browserify>process": true } }, "@sentry/integrations": { "globals": { - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setTimeout": true + "Request": true, + "__SENTRY_DEBUG__": true, + "console.log": true }, "packages": { - "@sentry/integrations>tslib": true, - "@sentry/types": true, "@sentry/utils": true, "localforage": true } }, - "@sentry/integrations>tslib": { - "globals": { - "define": true - } - }, "@sentry/utils": { "globals": { "CustomEvent": true, @@ -2038,22 +2174,22 @@ "Headers": true, "Request": true, "Response": true, + "TextEncoder": true, + "URL": true, "XMLHttpRequest.prototype": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, "clearTimeout": true, "console.error": true, "document": true, - "setTimeout": true + "new": true, + "setTimeout": true, + "target": true }, "packages": { - "@sentry/utils>tslib": true, "browserify>process": true } }, - "@sentry/utils>tslib": { - "globals": { - "define": true - } - }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -4490,6 +4626,12 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "superstruct": { + "globals": { + "console.warn": true, + "define": true + } + }, "uuid": { "globals": { "crypto": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 6f388ca84..8b3e07fcc 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -186,8 +186,14 @@ "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": true, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { @@ -214,7 +220,7 @@ "crypto": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true } }, "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { @@ -222,7 +228,19 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@ethersproject/abi": { @@ -1933,9 +1951,15 @@ }, "@metamask/rpc-methods": { "packages": { + "@metamask/browser-passworder": true, "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, - "@metamask/utils": true, + "@metamask/permission-controller": true, + "@metamask/rpc-methods>@metamask/utils": true, + "@metamask/rpc-methods>nanoid": true, + "@metamask/snaps-ui": true, + "@metamask/snaps-utils": true, + "eth-rpc-errors": true, "superstruct": true } }, @@ -1944,6 +1968,18 @@ "crypto.getRandomValues": true } }, + "@metamask/rpc-methods>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/rpc-methods>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2068,6 +2104,81 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-ui": { + "packages": { + "@metamask/snaps-ui>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-ui>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils": { + "globals": { + "TextDecoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true + }, + "packages": { + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils>@metamask/utils": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>cron-parser": { + "packages": { + "browserify>browser-resolve": true, + "luxon": true + } + }, + "@metamask/snaps-utils>rfdc": { + "packages": { + "browserify>buffer": true + } + }, + "@metamask/snaps-utils>validate-npm-package-name": { + "packages": { + "@metamask/snaps-utils>validate-npm-package-name>builtins": true + } + }, + "@metamask/snaps-utils>validate-npm-package-name>builtins": { + "packages": { + "browserify>process": true, + "semver": true + } + }, "@metamask/subject-metadata-controller": { "packages": { "@metamask/subject-metadata-controller>@metamask/base-controller": true @@ -2169,85 +2280,110 @@ }, "@sentry/browser": { "globals": { + "TextDecoder": true, + "TextEncoder": true, "XMLHttpRequest": true, + "__SENTRY_DEBUG__": true, + "__SENTRY_RELEASE__": true, + "indexedDB.open": true, "setTimeout": true }, + "packages": { + "@sentry/browser>@sentry-internal/tracing": true, + "@sentry/browser>@sentry/core": true, + "@sentry/browser>@sentry/replay": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry-internal/tracing": { + "globals": { + "Headers": true, + "PerformanceObserver": true, + "Request": true, + "__SENTRY_DEBUG__": true, + "addEventListener": true, + "performance.getEntriesByType": true, + "removeEventListener": true + }, "packages": { "@sentry/browser>@sentry/core": true, - "@sentry/browser>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, "@sentry/browser>@sentry/core": { "globals": { + "__SENTRY_DEBUG__": true, + "__SENTRY_TRACING__": true, "clearInterval": true, - "setInterval": true + "clearTimeout": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal": true, - "@sentry/browser>@sentry/core>tslib": true, - "@sentry/types": true, "@sentry/utils": true } }, - "@sentry/browser>@sentry/core>@sentry/hub": { + "@sentry/browser>@sentry/replay": { "globals": { - "clearInterval": true, - "setInterval": true + "Blob": true, + "CSSConditionRule": true, + "CSSGroupingRule": true, + "CSSMediaRule": true, + "CSSSupportsRule": true, + "DragEvent": true, + "Element": true, + "FormData": true, + "HTMLCanvasElement": true, + "HTMLElement.prototype": true, + "HTMLFormElement": true, + "HTMLImageElement": true, + "HTMLInputElement.prototype": true, + "HTMLOptionElement.prototype": true, + "HTMLSelectElement.prototype": true, + "HTMLTextAreaElement.prototype": true, + "Headers": true, + "ImageData": true, + "MouseEvent": true, + "MutationObserver": true, + "Node.prototype.contains": true, + "PerformanceObserver": true, + "TextEncoder": true, + "URL": true, + "URLSearchParams": true, + "Worker": true, + "Zone": true, + "__SENTRY_DEBUG__": true, + "__rrMutationObserver": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "innerHeight": true, + "innerWidth": true, + "location.href": true, + "pageXOffset": true, + "pageYOffset": true, + "requestAnimationFrame": true, + "setTimeout": true }, "packages": { - "@sentry/browser>@sentry/core>@sentry/hub>tslib": true, - "@sentry/types": true, - "@sentry/utils": true - } - }, - "@sentry/browser>@sentry/core>@sentry/hub>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal": { - "packages": { - "@sentry/browser>@sentry/core>@sentry/hub": true, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": true - } - }, - "@sentry/browser>@sentry/core>@sentry/minimal>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>@sentry/core>tslib": { - "globals": { - "define": true - } - }, - "@sentry/browser>tslib": { - "globals": { - "define": true + "@sentry/browser>@sentry/core": true, + "@sentry/utils": true, + "browserify>process": true } }, "@sentry/integrations": { "globals": { - "clearTimeout": true, - "console.error": true, - "console.log": true, - "setTimeout": true + "Request": true, + "__SENTRY_DEBUG__": true, + "console.log": true }, "packages": { - "@sentry/integrations>tslib": true, - "@sentry/types": true, "@sentry/utils": true, "localforage": true } }, - "@sentry/integrations>tslib": { - "globals": { - "define": true - } - }, "@sentry/utils": { "globals": { "CustomEvent": true, @@ -2259,22 +2395,22 @@ "Headers": true, "Request": true, "Response": true, + "TextEncoder": true, + "URL": true, "XMLHttpRequest.prototype": true, + "__SENTRY_BROWSER_BUNDLE__": true, + "__SENTRY_DEBUG__": true, "clearTimeout": true, "console.error": true, "document": true, - "setTimeout": true + "new": true, + "setTimeout": true, + "target": true }, "packages": { - "@sentry/utils>tslib": true, "browserify>process": true } }, - "@sentry/utils>tslib": { - "globals": { - "define": true - } - }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -4711,6 +4847,12 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "superstruct": { + "globals": { + "console.warn": true, + "define": true + } + }, "uuid": { "globals": { "crypto": true, diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 89e2820e1..a615563a8 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -168,9 +168,13 @@ }, "@babel/eslint-parser": { "builtin": { - "path": true + "module": true, + "path": true, + "worker_threads": true }, "globals": { + "__dirname": true, + "process.cwd": true, "process.versions": true }, "packages": { @@ -980,7 +984,6 @@ "packages": { "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>are-we-there-yet": true, "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge": true, - "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>console-control-strings": true, "@storybook/react>@storybook/node-logger>npmlog>console-control-strings": true, "nyc>yargs>set-blocking": true } @@ -1009,9 +1012,6 @@ "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>aproba": true, "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width": true, "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi": true, - "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>console-control-strings": true, - "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>gauge>has-unicode": true, - "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>gauge>wide-align": true, "@storybook/react>@storybook/node-logger>npmlog>console-control-strings": true, "@storybook/react>@storybook/node-logger>npmlog>gauge>has-unicode": true, "@storybook/react>@storybook/node-logger>npmlog>gauge>wide-align": true, @@ -1049,17 +1049,18 @@ "globals": { "__dirname": true, "__filename.slice": true, + "console.warn": true, "process.cwd": true, "setTimeout": true }, "packages": { "@lavamoat/lavapack>combine-source-map": true, - "@lavamoat/lavapack>lavamoat-core": true, + "@lavamoat/lavapack>convert-source-map": true, "@lavamoat/lavapack>readable-stream": true, "@lavamoat/lavapack>umd": true, "browserify>JSONStream": true, "lavamoat>json-stable-stringify": true, - "nyc>convert-source-map": true, + "lavamoat>lavamoat-core": true, "through2": true } }, @@ -1086,26 +1087,12 @@ "@lavamoat/lavapack>combine-source-map>inline-source-map>source-map": true } }, - "@lavamoat/lavapack>lavamoat-core": { - "builtin": { - "events": true, - "fs.existsSync": true, - "fs.readFileSync": true, - "fs.writeFileSync": true, - "path.extname": true, - "path.join": true - }, + "@lavamoat/lavapack>convert-source-map": { "globals": { - "__dirname": true, - "console.error": true, - "console.warn": true, - "define": true - }, - "packages": { - "lavamoat>json-stable-stringify": true, - "lavamoat>lavamoat-core>merge-deep": true, - "lavamoat>lavamoat-tofu": true, - "nyc>process-on-spawn>fromentries": true + "Buffer": true, + "atob": true, + "btoa": true, + "value": true } }, "@lavamoat/lavapack>readable-stream": { @@ -1149,21 +1136,6 @@ "string.prototype.matchall>side-channel": true } }, - "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>gauge>has-unicode": { - "builtin": { - "os.type": true - }, - "globals": { - "process.env.LANG": true, - "process.env.LC_ALL": true, - "process.env.LC_CTYPE": true - } - }, - "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>gauge>wide-align": { - "packages": { - "yargs>string-width": true - } - }, "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": { "builtin": { "os.homedir": true @@ -2884,12 +2856,12 @@ "eslint-plugin-react>estraverse": true, "eslint-plugin-react>jsx-ast-utils": true, "eslint-plugin-react>object.entries": true, + "eslint-plugin-react>object.fromentries": true, "eslint-plugin-react>object.hasown": true, "eslint-plugin-react>object.values": true, "eslint-plugin-react>resolve": true, "eslint-plugin-react>semver": true, "eslint>minimatch": true, - "lavamoat>object.fromentries": true, "prop-types": true, "string.prototype.matchall": true } @@ -2949,6 +2921,13 @@ "string.prototype.matchall>es-abstract": true } }, + "eslint-plugin-react>object.fromentries": { + "packages": { + "globalthis>define-properties": true, + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>es-abstract": true + } + }, "eslint-plugin-react>object.hasown": { "packages": { "string.prototype.matchall>es-abstract": true @@ -3010,6 +2989,9 @@ "util": true }, "globals": { + "__filename": true, + "process.cwd": true, + "process.emitWarning": true, "process.platform": true }, "packages": { @@ -4895,20 +4877,9 @@ }, "packages": { "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": true, - "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": true, "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": { - "builtin": { - "os.homedir": true - }, - "globals": { - "process.env": true, - "process.getuid": true, - "process.platform": true - } - }, "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": { "globals": { "process.env.SystemRoot": true, @@ -4930,34 +4901,9 @@ "setTimeout": true }, "packages": { - "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob": true, "nyc>glob": true } }, - "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob": { - "builtin": { - "assert": true, - "events.EventEmitter": true, - "fs": true, - "path.join": true, - "path.resolve": true, - "util": true - }, - "globals": { - "console.error": true, - "process.cwd": true, - "process.nextTick": true, - "process.platform": true - }, - "packages": { - "eslint>minimatch": true, - "gulp-watch>path-is-absolute": true, - "nyc>glob>fs.realpath": true, - "nyc>glob>inflight": true, - "pump>once": true, - "pumpify>inherits": true - } - }, "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": { "globals": { "console": true, @@ -6203,8 +6149,8 @@ "setTimeout": true }, "packages": { + "@lavamoat/lavapack": true, "duplexify": true, - "lavamoat-browserify>@lavamoat/lavapack": true, "lavamoat-browserify>browser-resolve": true, "lavamoat-browserify>concat-stream": true, "lavamoat-browserify>readable-stream": true, @@ -6214,37 +6160,6 @@ "lavamoat>lavamoat-core": true } }, - "lavamoat-browserify>@lavamoat/lavapack": { - "builtin": { - "assert": true, - "buffer.Buffer.from": true, - "fs.promises.readFile": true, - "fs.promises.writeFile": true, - "fs.readFileSync": true, - "path.join": true, - "path.relative": true - }, - "globals": { - "__dirname": true, - "process.cwd": true, - "setTimeout": true - }, - "packages": { - "@lavamoat/lavapack>combine-source-map": true, - "@lavamoat/lavapack>umd": true, - "browserify>JSONStream": true, - "lavamoat-browserify>@lavamoat/lavapack>through2": true, - "lavamoat-browserify>readable-stream": true, - "lavamoat>json-stable-stringify": true, - "lavamoat>lavamoat-core": true, - "nyc>convert-source-map": true - } - }, - "lavamoat-browserify>@lavamoat/lavapack>through2": { - "packages": { - "lavamoat-browserify>readable-stream": true - } - }, "lavamoat-browserify>browser-resolve": { "builtin": { "fs.readFile": true, @@ -6381,8 +6296,7 @@ "packages": { "lavamoat>json-stable-stringify": true, "lavamoat>lavamoat-core>merge-deep": true, - "lavamoat>lavamoat-tofu": true, - "nyc>process-on-spawn>fromentries": true + "lavamoat>lavamoat-tofu": true } }, "lavamoat>lavamoat-core>merge-deep": { @@ -6458,13 +6372,6 @@ "depcheck>@babel/traverse": true } }, - "lavamoat>object.fromentries": { - "packages": { - "globalthis>define-properties": true, - "string.prototype.matchall>call-bind": true, - "string.prototype.matchall>es-abstract": true - } - }, "lodash": { "globals": { "define": true @@ -8678,6 +8585,12 @@ "jsdom>request>is-typedarray": true } }, + "superstruct": { + "globals": { + "console.warn": true, + "define": true + } + }, "terser": { "globals": { "Buffer": true, @@ -9036,6 +8949,7 @@ }, "globals": { "Error": true, + "__dirname": true, "console": true, "process": true }, @@ -9050,6 +8964,9 @@ } }, "yargs>cliui": { + "globals": { + "process": true + }, "packages": { "eslint>strip-ansi": true, "yargs>cliui>wrap-ansi": true, diff --git a/package.json b/package.json index fae1202fe..ae531d703 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "fitness-functions": "ts-node development/fitness-functions/index.ts", "generate-beta-commit": "node ./development/generate-beta-commit.js", "validate-branch-name": "validate-branch-name", - "label-prs": "ts-node ./.github/scripts/label-prs.ts", "add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts" }, "resolutions": { @@ -226,11 +225,11 @@ "@metamask/address-book-controller": "^3.0.0", "@metamask/announcement-controller": "^4.0.0", "@metamask/approval-controller": "^3.1.0", - "@metamask/assets-controllers": "^9.1.0", + "@metamask/assets-controllers": "^9.2.0", "@metamask/base-controller": "^3.0.0", "@metamask/browser-passworder": "^4.1.0", "@metamask/contract-metadata": "^2.3.1", - "@metamask/controller-utils": "^4.0.0", + "@metamask/controller-utils": "^4.0.1", "@metamask/design-tokens": "^1.9.0", "@metamask/desktop": "^0.3.0", "@metamask/eth-json-rpc-middleware": "^11.0.0", @@ -251,31 +250,31 @@ "@metamask/permission-controller": "^4.0.0", "@metamask/phishing-controller": "^3.0.0", "@metamask/post-message-stream": "^6.0.0", - "@metamask/providers": "^10.2.1", + "@metamask/providers": "^11.1.0", "@metamask/rate-limit-controller": "^3.0.0", - "@metamask/rpc-methods": "^0.32.2", - "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.34.0-flask.1", + "@metamask/rpc-methods": "^1.0.0-prerelease.1", + "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.35.2-flask.1", "@metamask/safe-event-emitter": "^2.0.0", "@metamask/scure-bip39": "^2.0.3", "@metamask/signature-controller": "^4.0.1", "@metamask/slip44": "^3.0.0", "@metamask/smart-transactions-controller": "^3.1.0", - "@metamask/snaps-controllers": "^0.32.2", - "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.34.0-flask.1", - "@metamask/snaps-ui": "^0.32.2", - "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.34.0-flask.1", - "@metamask/snaps-utils": "^0.32.2", - "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.34.0-flask.1", + "@metamask/snaps-controllers": "^1.0.0-prerelease.1", + "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.35.2-flask.1", + "@metamask/snaps-ui": "^1.0.0-prerelease.1", + "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.35.2-flask.1", + "@metamask/snaps-utils": "^1.0.0-prerelease.1", + "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.35.2-flask.1", "@metamask/subject-metadata-controller": "^2.0.0", "@metamask/utils": "^5.0.0", "@ngraveio/bc-ur": "^1.1.6", "@popperjs/core": "^2.4.0", "@reduxjs/toolkit": "^1.6.2", "@segment/loosely-validate-event": "^2.0.0", - "@sentry/browser": "^6.0.0", - "@sentry/integrations": "^6.0.0", - "@sentry/types": "^6.0.1", - "@sentry/utils": "^6.0.1", + "@sentry/browser": "^7.53.0", + "@sentry/integrations": "^7.53.0", + "@sentry/types": "^7.53.0", + "@sentry/utils": "^7.53.0", "@truffle/codec": "^0.14.12", "@truffle/decoder": "^5.3.5", "@zxing/browser": "^0.0.10", @@ -371,7 +370,7 @@ "@babel/register": "^7.5.5", "@ethersproject/bignumber": "^5.7.0", "@lavamoat/allow-scripts": "^2.0.3", - "@lavamoat/lavapack": "^5.0.0", + "@lavamoat/lavapack": "^5.2.0", "@metamask/auto-changelog": "^2.1.0", "@metamask/eslint-config": "^9.0.0", "@metamask/eslint-config-jest": "^9.0.0", @@ -380,7 +379,7 @@ "@metamask/eslint-config-typescript": "^9.0.1", "@metamask/forwarder": "^1.1.0", "@metamask/phishing-warning": "^2.1.0", - "@metamask/test-dapp": "^7.0.0", + "@metamask/test-dapp": "^7.0.1", "@sentry/cli": "^1.58.0", "@storybook/addon-a11y": "^7.0.11", "@storybook/addon-actions": "^7.0.11", @@ -466,7 +465,7 @@ "fast-glob": "^3.2.2", "fs-extra": "^8.1.0", "ganache": "^v7.0.4", - "geckodriver": "^3.2.0", + "geckodriver": "^4.0.4", "gh-pages": "^5.0.0", "globby": "^11.0.4", "gulp": "^4.0.2", @@ -494,8 +493,8 @@ "jsdom": "^11.2.0", "junit-report-merger": "^4.0.0", "koa": "^2.7.0", - "lavamoat": "^6.3.0", - "lavamoat-browserify": "^15.5.0", + "lavamoat": "^7.1.0", + "lavamoat-browserify": "^15.7.0", "lavamoat-viz": "^6.0.9", "lockfile-lint": "^4.9.6", "loose-envify": "^1.4.0", diff --git a/shared/constants/security-provider.ts b/shared/constants/security-provider.ts new file mode 100644 index 000000000..1862e8181 --- /dev/null +++ b/shared/constants/security-provider.ts @@ -0,0 +1,13 @@ +/** + * @typedef {object} SecurityProviderMessageSeverity + * @property {0} NOT_MALICIOUS - Indicates message is not malicious + * @property {1} MALICIOUS - Indicates message is malicious + * @property {2} NOT_SAFE - Indicates message is not safe + */ + +/** @type {SecurityProviderMessageSeverity} */ +export const SECURITY_PROVIDER_MESSAGE_SEVERITY = { + NOT_MALICIOUS: 0, + MALICIOUS: 1, + NOT_SAFE: 2, +}; diff --git a/shared/constants/snaps/permissions.ts b/shared/constants/snaps/permissions.ts index 2a5b3f1f6..c77d4a3c4 100644 --- a/shared/constants/snaps/permissions.ts +++ b/shared/constants/snaps/permissions.ts @@ -12,14 +12,19 @@ export const EndowmentPermissions = Object.freeze({ // Methods / permissions in external packages that we are temporarily excluding. export const ExcludedSnapPermissions = Object.freeze({ + // TODO: Enable in Flask + ///: BEGIN:ONLY_INCLUDE_IN(build-main,build-flask) + snap_manageAccounts: + 'This permission is still in development and therefore not available.', + ///: END:ONLY_INCLUDE_IN eth_accounts: 'eth_accounts is disabled. For more information please see https://github.com/MetaMask/snaps-monorepo/issues/990.', }); export const ExcludedSnapEndowments = Object.freeze({ + ///: BEGIN:ONLY_INCLUDE_IN(build-main) 'endowment:keyring': 'This endowment is still in development therefore not available.', - ///: BEGIN:ONLY_INCLUDE_IN(build-main) 'endowment:long-running': 'endowment:long-running is deprecated. For more information please see https://github.com/MetaMask/snaps-monorepo/issues/945.', ///: END:ONLY_INCLUDE_IN diff --git a/shared/modules/security-provider.utils.test.ts b/shared/modules/security-provider.utils.test.ts new file mode 100644 index 000000000..c32ea7d38 --- /dev/null +++ b/shared/modules/security-provider.utils.test.ts @@ -0,0 +1,30 @@ +import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../constants/security-provider'; +import { isSuspiciousResponse } from './security-provider.utils'; + +describe('security-provider util', () => { + describe('isSuspiciousResponse', () => { + it('should return false if the response does not exist', () => { + const result = isSuspiciousResponse(undefined); + expect(result).toBeFalsy(); + }); + + it('should return false when flagAsDangerous exists and is not malicious', () => { + const result = isSuspiciousResponse({ + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS, + }); + expect(result).toBeFalsy(); + }); + + it('should return true when flagAsDangerous exists and is malicious or not safe', () => { + const result = isSuspiciousResponse({ + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_SAFE, + }); + expect(result).toBeTruthy(); + }); + + it('should return true if the response exists but is empty', () => { + const result = isSuspiciousResponse({}); + expect(result).toBeTruthy(); + }); + }); +}); diff --git a/shared/modules/security-provider.utils.ts b/shared/modules/security-provider.utils.ts new file mode 100644 index 000000000..3c341b4aa --- /dev/null +++ b/shared/modules/security-provider.utils.ts @@ -0,0 +1,19 @@ +import { Json } from '@metamask/utils'; +import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../constants/security-provider'; + +export function isSuspiciousResponse( + securityProviderResponse: Record | undefined, +): boolean { + if (!securityProviderResponse) { + return false; + } + + const isFlagged = + securityProviderResponse.flagAsDangerous !== undefined && + securityProviderResponse.flagAsDangerous !== + SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS; + + const isNotVerified = Object.keys(securityProviderResponse).length === 0; + + return isFlagged || isNotVerified; +} diff --git a/shared/notifications/institutional/index.js b/shared/notifications/institutional/index.js new file mode 100644 index 000000000..78b93399e --- /dev/null +++ b/shared/notifications/institutional/index.js @@ -0,0 +1,35 @@ +export const UI_INSTITUTIONAL_NOTIFICATIONS = { + 1: { + id: 11, + date: '2022-08-28', + image: { + src: 'images/portfolio.svg', + }, + hideDate: true, + descriptionInBullets: true, + }, +}; + +export const getTranslatedInstitutionalUINotifications = (t, locale) => { + const formattedLocale = locale.replace('_', '-'); + return { + 1: { + ...UI_INSTITUTIONAL_NOTIFICATIONS[11], + title: 'Portfolio dashboard', + description: [ + 'Portfolio snapshots', + 'Filtering by account and network', + 'Sector and protocol allocation', + 'Improved navigation', + ], + date: new Intl.DateTimeFormat(formattedLocale).format( + new Date(UI_INSTITUTIONAL_NOTIFICATIONS[11].date), + ), + customButton: { + name: 'mmi-portfolio', + text: t('viewPortfolioDashboard'), + logo: true, + }, + }, + }; +}; diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 040ed2166..42a8ba657 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -674,6 +674,19 @@ async function terminateServiceWorker(driver) { await driver.closeWindowHandle(serviceWorkerTab); } +/** + * This method assumes the extension is open, the dapp is open and waits for a + * third window handle to open (the notification window). Once it does it + * switches to the new window. + * + * @param {WebDriver} driver + */ +async function switchToNotificationWindow(driver) { + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles); +} + module.exports = { DAPP_URL, DAPP_ONE_URL, @@ -720,4 +733,5 @@ module.exports = { switchToWindow, sleepSeconds, terminateServiceWorker, + switchToNotificationWindow, }; diff --git a/test/e2e/metrics/permissions-approved.spec.js b/test/e2e/metrics/permissions-approved.spec.js new file mode 100644 index 000000000..df479b952 --- /dev/null +++ b/test/e2e/metrics/permissions-approved.spec.js @@ -0,0 +1,121 @@ +const { strict: assert } = require('assert'); +const { + defaultGanacheOptions, + switchToNotificationWindow, + withFixtures, + openDapp, + unlockWallet, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); + +/** + * mocks the segment api multiple times for specific payloads that we expect to + * see when these tests are run. In this case we are looking for + * 'Permissions Requested' and 'Permissions Received'. Do not use the constants + * from the metrics constants files, because if these change we want a strong + * indicator to our data team that the shape of data will change. + * + * @param {import('mockttp').Mockttp} mockServer + * @returns {Promise[]} + */ +async function mockSegment(mockServer) { + return [ + await mockServer + .forPost('https://api.segment.io/v1/batch') + .withJsonBodyIncluding({ + batch: [{ type: 'track', event: 'Permissions Requested' }], + }) + .thenCallback(() => { + return { + statusCode: 200, + }; + }), + await mockServer + .forPost('https://api.segment.io/v1/batch') + .withJsonBodyIncluding({ + batch: [{ type: 'track', event: 'Permissions Approved' }], + }) + .thenCallback(() => { + return { + statusCode: 200, + }; + }), + ]; +} + +/** + * This method handles getting the mocked requests to the segment server + * + * @param {WebDriver} driver + * @param {import('mockttp').Mockttp} mockedEndpoints + * @returns {import('mockttp/dist/pluggable-admin').MockttpClientResponse[]} + */ +async function getEventPayloads(driver, mockedEndpoints) { + await driver.wait(async () => { + let isPending = true; + for (const mockedEndpoint of mockedEndpoints) { + isPending = await mockedEndpoint.isPending(); + } + return isPending === false; + }, 10000); + const mockedRequests = []; + for (const mockedEndpoint of mockedEndpoints) { + mockedRequests.push(...(await mockedEndpoint.getSeenRequests())); + } + return mockedRequests.map((req) => req.body.json.batch).flat(); +} + +describe('Permissions Approved Event', function () { + it('Successfully tracked when connecting to dapp', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + defaultGanacheOptions, + title: this.test.title, + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await switchToNotificationWindow(driver); + await driver.clickElement({ + text: 'Next', + tag: 'button', + }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { + method: 'eth_requestAccounts', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + assert.deepStrictEqual(events[1].properties, { + method: 'eth_requestAccounts', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + }, + ); + }); +}); diff --git a/test/e2e/metrics/signature-approved.spec.js b/test/e2e/metrics/signature-approved.spec.js new file mode 100644 index 000000000..88a61131a --- /dev/null +++ b/test/e2e/metrics/signature-approved.spec.js @@ -0,0 +1,323 @@ +const { strict: assert } = require('assert'); +const { + defaultGanacheOptions, + switchToNotificationWindow, + withFixtures, + regularDelayMs, + openDapp, + unlockWallet, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); + +/** + * mocks the segment api multiple times for specific payloads that we expect to + * see when these tests are run. In this case we are looking for + * 'Signature Requested' and 'Signature Received'. Do not use the constants + * from the metrics constants files, because if these change we want a strong + * indicator to our data team that the shape of data will change. + * + * @param {import('mockttp').Mockttp} mockServer + * @returns {Promise[]} + */ +async function mockSegment(mockServer) { + return [ + await mockServer + .forPost('https://api.segment.io/v1/batch') + .withJsonBodyIncluding({ + batch: [{ type: 'track', event: 'Signature Requested' }], + }) + .thenCallback(() => { + return { + statusCode: 200, + }; + }), + await mockServer + .forPost('https://api.segment.io/v1/batch') + .withJsonBodyIncluding({ + batch: [{ type: 'track', event: 'Signature Approved' }], + }) + .thenCallback(() => { + return { + statusCode: 200, + }; + }), + ]; +} + +/** + * Some signing methods have extra security that requires the user to click a + * button to validate that they have verified the details. This method handles + * performing the necessary steps to click that button. + * + * @param {WebDriver} driver + */ +async function validateContractDetails(driver) { + const verifyContractDetailsButton = await driver.findElement( + '.signature-request-content__verify-contract-details', + ); + + verifyContractDetailsButton.click(); + await driver.clickElement({ text: 'Got it', tag: 'button' }); + + // Approve signing typed data + await driver.clickElement('[data-testid="signature-request-scroll-button"]'); + await driver.delay(regularDelayMs); +} + +/** + * This method handles clicking the sign button on signature confrimation + * screen. + * + * @param {WebDriver} driver + */ +async function clickSignOnSignatureConfirmation(driver) { + await driver.clickElement({ text: 'Sign', tag: 'button' }); + await driver.waitUntilXWindowHandles(2); + await driver.getAllWindowHandles(); +} + +/** + * This method handles getting the mocked requests to the segment server + * + * @param {WebDriver} driver + * @param {import('mockttp').Mockttp} mockedEndpoints + * @returns {import('mockttp/dist/pluggable-admin').MockttpClientResponse[]} + */ +async function getEventPayloads(driver, mockedEndpoints) { + await driver.wait(async () => { + let isPending = true; + for (const mockedEndpoint of mockedEndpoints) { + isPending = await mockedEndpoint.isPending(); + } + return isPending === false; + }, 10000); + const mockedRequests = []; + for (const mockedEndpoint of mockedEndpoints) { + mockedRequests.push(...(await mockedEndpoint.getSeenRequests())); + } + return mockedRequests.map((req) => req.body.json.batch).flat(); +} + +describe('Signature Approved Event', function () { + it('Successfully tracked for signTypedData_v4', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + defaultGanacheOptions, + title: this.test.title, + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + // creates a sign typed data signature request + await driver.clickElement('#signTypedDataV4'); + await switchToNotificationWindow(driver); + await validateContractDetails(driver); + await clickSignOnSignatureConfirmation(driver); + const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { + signature_type: 'eth_signTypedData_v4', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + assert.deepStrictEqual(events[1].properties, { + signature_type: 'eth_signTypedData_v4', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + }, + ); + }); + it('Successfully tracked for signTypedData_v3', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + defaultGanacheOptions, + title: this.test.title, + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + // creates a sign typed data signature request + await driver.clickElement('#signTypedDataV3'); + await switchToNotificationWindow(driver); + await validateContractDetails(driver); + await clickSignOnSignatureConfirmation(driver); + const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { + signature_type: 'eth_signTypedData_v3', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + assert.deepStrictEqual(events[1].properties, { + signature_type: 'eth_signTypedData_v3', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + }, + ); + }); + it('Successfully tracked for signTypedData', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + defaultGanacheOptions, + title: this.test.title, + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + // creates a sign typed data signature request + await driver.clickElement('#signTypedData'); + await switchToNotificationWindow(driver); + await clickSignOnSignatureConfirmation(driver); + const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { + signature_type: 'eth_signTypedData', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + assert.deepStrictEqual(events[1].properties, { + signature_type: 'eth_signTypedData', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + }, + ); + }); + it('Successfully tracked for personalSign', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + defaultGanacheOptions, + title: this.test.title, + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + // creates a sign typed data signature request + await driver.clickElement('#personalSign'); + await switchToNotificationWindow(driver); + await clickSignOnSignatureConfirmation(driver); + const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { + signature_type: 'personal_sign', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + assert.deepStrictEqual(events[1].properties, { + signature_type: 'personal_sign', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + }, + ); + }); + it('Successfully tracked for eth_sign', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + disabledRpcMethodPreferences: { + eth_sign: true, + }, + }) + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .build(), + defaultGanacheOptions, + title: this.test.title, + testSpecificMock: mockSegment, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + // creates a sign typed data signature request + await driver.clickElement('#ethSign'); + await switchToNotificationWindow(driver); + await driver.delay(regularDelayMs); + await driver.clickElement('[data-testid="page-container-footer-next"]'); + await driver.clickElement( + '.signature-request-warning__footer__sign-button', + ); + const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { + signature_type: 'eth_sign', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + assert.deepStrictEqual(events[1].properties, { + signature_type: 'eth_sign', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + }); + }, + ); + }); +}); diff --git a/test/e2e/nft/import-erc1155.spec.js b/test/e2e/nft/import-erc1155.spec.js index 598b70182..e27155e3c 100644 --- a/test/e2e/nft/import-erc1155.spec.js +++ b/test/e2e/nft/import-erc1155.spec.js @@ -35,7 +35,7 @@ describe('Import ERC1155 NFT', function () { // After login, go to NFTs tab, open the import NFT/ERC1155 form await driver.clickElement('[data-testid="home__nfts-tab"]'); - await driver.clickElement({ text: 'Import NFTs', tag: 'a' }); + await driver.clickElement({ text: 'Import NFT', tag: 'button' }); // Enter a valid NFT that belongs to user and check success message appears await driver.fill('[data-testid="address"]', contractAddress); @@ -83,7 +83,7 @@ describe('Import ERC1155 NFT', function () { // After login, go to NFTs tab, open the import NFT form await driver.clickElement('[data-testid="home__nfts-tab"]'); - await driver.clickElement({ text: 'Import NFTs', tag: 'a' }); + await driver.clickElement({ text: 'Import NFT', tag: 'button' }); // Enter an NFT that not belongs to user with a valid address and an invalid token id await driver.fill('[data-testid="address"]', contractAddress); diff --git a/test/e2e/nft/import-nft.spec.js b/test/e2e/nft/import-nft.spec.js index 60e375b5c..373edc70a 100644 --- a/test/e2e/nft/import-nft.spec.js +++ b/test/e2e/nft/import-nft.spec.js @@ -35,7 +35,7 @@ describe('Import NFT', function () { // After login, go to NFTs tab, open the import NFT form await driver.clickElement('[data-testid="home__nfts-tab"]'); - await driver.clickElement({ text: 'Import NFTs', tag: 'a' }); + await driver.clickElement({ text: 'Import NFT', tag: 'button' }); // Enter a valid NFT that belongs to user and check success message appears await driver.fill('[data-testid="address"]', contractAddress); @@ -82,7 +82,7 @@ describe('Import NFT', function () { // After login, go to NFTs tab, open the import NFT form await driver.clickElement('[data-testid="home__nfts-tab"]'); - await driver.clickElement({ text: 'Import NFTs', tag: 'a' }); + await driver.clickElement({ text: 'Import NFT', tag: 'button' }); // Enter an NFT that not belongs to user with a valid address and an invalid token id await driver.fill('[data-testid="address"]', contractAddress); diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index af5849b37..d045233e5 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -72,6 +72,7 @@ async function main() { ...(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'), ]; diff --git a/test/e2e/tests/address-book.spec.js b/test/e2e/tests/address-book.spec.js index d730a4fc9..19f526fa2 100644 --- a/test/e2e/tests/address-book.spec.js +++ b/test/e2e/tests/address-book.spec.js @@ -171,7 +171,7 @@ describe('Address Book', function () { await driver.clickElement({ text: 'Test Name 1', tag: 'p' }); await driver.clickElement({ text: 'Edit', tag: 'button' }); - await driver.clickElement({ text: 'Delete account', tag: 'a' }); + await driver.clickElement({ text: 'Delete contact', tag: 'a' }); // it checks if account is deleted const contact = await driver.findElement( '.send__select-recipient-wrapper__group-item', diff --git a/test/e2e/tests/encrypt-decrypt.spec.js b/test/e2e/tests/encrypt-decrypt.spec.js index 47e453de9..59e87648e 100644 --- a/test/e2e/tests/encrypt-decrypt.spec.js +++ b/test/e2e/tests/encrypt-decrypt.spec.js @@ -2,6 +2,59 @@ const { strict: assert } = require('assert'); const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); +async function getEncryptionKey(driver) { + await driver.clickElement('#getEncryptionKeyButton'); + await driver.waitUntilXWindowHandles(3); + let windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles); + await driver.waitForSelector({ + css: '.request-encryption-public-key__header__text', + text: 'Request encryption public key', + }); + await driver.clickElement({ text: 'Provide', tag: 'button' }); + await driver.waitUntilXWindowHandles(2); + windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + return await driver.findElement('#encryptionKeyDisplay'); +} + +async function encryptMessage(driver, message) { + await driver.fill('#encryptMessageInput', message); + await driver.clickElement('#encryptButton'); + await driver.waitForSelector({ + css: '#ciphertextDisplay', + text: '0x', + }); +} + +async function decryptMessage(driver) { + await driver.clickElement('#decryptButton'); + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles); + await driver.waitForSelector({ + css: '.request-decrypt-message__header__text', + text: 'Decrypt request', + }); +} + +async function verifyDecryptedMessageMM(driver, message) { + await driver.clickElement({ text: 'Decrypt message', tag: 'div' }); + const notificationMessage = await driver.isElementPresent({ + text: message, + tag: 'div', + }); + assert.equal(notificationMessage, true); + await driver.clickElement({ text: 'Decrypt', tag: 'button' }); +} + +async function verifyDecryptedMessageDapp(driver, message) { + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + const clearTextLabel = await driver.findElement('#cleartextDisplay'); + assert.equal(await clearTextLabel.getText(), message); +} + describe('Encrypt Decrypt', function () { const ganacheOptions = { accounts: [ @@ -31,66 +84,85 @@ describe('Encrypt Decrypt', function () { await openDapp(driver); // ------ Get Encryption key ------ - await driver.clickElement('#getEncryptionKeyButton'); - await driver.waitUntilXWindowHandles(3); - let windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - await driver.waitForSelector({ - css: '.request-encryption-public-key__header__text', - text: 'Request encryption public key', - }); - await driver.clickElement({ text: 'Provide', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - const encryptionKeyLabel = await driver.findElement( - '#encryptionKeyDisplay', - ); + const encryptionKeyLabel = await getEncryptionKey(driver); assert.equal(await encryptionKeyLabel.getText(), encryptionKey); // ------ Encrypt ------ - await driver.fill('#encryptMessageInput', message); - await driver.clickElement('#encryptButton'); - await driver.waitForSelector({ - css: '#ciphertextDisplay', - text: '0x', - }); + await encryptMessage(driver, message); // ------ Decrypt ------ - await driver.clickElement('#decryptButton'); - await driver.waitUntilXWindowHandles(3); - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - await driver.waitForSelector({ - css: '.request-decrypt-message__header__text', - text: 'Decrypt request', - }); + await decryptMessage(driver); + // Account balance is converted properly const decryptAccountBalanceLabel = await driver.findElement( '.request-decrypt-message__balance-value', ); assert.equal(await decryptAccountBalanceLabel.getText(), '25 ETH'); // Verify message in MetaMask Notification - await driver.clickElement({ text: 'Decrypt message', tag: 'div' }); - const notificationMessage = await driver.isElementPresent({ - text: message, - tag: 'div', - }); - assert.equal(notificationMessage, true); - await driver.clickElement({ text: 'Decrypt', tag: 'button' }); + await verifyDecryptedMessageMM(driver, message); // Verify message in Test Dapp await driver.waitUntilXWindowHandles(2); - windowHandles = await driver.getAllWindowHandles(); + await verifyDecryptedMessageDapp(driver, message); + }, + ); + }); + + it('should encrypt and decrypt multiple messages', async function () { + const message2 = 'Hello, Alice!'; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .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); + await openDapp(driver); + + // ------ Get Encryption key ------ + const encryptionKeyLabel = await getEncryptionKey(driver); + assert.equal(await encryptionKeyLabel.getText(), encryptionKey); + + // ------ Encrypt Message 1------ + await encryptMessage(driver, message); + + // ------ Decrypt Message 1 ------ + await decryptMessage(driver); + + // ------ Switch to Dapp ------ + let windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - const clearTextLabel = await driver.findElement('#cleartextDisplay'); - assert.equal(await clearTextLabel.getText(), message); + + // ------ Encrypt Message 2------ + await encryptMessage(driver, message2); + + // ------ Decrypt Message 2 ------ + await decryptMessage(driver); + + // Verify message 1 in MetaMask Notification + await verifyDecryptedMessageMM(driver, message); + + // Verify message 1 in Test Dapp + await verifyDecryptedMessageDapp(driver, message); + + // ------ Switch to Dapp ------ + windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + + // Verify message 2 in MetaMask Notification + await verifyDecryptedMessageMM(driver, message2); + + // Verify message 2 in Test Dapp + await verifyDecryptedMessageDapp(driver, message2); }, ); }); diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index f70317916..194578558 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -5,7 +5,8 @@ const FixtureBuilder = require('../fixture-builder'); describe('Sentry errors', function () { async function mockSentry(mockServer) { return await mockServer - .forPost('https://sentry.io/api/0000000/store/') + .forPost('https://sentry.io/api/0000000/envelope/') + .withBodyIncluding('Test Error') .thenCallback(() => { return { statusCode: 200, @@ -48,7 +49,8 @@ describe('Sentry errors', function () { return isPending === false; }, 10000); const [mockedRequest] = await mockedEndpoint.getSeenRequests(); - const mockJsonBody = mockedRequest.body.json; + const mockTextBody = mockedRequest.body.text.split('\n'); + const mockJsonBody = JSON.parse(mockTextBody[2]); const { level, extra } = mockJsonBody; const [{ type, value }] = mockJsonBody.exception.values; const { participateInMetaMetrics } = extra.appState.store.metamask; diff --git a/test/e2e/tests/eth-sign.spec.js b/test/e2e/tests/eth-sign.spec.js index ce8da87bb..26d585ea5 100644 --- a/test/e2e/tests/eth-sign.spec.js +++ b/test/e2e/tests/eth-sign.spec.js @@ -5,6 +5,7 @@ const { DAPP_URL, defaultGanacheOptions, unlockWallet, + regularDelayMs, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -36,7 +37,7 @@ describe('Eth sign', function () { }); it('can initiate and confirm a eth sign', async function () { - const expectedPersonalMessage = + const expectedEthSignMessage = '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0'; const expectedEthSignResult = '"0x816ab6c5d5356548cc4e004ef35a37fdfab916742a2bbeda756cd064c3d3789a6557d41d49549be1de249e1937a8d048996dfcc70d0552111605dc7cc471e8531b"'; @@ -69,23 +70,11 @@ describe('Eth sign', function () { windowHandles, ); - await driver.findElement({ - css: '.request-signature__content__title', - text: 'Signature request', - }); + await verifyAndAssertEthSign(driver, DAPP_URL, expectedEthSignMessage); - await driver.findElement({ - css: '.request-signature__origin', - text: DAPP_URL, - }); - - await driver.findElement({ - css: '.request-signature__row-value', - text: expectedPersonalMessage, - }); - - await driver.clickElement('[data-testid="page-container-footer-next"]'); - await driver.clickElement( + await approveEthSign( + driver, + '[data-testid="page-container-footer-next"]', '.signature-request-warning__footer__sign-button', ); // Switch to the Dapp @@ -101,4 +90,103 @@ describe('Eth sign', function () { }, ); }); + + it('can queue multiple eth sign and confirm', async function () { + const expectedEthSignMessage = + '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0'; + const expectedEthSignResult = + '"0x816ab6c5d5356548cc4e004ef35a37fdfab916742a2bbeda756cd064c3d3789a6557d41d49549be1de249e1937a8d048996dfcc70d0552111605dc7cc471e8531b"'; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPreferencesController({ + disabledRpcMethodPreferences: { + eth_sign: true, + }, + }) + .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); + // Create eth sign + await driver.clickElement('#ethSign'); + + // Wait for Signature request popup + await driver.waitUntilXWindowHandles(3); + let windowHandles = await driver.getAllWindowHandles(); + + // Switch to Dapp + await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + + // Create second eth sign + await driver.clickElement('#ethSign'); + + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + + await driver.waitForSelector({ + text: 'Reject 2 requests', + tag: 'a', + }); + + await verifyAndAssertEthSign(driver, DAPP_URL, expectedEthSignMessage); + + // Confirm first eth sign + await approveEthSign( + driver, + '[data-testid="page-container-footer-next"]', + '.signature-request-warning__footer__sign-button', + ); + + // Confirm second eth sign + await approveEthSign( + driver, + '[data-testid="page-container-footer-next"]', + '.signature-request-warning__footer__sign-button', + ); + + // Switch to the Dapp + await driver.waitUntilXWindowHandles(2); + windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + + // Verify last confirmed request + const result = await driver.findElement('#ethSignResult'); + assert.equal(await result.getText(), expectedEthSignResult); + }, + ); + }); }); + +async function verifyAndAssertEthSign(driver, dappUrl, expectedMessage) { + await driver.findElement({ + css: '.request-signature__content__title', + text: 'Signature request', + }); + + await driver.findElement({ + css: '.request-signature__origin', + text: dappUrl, + }); + + await driver.findElement({ + css: '.request-signature__row-value', + text: expectedMessage, + }); +} + +async function approveEthSign(driver, buttonTestId, signButtonClass) { + await driver.clickElement(buttonTestId); + await driver.clickElement(signButtonClass); + await driver.delay(regularDelayMs); +} diff --git a/test/e2e/tests/gas-estimates.spec.js b/test/e2e/tests/gas-estimates.spec.js new file mode 100644 index 000000000..a7d121f31 --- /dev/null +++ b/test/e2e/tests/gas-estimates.spec.js @@ -0,0 +1,171 @@ +const { + convertToHexValue, + withFixtures, + logInWithBalanceValidation, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); + +describe('Gas estimates generated by MetaMask', function () { + const baseGanacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + const preLondonGanacheOptions = { + ...baseGanacheOptions, + hardfork: 'berlin', + }; + const postLondonGanacheOptions = { + ...baseGanacheOptions, + hardfork: 'london', + }; + + describe('Send on a network that is EIP-1559 compatible', function () { + it('show expected gas defaults', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: postLondonGanacheOptions, + title: this.test.title, + }, + async ({ driver, ganacheServer }) => { + await driver.navigate(); + await logInWithBalanceValidation(driver, ganacheServer); + + await driver.clickElement('[data-testid="eth-overview-send"]'); + + await driver.fill( + 'input[placeholder="Enter public address (0x) or ENS name"]', + '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + ); + + await driver.fill('.unit-input__input', '1'); + + // Check that the gas estimation is what we expect + await driver.findElement({ + cass: '[data-testid="confirm-gas-display"]', + text: '0.00043983', + }); + }, + ); + }); + + it('show expected gas defaults when API is down', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: postLondonGanacheOptions, + testSpecificMock: (mockServer) => { + mockServer + .forGet( + 'https://gas-api.metaswap.codefi.network/networks/1337/suggestedGasFees', + ) + .thenCallback(() => { + return { + json: { + error: 'cannot get gas prices for chain id 1337', + }, + statusCode: 503, + }; + }); + }, + title: this.test.title, + }, + async ({ driver, ganacheServer }) => { + await driver.navigate(); + await logInWithBalanceValidation(driver, ganacheServer); + + await driver.clickElement('[data-testid="eth-overview-send"]'); + + await driver.fill( + 'input[placeholder="Enter public address (0x) or ENS name"]', + '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + ); + + await driver.fill('.unit-input__input', '1'); + + // Check that the gas estimation is what we expect + await driver.findElement({ + cass: '[data-testid="confirm-gas-display"]', + text: '0.00043983', + }); + }, + ); + }); + + it('show expected gas defaults when the network is not supported', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: postLondonGanacheOptions, + testSpecificMock: (mockServer) => { + mockServer + .forGet( + 'https://gas-api.metaswap.codefi.network/networks/1337/suggestedGasFees', + ) + .thenCallback(() => { + return { + statusCode: 422, + }; + }); + }, + title: this.test.title, + }, + async ({ driver, ganacheServer }) => { + await driver.navigate(); + await logInWithBalanceValidation(driver, ganacheServer); + + await driver.clickElement('[data-testid="eth-overview-send"]'); + + await driver.fill( + 'input[placeholder="Enter public address (0x) or ENS name"]', + '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + ); + + await driver.fill('.unit-input__input', '1'); + + // Check that the gas estimation is what we expect + await driver.findElement({ + cass: '[data-testid="confirm-gas-display"]', + text: '0.00043983', + }); + }, + ); + }); + }); + + describe('Send on a network that is not EIP-1559 compatible', function () { + it('show expected gas defaults', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: preLondonGanacheOptions, + title: this.test.title, + }, + async ({ driver, ganacheServer }) => { + await driver.navigate(); + await logInWithBalanceValidation(driver, ganacheServer); + + await driver.clickElement('[data-testid="eth-overview-send"]'); + + await driver.fill( + 'input[placeholder="Enter public address (0x) or ENS name"]', + '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + ); + + await driver.fill('.unit-input__input', '1'); + + // Check that the gas estimation is what we expect + await driver.findElement({ + cass: '[data-testid="confirm-gas-display"]', + text: '0.000042', + }); + }, + ); + }); + }); +}); diff --git a/test/e2e/tests/personal-sign.spec.js b/test/e2e/tests/personal-sign.spec.js index 9ecd04f52..10e37a7c7 100644 --- a/test/e2e/tests/personal-sign.spec.js +++ b/test/e2e/tests/personal-sign.spec.js @@ -1,5 +1,10 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + openDapp, + regularDelayMs, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('Personal sign', function () { @@ -33,7 +38,7 @@ describe('Personal sign', function () { await driver.clickElement('#personalSign'); await driver.waitUntilXWindowHandles(3); - let windowHandles = await driver.getAllWindowHandles(); + const windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle( 'MetaMask Notification', windowHandles, @@ -47,23 +52,92 @@ describe('Personal sign', function () { await driver.clickElement('[data-testid="page-container-footer-next"]'); - // Switch to the Dapp - await driver.waitUntilXWindowHandles(2); - windowHandles = await driver.getAllWindowHandles(); + await verifyAndAssertPersonalMessage(driver, publicAddress); + }, + ); + }); + it('can queue multiple personal signs and confirm', async function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions, + title: this.test.title, + }, + async ({ driver, ganacheServer }) => { + const addresses = await ganacheServer.getAccounts(); + const publicAddress = addresses[0]; + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await openDapp(driver); + // Create personal sign + await driver.clickElement('#personalSign'); + + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + + // Switch to Dapp await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - // Verify - await driver.clickElement('#personalSignVerify'); - const verifySigUtil = await driver.findElement( - '#personalSignVerifySigUtilResult', + // Create second personal sign + await driver.clickElement('#personalSign'); + + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, ); - const verifyECRecover = await driver.waitForSelector({ - css: '#personalSignVerifyECRecoverResult', - text: publicAddress, + + await driver.waitForSelector({ + text: 'Reject 2 requests', + tag: 'a', }); - assert.equal(await verifySigUtil.getText(), publicAddress); - assert.equal(await verifyECRecover.getText(), publicAddress); + + const personalMessageRow = await driver.findElement( + '.request-signature__row-value', + ); + const personalMessage = await personalMessageRow.getText(); + assert.equal(personalMessage, 'Example `personal_sign` message'); + + // Confirm first personal sign + await driver.clickElement('[data-testid="page-container-footer-next"]'); + await driver.delay(regularDelayMs); + // Confirm second personal sign + await driver.clickElement('[data-testid="page-container-footer-next"]'); + + await verifyAndAssertPersonalMessage(driver, publicAddress); }, ); }); }); + +async function verifyAndAssertPersonalMessage(driver, publicAddress) { + // Switch to the Dapp + await driver.waitUntilXWindowHandles(2); + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + + // Verify last confirmed personal sign + await driver.clickElement('#personalSignVerify'); + const verifySigUtil = await driver.findElement( + '#personalSignVerifySigUtilResult', + ); + const verifyECRecover = await driver.waitForSelector({ + css: '#personalSignVerifyECRecoverResult', + text: publicAddress, + }); + assert.equal(await verifySigUtil.getText(), publicAddress); + assert.equal(await verifyECRecover.getText(), publicAddress); +} diff --git a/test/e2e/tests/portfolio-site.spec.js b/test/e2e/tests/portfolio-site.spec.js index 2bdb413d5..93e0d0877 100644 --- a/test/e2e/tests/portfolio-site.spec.js +++ b/test/e2e/tests/portfolio-site.spec.js @@ -26,7 +26,7 @@ describe('Portfolio site', function () { await driver.press('#password', driver.Key.ENTER); // Click Portfolio site - await driver.clickElement('[data-testid="home__portfolio-site"]'); + await driver.clickElement('[data-testid="eth-overview-portfolio"]'); await driver.waitUntilXWindowHandles(2); const windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); @@ -34,7 +34,7 @@ describe('Portfolio site', function () { // Verify site assert.equal( await driver.getCurrentUrl(), - 'http://127.0.0.1:8080/?metamaskEntry=ext&metametricsId=null', + 'http://127.0.0.1:8080/?metamaskEntry=ext_portfolio_button&metametricsId=null', ); }, ); diff --git a/test/e2e/tests/signature-request.spec.js b/test/e2e/tests/signature-request.spec.js index f16bdf4ff..9ef703389 100644 --- a/test/e2e/tests/signature-request.spec.js +++ b/test/e2e/tests/signature-request.spec.js @@ -1,240 +1,238 @@ const { strict: assert } = require('assert'); const { - convertToHexValue, withFixtures, regularDelayMs, openDapp, DAPP_URL, + convertToHexValue, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -describe('Sign Typed Data V4 Signature Request', function () { - it('can initiate and confirm a Signature Request', async function () { - const ganacheOptions = { - accounts: [ - { - secretKey: - '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', - balance: convertToHexValue(25000000000000000000), - }, - ], - }; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions, - title: this.test.title, - }, - async ({ driver, ganacheServer }) => { - const addresses = await ganacheServer.getAccounts(); - const publicAddress = addresses[0]; - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - await openDapp(driver); - - // creates a sign typed data signature request - await driver.clickElement('#signTypedDataV4'); - - await driver.waitUntilXWindowHandles(3); - let windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - - const title = await driver.findElement( - '.signature-request__content__title', - ); - const origin = await driver.findElement('.signature-request__origin'); - const verifyContractDetailsButton = await driver.findElement( - '.signature-request-content__verify-contract-details', - ); - const message = await driver.findElement( - '.signature-request-data__node__value', - ); - - assert.equal(await title.getText(), 'Signature request'); - assert.equal(await origin.getText(), DAPP_URL); - - verifyContractDetailsButton.click(); - await driver.findElement({ text: 'Third-party details', tag: 'h5' }); - await driver.findElement('[data-testid="recipient"]'); - await driver.clickElement({ text: 'Got it', tag: 'button' }); - - assert.equal(await message.getText(), 'Hello, Bob!'); - // Approve signing typed data - await driver.clickElement( - '[data-testid="signature-request-scroll-button"]', - ); - await driver.delay(regularDelayMs); - await driver.clickElement({ text: 'Sign', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - windowHandles = await driver.getAllWindowHandles(); - - // switch to the Dapp and verify the signed addressed - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - await driver.clickElement('#signTypedDataV4Verify'); - const recoveredAddress = await driver.findElement( - '#signTypedDataV4VerifyResult', - ); - assert.equal(await recoveredAddress.getText(), publicAddress); - }, - ); - }); -}); - -/* eslint-disable-next-line mocha/max-top-level-suites */ -describe('Sign Typed Data V3 Signature Request', function () { - it('can initiate and confirm a Signature Request', async function () { - const ganacheOptions = { - accounts: [ - { - secretKey: - '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', - balance: convertToHexValue(25000000000000000000), - }, - ], - }; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions, - title: this.test.title, - }, - async ({ driver, ganacheServer }) => { - const addresses = await ganacheServer.getAccounts(); - const publicAddress = addresses[0]; - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - await openDapp(driver); - - // creates a sign typed data signature request - await driver.clickElement('#signTypedDataV3'); - - await driver.waitUntilXWindowHandles(3); - let windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - - const title = await driver.findElement( - '.signature-request__content__title', - ); - const origin = await driver.findElement('.signature-request__origin'); - const verifyContractDetailsButton = await driver.findElement( - '.signature-request-content__verify-contract-details', - ); - - const messages = await driver.findElements( - '.signature-request-data__node__value', - ); - - assert.equal(await title.getText(), 'Signature request'); - assert.equal(await origin.getText(), DAPP_URL); - - verifyContractDetailsButton.click(); - await driver.findElement({ text: 'Third-party details', tag: 'h5' }); - await driver.findElement('[data-testid="recipient"]'); - await driver.clickElement({ text: 'Got it', tag: 'button' }); - - assert.equal(await messages[4].getText(), 'Hello, Bob!'); - - // Approve signing typed data - await driver.clickElement( - '[data-testid="signature-request-scroll-button"]', - ); - await driver.delay(regularDelayMs); - await driver.clickElement({ text: 'Sign', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - windowHandles = await driver.getAllWindowHandles(); - - // switch to the Dapp and verify the signed addressed - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - await driver.clickElement('#signTypedDataV3Verify'); - const recoveredAddress = await driver.findElement( - '#signTypedDataV3VerifyResult', - ); - assert.equal(await recoveredAddress.getText(), publicAddress); - }, - ); - }); -}); +const signatureRequestType = { + signTypedData: 'Sign Typed Data', + signTypedDataV3: 'Sign Typed Data V3', + signTypedDataV4: 'Sign Typed Data V4', +}; +const testData = [ + { + type: signatureRequestType.signTypedData, + buttonId: '#signTypedData', + verifyId: '#signTypedDataVerify', + verifyResultId: '#signTypedDataVerifyResult', + expectedMessage: 'Hi, Alice!', + verifyAndAssertMessage: { + titleClass: '.request-signature__content__title', + originClass: '.request-signature__origin', + messageClass: '.request-signature__row-value', + }, + }, + { + type: signatureRequestType.signTypedDataV3, + buttonId: '#signTypedDataV3', + verifyId: '#signTypedDataV3Verify', + verifyResultId: '#signTypedDataV3VerifyResult', + expectedMessage: 'Hello, Bob!', + verifyAndAssertMessage: { + titleClass: '.signature-request__content__title', + originClass: '.signature-request__origin', + messageClass: '.signature-request-data__node__value', + }, + }, + { + type: signatureRequestType.signTypedDataV4, + buttonId: '#signTypedDataV4', + verifyId: '#signTypedDataV4Verify', + verifyResultId: '#signTypedDataV4VerifyResult', + expectedMessage: 'Hello, Bob!', + verifyAndAssertMessage: { + titleClass: '.signature-request__content__title', + originClass: '.signature-request__origin', + messageClass: '.signature-request-data__node__value', + }, + }, +]; +const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], +}; describe('Sign Typed Data Signature Request', function () { - it('can initiate and confirm a Signature Request', async function () { - const ganacheOptions = { - accounts: [ + testData.forEach((data) => { + it(`can initiate and confirm a Signature Request of ${data.type}`, async function () { + await withFixtures( { - secretKey: - '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', - balance: convertToHexValue(25000000000000000000), + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions, + title: this.test.title, }, - ], - }; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions, - title: this.test.title, - }, - async ({ driver, ganacheServer }) => { - const addresses = await ganacheServer.getAccounts(); - const publicAddress = addresses[0]; - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + async ({ driver, ganacheServer }) => { + const addresses = await ganacheServer.getAccounts(); + const publicAddress = addresses[0]; + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); - await openDapp(driver); + await openDapp(driver); - // creates a sign typed data signature request - await driver.clickElement('#signTypedData'); + // creates a sign typed data signature request + await driver.clickElement(data.buttonId); - await driver.waitUntilXWindowHandles(3); - let windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); + await driver.waitUntilXWindowHandles(3); + let windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); - const title = await driver.findElement( - '.request-signature__content__title', - ); - const origin = await driver.findElement('.request-signature__origin'); - const message = await driver.findElements( - '.request-signature__row-value', - ); - assert.equal(await title.getText(), 'Signature request'); - assert.equal(await origin.getText(), DAPP_URL); - assert.equal(await message[0].getText(), 'Hi, Alice!'); - assert.equal(await message[1].getText(), '1337'); + await verifyAndAssertSignTypedData( + driver, + data.type, + data.verifyAndAssertMessage.titleClass, + data.verifyAndAssertMessage.originClass, + data.verifyAndAssertMessage.messageClass, + data.expectedMessage, + ); - // Approve signing typed data - await driver.clickElement({ text: 'Sign', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - windowHandles = await driver.getAllWindowHandles(); + // Approve signing typed data + await approveSignatureRequest( + driver, + data.type, + '[data-testid="signature-request-scroll-button"]', + ); + await driver.waitUntilXWindowHandles(2); + windowHandles = await driver.getAllWindowHandles(); - // switch to the Dapp and verify the signed addressed - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - await driver.clickElement('#signTypedDataVerify'); - const recoveredAddress = await driver.findElement( - '#signTypedDataVerifyResult', - ); - assert.equal(await recoveredAddress.getText(), publicAddress); - }, - ); + // switch to the Dapp and verify the signed address + await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + await driver.clickElement(data.verifyId); + const recoveredAddress = await driver.findElement( + data.verifyResultId, + ); + + assert.equal(await recoveredAddress.getText(), publicAddress); + }, + ); + }); + }); + + testData.forEach((data) => { + it(`can queue multiple Signature Requests of ${data.type} and confirm`, async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions, + title: this.test.title, + }, + async ({ driver, ganacheServer }) => { + const addresses = await ganacheServer.getAccounts(); + const publicAddress = addresses[0]; + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await openDapp(driver); + + // creates multiple sign typed data signature requests + await driver.clickElement(data.buttonId); + + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + // switches to Dapp + await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + // creates second sign typed data signature request + await driver.clickElement(data.buttonId); + + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + + await driver.waitForSelector({ + text: 'Reject 2 requests', + tag: 'a', + }); + + await verifyAndAssertSignTypedData( + driver, + data.type, + data.verifyAndAssertMessage.titleClass, + data.verifyAndAssertMessage.originClass, + data.verifyAndAssertMessage.messageClass, + data.expectedMessage, + ); + + // approve first signature request + await approveSignatureRequest( + driver, + data.type, + '[data-testid="signature-request-scroll-button"]', + ); + await driver.waitUntilXWindowHandles(3); + + // approve second signature request + await approveSignatureRequest( + driver, + data.type, + '[data-testid="signature-request-scroll-button"]', + ); + await driver.waitUntilXWindowHandles(2); + + // switch to the Dapp and verify the signed address for each request + await driver.switchToWindowWithTitle('E2E Test Dapp'); + await driver.clickElement(data.verifyId); + const recoveredAddress = await driver.findElement( + data.verifyResultId, + ); + assert.equal(await recoveredAddress.getText(), publicAddress); + }, + ); + }); }); }); + +async function verifyAndAssertSignTypedData( + driver, + type, + titleClass, + originClass, + messageClass, + expectedMessage, +) { + const title = await driver.findElement(titleClass); + const origin = await driver.findElement(originClass); + + assert.equal(await title.getText(), 'Signature request'); + assert.equal(await origin.getText(), DAPP_URL); + + const messages = await driver.findElements(messageClass); + if (type !== signatureRequestType.signTypedData) { + const verifyContractDetailsButton = await driver.findElement( + '.signature-request-content__verify-contract-details', + ); + verifyContractDetailsButton.click(); + await driver.findElement({ text: 'Third-party details', tag: 'h5' }); + await driver.findElement('[data-testid="recipient"]'); + await driver.clickElement({ text: 'Got it', tag: 'button' }); + } + const messageNumber = type === signatureRequestType.signTypedDataV3 ? 4 : 0; + assert.equal(await messages[messageNumber].getText(), expectedMessage); +} + +async function approveSignatureRequest(driver, type, buttonElementId) { + if (type !== signatureRequestType.signTypedData) { + await driver.clickElement(buttonElementId); + } + await driver.delay(regularDelayMs); + await driver.clickElement({ text: 'Sign', tag: 'button' }); +} diff --git a/test/env.js b/test/env.js index cb66e3d3b..4056fc287 100644 --- a/test/env.js +++ b/test/env.js @@ -1,4 +1,4 @@ process.env.METAMASK_ENVIRONMENT = 'test'; process.env.SUPPORT_LINK = 'https://support.metamask.io'; process.env.IFRAME_EXECUTION_ENVIRONMENT_URL = - 'https://execution.metamask.io/0.16.0-flask.1/index.html'; + 'https://execution.metamask.io/0.35.2-flask.1/index.html'; diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index f6ebf8c03..10c627637 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -10,7 +10,6 @@ import { getNativeCurrencyImage, getDetectedTokensInCurrentNetwork, getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, - getTokenList, } from '../../../selectors'; import { getNativeCurrency } from '../../../ducks/metamask/metamask'; import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'; @@ -56,19 +55,15 @@ const AssetList = ({ onClickAsset }) => { const primaryTokenImage = useSelector(getNativeCurrencyImage); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork) || []; - const istokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector( + const isTokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector( getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, ); - const tokenList = useSelector(getTokenList); - const tokenData = Object.values(tokenList).find( - (token) => token.symbol === primaryCurrencyProperties.suffix, - ); - const title = tokenData?.name || primaryCurrencyProperties.suffix; + return ( <> onClickAsset(nativeCurrency)} - title={title} + title={nativeCurrency} primary={ primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value } @@ -82,14 +77,14 @@ const AssetList = ({ onClickAsset }) => { }} /> {detectedTokens.length > 0 && - !istokenDetectionInactiveOnNonMainnetSupportedNetwork ? ( + !isTokenDetectionInactiveOnNonMainnetSupportedNetwork ? ( setShowDetectedTokens(true)} margin={4} /> ) : null} 0 ? 0 : 4}> - + {showDetectedTokens && ( diff --git a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js index 72b6eacf9..377d7eaeb 100644 --- a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js +++ b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js @@ -1,11 +1,10 @@ import { useSelector } from 'react-redux'; import React, { useEffect } from 'react'; -import { Text } from '../../component-library'; import { EditGasModes, PriorityLevels } from '../../../../shared/constants/gas'; import { AlignItems, - DISPLAY, - FLEX_DIRECTION, + Display, + FlexDirection, TextVariant, } from '../../../helpers/constants/design-system'; import { getAppIsLoading } from '../../../selectors'; @@ -16,10 +15,10 @@ import { useTransactionModalContext } from '../../../contexts/transaction-modal' import EditGasFeeButton from '../edit-gas-fee-button'; import GasDetailsItem from '../gas-details-item'; import Box from '../../ui/box'; -import Button from '../../ui/button'; import InfoTooltip from '../../ui/info-tooltip'; import Popover from '../../ui/popover'; import AppLoadingSpinner from '../app-loading-spinner'; +import { Text, Button, ButtonLink } from '../../component-library'; const CancelSpeedupPopover = () => { const { @@ -98,11 +97,11 @@ const CancelSpeedupPopover = () => {
{t('cancelSpeedUpLabel', [ {t('replace')}, @@ -110,42 +109,39 @@ const CancelSpeedupPopover = () => { - {t('cancelSpeedUpTransactionTooltip', [ - editGasMode === EditGasModes.cancel - ? t('cancel') - : t('speedUp'), - ])} - - + <> + + {t('cancelSpeedUpTransactionTooltip', [ + editGasMode === EditGasModes.cancel + ? t('cancel') + : t('speedUp'), + ])} + + + {t('learnMoreUpperCase')} + + } /> -
- +
{!appIsLoading && } - - +
+
- +
- +
); diff --git a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.stories.js b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.stories.js new file mode 100644 index 000000000..0b2d541d7 --- /dev/null +++ b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.stories.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import BigNumber from 'bignumber.js'; +import configureStore from '../../../store/store'; +import { TransactionModalContext } from '../../../contexts/transaction-modal'; +import mockEstimates from '../../../../test/data/mock-estimates.json'; +import mockState from '../../../../test/data/mock-state.json'; +import { + EditGasModes, + GasEstimateTypes, +} from '../../../../shared/constants/gas'; +import { decGWEIToHexWEI } from '../../../../shared/modules/conversion.utils'; +import { GasFeeContextProvider } from '../../../contexts/gasFee'; +import CancelSpeedupPopover from './cancel-speedup-popover'; + +const store = configureStore({ + metamask: { + ...mockState.metamask, + accounts: { + [mockState.metamask.selectedAddress]: { + address: mockState.metamask.selectedAddress, + balance: '0x1F4', + }, + }, + gasFeeEstimates: mockEstimates[GasEstimateTypes.feeMarket].gasFeeEstimates, + }, +}); + +const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_DEC_GWEI = + mockEstimates[GasEstimateTypes.feeMarket].gasFeeEstimates.medium + .suggestedMaxFeePerGas; + +const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI = new BigNumber( + decGWEIToHexWEI(MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_DEC_GWEI), + 16, +); + +const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_HEX_WEI = + MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI.toString(16); + +export default { + title: 'Components/App/CancelSpeedupPopover', + component: CancelSpeedupPopover, + decorators: [ + (story) => ( + + + undefined, + currentModal: 'cancelSpeedUpTransaction', + }} + > + {story()} + + + + ), + ], +}; + +export const DefaultStory = (args) => { + return ( +
+ +
+ ); +}; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js index 1d8c28056..1512b8992 100644 --- a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js +++ b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js @@ -125,7 +125,7 @@ describe('CancelSpeedupPopover', () => { it('information tooltip should contain the correct text if editGasMode is cancel', async () => { await act(async () => render()); expect( - InfoTooltip.mock.calls[0][0].contentText.props.children[0], + InfoTooltip.mock.calls[0][0].contentText.props.children[0].props.children, ).toStrictEqual( 'To Cancel a transaction the gas fee must be increased by at least 10% for it to be recognized by the network.', ); @@ -134,7 +134,7 @@ describe('CancelSpeedupPopover', () => { it('information tooltip should contain the correct text if editGasMode is speedup', async () => { await act(async () => render({ editGasMode: EditGasModes.speedUp })); expect( - InfoTooltip.mock.calls[0][0].contentText.props.children[0], + InfoTooltip.mock.calls[0][0].contentText.props.children[0].props.children, ).toStrictEqual( 'To Speed up a transaction the gas fee must be increased by at least 10% for it to be recognized by the network.', ); diff --git a/ui/components/app/cancel-speedup-popover/index.scss b/ui/components/app/cancel-speedup-popover/index.scss index cd7886982..4127a7eac 100644 --- a/ui/components/app/cancel-speedup-popover/index.scss +++ b/ui/components/app/cancel-speedup-popover/index.scss @@ -20,8 +20,7 @@ height: calc(100% + 30px); } - &__separator { + &__description { border-bottom: 1px solid var(--color-border-default); - width: 100%; } } diff --git a/ui/components/app/confirm-gas-display/__snapshots__/confirm-gas-display.test.js.snap b/ui/components/app/confirm-gas-display/__snapshots__/confirm-gas-display.test.js.snap index a993864ba..edd5c141f 100644 --- a/ui/components/app/confirm-gas-display/__snapshots__/confirm-gas-display.test.js.snap +++ b/ui/components/app/confirm-gas-display/__snapshots__/confirm-gas-display.test.js.snap @@ -4,6 +4,7 @@ exports[`ConfirmGasDisplay should match snapshot 1`] = `
{ checkNetworkAndAccountSupports1559, ); const supportsEIP1559 = networkAndAccountSupports1559 && !isLegacyTxn; + const dataTestId = 'confirm-gas-display'; return supportsEIP1559 ? ( - + ) : ( - + ); }; diff --git a/ui/components/app/confirm-gas-display/confirm-gas-display.stories.js b/ui/components/app/confirm-gas-display/confirm-gas-display.stories.js new file mode 100644 index 000000000..a8def4fc4 --- /dev/null +++ b/ui/components/app/confirm-gas-display/confirm-gas-display.stories.js @@ -0,0 +1,19 @@ +import React from 'react'; +import ConfirmGasDisplay from './confirm-gas-display'; + +export default { + title: 'Components/App/ConfirmGasDisplay', + component: ConfirmGasDisplay, + argTypes: { + userAcknowledgedGasMissing: { + control: 'boolean', + }, + }, + args: { + userAcknowledgedGasMissing: true, + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/confirm-gas-display/confirm-legacy-gas-display/confirm-legacy-gas-display.js b/ui/components/app/confirm-gas-display/confirm-legacy-gas-display/confirm-legacy-gas-display.js index 124fbb0d9..15e0abfb6 100644 --- a/ui/components/app/confirm-gas-display/confirm-legacy-gas-display/confirm-legacy-gas-display.js +++ b/ui/components/app/confirm-gas-display/confirm-legacy-gas-display/confirm-legacy-gas-display.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { useI18nContext } from '../../../../hooks/useI18nContext'; @@ -31,7 +32,7 @@ import { Icon, IconName } from '../../../component-library'; const renderHeartBeatIfNotInTest = () => process.env.IN_TEST ? null : ; -const ConfirmLegacyGasDisplay = () => { +const ConfirmLegacyGasDisplay = ({ 'data-testid': dataTestId } = {}) => { const t = useI18nContext(); // state selectors @@ -55,6 +56,7 @@ const ConfirmLegacyGasDisplay = () => { return [ { return ( @@ -186,4 +189,8 @@ const ConfirmLegacyGasDisplay = () => { ); }; +ConfirmLegacyGasDisplay.propTypes = { + 'data-testid': PropTypes.string, +}; + export default ConfirmLegacyGasDisplay; diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index fd0651e13..713cd9293 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -10,8 +10,9 @@ import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../../helpers/constants/erro import Typography from '../../../ui/typography'; import { TypographyVariant } from '../../../../helpers/constants/design-system'; +import { isSuspiciousResponse } from '../../../../../shared/modules/security-provider.utils'; import SecurityProviderBannerMessage from '../../security-provider-banner-message/security-provider-banner-message'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../../security-provider-banner-message/security-provider-banner-message.constants'; + import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'; export default class ConfirmPageContainerContent extends Component { @@ -214,15 +215,11 @@ export default class ConfirmPageContainerContent extends Component { {ethGasPriceWarning && ( )} - {(txData?.securityProviderResponse?.flagAsDangerous !== undefined && - txData?.securityProviderResponse?.flagAsDangerous !== - SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS) || - (txData?.securityProviderResponse && - Object.keys(txData.securityProviderResponse).length === 0) ? ( + {isSuspiciousResponse(txData?.securityProviderResponse) && ( - ) : null} + )} { @@ -155,7 +155,7 @@ describe('Confirm Page Container Content', () => { it('should not render SecurityProviderBannerMessage component when flagAsDangerous is not malicious', () => { props.txData.securityProviderResponse = { - flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS, }; const { queryByText } = renderWithProvider( diff --git a/ui/components/app/confirm-subtitle/confirm-subtitle.js b/ui/components/app/confirm-subtitle/confirm-subtitle.js index bac8e4ca5..1ac59dcd1 100644 --- a/ui/components/app/confirm-subtitle/confirm-subtitle.js +++ b/ui/components/app/confirm-subtitle/confirm-subtitle.js @@ -3,11 +3,7 @@ import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { SECONDARY } from '../../../helpers/constants/common'; -import { - Color, - FONT_WEIGHT, - TextVariant, -} from '../../../helpers/constants/design-system'; +import { Color, TextVariant } from '../../../helpers/constants/design-system'; import { isNFTAssetStandard } from '../../../helpers/utils/transactions.util'; import { getShouldShowFiat } from '../../../selectors'; import { useTransactionInfo } from '../../../hooks/useTransactionInfo'; @@ -35,7 +31,6 @@ const ConfirmSubTitle = ({ diff --git a/ui/components/app/connected-accounts-permissions/connected-account-permissions.stories.js b/ui/components/app/connected-accounts-permissions/connected-account-permissions.stories.js new file mode 100644 index 000000000..228abcc97 --- /dev/null +++ b/ui/components/app/connected-accounts-permissions/connected-account-permissions.stories.js @@ -0,0 +1,25 @@ +import React from 'react'; +import ConnectedAccountsPermissions from './connected-accounts-permissions'; + +export default { + title: 'Components/App/ConnectedAccountsPermissions', + component: ConnectedAccountsPermissions, + argTypes: { + permission: { + control: 'array', + }, + }, + args: { + permissions: [ + { key: 'permission1' }, + { key: 'permission2' }, + { key: 'permission3' }, + ], + }, +}; + +export const DefaultStory = (args) => ( + +); + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/custom-spending-cap/__snapshots__/custom-spending-cap.test.js.snap b/ui/components/app/custom-spending-cap/__snapshots__/custom-spending-cap.test.js.snap index a62361e35..46ca44ffb 100644 --- a/ui/components/app/custom-spending-cap/__snapshots__/custom-spending-cap.test.js.snap +++ b/ui/components/app/custom-spending-cap/__snapshots__/custom-spending-cap.test.js.snap @@ -15,13 +15,13 @@ exports[`CustomSpendingCap should match snapshot 1`] = ` class="form-field" >
+ )} - - {t('missingNFT')} - - - {!isMainnet && Object.keys(collections).length < 1 ? null : ( - <> - - {isMainnet && !useNftDetection ? ( - - ) : ( - - )} - - - {t('or')} - - - )} - - - - + {t('importNFT')} + + {!isMainnet && Object.keys(collections).length < 1 ? null : ( + <> + + {isMainnet && !useNftDetection ? ( + + {t('enableAutoDetect')} + + ) : ( + + {t('refreshList')} + + )} + + + )} ); diff --git a/ui/components/app/nfts-tab/nfts-tab.test.js b/ui/components/app/nfts-tab/nfts-tab.test.js index 0ab8e29e6..2cc2048d1 100644 --- a/ui/components/app/nfts-tab/nfts-tab.test.js +++ b/ui/components/app/nfts-tab/nfts-tab.test.js @@ -303,7 +303,7 @@ describe('NFT Items', () => { onAddNFT: onAddNFTStub, }); expect(onAddNFTStub).toHaveBeenCalledTimes(0); - fireEvent.click(screen.queryByText('Import NFTs')); + fireEvent.click(screen.queryByText('Import NFT')); expect(onAddNFTStub).toHaveBeenCalledTimes(1); }); }); diff --git a/ui/components/app/security-provider-banner-message/security-provider-banner-message.constants.js b/ui/components/app/security-provider-banner-message/security-provider-banner-message.constants.js deleted file mode 100644 index fc30d0b5c..000000000 --- a/ui/components/app/security-provider-banner-message/security-provider-banner-message.constants.js +++ /dev/null @@ -1,5 +0,0 @@ -export const SECURITY_PROVIDER_MESSAGE_SEVERITIES = { - NOT_MALICIOUS: 0, - MALICIOUS: 1, - NOT_SAFE: 2, -}; diff --git a/ui/components/app/security-provider-banner-message/security-provider-banner-message.js b/ui/components/app/security-provider-banner-message/security-provider-banner-message.js index 71c928edd..fdaba451f 100644 --- a/ui/components/app/security-provider-banner-message/security-provider-banner-message.js +++ b/ui/components/app/security-provider-banner-message/security-provider-banner-message.js @@ -6,9 +6,9 @@ import { Size, TextVariant, } from '../../../helpers/constants/design-system'; +import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider'; import { I18nContext } from '../../../../.storybook/i18n'; import { BannerAlert, ButtonLink, Text } from '../../component-library'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from './security-provider-banner-message.constants'; export default function SecurityProviderBannerMessage({ securityProviderResponse, @@ -21,7 +21,7 @@ export default function SecurityProviderBannerMessage({ if ( securityProviderResponse.flagAsDangerous === - SECURITY_PROVIDER_MESSAGE_SEVERITIES.MALICIOUS + SECURITY_PROVIDER_MESSAGE_SEVERITY.MALICIOUS ) { messageTitle = securityProviderResponse.reason_header === '' @@ -34,7 +34,7 @@ export default function SecurityProviderBannerMessage({ severity = SEVERITIES.DANGER; } else if ( securityProviderResponse.flagAsDangerous === - SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_SAFE + SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_SAFE ) { messageTitle = t('requestMayNotBeSafe'); messageText = t('requestMayNotBeSafeError'); diff --git a/ui/components/app/security-provider-banner-message/security-provider-banner-message.test.js b/ui/components/app/security-provider-banner-message/security-provider-banner-message.test.js index 4fd21f7e9..f131fad16 100644 --- a/ui/components/app/security-provider-banner-message/security-provider-banner-message.test.js +++ b/ui/components/app/security-provider-banner-message/security-provider-banner-message.test.js @@ -2,8 +2,8 @@ import { fireEvent } from '@testing-library/react'; import React from 'react'; import configureMockStore from 'redux-mock-store'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider'; import SecurityProviderBannerMessage from './security-provider-banner-message'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from './security-provider-banner-message.constants'; describe('Security Provider Banner Message', () => { const store = configureMockStore()({}); @@ -12,7 +12,7 @@ describe('Security Provider Banner Message', () => { it('should render SecurityProviderBannerMessage component properly when flagAsDangerous is malicious', () => { const securityProviderResponse = { - flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.MALICIOUS, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.MALICIOUS, reason: 'Approval is to an unverified smart contract known for stealing NFTs in the past.', reason_header: 'This could be a scam', @@ -34,7 +34,7 @@ describe('Security Provider Banner Message', () => { it('should render SecurityProviderBannerMessage component properly when flagAsDangerous is not safe', () => { const securityProviderResponse = { - flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_SAFE, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_SAFE, reason: 'Some reason...', reason_header: 'Some reason header...', }; @@ -99,7 +99,7 @@ describe('Security Provider Banner Message', () => { it('should navigate to the OpenSea web page when clicked on the OpenSea button', () => { const securityProviderResponse = { - flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_SAFE, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_SAFE, reason: 'Some reason...', reason_header: 'Some reason header...', }; @@ -122,7 +122,7 @@ describe('Security Provider Banner Message', () => { it('should render SecurityProviderBannerMessage component properly, with predefined reason message, when a request is malicious and there is no reason given', () => { const securityProviderResponse = { - flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.MALICIOUS, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.MALICIOUS, reason: '', reason_header: 'Some reason header...', }; @@ -145,7 +145,7 @@ describe('Security Provider Banner Message', () => { it('should render SecurityProviderBannerMessage component properly, with predefined reason_header message, when a request is malicious and there is no reason header given', () => { const securityProviderResponse = { - flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.MALICIOUS, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.MALICIOUS, reason: 'Some reason...', reason_header: '', }; @@ -166,7 +166,7 @@ describe('Security Provider Banner Message', () => { it('should render SecurityProviderBannerMessage component properly, with predefined reason and reason_header messages, when a request is malicious and there are no reason and reason header given', () => { const securityProviderResponse = { - flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.MALICIOUS, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.MALICIOUS, reason: '', reason_header: '', }; diff --git a/ui/components/app/signature-request-original/signature-request-original.component.js b/ui/components/app/signature-request-original/signature-request-original.component.js index 4b72ea529..2fea925af 100644 --- a/ui/components/app/signature-request-original/signature-request-original.component.js +++ b/ui/components/app/signature-request-original/signature-request-original.component.js @@ -13,6 +13,7 @@ import { ///: END:ONLY_INCLUDE_IN } from '../../../helpers/utils/util'; import { stripHexPrefix } from '../../../../shared/modules/hexstring-utils'; +import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; import Button from '../../ui/button'; import SiteOrigin from '../../ui/site-origin'; import Typography from '../../ui/typography/typography'; @@ -32,7 +33,6 @@ import { } from '../../../helpers/constants/design-system'; import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../security-provider-banner-message/security-provider-banner-message.constants'; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) import { Icon, IconName, Text } from '../../component-library'; import Box from '../../ui/box/box'; @@ -133,15 +133,11 @@ export default class SignatureRequestOriginal extends Component { return (
- {(txData?.securityProviderResponse?.flagAsDangerous !== undefined && - txData?.securityProviderResponse?.flagAsDangerous !== - SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS) || - (txData?.securityProviderResponse && - Object.keys(txData.securityProviderResponse).length === 0) ? ( + {isSuspiciousResponse(txData?.securityProviderResponse) && ( - ) : null} + )} { ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) @@ -160,11 +156,7 @@ export default class SignatureRequestOriginal extends Component { color={IconColor.infoDefault} marginRight={2} /> - + {this.context.t('mismatchAccount', [ shortenAddress(this.props.selectedAccount.address), shortenAddress(this.props.fromAccount.address), diff --git a/ui/components/app/signature-request-original/signature-request-original.test.js b/ui/components/app/signature-request-original/signature-request-original.test.js index d9c136a86..030451514 100644 --- a/ui/components/app/signature-request-original/signature-request-original.test.js +++ b/ui/components/app/signature-request-original/signature-request-original.test.js @@ -3,10 +3,10 @@ import configureMockStore from 'redux-mock-store'; import { fireEvent, screen } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import { MESSAGE_TYPE } from '../../../../shared/constants/app'; +import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider'; import mockState from '../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import configureStore from '../../../store/store'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../security-provider-banner-message/security-provider-banner-message.constants'; import { resolvePendingApproval, rejectPendingApproval, @@ -168,7 +168,7 @@ describe('SignatureRequestOriginal', () => { it('should not render SecurityProviderBannerMessage component when flagAsDangerous is not malicious', () => { props.txData.securityProviderResponse = { - flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS, }; render(); diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe.js b/ui/components/app/signature-request-siwe/signature-request-siwe.js index 33529e300..ddd4319e5 100644 --- a/ui/components/app/signature-request-siwe/signature-request-siwe.js +++ b/ui/components/app/signature-request-siwe/signature-request-siwe.js @@ -19,6 +19,7 @@ import { unconfirmedMessagesHashSelector, } from '../../../selectors'; import { getAccountByAddress, valuesFor } from '../../../helpers/utils/util'; +import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; import { formatMessageParams } from '../../../../shared/modules/siwe'; import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck'; @@ -35,7 +36,6 @@ import { } from '../../../store/actions'; import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../security-provider-banner-message/security-provider-banner-message.constants'; import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; import { getMostRecentOverviewPage } from '../../../ducks/history/history'; import LedgerInstructionField from '../ledger-instruction-field'; @@ -78,12 +78,9 @@ export default function SignatureRequestSIWE({ txData }) { const [hasAgreedToDomainWarning, setHasAgreedToDomainWarning] = useState(false); - const showSecurityProviderBanner = - (txData?.securityProviderResponse?.flagAsDangerous !== undefined && - txData?.securityProviderResponse?.flagAsDangerous !== - SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS) || - (txData?.securityProviderResponse && - Object.keys(txData.securityProviderResponse).length === 0); + const showSecurityProviderBanner = isSuspiciousResponse( + txData?.securityProviderResponse, + ); const onSign = useCallback(async () => { try { diff --git a/ui/components/app/signature-request/__snapshots__/signature-request.component.test.js.snap b/ui/components/app/signature-request/__snapshots__/signature-request.component.test.js.snap index 73294848d..61c4698d9 100644 --- a/ui/components/app/signature-request/__snapshots__/signature-request.component.test.js.snap +++ b/ui/components/app/signature-request/__snapshots__/signature-request.component.test.js.snap @@ -206,12 +206,13 @@ exports[`Signature Request Component render should match snapshot when we are us

Signature request

Only sign this message if you fully understand the content and trust the requesting site.
@@ -223,7 +224,7 @@ exports[`Signature Request Component render should match snapshot when we are us tabindex="0" >
Verify third-party details
@@ -237,7 +238,7 @@ exports[`Signature Request Component render should match snapshot when we are us class="box signature-request-message__root box--margin-2 box--padding-top-3 box--padding-right-3 box--padding-bottom-3 box--flex-direction-row box--background-color-background-default box--rounded-xl box--border-color-border-muted box--border-style-solid box--border-width-1" >

Mail

@@ -982,12 +983,13 @@ exports[`Signature Request Component render should match snapshot when we want t

Signature request

Only sign this message if you fully understand the content and trust the requesting site.
@@ -999,7 +1001,7 @@ exports[`Signature Request Component render should match snapshot when we want t tabindex="0" >
Verify third-party details
@@ -1013,7 +1015,7 @@ exports[`Signature Request Component render should match snapshot when we want t class="box signature-request-message__root box--margin-2 box--padding-top-3 box--padding-right-3 box--padding-bottom-3 box--flex-direction-row box--background-color-background-default box--rounded-xl box--border-color-border-muted box--border-style-solid box--border-width-1" >

Mail

diff --git a/ui/components/app/signature-request/index.scss b/ui/components/app/signature-request/index.scss index a53a9d71c..5cc2a0981 100644 --- a/ui/components/app/signature-request/index.scss +++ b/ui/components/app/signature-request/index.scss @@ -47,12 +47,6 @@ flex-direction: column; min-height: min-content; - &__title { - @include H5; - - font-weight: 500; - } - &__info { @include H7; diff --git a/ui/components/app/signature-request/signature-request-message/signature-request-message.js b/ui/components/app/signature-request/signature-request-message/signature-request-message.js index d343a6584..52322d084 100644 --- a/ui/components/app/signature-request/signature-request-message/signature-request-message.js +++ b/ui/components/app/signature-request/signature-request-message/signature-request-message.js @@ -3,11 +3,10 @@ import PropTypes from 'prop-types'; import { debounce } from 'lodash'; import { I18nContext } from '../../../../contexts/i18n'; import Box from '../../../ui/box'; -import Typography from '../../../ui/typography'; +import { Text } from '../../../component-library'; import { - DISPLAY, - FONT_WEIGHT, - FLEX_DIRECTION, + Display, + FlexDirection, AlignItems, JustifyContent, Color, @@ -15,6 +14,7 @@ import { BorderColor, BorderRadius, TextColor, + FontWeight, } from '../../../../helpers/constants/design-system'; import SignatureRequestData from '../signature-request-data'; @@ -44,14 +44,14 @@ export default function SignatureRequestMessage({ return ( {messageIsScrollable ? ( - {primaryType} - +
diff --git a/ui/components/app/signature-request/signature-request-message/signature-request-message.stories.js b/ui/components/app/signature-request/signature-request-message/signature-request-message.stories.js index 9449e33a5..db848c04e 100644 --- a/ui/components/app/signature-request/signature-request-message/signature-request-message.stories.js +++ b/ui/components/app/signature-request/signature-request-message/signature-request-message.stories.js @@ -4,7 +4,6 @@ import SignatureRequestMessage from './signature-request-message'; export default { title: 'Components/App/SignatureRequestMessage', - component: SignatureRequestMessage, argTypes: { data: { control: 'object' }, diff --git a/ui/components/app/signature-request/signature-request.component.js b/ui/components/app/signature-request/signature-request.component.js index 60c92b296..0e4d2e4eb 100644 --- a/ui/components/app/signature-request/signature-request.component.js +++ b/ui/components/app/signature-request/signature-request.component.js @@ -14,34 +14,35 @@ import { import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics'; import SiteOrigin from '../../ui/site-origin'; import Button from '../../ui/button'; -import Typography from '../../ui/typography/typography'; import ContractDetailsModal from '../modals/contract-details-modal/contract-details-modal'; import { - TypographyVariant, - FontWeight, TextAlign, TextColor, + TextVariant, ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) IconColor, - DISPLAY, - BLOCK_SIZES, - TextVariant, + Display, + BlockSize, BackgroundColor, ///: END:ONLY_INCLUDE_IN } from '../../../helpers/constants/design-system'; import NetworkAccountBalanceHeader from '../network-account-balance-header'; import { Numeric } from '../../../../shared/modules/Numeric'; +import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; import { EtherDenomination } from '../../../../shared/constants/common'; import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../security-provider-banner-message/security-provider-banner-message.constants'; import { formatCurrency } from '../../../helpers/utils/confirm-tx.util'; import { getValueFromWeiHex } from '../../../../shared/modules/conversion.utils'; -///: BEGIN:ONLY_INCLUDE_IN(build-mmi) -import { Icon, IconName, Text } from '../../component-library'; -import Box from '../../ui/box/box'; -///: END:ONLY_INCLUDE_IN +import { + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + Box, + Icon, + IconName, + ///: END:ONLY_INCLUDE_IN + Text, +} from '../../component-library'; import Footer from './signature-request-footer'; import Message from './signature-request-message'; @@ -284,23 +285,19 @@ export default class SignatureRequest extends PureComponent { />
- {(txData?.securityProviderResponse?.flagAsDangerous !== undefined && - txData?.securityProviderResponse?.flagAsDangerous !== - SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS) || - (txData?.securityProviderResponse && - Object.keys(txData.securityProviderResponse).length === 0) ? ( + {isSuspiciousResponse(txData?.securityProviderResponse) && ( - ) : null} + )} { ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) this.props.selectedAccount.address === address ? null : ( {this.context.t('mismatchAccount', [ shortenAddress(this.props.selectedAccount.address), @@ -334,26 +330,25 @@ export default class SignatureRequest extends PureComponent { />
- {this.context.t('sigRequest')} - - + {this.context.t('signatureRequestGuidance')} - + {verifyingContract ? (
) : null} diff --git a/ui/components/app/signature-request/signature-request.component.test.js b/ui/components/app/signature-request/signature-request.component.test.js index eebae4d46..81f18932a 100644 --- a/ui/components/app/signature-request/signature-request.component.test.js +++ b/ui/components/app/signature-request/signature-request.component.test.js @@ -3,7 +3,7 @@ import { fireEvent } from '@testing-library/react'; import configureMockStore from 'redux-mock-store'; import mockState from '../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../security-provider-banner-message/security-provider-banner-message.constants'; +import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider'; import SignatureRequest from './signature-request.component'; const baseProps = { @@ -308,8 +308,7 @@ describe('Signature Request Component', () => { txData={{ msgParams, securityProviderResponse: { - flagAsDangerous: - SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS, }, }} unapprovedMessagesCount={2} @@ -345,8 +344,7 @@ describe('Signature Request Component', () => { txData={{ msgParams, securityProviderResponse: { - flagAsDangerous: - SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS, + flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS, }, }} unapprovedMessagesCount={2} diff --git a/ui/components/app/signature-request/signature-request.stories.js b/ui/components/app/signature-request/signature-request.stories.js index fbae8f99c..1fc2e555b 100644 --- a/ui/components/app/signature-request/signature-request.stories.js +++ b/ui/components/app/signature-request/signature-request.stories.js @@ -9,7 +9,6 @@ const [MOCK_PRIMARY_IDENTITY, MOCK_SECONDARY_IDENTITY] = Object.values( export default { title: 'Components/App/SignatureRequest', - component: SignatureRequest, parameters: { docs: { @@ -30,6 +29,10 @@ export default { clearConfirmTransaction: { action: 'Clean Confirm' }, cancel: { action: 'Cancel' }, sign: { action: 'Sign' }, + showRejectTransactionsConfirmationModal: { + action: 'showRejectTransactionsConfirmationModal', + }, + cancelAll: { action: 'cancelAll' }, }, }; @@ -79,6 +82,8 @@ DefaultStory.args = { fromAccount: MOCK_PRIMARY_IDENTITY, providerConfig: { name: 'Goerli ETH' }, selectedAccount: MOCK_PRIMARY_IDENTITY, + hardwareWalletRequiresConnection: false, + currentCurrency: 'usd', }; export const AccountMismatchStory = (args) => { diff --git a/ui/components/app/transaction-detail-item/transaction-detail-item.component.js b/ui/components/app/transaction-detail-item/transaction-detail-item.component.js index a90bad16a..ed1578b62 100644 --- a/ui/components/app/transaction-detail-item/transaction-detail-item.component.js +++ b/ui/components/app/transaction-detail-item/transaction-detail-item.component.js @@ -14,6 +14,7 @@ import { } from '../../../helpers/constants/design-system'; export default function TransactionDetailItem({ + 'data-testid': dataTestId, detailTitle = '', detailText, detailTitleColor = Color.textDefault, @@ -24,7 +25,7 @@ export default function TransactionDetailItem({ flexWidthValues = false, }) { return ( -
+
{ +const EthOverview = ({ className, showAddress }) => { const dispatch = useDispatch(); const t = useContext(I18nContext); const history = useHistory(); const balanceIsCached = useSelector(isBalanceCached); const showFiat = useSelector(getShouldShowFiat); const balance = useSelector(getSelectedAccountCachedBalance); - const primaryTokenImage = useSelector(getNativeCurrencyImage); return ( { EthOverview.propTypes = { className: PropTypes.string, -}; - -EthOverview.defaultProps = { - className: undefined, + showAddress: PropTypes.bool, }; export default EthOverview; diff --git a/ui/components/app/wallet-overview/eth-overview.test.js b/ui/components/app/wallet-overview/eth-overview.test.js index 79a183700..7a1a884eb 100644 --- a/ui/components/app/wallet-overview/eth-overview.test.js +++ b/ui/components/app/wallet-overview/eth-overview.test.js @@ -71,7 +71,7 @@ describe('EthOverview', () => { const store = configureMockStore([thunk])(mockStore); const ETH_OVERVIEW_BUY = 'eth-overview-buy'; const ETH_OVERVIEW_BRIDGE = 'eth-overview-bridge'; - const ETH_OVERVIEW_PORTFOLIO = 'home__portfolio-site'; + const ETH_OVERVIEW_PORTFOLIO = 'eth-overview-portfolio'; const ETH_OVERVIEW_PRIMARY_CURRENCY = 'eth-overview__primary-currency'; const ETH_OVERVIEW_SECONDARY_CURRENCY = 'eth-overview__secondary-currency'; diff --git a/ui/components/app/wallet-overview/token-overview.js b/ui/components/app/wallet-overview/token-overview.js index 131e9f9c6..12bd186a3 100644 --- a/ui/components/app/wallet-overview/token-overview.js +++ b/ui/components/app/wallet-overview/token-overview.js @@ -41,6 +41,7 @@ const TokenOverview = ({ className, token }) => { return (
diff --git a/ui/components/app/wallet-overview/wallet-overview.js b/ui/components/app/wallet-overview/wallet-overview.js index a56f8b8d8..f24957954 100644 --- a/ui/components/app/wallet-overview/wallet-overview.js +++ b/ui/components/app/wallet-overview/wallet-overview.js @@ -7,15 +7,22 @@ import { getSelectedIdentity } from '../../../selectors'; import { AddressCopyButton } from '../../multichain'; import Box from '../../ui/box/box'; -const WalletOverview = ({ balance, buttons, className }) => { +const WalletOverview = ({ + balance, + buttons, + className, + showAddress = false, +}) => { const selectedIdentity = useSelector(getSelectedIdentity); const checksummedAddress = toChecksumHexAddress(selectedIdentity?.address); return (
- - - + {showAddress ? ( + + + + ) : null} {balance}
{buttons}
@@ -27,10 +34,7 @@ WalletOverview.propTypes = { balance: PropTypes.element.isRequired, buttons: PropTypes.element.isRequired, className: PropTypes.string, -}; - -WalletOverview.defaultProps = { - className: undefined, + showAddress: PropTypes.bool, }; export default WalletOverview; diff --git a/ui/components/app/whats-new-popup/whats-new-popup.js b/ui/components/app/whats-new-popup/whats-new-popup.js index 0b505eac3..5743cc841 100644 --- a/ui/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/components/app/whats-new-popup/whats-new-popup.js @@ -7,9 +7,14 @@ import { debounce } from 'lodash'; import { getCurrentLocale } from '../../../ducks/locale/locale'; import { I18nContext } from '../../../contexts/i18n'; import { useEqualityCheck } from '../../../hooks/useEqualityCheck'; -import Button from '../../ui/button'; import Popover from '../../ui/popover'; -import { Text } from '../../component-library'; +import { + Button, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + IconName, + ///: END:ONLY_INCLUDE_IN + Text, +} from '../../component-library'; import { updateViewedNotifications } from '../../../store/actions'; import { getTranslatedUINotifications } from '../../../../shared/notifications'; import { getSortedAnnouncementsToShow } from '../../../selectors'; @@ -20,7 +25,12 @@ import { EXPERIMENTAL_ROUTE, SECURITY_ROUTE, } from '../../../helpers/constants/routes'; -import { TextVariant } from '../../../helpers/constants/design-system'; +import { + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + Size, + ///: END:ONLY_INCLUDE_IN + TextVariant, +} from '../../../helpers/constants/design-system'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { @@ -118,15 +128,36 @@ const renderDescription = (description) => { ); }; -const renderFirstNotification = ( +const renderFirstNotification = ({ notification, idRefMap, history, isLast, trackEvent, -) => { - const { id, date, title, description, image, actionText } = notification; + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + mmiPortfolioUrl, + seenNotifications, + onClose, + ///: END:ONLY_INCLUDE_IN +}) => { + const { + id, + date, + title, + description, + image, + actionText, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + customButton, + hideDate, + ///: END:ONLY_INCLUDE_IN + } = notification; const actionFunction = getActionFunctionById(id, history); + let showNotificationDate = true; + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + showNotificationDate = !hideDate; + ///: END:ONLY_INCLUDE_IN const imageComponent = image && ( {renderDescription(description)}
-
{date}
+ {showNotificationDate && ( +
{date}
+ )}
{placeImageBelowDescription && imageComponent} {actionText && ( @@ -173,6 +206,25 @@ const renderFirstNotification = ( {actionText} )} + { + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + customButton && customButton.name === 'mmi-portfolio' && ( + + ) + ///: END:ONLY_INCLUDE_IN + }
{ +}) => { const { id, date, title, description, actionText } = notification; const actionFunction = getActionFunctionById(id, history); @@ -217,7 +269,12 @@ const renderSubsequentNotification = ( ); }; -export default function WhatsNewPopup({ onClose }) { +export default function WhatsNewPopup({ + onClose, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + mmiPortfolioUrl, + ///: END:ONLY_INCLUDE_IN +}) { const t = useContext(I18nContext); const history = useHistory(); @@ -295,6 +352,16 @@ export default function WhatsNewPopup({ onClose }) { }; }, [idRefMap, setSeenNotifications]); + // Display the swaps notification with full image + // Displays the NFTs & OpenSea notifications 18,19 with full image + const notificationRenderers = { + 0: renderFirstNotification, + 1: renderFirstNotification, + 18: renderFirstNotification, + 19: renderFirstNotification, + 21: renderFirstNotification, + }; + return ( { const notification = getTranslatedUINotifications(t, locale)[id]; const isLast = index === notifications.length - 1; - // Display the swaps notification with full image - // Displays the NFTs & OpenSea notifications 18,19 with full image - return index === 0 || id === 1 || id === 18 || id === 19 || id === 21 - ? renderFirstNotification( - notification, - idRefMap, - history, - isLast, - trackEvent, - ) - : renderSubsequentNotification( - notification, - idRefMap, - history, - isLast, - ); + // Choose the appropriate rendering function based on the id + let renderNotification = + notificationRenderers[id] || renderSubsequentNotification; + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + renderNotification = renderFirstNotification; + ///: END:ONLY_INCLUDE_IN + + return renderNotification({ + notification, + idRefMap, + history, + isLast, + trackEvent, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + mmiPortfolioUrl, + seenNotifications, + onClose, + ///: END:ONLY_INCLUDE_IN + }); })}
@@ -345,4 +416,7 @@ export default function WhatsNewPopup({ onClose }) { WhatsNewPopup.propTypes = { onClose: PropTypes.func.isRequired, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + mmiPortfolioUrl: PropTypes.string.isRequired, + ///: END:ONLY_INCLUDE_IN }; diff --git a/ui/components/component-library/label/__snapshots__/label.test.js.snap b/ui/components/component-library/label/__snapshots__/label.test.js.snap index f72fcd1f7..3691978d9 100644 --- a/ui/components/component-library/label/__snapshots__/label.test.js.snap +++ b/ui/components/component-library/label/__snapshots__/label.test.js.snap @@ -3,7 +3,7 @@ exports[`label should render text inside the label 1`] = `
diff --git a/ui/components/component-library/label/label.js b/ui/components/component-library/label/label.js index c7cfa08b7..072dde236 100644 --- a/ui/components/component-library/label/label.js +++ b/ui/components/component-library/label/label.js @@ -1,13 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { Text } from '../text'; import { - FONT_WEIGHT, + FontWeight, TextVariant, - DISPLAY, + Display, AlignItems, } from '../../../helpers/constants/design-system'; +import { Text } from '../text'; export const Label = ({ htmlFor, className, children, ...props }) => ( ( as="label" htmlFor={htmlFor} variant={TextVariant.bodyMd} - fontWeight={FONT_WEIGHT.BOLD} - display={DISPLAY.INLINE_FLEX} + fontWeight={FontWeight.Medium} + display={Display.InlineFlex} alignItems={AlignItems.center} {...props} > diff --git a/ui/components/component-library/popover/popover.stories.tsx b/ui/components/component-library/popover/popover.stories.tsx index ca297daf8..bf54ed248 100644 --- a/ui/components/component-library/popover/popover.stories.tsx +++ b/ui/components/component-library/popover/popover.stories.tsx @@ -731,7 +731,7 @@ export const Offset: StoryFn = (args) => { ); }; -export const onPressEscKey: StoryFn = (args) => { +export const OnPressEscKey: StoryFn = (args) => { const [referenceElement, setReferenceElement] = useState(); const [isOpen, setIsOpen] = useState(false); diff --git a/ui/components/institutional/compliance-details/__snapshots__/compliance-details.test.js.snap b/ui/components/institutional/compliance-details/__snapshots__/compliance-details.test.js.snap index bd4b24a02..36420ca9f 100644 --- a/ui/components/institutional/compliance-details/__snapshots__/compliance-details.test.js.snap +++ b/ui/components/institutional/compliance-details/__snapshots__/compliance-details.test.js.snap @@ -3,10 +3,10 @@ exports[`ComplianceDetails should render correctly 1`] = `

diff --git a/ui/components/institutional/custody-labels/__snapshots__/custody-labels.test.js.snap b/ui/components/institutional/custody-labels/__snapshots__/custody-labels.test.js.snap index 78c08a9a0..d79ae3d87 100644 --- a/ui/components/institutional/custody-labels/__snapshots__/custody-labels.test.js.snap +++ b/ui/components/institutional/custody-labels/__snapshots__/custody-labels.test.js.snap @@ -3,7 +3,7 @@ exports[`CustodyLabels Component should render correctly 1`] = `