diff --git a/.circleci/config.yml b/.circleci/config.yml index 8884b4efb241..71d4ec2787d5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -814,18 +814,11 @@ jobs: yarn test:e2e:chrome --retries 2 --debug fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-chrome-mv3: executor: node-browsers @@ -848,7 +841,7 @@ jobs: command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:chrome --retries 2 --debug --mv3 || echo "Temporarily suppressing MV3 e2e test failures" + yarn test:e2e:chrome --retries 2 --debug || echo "Temporarily suppressing MV3 e2e test failures" fi no_output_timeout: 20m - store_artifacts: @@ -879,18 +872,11 @@ jobs: yarn test:e2e:chrome:rpc --retries 2 fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-chrome-rpc-mmi: executor: node-browsers @@ -915,18 +901,11 @@ jobs: yarn test:e2e:chrome:rpc --retries 2 --debug --build-type=mmi fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-firefox-snaps: executor: node-browsers @@ -952,18 +931,11 @@ jobs: yarn test:e2e:firefox:snaps --retries 2 --debug --build-type=main fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-chrome-snaps: executor: node-browsers @@ -989,18 +961,11 @@ jobs: yarn test:e2e:chrome:snaps --retries 2 --debug --build-type=main fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-firefox-snaps-flask: executor: node-browsers @@ -1026,18 +991,11 @@ jobs: yarn test:e2e:firefox:snaps --retries 2 --debug --build-type=flask fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-chrome-snaps-flask: executor: node-browsers @@ -1063,18 +1021,11 @@ jobs: yarn test:e2e:chrome:snaps --retries 2 --debug --build-type=flask fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-chrome-mmi: executor: node-browsers @@ -1100,18 +1051,11 @@ jobs: yarn test:e2e:chrome:mmi --retries 2 --debug --build-type=mmi fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e test-e2e-firefox: executor: node-browsers-medium-plus @@ -1137,18 +1081,11 @@ jobs: yarn test:e2e:firefox --retries 2 --debug fi no_output_timeout: 20m - - run: - name: Merge JUnit report - command: | - if [ "$(ls -A test/test-results/e2e)" ]; then - yarn test:e2e:report - fi - when: always - store_artifacts: path: test-artifacts destination: test-artifacts - store_test_results: - path: test/test-results/e2e.xml + path: test/test-results/e2e benchmark: executor: node-browsers-medium-plus diff --git a/.circleci/scripts/chrome-install.sh b/.circleci/scripts/chrome-install.sh index 2cf0bdb0e393..559907193c12 100755 --- a/.circleci/scripts/chrome-install.sh +++ b/.circleci/scripts/chrome-install.sh @@ -7,12 +7,12 @@ set -o pipefail sudo apt-get update # To get the latest version, see -CHROME_VERSION='116.0.5845.179-1' +CHROME_VERSION='118.0.5993.88-1' CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb" CHROME_BINARY_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/${CHROME_BINARY}" # To retrieve this checksum, run the `wget` and `shasum` commands below -CHROME_BINARY_SHA512SUM='cbdad3f5c928ef79a46a3619054b3c4a73a99f942f9bf4ea75d37d6434912da5c01f6ee30718a58e869ff6b57b10bb7fea1cf91885a25aac290a50a2ee3c03c4' +CHROME_BINARY_SHA512SUM='cae6a5cd8632ad350b41f4dfaf80449e6cf19d0b02816b9a1600f54b15df2adf5c4ded3792bfbe3855fa11a79ea256622f50180aa3c6779cedd75a55e7a6da9d' wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}" diff --git a/.depcheckrc.yml b/.depcheckrc.yml index 972b13b06e8a..f205629debef 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -32,6 +32,7 @@ ignores: - 'geckodriver' - 'jest' - 'lavamoat-viz' + - 'mocha-junit-reporter' - 'prettier-plugin-sort-json' # automatically imported by prettier - 'source-map-explorer' - 'playwright' @@ -41,6 +42,7 @@ ignores: # storybook - '@storybook/cli' - '@storybook/core' + - '@storybook/addon-designs' - '@storybook/addon-essentials' - '@storybook/addon-a11y' - '@storybook/addon-mdx-gfm' diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4eb64441690d..7073870aba1f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,7 +9,6 @@ * @MetaMask/extension-devs **/snaps/** @MetaMask/snaps-devs -**/flask/** @MetaMask/extension-devs @MetaMask/snaps-devs development/ @MetaMask/extension-devs @kumavis lavamoat/ @MetaMask/extension-devs @MetaMask/supply-chain @MetaMask/snaps-devs @@ -24,6 +23,17 @@ lavamoat/ @MetaMask/extension-devs @MetaMask/supply-c # should be brought to the attention of engineering leadership for # discussion .circleci/ @MetaMask/library-admins @kumavis @brad-decker + +# The privacy-snapshot.json file includes a list of all hosts that the +# extension communicates with during the E2E test suite runs. It is not a +# complete list of all hosts that the extension communicates with until the E2E +# test suite has full coverage. Anytime the privacy-snapshot file changes, +# extra scrutiny should be applied to the pull request to confirm that it does +# not broaden the number of hosts the extension communicates with without also +# providing a path for users to avoid that communication. MetaMask strives to +# make all such communication opt IN versus opt OUT. +privacy-snapshot.json @MetaMask/extension-privacy-reviewers + # The CODEOWNERS file constitutes an agreement amongst organization # admins and maintainers to restrict approval capabilities to a subset # of contributors. Modifications to this file result in a modification of diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index df752860bfb1..f8a3d35d16bc 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -26,10 +26,10 @@ body: label: Expected behavior description: What did you expect to happen? - type: textarea - id: screenshot + id: screenshot-recording attributes: - label: Screenshots - description: Please include screenshots if applicable! + label: Screenshots/Recordings + description: Please include screenshots/recordings if applicable! (https://recordit.co/ is recommended) - type: textarea id: reproduce attributes: diff --git a/.github/ISSUE_TEMPLATE/general-issue.yml b/.github/ISSUE_TEMPLATE/general-issue.yml index f23b47082e5a..5bd95322ec50 100644 --- a/.github/ISSUE_TEMPLATE/general-issue.yml +++ b/.github/ISSUE_TEMPLATE/general-issue.yml @@ -13,7 +13,7 @@ body: id: description attributes: label: What is this about? - placeholder: Describe the issue here. + placeholder: As a user, describe the issue here. validations: required: true diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index fcdebbcb5215..19b3e0e628ca 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -1,40 +1,42 @@ ## **Description** -_Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions:_ -_1. What is the reason for the change?_ -_2. What is the improvement/solution?_ + + + +## **Related issues** + +Fixes: # ## **Manual testing steps** -_1. Step1:_ -_2. Step2:_ -_3. ..._ +1. Go to this page... +2. +3. ## **Screenshots/Recordings** -_If applicable, add screenshots and/or recordings to visualize the before and after of your change._ + ### **Before** -_[screenshot]_ + ### **After** -_[screenshot]_ - -## **Related issues** - -_Fixes #???_ + ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). -- [ ] I've clearly explained: - - [ ] What problem this PR is solving. - - [ ] How this problem was solved. - - [ ] How reviewers can test my changes. -- [ ] I’ve indicated what issue this PR is linked to: Fixes #??? -- [ ] I’ve included tests if applicable. -- [ ] I’ve documented any added code. +- [ ] I've clearly explained what problem this PR is solving and how it is solved. +- [ ] I've linked related issues +- [ ] I've included manual testing steps +- [ ] I've included screenshots/recordings if applicable +- [ ] I’ve included tests if applicable +- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". diff --git a/.github/scripts/check-issue-template-and-add-labels.ts b/.github/scripts/check-issue-template-and-add-labels.ts new file mode 100644 index 000000000000..31e9dadf567f --- /dev/null +++ b/.github/scripts/check-issue-template-and-add-labels.ts @@ -0,0 +1,551 @@ +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import { GitHub } from '@actions/github/lib/utils'; + +// A labelable object can be a pull request or an issue +interface Labelable { + id: string; + number: number; + repoOwner: string; + repoName: string; + body: string; + author: string; + labels: { + id: string; + name: string; + }[]; +} + +// An enum, to categorise issues, based on template it matches +enum IssueType { + GeneralIssue, + BugReport, + None, +} + +// Titles of our two issues templates ('general-issue.yml' and 'bug-report.yml' issue) +const generalIssueTemplateTitles = [ + '### What is this about?', + '### Scenario', + '### Design', + '### Technical Details', + '### Threat Modeling Framework', + '### Acceptance Criteria', + '### References', +]; +const bugReportTemplateTitles = [ + '### Describe the bug', + '### Expected behavior', + '### Screenshots', // TODO: replace '### Screenshots' by '### Screenshots/Recordings' in January 2024 (as most issues will meet this criteria by then) + '### Steps to reproduce', + '### Error messages or log output', + '### Version', + '### Build type', + '### Browser', + '### Operating system', + '### Hardware wallet', + '### Additional context', + '### Severity' +]; + +// External contributor label +const externalContributorLabelName = `external-contributor`; +const externalContributorLabelColor = 'B60205'; // red +const externalContributorLabelDescription = `Issue or PR created by user outside MetaMask organisation`; + +// Craft invalid issue template label +const invalidIssueTemplateLabelName = `INVALID-ISSUE-TEMPLATE`; +const invalidIssueTemplateLabelColor = 'EDEDED'; // grey +const invalidIssueTemplateLabelDescription = `Issue's body doesn't match any issue template.`; + +main().catch((error: Error): void => { + console.error(error); + process.exit(1); +}); + +async function main(): Promise { + // "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions. + // We can't use "GITHUB_TOKEN" here, as its permissions don't allow neither to create new labels + // nor to retrieve the list of organisations a user belongs to. + // In our case, we may want to create "regression-prod-x.y.z" label when it doesn't already exist. + // We may also want to retrieve the list of organisations a user belongs to. + // As a consequence, we need to create our own "LABEL_TOKEN" with "repo" and "read:org" permissions. + // Such a token allows both to create new labels and fetch user's list of organisations. + const personalAccessToken = process.env.LABEL_TOKEN; + if (!personalAccessToken) { + core.setFailed('LABEL_TOKEN not found'); + process.exit(1); + } + + // Retrieve pull request info from context + const issueRepoOwner = context.repo.owner; + const issueRepoName = context.repo.repo; + const issueNumber = context.payload.issue?.number; + if (!issueNumber) { + core.setFailed('Issue number not found'); + process.exit(1); + } + + // Initialise octokit, required to call Github GraphQL API + const octokit: InstanceType = getOctokit(personalAccessToken, { + previews: ['bane'], // The "bane" preview is required for adding, updating, creating and deleting labels. + }); + + // Retrieve issue + const issue: Labelable = await retrieveIssue( + octokit, + issueRepoOwner, + issueRepoName, + issueNumber, + ); + + // Add external contributor label to the issue, in case author is not part of the MetaMask organisation + await addExternalContributorLabel(octokit, issue); + + // Check if issue's body matches one of the two issues templates ('general-issue.yml' or 'bug-report.yml') + const issueType: IssueType = extractIssueTypeFromIssueBody(issue.body); + + if (issueType === IssueType.GeneralIssue) { + console.log("Issue matches 'general-issue.yml' template."); + await removeInvalidIssueTemplateLabelIfPresent(octokit, issue); + } else if (issueType === IssueType.BugReport) { + console.log("Issue matches 'bug-report.yml' template."); + await removeInvalidIssueTemplateLabelIfPresent(octokit, issue); + + // Extract release version from issue body (is existing) + const releaseVersion = extractReleaseVersionFromIssueBody(issue.body); + + // Add regression prod label to the issue if release version was found is issue body + if (releaseVersion) { + await addRegressionProdLabel(octokit, releaseVersion, issue); + } else { + console.log( + `No release version was found in body of issue ${issue?.number}.`, + ); + } + } else { + const errorMessage = + "Issue body does not match any of expected templates ('general-issue.yml' or 'bug-report.yml')."; + console.log(errorMessage); + + // Add invalid issue template label to the issue, in case issue doesn't match any template + await addInvalidIssueTemplateLabel(octokit, issue); + + // Github action shall fail in case issue doesn't match any template + throw new Error(errorMessage); + } +} + +// This helper function checks if issue's body matches one of the two issues templates ('general-issue.yml' or 'bug-report.yml'). +function extractIssueTypeFromIssueBody(issueBody: string): IssueType { + let missingGeneralIssueTitle: boolean = false; + for (const title of generalIssueTemplateTitles) { + if (!issueBody.includes(title)) { + missingGeneralIssueTitle = true; + } + } + + let missingBugReportTitle: boolean = false; + for (const title of bugReportTemplateTitles) { + if (!issueBody.includes(title)) { + missingBugReportTitle = true; + } + } + + if (!missingGeneralIssueTitle) { + return IssueType.GeneralIssue; + } else if (!missingBugReportTitle) { + return IssueType.BugReport; + } else { + return IssueType.None; + } +} + +// This helper function checks if issue's body has a bug report format. +function extractReleaseVersionFromIssueBody( + issueBody: string, +): string | undefined { + // Remove newline characters + const cleanedIssueBody = issueBody.replace(/\r?\n/g, ' '); + + // Extract version from the cleaned issue body + const regex = /### Version\s+((.*?)(?= |$))/; + const versionMatch = cleanedIssueBody.match(regex); + const version = versionMatch?.[1]; + + // Check if version is in the format x.y.z + if (version && !/^(\d+\.)?(\d+\.)?(\*|\d+)$/.test(version)) { + throw new Error('Version is not in the format x.y.z'); + } + + return version; +} + +// This function adds the "external-contributor" label to the issue, in case author is not part of the MetaMask organisation +async function addExternalContributorLabel( + octokit: InstanceType, + issue: Labelable, +): Promise { + // If author is not part of the MetaMask organisation + if (!(await userBelongsToMetaMaskOrg(octokit, issue?.author))) { + // Add external contributor label to the issue + await addLabelToLabelable( + octokit, + issue, + externalContributorLabelName, + externalContributorLabelColor, + externalContributorLabelDescription, + ); + } +} + +// This function adds the correct "regression-prod-x.y.z" label to the issue, and removes other ones +async function addRegressionProdLabel( + octokit: InstanceType, + releaseVersion: string, + issue: Labelable, +): Promise { + // Craft regression prod label to add + const regressionProdLabelName = `regression-prod-${releaseVersion}`; + const regressionProdLabelColor = '5319E7'; // violet + const regressionProdLabelDescription = `Regression bug that was found in production in release ${releaseVersion}`; + + let regressionProdLabelFound: boolean = false; + const regressionProdLabelsToBeRemoved: { + id: string; + name: string; + }[] = []; + + // Loop over issue's labels, to see if regression labels are either missing, or to be removed + issue?.labels?.forEach((label) => { + if (label?.name === regressionProdLabelName) { + regressionProdLabelFound = true; + } else if (label?.name?.startsWith('regression-prod-')) { + regressionProdLabelsToBeRemoved.push(label); + } + }); + + // Add regression prod label to the issue if missing + if (regressionProdLabelFound) { + console.log( + `Issue ${issue?.number} already has ${regressionProdLabelName} label.`, + ); + } else { + console.log( + `Add ${regressionProdLabelName} label to issue ${issue?.number}.`, + ); + await addLabelToLabelable( + octokit, + issue, + regressionProdLabelName, + regressionProdLabelColor, + regressionProdLabelDescription, + ); + } + + // Remove other regression prod label from the issue + await Promise.all( + regressionProdLabelsToBeRemoved.map((label) => { + removeLabelFromLabelable(octokit, issue, label?.id); + }), + ); +} + +// This function adds the "INVALID-ISSUE-TEMPLATE" label to the issue +async function addInvalidIssueTemplateLabel( + octokit: InstanceType, + issue: Labelable, +): Promise { + // Add label to issue + await addLabelToLabelable( + octokit, + issue, + invalidIssueTemplateLabelName, + invalidIssueTemplateLabelColor, + invalidIssueTemplateLabelDescription, + ); +} + +// This function removes the "INVALID-ISSUE-TEMPLATE" label from the issue, in case it's present +async function removeInvalidIssueTemplateLabelIfPresent( + octokit: InstanceType, + issue: Labelable, +): Promise { + // Check if label is present on issue + const label = issue?.labels?.find( + (label) => label.name === invalidIssueTemplateLabelName, + ); + + if (label?.id) { + // Remove label from issue + await removeLabelFromLabelable(octokit, issue, label.id); + } +} + +// This function retrieves the repo +async function retrieveRepo( + octokit: InstanceType, + repoOwner: string, + repoName: string, +): Promise { + const retrieveRepoQuery = ` + query RetrieveRepo($repoOwner: String!, $repoName: String!) { + repository(owner: $repoOwner, name: $repoName) { + id + } + } +`; + + const retrieveRepoResult: { + repository: { + id: string; + }; + } = await octokit.graphql(retrieveRepoQuery, { + repoOwner, + repoName, + }); + + const repoId = retrieveRepoResult?.repository?.id; + + return repoId; +} + +// This function retrieves the label on a specific repo +async function retrieveLabel( + octokit: InstanceType, + repoOwner: string, + repoName: string, + labelName: string, +): Promise { + const retrieveLabelQuery = ` + query RetrieveLabel($repoOwner: String!, $repoName: String!, $labelName: String!) { + repository(owner: $repoOwner, name: $repoName) { + label(name: $labelName) { + id + } + } + } + `; + + const retrieveLabelResult: { + repository: { + label: { + id: string; + }; + }; + } = await octokit.graphql(retrieveLabelQuery, { + repoOwner, + repoName, + labelName, + }); + + const labelId = retrieveLabelResult?.repository?.label?.id; + + return labelId; +} + +// This function creates the label on a specific repo +async function createLabel( + octokit: InstanceType, + repoId: string, + labelName: string, + labelColor: string, + labelDescription: string, +): Promise { + const createLabelMutation = ` + mutation CreateLabel($repoId: ID!, $labelName: String!, $labelColor: String!, $labelDescription: String) { + createLabel(input: {repositoryId: $repoId, name: $labelName, color: $labelColor, description: $labelDescription}) { + label { + id + } + } + } + `; + + const createLabelResult: { + createLabel: { + label: { + id: string; + }; + }; + } = await octokit.graphql(createLabelMutation, { + repoId, + labelName, + labelColor, + labelDescription, + }); + + const labelId = createLabelResult?.createLabel?.label?.id; + + return labelId; +} + +// This function creates or retrieves the label on a specific repo +async function createOrRetrieveLabel( + octokit: InstanceType, + repoOwner: string, + repoName: string, + labelName: string, + labelColor: string, + labelDescription: string, +): Promise { + // Check if label already exists on the repo + let labelId = await retrieveLabel(octokit, repoOwner, repoName, labelName); + + // If label doesn't exist on the repo, create it + if (!labelId) { + // Retrieve PR's repo + const repoId = await retrieveRepo(octokit, repoOwner, repoName); + + // Create label on repo + labelId = await createLabel( + octokit, + repoId, + labelName, + labelColor, + labelDescription, + ); + } + + return labelId; +} + +// This function retrieves the issue on a specific repo +async function retrieveIssue( + octokit: InstanceType, + repoOwner: string, + repoName: string, + issueNumber: number, +): Promise { + const retrieveIssueQuery = ` + query GetIssue($repoOwner: String!, $repoName: String!, $issueNumber: Int!) { + repository(owner: $repoOwner, name: $repoName) { + issue(number: $issueNumber) { + id + body + author { + login + } + labels(first: 100) { + nodes { + id + name + } + } + } + } + } + `; + + const retrieveIssueResult: { + repository: { + issue: { + id: string; + body: string; + author: { + login: string; + }; + labels: { + nodes: { + id: string; + name: string; + }[]; + }; + }; + }; + } = await octokit.graphql(retrieveIssueQuery, { + repoOwner, + repoName, + issueNumber, + }); + + const issue: Labelable = { + id: retrieveIssueResult?.repository?.issue?.id, + number: issueNumber, + repoOwner: repoOwner, + repoName: repoName, + body: retrieveIssueResult?.repository?.issue?.body, + author: retrieveIssueResult?.repository?.issue?.author?.login, + labels: retrieveIssueResult?.repository?.issue?.labels?.nodes, + }; + + return issue; +} + +// This function adds label to a labelable object (i.e. a pull request or an issue) +async function addLabelToLabelable( + octokit: InstanceType, + labelable: Labelable, + labelName: string, + labelColor: string, + labelDescription: string, +): Promise { + // Retrieve label from the labelable's repo, or create label if required + const labelId = await createOrRetrieveLabel( + octokit, + labelable?.repoOwner, + labelable?.repoName, + labelName, + labelColor, + labelDescription, + ); + + const addLabelsToLabelableMutation = ` + mutation AddLabelsToLabelable($labelableId: ID!, $labelIds: [ID!]!) { + addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) { + clientMutationId + } + } + `; + + await octokit.graphql(addLabelsToLabelableMutation, { + labelableId: labelable?.id, + labelIds: [labelId], + }); +} + +// This function removes a label from a labelable object (i.e. a pull request or an issue) +async function removeLabelFromLabelable( + octokit: InstanceType, + labelable: Labelable, + labelId: string, +): Promise { + const removeLabelsFromLabelableMutation = ` + mutation RemoveLabelsFromLabelable($labelableId: ID!, $labelIds: [ID!]!) { + removeLabelsFromLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) { + clientMutationId + } + } + `; + + await octokit.graphql(removeLabelsFromLabelableMutation, { + labelableId: labelable?.id, + labelIds: [labelId], + }); +} + +// This function checks if user belongs to MetaMask organization on Github +async function userBelongsToMetaMaskOrg( + octokit: InstanceType, + username: string, +): Promise { + const userBelongsToMetaMaskOrgQuery = ` + query UserBelongsToMetaMaskOrg($login: String!) { + user(login: $login) { + organization(login: "MetaMask") { + id + } + } + } + `; + + const userBelongsToMetaMaskOrgResult: { + user: { + organization: { + id: string; + }; + }; + } = await octokit.graphql(userBelongsToMetaMaskOrgQuery, { login: username }); + + return Boolean(userBelongsToMetaMaskOrgResult?.user?.organization?.id); +} diff --git a/.github/workflows/check-issue-template-and-add-labels.yml b/.github/workflows/check-issue-template-and-add-labels.yml new file mode 100644 index 000000000000..11baaeafbc60 --- /dev/null +++ b/.github/workflows/check-issue-template-and-add-labels.yml @@ -0,0 +1,31 @@ +name: Check issue template and add labels + +on: + issues: + types: + - opened + - edited + +jobs: + add-regression-prod-label: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 # This retrieves only the latest commit. + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: yarn + + - name: Install dependencies + run: yarn --immutable + + - name: Check issue template and add labels + id: check-issue-template-and-add-labels + env: + LABEL_TOKEN: ${{ secrets.LABEL_TOKEN }} + run: npm run check-issue-template-and-add-labels diff --git a/.github/workflows/check-pr-labels.yml b/.github/workflows/check-pr-labels.yml index cc1cb37dbb53..cfd48e22c8b6 100644 --- a/.github/workflows/check-pr-labels.yml +++ b/.github/workflows/check-pr-labels.yml @@ -20,7 +20,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 with: - fetch-depth: 0 # This is needed to checkout all branches + fetch-depth: 1 # This retrieves only the latest commit. - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/.storybook/initial-states/approval-screens/token-approval.js b/.storybook/initial-states/approval-screens/token-approval.js index 08aa375d7c0f..66ebe7e45d8f 100644 --- a/.storybook/initial-states/approval-screens/token-approval.js +++ b/.storybook/initial-states/approval-screens/token-approval.js @@ -2,7 +2,6 @@ export const currentNetworkTxListSample = { "id": 7900715443136469, "time": 1621395091737, "status": "unapproved", - "metamaskNetworkId": "1337", "chainId": "0x539", "loadingDefaults": false, "txParams": { @@ -20,7 +19,6 @@ export const currentNetworkTxListSample = { "id": 7900715443136469, "time": 1621395091737, "status": "unapproved", - "metamaskNetworkId": "1337", "chainId": "0x539", "loadingDefaults": true, "txParams": { @@ -53,4 +51,4 @@ export const subjectMetadata = { "iconUrl": "https://metamask.github.io/test-dapp/metamask-fox.svg", "subjectType": "website" } -} \ No newline at end of file +} diff --git a/.storybook/initial-states/transactions.js b/.storybook/initial-states/transactions.js index cc0f0e1be3c6..6dd76b2be7e8 100644 --- a/.storybook/initial-states/transactions.js +++ b/.storybook/initial-states/transactions.js @@ -24,7 +24,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 643368596521636, time: 1653527035634, status: 'submitted', - metamaskNetworkId: '5', originalGasEstimate: '5208', userEditedGasLimit: false, chainId: '0x5', @@ -50,7 +49,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 643368596521636, time: 1653527035634, status: 'approved', - metamaskNetworkId: '5', originalGasEstimate: '5208', userEditedGasLimit: false, chainId: '0x5', @@ -158,7 +156,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { }, id: 7694052085150913, loadingDefaults: true, - metamaskNetworkId: '5', origin: 'https://remix.ethereum.org', originalGasEstimate: '0x118f4', sendFlowHistory: [], @@ -181,7 +178,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.DEPLOY_CONTRACT]: { blockNumber: '6195527', id: 4243712234858468, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1585088013000, txParams: { @@ -238,7 +235,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.INCOMING]: { blockNumber: '6477257', id: 4243712234858505, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1589314295000, txParams: { @@ -292,7 +289,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 3938342322880462, time: 1653459456297, status: 'failed', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '14609', userEditedGasLimit: false, chainId: '0x5', @@ -322,7 +319,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 3938342322880462, time: 1653459456297, status: 'approved', - metamaskNetworkId: '5', originalGasEstimate: '14609', userEditedGasLimit: false, chainId: '0x5', @@ -481,7 +477,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 4243712234858512, time: 1589314601567, status: 'confirmed', - metamaskNetworkId: '5', + chainId: '0x5', loadingDefaults: false, txParams: { from: '0xabca64466f257793eaa52fcfff5066894b76a149', @@ -497,7 +493,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.SMART]: { blockNumber: '6195527', id: 4243712234858468, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1585088013000, txParams: { @@ -517,7 +513,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.SWAP]: { blockNumber: '6195527', id: 4243712234858467, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1585088013000, txParams: { @@ -536,7 +532,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { [MOCK_TX_TYPE.SWAP_APPROVAL]: { blockNumber: '6195527', id: 4243712234858467, - metamaskNetworkId: '5', + chainId: '0x5', status: 'confirmed', time: 1585088013000, txParams: { @@ -556,7 +552,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058729, time: 1653457101080, status: 'submitted', - metamaskNetworkId: '5', originalGasEstimate: '0xb427', userEditedGasLimit: false, chainId: '0x5', @@ -584,7 +579,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058729, time: 1653457101080, status: 'unapproved', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0xb427', userEditedGasLimit: false, chainId: '0x5', @@ -760,7 +755,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 1441203963845330, time: 1652206763566, status: 'confirmed', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0x118e0', userEditedGasLimit: false, chainId: '0x5', @@ -861,7 +856,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058725, time: 1653457077370, status: 'confirmed', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x5', @@ -889,7 +884,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058725, time: 1653457077370, status: 'unapproved', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x5', @@ -1215,7 +1210,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058754, time: 1653457323504, status: 'confirmed', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0x10896', userEditedGasLimit: false, chainId: '0x5', @@ -1265,7 +1260,7 @@ export const MOCK_TRANSACTION_BY_TYPE = { id: 5177046356058754, time: 1653457323504, status: 'unapproved', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0x10896', userEditedGasLimit: false, chainId: '0x5', diff --git a/.storybook/main.js b/.storybook/main.js index 9a7c342c7de0..b2060367f6e7 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -23,6 +23,7 @@ module.exports = { 'storybook-dark-mode', '@whitespace/storybook-addon-html', '@storybook/addon-mdx-gfm', + '@storybook/addon-designs', ], staticDirs: ['../app', './images'], // Uses babel.config.js settings and prevents "Missing class properties transform" error diff --git a/.storybook/preview.js b/.storybook/preview.js index fa8e49cf588a..d3a060481250 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -15,7 +15,7 @@ import MetaMetricsProviderStorybook from './metametrics'; import testData from './test-data.js'; import { Router } from 'react-router-dom'; import { createBrowserHistory } from 'history'; -import { _setBackgroundConnection } from '../ui/store/action-queue'; +import { setBackgroundConnection } from '../ui/store/background-connection'; import MetaMaskStorybookTheme from './metamask-storybook-theme'; import { addons } from '@storybook/addons'; @@ -78,7 +78,7 @@ const proxiedBackground = new Proxy( }, }, ); -_setBackgroundConnection(proxiedBackground); +setBackgroundConnection(proxiedBackground); const metamaskDecorator = (story, context) => { const [isDark, setDark] = useState(false); diff --git a/.storybook/test-data.js b/.storybook/test-data.js index b78ae04c0620..85ed71007646 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -319,7 +319,6 @@ const state = { id: 3111025347726181, time: 1620710815484, status: 'unapproved', - metamaskNetworkId: '5', msgParams: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4', chainId: '0x5', loadingDefaults: false, @@ -339,7 +338,6 @@ const state = { id: 7786962153682822, time: 1620710815484, status: 'unapproved', - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: true, txParams: { @@ -586,7 +584,6 @@ const state = { dappSuggestedGasFees: null, id: 2360388496987298, loadingDefaults: true, - metamaskNetworkId: '56', origin: 'metamask', status: 'unapproved', time: 1629582710520, @@ -873,7 +870,6 @@ const state = { ], id: 7900715443136469, loadingDefaults: false, - metamaskNetworkId: '56', origin: 'metamask', r: '0x90a4dfb0646eef9815454d0ab543b5844acb8772101084565155c93ecce8ed69', rawTx: @@ -1153,7 +1149,7 @@ const state = { '0x2de9256a7c604586f7ecfd87ae9509851e217f588f9f85feed793c54ed2ce0aa': { blockNumber: '8888976', id: 4678200543090532, - metamaskNetworkId: '1', + chainId: '0x1', status: 'confirmed', time: 1573114896000, txParams: { @@ -1170,7 +1166,7 @@ const state = { '0x320a1fd769373578f78570e5d8f56e89bc7bce9657bb5f4c12d8fe790d471bfd': { blockNumber: '9453174', id: 4678200543090535, - metamaskNetworkId: '1', + chainId: '0x1', status: 'confirmed', time: 1581312411000, txParams: { @@ -1187,7 +1183,7 @@ const state = { '0x8add6c1ea089a8de9b15fa2056b1875360f17916755c88ace9e5092b7a4b1239': { blockNumber: '10892417', id: 4678200543090542, - metamaskNetworkId: '1', + chainId: '0x1', status: 'confirmed', time: 1600515224000, txParams: { @@ -1204,7 +1200,7 @@ const state = { '0x50be62ab1cabd03ff104c602c11fdef7a50f3d73c55006d5583ba97950ab1144': { blockNumber: '10902987', id: 4678200543090545, - metamaskNetworkId: '1', + chainId: '0x1', status: 'confirmed', time: 1600654021000, txParams: { @@ -1385,7 +1381,6 @@ const state = { isLoading: false, warning: null, buyView: {}, - isMouseUser: true, gasIsLoading: false, defaultHdPaths: { trezor: "m/44'/60'/0'/0", @@ -1425,7 +1420,6 @@ const state = { id: 3111025347726181, time: 1620723786838, status: 'unapproved', - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: false, txParams: { @@ -1444,7 +1438,6 @@ const state = { id: 3111025347726181, time: 1620723786838, status: 'unapproved', - metamaskNetworkId: '5', chainId: '0x5', loadingDefaults: true, txParams: { diff --git a/.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch b/.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch deleted file mode 100644 index c6a1743a7167..000000000000 --- a/.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/lib/index.js b/lib/index.js -index c991f62dc64553502e9911a7f21e77e008d7f438..e503c7494d21b13df85b10e1657b2af8ca4d964f 100644 ---- a/lib/index.js -+++ b/lib/index.js -@@ -222,7 +222,6 @@ var _transform = require("./transform"); - var _transformFile = require("./transform-file"); - var _transformAst = require("./transform-ast"); - var _parse = require("./parse"); --var thisFile = require("./index"); - const version = "7.21.5"; - exports.version = version; - const DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".es6", ".es", ".mjs", ".cjs"]); diff --git a/.yarn/patches/@babel-core-npm-7.23.2-b93f586907.patch b/.yarn/patches/@babel-core-npm-7.23.2-b93f586907.patch new file mode 100644 index 000000000000..fdae8d6b2b4e --- /dev/null +++ b/.yarn/patches/@babel-core-npm-7.23.2-b93f586907.patch @@ -0,0 +1,12 @@ +diff --git a/lib/index.js b/lib/index.js +index 64ff8344f6280d20988f8c3c81e1f248a1869e53..6739e7bd2271be6b861479ec384bbd007bdb5df8 100644 +--- a/lib/index.js ++++ b/lib/index.js +@@ -222,7 +222,6 @@ var _transform = require("./transform.js"); + var _transformFile = require("./transform-file.js"); + var _transformAst = require("./transform-ast.js"); + var _parse = require("./parse.js"); +-var thisFile = require("./index.js"); + ; + const version = "7.23.2"; + exports.version = version; diff --git a/.yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch b/.yarn/patches/@babel-runtime-npm-7.23.2-d013d6cf7e.patch similarity index 91% rename from .yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch rename to .yarn/patches/@babel-runtime-npm-7.23.2-d013d6cf7e.patch index 30144e224f5e..1197cddf1a1b 100644 --- a/.yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch +++ b/.yarn/patches/@babel-runtime-npm-7.23.2-d013d6cf7e.patch @@ -1,30 +1,10 @@ diff --git a/helpers/construct.js b/helpers/construct.js -index ecc013db4703c1c6ca8a5bba3db3955e75c1a972..08826bea9453f1351c08d44be9fffca92923fd76 100644 +index 3d3c232dcb226892cdf6181c6f4f4f40f2325fcc..da7e2696a244fadd35f0eeb5cae5f577ee3c7442 100644 --- a/helpers/construct.js +++ b/helpers/construct.js -@@ -1,22 +1,21 @@ +@@ -1,18 +1,21 @@ -var setPrototypeOf = require("./setPrototypeOf.js"); -+// All of MetaMask's supported browsers include `Reflect.construct` support, so -+// we don't need this polyfill. - -var isNativeReflectConstruct = require("./isNativeReflectConstruct.js"); -+// This Proxy preseves the two properties that were added by `@babel/runtime`. -+// I am not entire sure what these properties are for (maybe ES5/ES6 -+// interoperability?) but they have been preserved just in case. -+const reflectProxy = new Proxy( -+ Reflect.construct, -+ { -+ get: function (target, property) { -+ if (property === 'default') { -+ return target; -+ } else if (property === '__esModule') { -+ return true; -+ } -+ return Reflect.get(...arguments); -+ } -+ } -+); - -function _construct(Parent, args, Class) { - if (isNativeReflectConstruct()) { - module.exports = _construct = Reflect.construct.bind(), module.exports.__esModule = true, module.exports["default"] = module.exports; @@ -38,11 +18,28 @@ index ecc013db4703c1c6ca8a5bba3db3955e75c1a972..08826bea9453f1351c08d44be9fffca9 - return instance; - }, module.exports.__esModule = true, module.exports["default"] = module.exports; - } -- - return _construct.apply(null, arguments); -} -- -module.exports = _construct, module.exports.__esModule = true, module.exports["default"] = module.exports; \ No newline at end of file ++// All of MetaMask's supported browsers include `Reflect.construct` support, so ++// we don't need this polyfill. ++ ++// This Proxy preseves the two properties that were added by `@babel/runtime`. ++// I am not entire sure what these properties are for (maybe ES5/ES6 ++// interoperability?) but they have been preserved just in case. ++const reflectProxy = new Proxy( ++ Reflect.construct, ++ { ++ get: function (target, property) { ++ if (property === 'default') { ++ return target; ++ } else if (property === '__esModule') { ++ return true; ++ } ++ return Reflect.get(...arguments); ++ } ++ } ++); ++ +module.exports = reflectProxy; -\ No newline at end of file diff --git a/.yarn/patches/@metamask-eth-keyring-controller-npm-13.0.1-06ff83faad.patch b/.yarn/patches/@metamask-eth-keyring-controller-npm-13.0.1-06ff83faad.patch new file mode 100644 index 000000000000..c8df70a2c3ba --- /dev/null +++ b/.yarn/patches/@metamask-eth-keyring-controller-npm-13.0.1-06ff83faad.patch @@ -0,0 +1,20 @@ +# The `removeAccount` method of some keyrings is async (e.g. `SnapKeyring`), so +# this patch makes the `KeyringController` await the account removal. +# +# In the future, we must make sure that all keyrings implement the same +# interface. +# +# See: https://github.com/MetaMask/KeyringController/pull/280 +diff --git a/dist/KeyringController.js b/dist/KeyringController.js +index 3644209391f2cdf7ff5e2bd8f94517bff13b0f47..ed63d0ba4cb56df9fe3eb6b0a274f696375dd9dc 100644 +--- a/dist/KeyringController.js ++++ b/dist/KeyringController.js +@@ -259,7 +259,7 @@ class KeyringController extends events_1.EventEmitter { + if (!keyring.removeAccount) { + throw new Error(constants_1.KeyringControllerError.UnsupportedRemoveAccount); + } +- keyring.removeAccount(address); ++ await keyring.removeAccount(address); + this.emit('removedAccount', address); + const accounts = await keyring.getAccounts(); + // Check if this was the last/only account diff --git a/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch b/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch deleted file mode 100644 index 128ad3f80189..000000000000 --- a/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch +++ /dev/null @@ -1,17 +0,0 @@ -diff --git a/dist/SignatureController.js b/dist/SignatureController.js -index 46d4b4d0553f86d368d30b7e90a9dc2e03d26ef9..e7063a3753bc3821e661c11132e33304b4fce416 100644 ---- a/dist/SignatureController.js -+++ b/dist/SignatureController.js -@@ -280,8 +280,11 @@ _SignatureController_isEthSignEnabled = new WeakMap(), _SignatureController_getA - resultCallbacks = acceptResult.resultCallbacks; - } - catch (_a) { -+ signaturePromise.catch(() => { -+ // Expecting reject error but throwing manually rather than waiting -+ }); - __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_cancelAbstractMessage).call(this, messageManager, messageId); -- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the request.'); -+ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(`MetaMask ${messageName} Signature: User denied message signature.`); - } - yield signMessage(messageParamsWithId, signingOpts); - const signatureResult = yield signaturePromise; diff --git a/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch b/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch new file mode 100644 index 000000000000..692db45490f5 --- /dev/null +++ b/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch @@ -0,0 +1,23 @@ +diff --git a/dist/SignatureController.js b/dist/SignatureController.js +index 8ac1b2158ff4564fe2f942ca955bd337d78a94ef..c6552d874d830e610fcff791eb0f87f51fae1770 100644 +--- a/dist/SignatureController.js ++++ b/dist/SignatureController.js +@@ -278,6 +278,9 @@ _SignatureController_isEthSignEnabled = new WeakMap(), _SignatureController_getA + const messageParamsWithId = Object.assign(Object.assign(Object.assign({}, messageParams), { metamaskId: messageId }), (version && { version })); + const signaturePromise = messageManager.waitForFinishStatus(messageParamsWithId, messageName); + try { ++ signaturePromise.catch(() => { ++ // Expecting reject error but throwing manually rather than waiting ++ }); + // Signature request is proposed to the user + __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_addLog).call(this, signTypeForLogger, logging_controller_1.SigningStage.Proposed, messageParamsWithId); + const acceptResult = yield __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_requestApproval).call(this, messageParamsWithId, approvalType); +@@ -287,7 +290,7 @@ _SignatureController_isEthSignEnabled = new WeakMap(), _SignatureController_getA + // User rejected the signature request + __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_addLog).call(this, signTypeForLogger, logging_controller_1.SigningStage.Rejected, messageParamsWithId); + __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_cancelAbstractMessage).call(this, messageManager, messageId); +- throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the request.'); ++ throw eth_rpc_errors_1.ethErrors.provider.userRejectedRequest(`MetaMask ${messageName} Signature: User denied message signature.`); + } + yield signMessage(messageParamsWithId, signingOpts); + const signatureResult = yield signaturePromise; diff --git a/.yarn/patches/lavamoat-core-npm-14.4.1-c4e8bbb016.patch b/.yarn/patches/lavamoat-core-npm-14.4.1-c4e8bbb016.patch index abae9b29583c..07a662583315 100644 --- a/.yarn/patches/lavamoat-core-npm-14.4.1-c4e8bbb016.patch +++ b/.yarn/patches/lavamoat-core-npm-14.4.1-c4e8bbb016.patch @@ -1,3 +1,16 @@ +diff --git a/src/kernelTemplate.js b/src/kernelTemplate.js +index de8d73048b7bb7c4e74009b5c85ad919fe197ef0..e5bcb987cff013def8b2e8c767eb75a57ea0bd7a 100644 +--- a/src/kernelTemplate.js ++++ b/src/kernelTemplate.js +@@ -60,6 +60,8 @@ + errorTaming: 'unsafe', + // shows the full call stack + stackFiltering: 'verbose', ++ // prevents most common override mistake cases from tripping up users ++ overrideTaming: 'severe', + } + + lockdown(lockdownOptions) diff --git a/src/loadPolicy.js b/src/loadPolicy.js index ef71923f9282d6a5e9f74e6ec6fa0516f28f508b..0118fda7e1b0fa461ec01ceff8d7112d072f3dfb 100644 --- a/src/loadPolicy.js diff --git a/.yarn/patches/typescript-npm-4.4.4-3fedcc07a3.patch b/.yarn/patches/typescript-npm-4.4.4-3fedcc07a3.patch deleted file mode 100644 index 95cb488b1435..000000000000 --- a/.yarn/patches/typescript-npm-4.4.4-3fedcc07a3.patch +++ /dev/null @@ -1,242 +0,0 @@ -diff --git a/lib/typescript.js b/lib/typescript.js -index 323de6f4da00612e90e685142120736bfaeed37b..350e352e36f8bb6a870d7c24eaeae6bf7d648840 100644 ---- a/lib/typescript.js -+++ b/lib/typescript.js -@@ -24,11 +24,58 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { - return to.concat(ar || Array.prototype.slice.call(from)); - }; - var __assign = (this && this.__assign) || function () { -- __assign = Object.assign || function(t) { -+ __assign = function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; -- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) -- t[p] = s[p]; -+ for (var p in s) { -+ if (Object.prototype.hasOwnProperty.call(s, p)) { -+ /** -+ * In the original version of this package, this was: -+ * -+ * t[p] = s[p] -+ * -+ * Unfortunately LavaMoat trips up on this, so we have to change -+ * it. -+ * -+ * Internally LavaMoat uses `lockdown` (part of SES, which is -+ * part of Endo) to freeze modifications to "intrinsics" — core -+ * things like `Object.prototype`, `Function.prototype`, etc. -+ * This will cause code which is responsible for said -+ * modifications to fail at runtime, because it makes the -+ * properties of these intrinsics non-writable. -+ * -+ * The reason we have to change *this* code is that later on, -+ * this `__assign` function is used to merge two objects, and -+ * one of those objects contains a `constructor` property. As we -+ * know, `constructor` is a special property, as it's a property -+ * on `Object.prototype` that stores the constructor used to -+ * create that object. But when used in this context, there is -+ * nothing inherently special about it – it's just a property on -+ * an object we're setting. Unfortunately, that's not how it's -+ * being treated. Because `lockdown` freezes `Object.prototype`, -+ * `Object.prototype.constructor` is non-writable, and due to a -+ * "mistake" in the ES5 spec [1], that means `constructor` on -+ * *any* object is non-writable too. So an error is thrown when -+ * this code is executed. -+ * -+ * There is a way to get around this, which is to configure -+ * `lockdown` with the option `overrideTaming: 'severe'`. -+ * The mechanics of this option, as well as more information -+ * about the "mistake" this option solves, are explained here -+ * [2]. Unfortunately, we cannot enable this option because -+ * LavaMoat is the one running `lockdown` here [3]. So to work -+ * around this, we use `Object.defineProperty` to define the -+ * property we want. As this does not use property assignment -+ * (`object[key] = value`) but rather defines the property more -+ * directly, this bypasses the "override mistake". -+ * -+ * [1]: https://web.archive.org/web/20141230041441/http://wiki.ecmascript.org/doku.php?id=strawman:fixing_override_mistake -+ * [2]: https://github.com/endojs/endo/blob/864f086f87e1e7ef78a401a7550ff0aeb664bba0/packages/ses/src/enable-property-overrides.js#L28 -+ * [3]: https://github.com/LavaMoat/LavaMoat/blob/7c15bf8ba34ba1a9ceb3ffe591b1b2bfb084bead/packages/core/src/kernelTemplate.js#L32-L43 -+ */ -+ Object.defineProperty(t, p, Object.getOwnPropertyDescriptor(s, p)) -+ } -+ } - } - return t; - }; -@@ -9820,87 +9867,94 @@ var ts; - } - ts.tokenIsIdentifierOrKeywordOrGreaterThan = tokenIsIdentifierOrKeywordOrGreaterThan; - /** @internal */ -- ts.textToKeywordObj = (_a = { -- abstract: 126 /* AbstractKeyword */, -- any: 129 /* AnyKeyword */, -- as: 127 /* AsKeyword */, -- asserts: 128 /* AssertsKeyword */, -- bigint: 156 /* BigIntKeyword */, -- boolean: 132 /* BooleanKeyword */, -- break: 81 /* BreakKeyword */, -- case: 82 /* CaseKeyword */, -- catch: 83 /* CatchKeyword */, -- class: 84 /* ClassKeyword */, -- continue: 86 /* ContinueKeyword */, -- const: 85 /* ConstKeyword */ -- }, -- _a["" + "constructor"] = 133 /* ConstructorKeyword */, -- _a.debugger = 87 /* DebuggerKeyword */, -- _a.declare = 134 /* DeclareKeyword */, -- _a.default = 88 /* DefaultKeyword */, -- _a.delete = 89 /* DeleteKeyword */, -- _a.do = 90 /* DoKeyword */, -- _a.else = 91 /* ElseKeyword */, -- _a.enum = 92 /* EnumKeyword */, -- _a.export = 93 /* ExportKeyword */, -- _a.extends = 94 /* ExtendsKeyword */, -- _a.false = 95 /* FalseKeyword */, -- _a.finally = 96 /* FinallyKeyword */, -- _a.for = 97 /* ForKeyword */, -- _a.from = 154 /* FromKeyword */, -- _a.function = 98 /* FunctionKeyword */, -- _a.get = 135 /* GetKeyword */, -- _a.if = 99 /* IfKeyword */, -- _a.implements = 117 /* ImplementsKeyword */, -- _a.import = 100 /* ImportKeyword */, -- _a.in = 101 /* InKeyword */, -- _a.infer = 136 /* InferKeyword */, -- _a.instanceof = 102 /* InstanceOfKeyword */, -- _a.interface = 118 /* InterfaceKeyword */, -- _a.intrinsic = 137 /* IntrinsicKeyword */, -- _a.is = 138 /* IsKeyword */, -- _a.keyof = 139 /* KeyOfKeyword */, -- _a.let = 119 /* LetKeyword */, -- _a.module = 140 /* ModuleKeyword */, -- _a.namespace = 141 /* NamespaceKeyword */, -- _a.never = 142 /* NeverKeyword */, -- _a.new = 103 /* NewKeyword */, -- _a.null = 104 /* NullKeyword */, -- _a.number = 145 /* NumberKeyword */, -- _a.object = 146 /* ObjectKeyword */, -- _a.package = 120 /* PackageKeyword */, -- _a.private = 121 /* PrivateKeyword */, -- _a.protected = 122 /* ProtectedKeyword */, -- _a.public = 123 /* PublicKeyword */, -- _a.override = 157 /* OverrideKeyword */, -- _a.readonly = 143 /* ReadonlyKeyword */, -- _a.require = 144 /* RequireKeyword */, -- _a.global = 155 /* GlobalKeyword */, -- _a.return = 105 /* ReturnKeyword */, -- _a.set = 147 /* SetKeyword */, -- _a.static = 124 /* StaticKeyword */, -- _a.string = 148 /* StringKeyword */, -- _a.super = 106 /* SuperKeyword */, -- _a.switch = 107 /* SwitchKeyword */, -- _a.symbol = 149 /* SymbolKeyword */, -- _a.this = 108 /* ThisKeyword */, -- _a.throw = 109 /* ThrowKeyword */, -- _a.true = 110 /* TrueKeyword */, -- _a.try = 111 /* TryKeyword */, -- _a.type = 150 /* TypeKeyword */, -- _a.typeof = 112 /* TypeOfKeyword */, -- _a.undefined = 151 /* UndefinedKeyword */, -- _a.unique = 152 /* UniqueKeyword */, -- _a.unknown = 153 /* UnknownKeyword */, -- _a.var = 113 /* VarKeyword */, -- _a.void = 114 /* VoidKeyword */, -- _a.while = 115 /* WhileKeyword */, -- _a.with = 116 /* WithKeyword */, -- _a.yield = 125 /* YieldKeyword */, -- _a.async = 130 /* AsyncKeyword */, -- _a.await = 131 /* AwaitKeyword */, -- _a.of = 158 /* OfKeyword */, -- _a); -+ /** -+ * In the original version of this package, this object was built by -+ * initializing one object and then adding more properties to that object. -+ * This ends up throwing an error when this code is executed due to -+ * the same issue as explained at the top of this file: essentially, -+ * the `constructor` property of any object cannot be set due to the -+ * "override mistake". The fix for this is to just build one big object. -+ */ -+ ts.textToKeywordObj = { -+ abstract: 126 /* AbstractKeyword */, -+ any: 129 /* AnyKeyword */, -+ as: 127 /* AsKeyword */, -+ asserts: 128 /* AssertsKeyword */, -+ bigint: 156 /* BigIntKeyword */, -+ boolean: 132 /* BooleanKeyword */, -+ break: 81 /* BreakKeyword */, -+ case: 82 /* CaseKeyword */, -+ catch: 83 /* CatchKeyword */, -+ class: 84 /* ClassKeyword */, -+ continue: 86 /* ContinueKeyword */, -+ const: 85 /* ConstKeyword */, -+ ["constructor"]: 133 /* ConstructorKeyword */, -+ debugger: 87 /* DebuggerKeyword */, -+ declare: 134 /* DeclareKeyword */, -+ default: 88 /* DefaultKeyword */, -+ delete: 89 /* DeleteKeyword */, -+ do: 90 /* DoKeyword */, -+ else: 91 /* ElseKeyword */, -+ enum: 92 /* EnumKeyword */, -+ export: 93 /* ExportKeyword */, -+ extends: 94 /* ExtendsKeyword */, -+ false: 95 /* FalseKeyword */, -+ finally: 96 /* FinallyKeyword */, -+ for: 97 /* ForKeyword */, -+ from: 154 /* FromKeyword */, -+ function: 98 /* FunctionKeyword */, -+ get: 135 /* GetKeyword */, -+ if: 99 /* IfKeyword */, -+ implements: 117 /* ImplementsKeyword */, -+ import: 100 /* ImportKeyword */, -+ in: 101 /* InKeyword */, -+ infer: 136 /* InferKeyword */, -+ instanceof: 102 /* InstanceOfKeyword */, -+ interface: 118 /* InterfaceKeyword */, -+ intrinsic: 137 /* IntrinsicKeyword */, -+ is: 138 /* IsKeyword */, -+ keyof: 139 /* KeyOfKeyword */, -+ let: 119 /* LetKeyword */, -+ module: 140 /* ModuleKeyword */, -+ namespace: 141 /* NamespaceKeyword */, -+ never: 142 /* NeverKeyword */, -+ new: 103 /* NewKeyword */, -+ null: 104 /* NullKeyword */, -+ number: 145 /* NumberKeyword */, -+ object: 146 /* ObjectKeyword */, -+ package: 120 /* PackageKeyword */, -+ private: 121 /* PrivateKeyword */, -+ protected: 122 /* ProtectedKeyword */, -+ public: 123 /* PublicKeyword */, -+ override: 157 /* OverrideKeyword */, -+ readonly: 143 /* ReadonlyKeyword */, -+ require: 144 /* RequireKeyword */, -+ global: 155 /* GlobalKeyword */, -+ return: 105 /* ReturnKeyword */, -+ set: 147 /* SetKeyword */, -+ static: 124 /* StaticKeyword */, -+ string: 148 /* StringKeyword */, -+ super: 106 /* SuperKeyword */, -+ switch: 107 /* SwitchKeyword */, -+ symbol: 149 /* SymbolKeyword */, -+ this: 108 /* ThisKeyword */, -+ throw: 109 /* ThrowKeyword */, -+ true: 110 /* TrueKeyword */, -+ try: 111 /* TryKeyword */, -+ type: 150 /* TypeKeyword */, -+ typeof: 112 /* TypeOfKeyword */, -+ undefined: 151 /* UndefinedKeyword */, -+ unique: 152 /* UniqueKeyword */, -+ unknown: 153 /* UnknownKeyword */, -+ var: 113 /* VarKeyword */, -+ void: 114 /* VoidKeyword */, -+ while: 115 /* WhileKeyword */, -+ with: 116 /* WithKeyword */, -+ yield: 125 /* YieldKeyword */, -+ async: 130 /* AsyncKeyword */, -+ await: 131 /* AwaitKeyword */, -+ of: 158 /* OfKeyword */ -+ }; - var textToKeyword = new ts.Map(ts.getEntries(ts.textToKeywordObj)); - var textToToken = new ts.Map(ts.getEntries(__assign(__assign({}, ts.textToKeywordObj), { "{": 18 /* OpenBraceToken */, "}": 19 /* CloseBraceToken */, "(": 20 /* OpenParenToken */, ")": 21 /* CloseParenToken */, "[": 22 /* OpenBracketToken */, "]": 23 /* CloseBracketToken */, ".": 24 /* DotToken */, "...": 25 /* DotDotDotToken */, ";": 26 /* SemicolonToken */, ",": 27 /* CommaToken */, "<": 29 /* LessThanToken */, ">": 31 /* GreaterThanToken */, "<=": 32 /* LessThanEqualsToken */, ">=": 33 /* GreaterThanEqualsToken */, "==": 34 /* EqualsEqualsToken */, "!=": 35 /* ExclamationEqualsToken */, "===": 36 /* EqualsEqualsEqualsToken */, "!==": 37 /* ExclamationEqualsEqualsToken */, "=>": 38 /* EqualsGreaterThanToken */, "+": 39 /* PlusToken */, "-": 40 /* MinusToken */, "**": 42 /* AsteriskAsteriskToken */, "*": 41 /* AsteriskToken */, "/": 43 /* SlashToken */, "%": 44 /* PercentToken */, "++": 45 /* PlusPlusToken */, "--": 46 /* MinusMinusToken */, "<<": 47 /* LessThanLessThanToken */, ">": 48 /* GreaterThanGreaterThanToken */, ">>>": 49 /* GreaterThanGreaterThanGreaterThanToken */, "&": 50 /* AmpersandToken */, "|": 51 /* BarToken */, "^": 52 /* CaretToken */, "!": 53 /* ExclamationToken */, "~": 54 /* TildeToken */, "&&": 55 /* AmpersandAmpersandToken */, "||": 56 /* BarBarToken */, "?": 57 /* QuestionToken */, "??": 60 /* QuestionQuestionToken */, "?.": 28 /* QuestionDotToken */, ":": 58 /* ColonToken */, "=": 63 /* EqualsToken */, "+=": 64 /* PlusEqualsToken */, "-=": 65 /* MinusEqualsToken */, "*=": 66 /* AsteriskEqualsToken */, "**=": 67 /* AsteriskAsteriskEqualsToken */, "/=": 68 /* SlashEqualsToken */, "%=": 69 /* PercentEqualsToken */, "<<=": 70 /* LessThanLessThanEqualsToken */, ">>=": 71 /* GreaterThanGreaterThanEqualsToken */, ">>>=": 72 /* GreaterThanGreaterThanGreaterThanEqualsToken */, "&=": 73 /* AmpersandEqualsToken */, "|=": 74 /* BarEqualsToken */, "^=": 78 /* CaretEqualsToken */, "||=": 75 /* BarBarEqualsToken */, "&&=": 76 /* AmpersandAmpersandEqualsToken */, "??=": 77 /* QuestionQuestionEqualsToken */, "@": 59 /* AtToken */, "#": 62 /* HashToken */, "`": 61 /* BacktickToken */ }))); - /* diff --git a/.yarnrc.yml b/.yarnrc.yml index ddac0d46647c..5881cfe128a8 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -94,6 +94,18 @@ npmAuditIgnoreAdvisories: # @types/webextension-polyfill - 'webextension-polyfill-ts (deprecation)' + # Imported in @trezor/blockchain-link@npm:2.1.8, but not actually depended on + # by MetaMask + - 'ripple-lib (deprecation)' + + # Brought in by ethereumjs-utils, which is used in the extension and in many + # other dependencies. At the time of this exclusion, the extension has three + # old versions of ethereumjs-utils which should be upgraded to + # @ethereumjs/utils throughout our owned repositories. However even doing + # that may be insufficient due to dependencies we do not own still relying + # upon old versions of ethereumjs-utils. + - 'ethereum-cryptography (deprecation)' + npmRegistries: 'https://npm.pkg.github.com': npmAlwaysAuth: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 50cee8c83041..a75f094c96b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,78 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.3.0] +### Added +- Display total fiat balance on home screen ([#20941](https://github.com/MetaMask/metamask-extension/pull/20941)) +- Give the user the ability to opt out of 3rd party network validation when adding a new network ([#20816](https://github.com/MetaMask/metamask-extension/pull/20816)) +- Adds conversion buttons to buy, receive, and learn more about NFTs when an account is empty. ([#21049](https://github.com/MetaMask/metamask-extension/pull/21049)) +- What's New modal for the latest Buy and Sell features ([#20965](https://github.com/MetaMask/metamask-extension/pull/20965)) +- [Flask] Suggest names for Ethereum addresses in signature requests using installed snaps ([#20959](https://github.com/MetaMask/metamask-extension/pull/20959)) + +### Changed +- Move the token detection prompt to the top of the assets list ([#20932](https://github.com/MetaMask/metamask-extension/pull/20932)) +- Adds address validation to the Import NFT modal ([#21028](https://github.com/MetaMask/metamask-extension/pull/21028)) +- Add error message to the Import NFT modal if an NFT with that tokenId already exists ([#20940](https://github.com/MetaMask/metamask-extension/pull/20940)) +- Show a warning when sending 0 tokens ([#21091](https://github.com/MetaMask/metamask-extension/pull/21091)) +- Enable bridge button on the zkSync network, and for some tokens on other networks ([#21085](https://github.com/MetaMask/metamask-extension/pull/21085)) +- Stop displaying a mismatched account warning on the Sign In With Ethereum page ([#21107](https://github.com/MetaMask/metamask-extension/pull/21107)) +- Ensure "Enhanced Token Detection" is toggled off when opening "Advanced Configuration" during onboarding ([#20960](https://github.com/MetaMask/metamask-extension/pull/20960)) +- Improve loading speed/performance upon opening MetaMask ([#20843](https://github.com/MetaMask/metamask-extension/pull/20843)) +- Ensure logos are displayed on "Popular Networks" added from within MetaMask ([#20895](https://github.com/MetaMask/metamask-extension/pull/20895)) +- Show more characters in the addresses in the Connected Sites account list ([#21048](https://github.com/MetaMask/metamask-extension/pull/21048)) +- Replaces the "Add / Import / Hardware" links with a single button that takes the user to another quick modal to add, import, or connect a hardware wallet.([#21081](https://github.com/MetaMask/metamask-extension/pull/21081)) +- Disable the "Buy" button on the Sepolia network ([#20839](https://github.com/MetaMask/metamask-extension/pull/20839)) +- Update the "Buy" button to a "Buy & Sell" button ([#20891](https://github.com/MetaMask/metamask-extension/pull/20891)) +- Only display the Notification item in the dropdown menu if the user has installed a snap using the notifications permission ([#20913](https://github.com/MetaMask/metamask-extension/pull/20913)) +- Update Snaps Settings screen ([#21061](https://github.com/MetaMask/metamask-extension/pull/21061)) +- Site connection icon now indicates a connection if a dapp is connected to a snap ([#20811](https://github.com/MetaMask/metamask-extension/pull/20811)) +- Update copy on the remove snap modal ([#21065](https://github.com/MetaMask/metamask-extension/pull/21065)) +- Update the padding of the transaction insight dropdown ([#21022](https://github.com/MetaMask/metamask-extension/pull/21022)) +- [Flask] Suggest names for Ethereum addresses in signature requests using services such as ENS and Etherscan ([#20831](https://github.com/MetaMask/metamask-extension/pull/20831)) + +### Fixed +- Fix to ensure contract address is pre-populated, and NFT is removed from token list, when converting a token to an NFT ([#20747](https://github.com/MetaMask/metamask-extension/pull/20747)) +- Fix autodetect tokens link so that the user is correctly taken to the right scroll position in settings ([#20978](https://github.com/MetaMask/metamask-extension/pull/20978)) +- Ensure safe batch NFT transfers show display recipient address correctly ([#21042](https://github.com/MetaMask/metamask-extension/pull/21042)) +- Fix bug that can prevent MetaMask from loading, and be stuck on the loading screen, on Firefox ([#20992](https://github.com/MetaMask/metamask-extension/pull/20992)) +- Ensure correct network icon is displayed in the "You have switched to" modal when switching to "Popular Networks" ([#21016](https://github.com/MetaMask/metamask-extension/pull/21016)) +- Ensure disconnecting a single dapp from a snap only disconnects that dapp ([#20983](https://github.com/MetaMask/metamask-extension/pull/20983)) +- Fix the Snap npm link so that it leads to the stated version of the npm package ([#20897](https://github.com/MetaMask/metamask-extension/pull/20897)) +- Fix to prevent crashes when switching network during a snaps confirmation ([#21088](https://github.com/MetaMask/metamask-extension/pull/21088)) +- Fix issue that could cause crashes when attempting to render certain NFTs ([#21418](https://github.com/MetaMask/metamask-extension/pull/21418)) + + +## [11.2.0] +### Added +- Adds Swaps support for the zkSync Era network ([#20809](https://github.com/MetaMask/metamask-extension/pull/20809)) + +### Changed +- Increase account list height, so that it uses all available screen space and displays more accounts ([#20745](https://github.com/MetaMask/metamask-extension/pull/20745)) +- Update Snaps What's New text translations in 14 languages ([#20734](https://github.com/MetaMask/metamask-extension/pull/20734)) +- Remove hover background on Account Picker ([#20794](https://github.com/MetaMask/metamask-extension/pull/20794)) +- Show the first letter or number in a Snap's name as the icon, and not a symbol character, if there is no icon ([#20851](https://github.com/MetaMask/metamask-extension/pull/20851)) +- Set initial background color to system theme ([#20858](https://github.com/MetaMask/metamask-extension/pull/20858)) +- Increase network list height, so that it uses all available screen space and displays more networks ([#20801](https://github.com/MetaMask/metamask-extension/pull/20801)) +- Improve visual spacing and borders on connected sites in the snap details page ([#20854](https://github.com/MetaMask/metamask-extension/pull/20854)) +- [FLASK] Bump snaps packages ([#20567](https://github.com/MetaMask/metamask-extension/pull/20567)) +- [MMI] Added code fences to hide emojis just for MMI build ([#20754](https://github.com/MetaMask/metamask-extension/pull/20754)) +- [MMI] Show the NFT tab content for mmi ([#20830](https://github.com/MetaMask/metamask-extension/pull/20830)) +- [MMI] Changed the wrong privacy policy URL to the good one ([#20884](https://github.com/MetaMask/metamask-extension/pull/20884)) +- [MMI] Shows Stake & Portfolio buttons and hides the Buy and Bridge buttons ([#20767](https://github.com/MetaMask/metamask-extension/pull/20767)) + +### Fixed +- Ensure all NFT lists are sorted by the NFT's id ([#20796](https://github.com/MetaMask/metamask-extension/pull/20796)) +- Fix custom amount editing on token approval screens ([#20804](https://github.com/MetaMask/metamask-extension/pull/20804)) + +## [11.1.2] +### Fixed +- Prevent crashes for users that have NFTs without an image and/r limited image data ([#21176](https://github.com/MetaMask/metamask-extension/pull/21176)) + +## [11.1.1] +### Fixed +- Ensure NFT settings notice in the NFT import modal is shown and hidden correctly, and that the modal is hidden when clicking the link to settings ([#21100](https://github.com/MetaMask/metamask-extension/pull/21100)) +- Modify settings toggle copy to more accurately describe behaviour ([#21109](https://github.com/MetaMask/metamask-extension/pull/21109)) + ## [11.1.0] ### Added - What's New popup on dropping of support for Ledger support for firefox ([#19498](https://github.com/MetaMask/metamask-extension/pull/19498)) @@ -4035,7 +4107,11 @@ Update styles and spacing on the critical error page ([#20350](https://github.c ### Uncategorized - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.3.0...HEAD +[11.3.0]: https://github.com/MetaMask/metamask-extension/compare/v11.2.0...v11.3.0 +[11.2.0]: https://github.com/MetaMask/metamask-extension/compare/v11.1.2...v11.2.0 +[11.1.2]: https://github.com/MetaMask/metamask-extension/compare/v11.1.1...v11.1.2 +[11.1.1]: https://github.com/MetaMask/metamask-extension/compare/v11.1.0...v11.1.1 [11.1.0]: https://github.com/MetaMask/metamask-extension/compare/v11.0.0...v11.1.0 [11.0.0]: https://github.com/MetaMask/metamask-extension/compare/v10.35.1...v11.0.0 [10.35.1]: https://github.com/MetaMask/metamask-extension/compare/v10.35.0...v10.35.1 diff --git a/README.md b/README.md index adad35727fbc..a616d89e0ca2 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Differnt build types have different e2e tests sets. In order to run them look in ```console "test:e2e:chrome:mmi": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mmi", "test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps", - "test:e2e:chrome:mv3": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mv3", + "test:e2e:chrome:mv3": "ENABLE_MV3=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js", ``` Note: MMI runs a subset of MetaMask's e2e tests. To facilitate this, we have appended the `@no-mmi` tags to the names of those tests that are not applicable to this build type. @@ -164,8 +164,8 @@ Whenever you change dependencies (adding, removing, or updating, either in `pack ## Dapp Developer Resources -- [Extend MetaMask's features w/ MetaMask Snaps.](https://docs.metamask.io/guide/snaps.html) -- [Prompt your users to add and switch to a new network.](https://medium.com/metamask/connect-users-to-layer-2-networks-with-the-metamask-custom-networks-api-d0873fac51e5) -- [Change the logo that appears when your dapp connects to MetaMask.](https://docs.metamask.io/guide/defining-your-icon.html) +- [Extend MetaMask's features w/ MetaMask Snaps.](https://docs.metamask.io/snaps/) +- [Prompt your users to add and switch to a new network.](https://docs.metamask.io/wallet/how-to/add-network/) +- [Change the logo that appears when your dapp connects to MetaMask.](https://docs.metamask.io/wallet/how-to/display/icon/) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BbackgroundConnection%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 213d8876df39..f6666bf8bd5d 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Im Block-Explorer öffnen" }, - "openSea": { - "message": "OpenSea (Beta)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Ein $1-Konto erstellen", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "Snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Installiert" }, - "snapError": { - "message": "Snap-Fehler: '$1'. Fehler-Code: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Snap installieren" }, @@ -4045,9 +4035,6 @@ "message": "Alle Daten, die Sie mit Drittanbieterdiensten teilen, werden direkt von diesen Drittanbieterdiensten im Einklang mit deren Datenschutzerklärung erfasst. Für weitere Informationen lesen Sie bitte die jeweiligen Datenschutzerklärungen.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Verwalten Sie Ihre Snaps." - }, "snapsTermsOfUse": { "message": "Nutzungsbedingungen" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Nutzungsbedingungen" }, - "termsOfUse": { - "message": "Nutzungsbedingungen" - }, "termsOfUseAgreeText": { "message": " Ich akzeptiere die Nutzungsbedingungen, die meine Verwendung von MetaMask, einschließlich aller seiner Funktionen, betreffen" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "diese Sammlung" }, - "thisServiceIsExperimental": { - "message": "Dieser Dienst ist experimentell. Durch die Aktivierung dieser Funktion stimmen Sie den $1 von OpenSea zu.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Zeit" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "Erneutes Absenden der Transaktion mit Erhöhung der geschätzten Gas-Gebühr auf $1 zu $2." }, - "transactionSecurityCheck": { - "message": "Sicherheitsbenachrichtigungen aktivieren" - }, - "transactionSecurityCheckDescription": { - "message": "Wir verwenden APIs von Drittanbietern, um Risiken bei nicht unterzeichneten Transaktionen und Signaturanfragen zu erkennen und anzuzeigen, bevor Sie diese signieren. Diese Dienste haben Zugriff auf Ihre unsignierten Transaktions- und Signaturanfragen, Ihre Kontoadresse und Ihre bevorzugte Sprache." - }, "transactionSettings": { "message": "Transaktionseinstellungen" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Auf Opensea ansehen" }, - "viewPortfolioDashboard": { - "message": "Portfolio-Dashboard anzeigen" - }, "viewinCustodianApp": { "message": "In Verwahrungs-App anzeigen" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index b5f4828434af..1325504a2825 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Άνοιγμα στο Block Explorer" }, - "openSea": { - "message": "OpenSea + Blockaid (Δοκιμαστικά)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Δημιουργήστε έναν $1 λογαριασμό", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Εγκαταστάθηκε" }, - "snapError": { - "message": "Σφάλμα Snap: '$1'. Κωδικός Σφάλματος: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Εγκατάσταση του snap" }, @@ -4045,9 +4035,6 @@ "message": "Οποιεσδήποτε πληροφορίες μοιράζεστε με τις Υπηρεσίες Τρίτων θα συλλέγονται απευθείας από τις εν λόγω Υπηρεσίες Τρίτων σύμφωνα με τις πολιτικές απορρήτου τους. Ανατρέξτε στις πολιτικές απορρήτου τους για περισσότερες πληροφορίες.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Διαχειριστείτε τα Snaps σας" - }, "snapsTermsOfUse": { "message": "Όροι Χρήσης" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Όροι παροχής υπηρεσιών" }, - "termsOfUse": { - "message": "όροι χρήσης" - }, "termsOfUseAgreeText": { "message": " Συμφωνώ με τους Όρους Χρήσης, οι οποίοι ισχύουν για τη χρήση του MetaMask και όλων των λειτουργιών του" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "αυτή η συλλογή" }, - "thisServiceIsExperimental": { - "message": "Η υπηρεσία αυτή είναι πειραματική. Ενεργοποιώντας αυτή τη λειτουργία, συμφωνείτε με τους $1 του OpenSea.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Ώρα" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "Η συναλλαγή υποβλήθηκε ξανά με το εκτιμώμενο τέλος συναλλαγής να έχει αυξηθεί στα $1 στις $2" }, - "transactionSecurityCheck": { - "message": "Ενεργοποίηση ειδοποιήσεων ασφαλείας" - }, - "transactionSecurityCheckDescription": { - "message": "Χρησιμοποιούμε API τρίτων για τον εντοπισμό και την εμφάνιση των κινδύνων που ενέχουν τα ανυπόγραφα αιτήματα συναλλαγών και υπογραφών πριν τα υπογράψετε. Αυτές οι υπηρεσίες θα έχουν πρόσβαση στα ανυπόγραφα αιτήματα συναλλαγών και υπογραφών σας, στη διεύθυνση του λογαριασμού σας και στην προτιμώμενη γλώσσα σας." - }, "transactionSettings": { "message": "Ρυθμίσεις συναλλαγών" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Προβολή στο Opensea" }, - "viewPortfolioDashboard": { - "message": "Προβολή πίνακα ελέγχου χαρτοφυλακίου" - }, "viewinCustodianApp": { "message": "Προβολή στην εφαρμογή θεματοφύλακα" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8d3c090ca1c5..d4d80c31d7ad 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -153,6 +153,9 @@ "accountSelectionRequired": { "message": "You need to select an account!" }, + "accountSnapsFeedback": { + "message": "Submit feedback on this feature" + }, "accountsConnected": { "message": "Accounts connected" }, @@ -180,6 +183,9 @@ "addAccount": { "message": "Add account" }, + "addAccountSnapModalHeader": { + "message": "Add Account Snap" + }, "addAcquiredTokens": { "message": "Add the tokens you've acquired using MetaMask" }, @@ -246,6 +252,9 @@ "addIPFSGateway": { "message": "Add your preferred IPFS gateway" }, + "addImportAccount": { + "message": "Add account or hardware wallet" + }, "addMemo": { "message": "Add memo" }, @@ -259,6 +268,9 @@ "message": "This network connection relies on third parties. This connection may be less reliable or enable third-parties to track activity. $1", "description": "$1 is Learn more link" }, + "addNewAccount": { + "message": "Add a new account" + }, "addNewToken": { "message": "Add new token" }, @@ -272,10 +284,10 @@ "message": "Discover options to keep your account secure with MetaMask Snaps" }, "addSnapAccountToggle": { - "message": "Enable \"Add Snap account\"" + "message": "Enable \"Add account Snap (Beta)\"" }, "addSnapAccountsDescription": { - "message": "Turning on this feature will give you the option to add a Snap account right from your account list. If you install a Snap account, remember that it is a third-party service." + "message": "Turning on this feature will give you the option to add the new Beta account Snaps right from your account list. If you install an account Snap, remember that it is a third-party service." }, "addSuggestedNFTs": { "message": "Add suggested NFTs" @@ -501,6 +513,9 @@ "backupApprovalNotice": { "message": "Backup your Secret Recovery Phrase to keep your wallet and funds secure." }, + "backupKeyringSnapReminder": { + "message": "Be sure you can access any accounts created by this Snap on your own before removing it" + }, "backupNow": { "message": "Backup now" }, @@ -593,6 +608,9 @@ "blockaidDescriptionTransferFarming": { "message": "If you approve this request, a third party known for scams will take all your assets." }, + "blockaidMessage": { + "message": "Privacy preserving - no data is shared with third parties. Available on Ethereum Mainnet." + }, "blockaidTitleDeceptive": { "message": "This is a deceptive request" }, @@ -626,6 +644,9 @@ "busy": { "message": "Busy" }, + "buy": { + "message": "Buy" + }, "buyAndSell": { "message": "Buy & Sell" }, @@ -894,6 +915,9 @@ "continue": { "message": "Continue" }, + "continueMmiOnboarding": { + "message": "Continue MetaMask Institutional onboarding" + }, "contract": { "message": "Contract" }, @@ -1607,9 +1631,21 @@ "message": "Explore community-built Snaps to customize your web3 experience", "description": "Banner description displayed on Snaps list page in Settings when less than 6 Snaps is installed." }, + "extensionInsallCompleteDescription": { + "message": "Return to the MetaMask Institutional product onboarding to connect your custodial or self-custodial accounts." + }, + "extensionInsallCompleteTitle": { + "message": "Extension install complete" + }, "externalExtension": { "message": "External extension" }, + "externalNameSourcesSetting": { + "message": "Suggest address names" + }, + "externalNameSourcesSettingDescription": { + "message": "We pull data from third parties like Etherscan, Infura, and Lens Protocol, to suggest names for addresses on signatures requests. Turning on name suggestions exposes your IP address to these third parties." + }, "failed": { "message": "Failed" }, @@ -1791,6 +1827,9 @@ "goBack": { "message": "Go back" }, + "goToSite": { + "message": "Go to site" + }, "goerli": { "message": "Goerli test network" }, @@ -2015,6 +2054,12 @@ "install": { "message": "Install" }, + "installExtension": { + "message": "Install extension" + }, + "installExtensionDescription": { + "message": "The institution-compliant version of the world's leading web3 wallet, MetaMask." + }, "installOrigin": { "message": "Install origin" }, @@ -2131,6 +2176,24 @@ "message": "JSON File", "description": "format for importing an account" }, + "keyringAccountName": { + "message": "Account name" + }, + "keyringAccountPublicAddress": { + "message": "Public Address" + }, + "keyringSnapRemovalResult1": { + "message": "$1 $2removed", + "description": "Displays the result after removal of a keyring snap. $1 is the snap name, $2 is whether it is successful or not" + }, + "keyringSnapRemovalResultNotSuccessful": { + "message": "not ", + "description": "Displays the `not` word in $2." + }, + "keyringSnapRemoveConfirmation": { + "message": "Type $1 to confirm you want to remove this snap:", + "description": "Asks user to input the name nap prior to deleting the snap. $1 is the snap name" + }, "keystone": { "message": "Keystone" }, @@ -2250,6 +2313,12 @@ "loadingNFTs": { "message": "Loading NFTs..." }, + "loadingScreenHardwareWalletMessage": { + "message": "Please complete the transaction on the hardware wallet." + }, + "loadingScreenSnapMessage": { + "message": "Please complete the transaction on the Snap." + }, "loadingTokens": { "message": "Loading tokens..." }, @@ -2379,6 +2448,9 @@ "mmiNewNFTDetectedInNFTsTabMessage": { "message": "Let MetaMask Institutional automatically detect and display NFTs in your wallet." }, + "mmiPasswordSetupDetails": { + "message": "This password will unlock your MetaMask Institutional extension only." + }, "more": { "message": "more" }, @@ -2487,8 +2559,11 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" + "networkNameLinea": { + "message": "Linea" + }, + "networkNameOpMainnet": { + "message": "OP Mainnet" }, "networkNamePolygon": { "message": "Polygon" @@ -2600,6 +2675,9 @@ "nftDisclaimer": { "message": "Disclaimer: MetaMask pulls the media file from the source url. This url sometimes is changed by the marketplace the NFT was minted on." }, + "nftLearnMore": { + "message": "Learn more about NFTs" + }, "nftOptions": { "message": "NFT Options" }, @@ -3133,6 +3211,9 @@ "onboardingPinExtensionTitle": { "message": "Your MetaMask install is complete!" }, + "onboardingPinMmiExtensionLabel": { + "message": "Pin MetaMask Institutional" + }, "onboardingUsePhishingDetectionDescription": { "message": "Phishing detection alerts rely on communication with $1. jsDeliver will have access to your IP address. View $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" @@ -3143,6 +3224,10 @@ "onlyConnectTrust": { "message": "Only connect with sites you trust." }, + "openCustodianApp": { + "message": "Open $1 app", + "description": "The $1 is the name of the Custodian that will be open" + }, "openFullScreenForLedgerWebHid": { "message": "Go to full screen to connect your Ledger.", "description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid." @@ -3150,8 +3235,11 @@ "openInBlockExplorer": { "message": "Open in block explorer" }, - "openSea": { - "message": "OpenSea + Blockaid (Beta)" + "openSeaLabel": { + "message": "OpenSea + Blockaid" + }, + "openSeaMessage": { + "message": "Data is shared with third parties. Security providers will receive your unsigned transactions and signature requests." }, "openSeaNew": { "message": "OpenSea" @@ -3199,6 +3287,9 @@ "password": { "message": "Password" }, + "passwordMmiTermsWarning": { + "message": "I understand that MetaMask Institutional cannot recover this password for me. $1" + }, "passwordNotLongEnough": { "message": "Password not long enough" }, @@ -3314,6 +3405,14 @@ "message": "Let this Snap access your preferred language from your MetaMask settings. This can be used to localize and display the Snap's content using your language.", "description": "An extended description for the `snap_getLocale` permission" }, + "permission_keyring": { + "message": "Allow requests for adding and controlling Ethereum accounts", + "description": "The description for the `endowment:keyring` permission" + }, + "permission_keyringDescription": { + "message": "Let this Snap receive requests to add or remove accounts, plus sign and transact on behalf of these accounts.", + "description": "An extended description for the `endowment:keyring` permission" + }, "permission_lifecycleHooks": { "message": "Use lifecycle hooks.", "description": "The description for the `endowment:lifecycle-hooks` permission" @@ -3426,6 +3525,12 @@ "personalAddressDetected": { "message": "Personal address detected. Input the token contract address." }, + "pinExtensionDescription": { + "message": "Navigate to the extension menu and pin MetaMask Institutional for seamless access." + }, + "pinExtensionTitle": { + "message": "Pin extension" + }, "pleaseConfirm": { "message": "Please confirm" }, @@ -3446,6 +3551,9 @@ "message": "Preferred Ledger connection type", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" }, + "preferredProvider": { + "message": "Select your preferred provider" + }, "preparingSwap": { "message": "Preparing swap..." }, @@ -3586,6 +3694,12 @@ "removeJWTDescription": { "message": "Are you sure you want to remove this token? All accounts assigned to this token will be removed from extension as well: " }, + "removeKeyringSnap": { + "message": "Removing this Snap removes these accounts from MetaMask:" + }, + "removeKeyringSnapToolTip": { + "message": "The snap controls the accounts, and by removing it, the accounts will be removed from MetaMask, too, but they will remain in the blockchain." + }, "removeNFT": { "message": "Remove NFT" }, @@ -3795,7 +3909,7 @@ "message": "Security alerts" }, "securityAlertsDescription": { - "message": "This feature alerts you to malicious activity on Ethereum Mainnet by actively reviewing transaction and signature requests while preserving your privacy. Your data isn't shared with the third party providing this service. Always do your own due diligence before approving any requests. There's no guarantee that this feature will detect all malicious activity." + "message": "This feature alerts you to malicious activity by actively reviewing transaction and signature requests. Always do your own due diligence before approving any requests. There's no guarantee that this feature will detect all malicious activity." }, "securityAndPrivacy": { "message": "Security & privacy" @@ -3912,6 +4026,9 @@ "send": { "message": "Send" }, + "sendAToken": { + "message": "Send a token" + }, "sendBugReport": { "message": "Send us a bug report." }, @@ -3939,6 +4056,9 @@ "message": "Warning: you are about to send to a token contract which could result in a loss of funds. $1", "description": "$1 is a clickable link with text defined by the 'learnMoreUpperCase' key. The link will open to a support article regarding the known contract address warning" }, + "sendingZeroAmount": { + "message": "You are sending 0 $1." + }, "sepolia": { "message": "Sepolia test network" }, @@ -3953,7 +4073,7 @@ "description": "The token symbol that is being approved" }, "settingAddSnapAccount": { - "message": "Add snap account" + "message": "Add account Snap" }, "settings": { "message": "Settings" @@ -3961,6 +4081,9 @@ "settingsSearchMatchingNotFound": { "message": "No matching results found." }, + "settingsSubHeadingSignatures": { + "message": "Signature requests" + }, "show": { "message": "Show" }, @@ -4061,11 +4184,42 @@ "snapAccountCreated": { "message": "Your account is ready!" }, + "snapAccountLegalDisclaimerExperimentalBetaDescription": { + "message": "Account Management Snaps are released as part of an experimental Beta release. You agree that you will only use one of these Snaps if you understand how it functions and how to use it, and you have fully read and understand all risk warnings and other disclosures made available by the Third Party Service provider relating to use of their Snap. You agree that Consensys is not responsible for any injury that you incur as a result of your use of this Snap.\n\nYou will not be able to use your MetaMask Secret Recovery Phrase to recover accounts added through Account Management Snaps. If your account credentials are lost or compromised, MetaMask will not be able to help you. If the Snap or the associated dapp is hacked or ceases to function, you may not be able to access your account and the funds in your account." + }, + "snapAccountLegalDisclaimerExperimentalBetaSubtitle": { + "message": "Experimental Beta" + }, + "snapAccountLegalDisclaimerPrivacyDescription": { + "message": "Any information you share with Third Party Services will be collected directly by those Third Party Services in accordance with their privacy policies. Please refer to their privacy policies for more information. Consensys has no access to information you share with Third Party Services." + }, + "snapAccountLegalDisclaimerPrivacySubtitle": { + "message": "Privacy" + }, + "snapAccountLegalDisclaimerTermsOfUseLink": { + "message": "Consensys Terms of Use" + }, + "snapAccountLegalDisclaimerThirdPartyDescription": { + "message": "You acknowledge that Account Management Snaps are a Third Party Service, as defined in the $1. Your use of Third Party Services is governed by separate terms and conditions set forth by the Third Party Service provider. Consensys does not recommend the use of any Snap by any particular person for any particular reason. You access, rely upon or use the Third Party Service at your own risk. Consensys disclaims all responsibility and liability for any losses on account of your use of Third Party Services.", + "description": "$1 is a link to the ConsenSys Terms of Use" + }, + "snapAccountLegalDisclaimerThirdPartySubtitle": { + "message": "Third-Party Service" + }, + "snapAccountLegalDisclaimerTitle": { + "message": "Important Information" + }, + "snapAccountRedirectFinishSigningTitle": { + "message": "Finish signing" + }, + "snapAccountRedirectSiteDescription": { + "message": "Follow the instructions from $1" + }, "snapAccountRemoved": { "message": "Account removed" }, "snapAccounts": { - "message": "Snap accounts" + "message": "Account Snaps" }, "snapAccountsDescription": { "message": "Accounts controlled by third-party Snaps." @@ -4082,14 +4236,14 @@ "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." }, "snapCreateAccountSubtitle": { - "message": "Choose how to secure your new account using MetaMask Snaps." + "message": "Choose how to secure your new account using MetaMask Snaps. If you install an account Snap, remember that it is a third-party service." }, "snapCreateAccountTitle": { - "message": "Create a $1 account", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "message": "Create an account $1 (Beta)", + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { - "message": "snap", + "message": "Snap", "description": "$1 of the snapCreateAccountTitle" }, "snapCreatedByMetaMask": { @@ -4117,15 +4271,11 @@ "message": "Website" }, "snapDetailsCreateASnapAccount": { - "message": "Create a Snap Account" + "message": "Add an account Snap (Beta)" }, "snapDetailsInstalled": { "message": "Installed" }, - "snapError": { - "message": "Snap Error: '$1'. Error Code: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Install snap" }, @@ -4196,6 +4346,9 @@ "snapUpdateSuccess": { "message": "Update complete" }, + "snapUrlIsBlocked": { + "message": "This Snap wants to take you to a blocked site. $1." + }, "snaps": { "message": "Snaps" }, @@ -4226,9 +4379,6 @@ "snapsSettings": { "message": "Snap settings" }, - "snapsSettingsDescription": { - "message": "Manage your Snaps" - }, "snapsTermsOfUse": { "message": "Terms of Use" }, @@ -4242,6 +4392,10 @@ "someNetworksMayPoseSecurity": { "message": "Some networks may pose security and/or privacy risks. Understand the risks before adding & using a network." }, + "somethingDoesntLookRight": { + "message": "Something doesn't look right? $1", + "description": "A false positive message for users to contact support. $1 is a link to the support page." + }, "somethingIsWrong": { "message": "Something's gone wrong. Try reloading the page." }, @@ -4980,9 +5134,6 @@ "termsOfService": { "message": "Terms of service" }, - "termsOfUse": { - "message": "terms of use" - }, "termsOfUseAgreeText": { "message": " I agree to the Terms of Use, which apply to my use of MetaMask and all of its features" }, @@ -5011,10 +5162,6 @@ "thisCollection": { "message": "this collection" }, - "thisServiceIsExperimental": { - "message": "This service is experimental. By enabling this feature, you agree to OpenSea's $1.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Time" }, @@ -5224,18 +5371,18 @@ "transactionHistoryTotalGasFee": { "message": "Total gas fee" }, + "transactionInsightsDeprecationInfo": { + "message": "Explore more ways to get transaction insights with" + }, + "transactionInsightsDeprecationTitle": { + "message": "This feature will be deprecated soon" + }, "transactionNote": { "message": "Transaction note" }, "transactionResubmitted": { "message": "Transaction resubmitted with estimated gas fee increased to $1 at $2" }, - "transactionSecurityCheck": { - "message": "Enable security alerts" - }, - "transactionSecurityCheckDescription": { - "message": "We use third-party APIs to detect and display risks involved in unsigned transaction and signature requests before you sign them. These services will have access to your unsigned transaction and signature requests, your account address, and your preferred language." - }, "transactionSettings": { "message": "Transaction settings" }, @@ -5467,9 +5614,6 @@ "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 7f9b105957b4..9f6b697fa5b0 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Abrir en el explorador de bloques" }, - "openSea": { - "message": "OpenSea + Blockaid (Beta)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Cree una cuenta $1", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Instalado" }, - "snapError": { - "message": "Error de snap: '$1'. Código de error: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Instalar snap" }, @@ -4045,9 +4035,6 @@ "message": "Cualquier información que comparta con Servicios de terceros será recopilada directamente por dichos Servicios de terceros de acuerdo con sus políticas de privacidad. Consulte sus políticas de privacidad para obtener más información.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Administre sus snaps" - }, "snapsTermsOfUse": { "message": "Términos de uso" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Términos de servicio" }, - "termsOfUse": { - "message": "términos de uso" - }, "termsOfUseAgreeText": { "message": " Acepto los Términos de uso, que se aplican al uso que hago de MetaMask y de todas sus funcionalidades" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "esta colección" }, - "thisServiceIsExperimental": { - "message": "Este servicio es experimental. Al habilitar esta función, usted acepta los $1 de OpenSea.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Tiempo" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "Transacción reenviada con la tarifa de gas aumentada a $1 en $2" }, - "transactionSecurityCheck": { - "message": "Habilitar alertas de seguridad" - }, - "transactionSecurityCheckDescription": { - "message": "Usamos API de terceros para detectar y mostrar los riesgos involucrados en transacciones sin firmar y solicitudes de firma antes de que las firme. Estos servicios tendrán acceso a su transacción no firmada y solicitudes de firma, la dirección de su cuenta y su idioma preferido." - }, "transactionSettings": { "message": "Ajustes de la transacción" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Ver en Opensea" }, - "viewPortfolioDashboard": { - "message": "Ver panel de cartera" - }, "viewinCustodianApp": { "message": "Ver en la aplicación de custodia" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 33cca3bfc540..87e542126e62 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Ouvrir dans l’explorateur de blocs" }, - "openSea": { - "message": "OpenSea + Blockaid (Beta)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Créer un compte $1", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Installé" }, - "snapError": { - "message": "Erreur de snap : « $1 ». Code d’erreur : « $2 »", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Installer Snap" }, @@ -4045,9 +4035,6 @@ "message": "Toute information que vous partagez avec des services tiers sera collectée directement par ces services tiers conformément à leur politique de confidentialité. Pour plus d’informations, veuillez consulter leur politique de confidentialité.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Gérez vos Snaps" - }, "snapsTermsOfUse": { "message": "Conditions d’utilisation" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Conditions de service" }, - "termsOfUse": { - "message": "conditions d’utilisation" - }, "termsOfUseAgreeText": { "message": " J’accepte les conditions d’utilisation de MetaMask et de ses fonctionnalités" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "cette collection" }, - "thisServiceIsExperimental": { - "message": "Ce service est expérimental. En activant cette fonctionnalité, vous acceptez les $1 d’OpenSea.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Temps" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "La transaction a été soumise à nouveau avec une augmentation du prix du gaz, désormais de $1 à $2" }, - "transactionSecurityCheck": { - "message": "Autoriser les fournisseurs de services de vérification des transactions" - }, - "transactionSecurityCheckDescription": { - "message": "Nous utilisons des API tierces pour détecter et afficher les risques liés aux transactions non signées et aux demandes de signature avant que vous ne les signiez. Ces services auront accès à vos transactions non signées et à vos demandes de signature, à l’adresse de votre compte et à la langue que vous préférez." - }, "transactionSettings": { "message": "Paramètres de la transaction" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Afficher sur Opensea" }, - "viewPortfolioDashboard": { - "message": "Afficher le tableau de bord du portefeuille" - }, "viewinCustodianApp": { "message": "Afficher dans l’application dépositaire" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 658e6bb42e33..04859ae00216 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "गोएर्ली" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "बहुभुज" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "ब्लॉक एक्सप्लोरर में खोलें" }, - "openSea": { - "message": "OpenSea + Blockaid (बीटा)" - }, "openSeaNew": { "message": "ओपनसी" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "एक $1 वाला अकाउंट बनाएं", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "Snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "इंस्टॉल किया गया" }, - "snapError": { - "message": "Snap एरर: '$1'. एरर कोड: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Snap इंस्टाल करें" }, @@ -4045,9 +4035,6 @@ "message": "थर्ड पार्टी सेवाओं के साथ आप जो भी सूचना शेयर करते हैं, उसे उन थर्ड पार्टी सेवाओं द्वारा उनकी अपनी गोपनीयता नीतियों के अनुसार सीधे एकत्र की जाएगी। अधिक जानकारी के लिए कृपया उनकी गोपनीयता नीतियां देखें।", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "अपने Snaps प्रबंधित करें" - }, "snapsTermsOfUse": { "message": "इस्तेमाल की शर्तें" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "सेवा की शर्तें" }, - "termsOfUse": { - "message": "इस्तेमाल की शर्तें" - }, "termsOfUseAgreeText": { "message": " मैं इस्तेमाल की शर्तों से सहमत हूं, जो MetaMask और इसकी सभी फीचर्स के मेरे इस्तेमाल पर लागू होती हैं" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "यह संग्रह" }, - "thisServiceIsExperimental": { - "message": "यह सेवा प्रायोगिक है। इस सुविधा को इनेबल करके, आप OpenSea के $1 से सहमत होते हैं।", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "समय" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "$2 गैस फ़ीस में $1 वृद्धि के साथ ट्रांसेक्शन फिर से सबमिट किया गया" }, - "transactionSecurityCheck": { - "message": "सुरक्षा अलर्ट एनेबल करें" - }, - "transactionSecurityCheckDescription": { - "message": "आपके हस्ताक्षर करने से पहले अहस्ताक्षरित ट्रांसेक्शन और हस्ताक्षर रिक्वेस्ट्स में शामिल जोखिमों का पता लगाने और दिखाने के लिए हम थर्ड-पार्टी एपीआई का इस्तेमाल करते हैं। इन सेवाओं के पास आपके अहस्ताक्षरित ट्रांसेक्शन और हस्ताक्षर अनुरोध, आपके अकाउंट का एड्रेस और आपकी पसंदीदा भाषा तक पहुंच होगी।" - }, "transactionSettings": { "message": "ट्रांसेक्शन संबंधी सेटिंग्स" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Opensea पर देखें" }, - "viewPortfolioDashboard": { - "message": "Portfolio डैशबोर्ड देखें" - }, "viewinCustodianApp": { "message": "Custodian ऐप में देखें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 4c4d238492bf..016492ffbc24 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Buka di block explorer" }, - "openSea": { - "message": "OpenSea + Blockaid (Beta)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Buat akun $1", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Terinstal" }, - "snapError": { - "message": "Kesalahan Snap: '$1'. Kode Kesalahan: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Instal Snap" }, @@ -4045,9 +4035,6 @@ "message": "Setiap informasi yang Anda bagikan kepada Layanan Pihak Ketiga akan dikumpulkan langsung oleh Layanan Pihak Ketiga tersebut sesuai dengan kebijakan privasinya. Baca kebijakan privasinya untuk informasi lebih lanjut.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Kelola Snap Anda" - }, "snapsTermsOfUse": { "message": "Ketentuan Penggunaan" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Ketentuan layanan" }, - "termsOfUse": { - "message": "ketentuan penggunaan" - }, "termsOfUseAgreeText": { "message": " Saya menyetujui Syarat Penggunaan, yang berlaku untuk penggunaan atas MetaMask dan semua fiturnya" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "koleksi ini" }, - "thisServiceIsExperimental": { - "message": "Layanan ini bersifat eksperimental. Dengan mengaktifkan fitur ini, Anda menyetujui $1 OpenSea.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Waktu" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "Transaksi dikirim kembali dengan estimasi biaya gas naik $1 pada $2" }, - "transactionSecurityCheck": { - "message": "Aktifkan peringatan keamanan" - }, - "transactionSecurityCheckDescription": { - "message": "Kami menggunakan API pihak ketiga untuk mendeteksi dan menampilkan berbagai risiko yang muncul dalam transaksi yang belum ditandatangani dan permintaan tanda tangan sebelum Anda menandatanganinya. Layanan ini akan dapat mengakses transaksi yang belum ditandatangani dan permintaan tanda tangan, alamat akun, serta bahasa pilihan Anda." - }, "transactionSettings": { "message": "Pengaturan transaksi" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Lihat di Opensea" }, - "viewPortfolioDashboard": { - "message": "Tampilkan Dasbor Portofolio" - }, "viewinCustodianApp": { "message": "Lihat di aplikasi kustodian" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 267ab31b4fdb..f9d329608aaa 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "ブロックエクスプローラーで開く" }, - "openSea": { - "message": "OpenSea + Blockaid (ベータ)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "$1アカウントの作成", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "インストール済み" }, - "snapError": { - "message": "snapエラー:「$1」。エラーコード:「$2」", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "snapをインストール" }, @@ -4045,9 +4035,6 @@ "message": "サードパーティサービスと共有する情報は、当該サードパーティサービスにより、それぞれのプライバシーポリシーに従い直接収集されます。詳細は、各サードパーティサービスのプライバシーポリシーをご覧ください。", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Snapsの管理" - }, "snapsTermsOfUse": { "message": "利用規約" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "サービス規約" }, - "termsOfUse": { - "message": "利用規約" - }, "termsOfUseAgreeText": { "message": " MetaMaskおよびそのすべての機能の利用に適用される利用規約に同意します。" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "このコレクション" }, - "thisServiceIsExperimental": { - "message": "このサービスは試験運用中です。この機能を有効にすることで、OpenSeaの$1に同意したものとみなされます。", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "時間" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "推定のガス代を$2で$1に増加し、トランザクションを再送信しました" }, - "transactionSecurityCheck": { - "message": "セキュリティアラートを有効にする" - }, - "transactionSecurityCheckDescription": { - "message": "当社はサードパーティAPIを使用して、ユーザーが署名する前に未署名のトランザクションおよび署名のリクエストに関するリスクを検出・表示します。このようなサービスは、ユーザーの未署名のトランザクションおよび署名のリクエスト、アカウントアドレス、希望言語にアクセスできます。" - }, "transactionSettings": { "message": "トランザクション設定" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Openseaで表示" }, - "viewPortfolioDashboard": { - "message": "Portfolioダッシュボードを表示" - }, "viewinCustodianApp": { "message": "カストディアンアプリで表示" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 6e6e5f1af2ce..2728db96fd0a 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "블록 탐색기 열기" }, - "openSea": { - "message": "OpenSea+ Blockaid(베타)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "$1 계정 생성", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "스냅", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "설치됨" }, - "snapError": { - "message": "스냅 오류: '$1'. 오류 코드: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "스냅 설치" }, @@ -4045,9 +4035,6 @@ "message": "타사와 공유하는 모든 정보는 해당 타사의 개인정보 처리방침에 따라 직접 수집됩니다. 더 자세한 내용은 해당 회사의 개인정보 처리방침을 참고하시기 바랍니다.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "스냅 관리" - }, "snapsTermsOfUse": { "message": "이용약관" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "서비스 약관" }, - "termsOfUse": { - "message": "이용약관" - }, "termsOfUseAgreeText": { "message": " 본인은 MetaMask의 사용 및 관련 모든 기능에 적용되는 이용약관에 동의합니다" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "이 컬렉션" }, - "thisServiceIsExperimental": { - "message": "이 서비스는 시험 기능입니다. 이 기능을 활성화하면 OpenSea의 $1에 동의하는 것입니다.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "시간" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "$2에서 가스비를 $1(으)로 올린 트랜잭션이 다시 제출되었습니다." }, - "transactionSecurityCheck": { - "message": "보안 경고 활성화" - }, - "transactionSecurityCheckDescription": { - "message": "서명하기 전에 해당 트랜잭션과 서명 요청 관련 위험을 탐지하고 표시하기 위해 타사 API를 이용합니다. 이러한 서비스는 해당 서비스가 아직 서명하지 않은 트랜잭션, 서명 요청, 계정 주소 및 기본 언어에 액세스할 수 있습니다." - }, "transactionSettings": { "message": "트랜잭션 설정" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Opensea에서 보기" }, - "viewPortfolioDashboard": { - "message": "포트폴리오 대시보드 보기" - }, "viewinCustodianApp": { "message": "수탁 앱에서 보기" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 908dcf4cf6c9..98aa3e11dc96 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Abrir no explorador de blocos" }, - "openSea": { - "message": "OpenSea + Blockaid (Beta)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Criar uma conta $1", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Instalado" }, - "snapError": { - "message": "Erro no snap: '$1'. Código de erro: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Instalar snap" }, @@ -4045,9 +4035,6 @@ "message": "Informações compartilhadas com Serviços de Terceiros serão coletadas diretamente por eles, de acordo com políticas de privacidade próprias. Por favor, consulte-as para obter mais informações.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Gerencie seus snaps" - }, "snapsTermsOfUse": { "message": "Termos de Uso" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Termos de Serviço" }, - "termsOfUse": { - "message": "termos de uso" - }, "termsOfUseAgreeText": { "message": " Eu concordo com os Termos de Uso, que se aplicam ao meu uso da MetaMask e de todos os seus recursos" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "esta coleção" }, - "thisServiceIsExperimental": { - "message": "Esse serviço é experimental. Ao ativar esse recurso, você concorda com os $1 da OpenSea.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Hora" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "Transação reenviada com taxa de gás aumentada para $1 às $2" }, - "transactionSecurityCheck": { - "message": "Ativar alertas de segurança" - }, - "transactionSecurityCheckDescription": { - "message": "Usamos APIs de terceiros para detectar e exibir riscos envolvidos em solicitações de assinatura e de transações não assinadas antes que você as assine. Esses serviços terão acesso às suas solicitações de assinatura e de transações não assinadas, ao endereço de sua conta e ao seu idioma preferencial." - }, "transactionSettings": { "message": "Configurações da transação" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Ver na OpenSea" }, - "viewPortfolioDashboard": { - "message": "Ver painel do portfólio" - }, "viewinCustodianApp": { "message": "Ver no app custodiante" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index a43db33c1199..683676b7b713 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Открыть в обозревателе блоков" }, - "openSea": { - "message": "OpenSea + Blockaid (бета-версия)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Создать новый счет $1", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Установлена" }, - "snapError": { - "message": "Ошибка snap: '$1'. Код ошибки: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Установить snap" }, @@ -4045,9 +4035,6 @@ "message": "Любая информация, которую вы передаете Сторонним службам, будет собираться непосредственно этими Сторонними службами в соответствии с их политикой конфиденциальности. Пожалуйста, обратитесь к их политике конфиденциальности для получения дополнительной информации.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Управление вашим Snaps" - }, "snapsTermsOfUse": { "message": "Условия использования" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Условия обслуживания" }, - "termsOfUse": { - "message": "условия использования" - }, "termsOfUseAgreeText": { "message": " Я согласен(-на) с Условиями использования, которые применяются к использованию мною MetaMask и всех его функций" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "эта коллекция" }, - "thisServiceIsExperimental": { - "message": "Эта услуга является экспериментальной. Включив эту функцию, вы соглашаетесь с $1 OpenSea.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Время" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "Транзакция отправлена повторно с платой за газ, увеличенной до $1, в $2" }, - "transactionSecurityCheck": { - "message": "Включить оповещения безопасности" - }, - "transactionSecurityCheckDescription": { - "message": "Мы используем сторонние API для обнаружения и отображения рисков, связанных с неподписанными транзакциями и запросами подписи, прежде чем вы их подпишете. Эти службы будут иметь доступ к вашим неподписанным транзакциям и запросам подписи, адресу вашего счета и предпочитаемому вами языку." - }, "transactionSettings": { "message": "Настройки транзакции" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Смотреть на OpenSea" }, - "viewPortfolioDashboard": { - "message": "Просмотр панели инструментов Portfolio" - }, "viewinCustodianApp": { "message": "Смотреть в приложении депозитария" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 014e1a06c385..5e0041d54984 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Buksan sa block explorer" }, - "openSea": { - "message": "OpenSea + Blockaid (Beta)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Gumawa ng $1 account", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Na-install" }, - "snapError": { - "message": "Snap Error: '$1'. Error Code: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "I-install ang snap" }, @@ -4045,9 +4035,6 @@ "message": "Ang anumang impormasyong ibinabahagi mo sa mga Serbisyo ng Third Party ay direktang kokolektahin ng mga Serbisyo ng Third Party na iyon alinsunod sa kanilang mga patakaran sa pagkapribado. Pakitingnan ang kanilang mga patakaran sa pagkapribado para sa higit pang impormasyon.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Pamahalaan ang iyong mga Snap" - }, "snapsTermsOfUse": { "message": "Mga Tuntunin ng Paggamit" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Mga Tuntunin ng Serbisyo" }, - "termsOfUse": { - "message": "mga tuntunin ng paggamit" - }, "termsOfUseAgreeText": { "message": " Sumasang-ayon ako sa Mga Tuntunin sa Paggamit, na nalalapat sa aking paggamit ng MetaMask at lahat ng feature nito" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "koleksyong ito" }, - "thisServiceIsExperimental": { - "message": "Ang serbisyong ito ay pang-eksperimento. Sa pamamagitan ng pagpapagana ng feature na ito, sumasang-ayon ka sa $1 ng OpenSea.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Oras" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "Isinumite ulit ang transaksyon na may tinantyang bayad sa gas na itinaas sa $1 sa $2" }, - "transactionSecurityCheck": { - "message": "Paganahin ang mga alerto sa seguridad" - }, - "transactionSecurityCheckDescription": { - "message": "Gumagamit kami ng mga third-party na API para tumuklas at ipakita ang mga panganib na kasangkot sa hindi napirmahang mga kahilingan sa transaksyon at paglagda bago mo pirmahan ang mga ito. Ang mga serbisyong ito ay magkakaroon ng access sa iyong hindi pa napirmahang mga kahilingan sa transaksyon at paglagda, address ng iyong account, at iyong nais na wika." - }, "transactionSettings": { "message": "Mga setting ng transaksyon" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Tingnan sa Opensea" }, - "viewPortfolioDashboard": { - "message": "Tingnan ang Dashboard ng Portfolio" - }, "viewinCustodianApp": { "message": "Tingnan sa app custodian" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 1ceb3c54d670..d7c2accbcec5 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Blok gezgininde aç" }, - "openSea": { - "message": "OpenSea + Blockad (Beta)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Bir $1 hesabı oluştur", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Yüklendi" }, - "snapError": { - "message": "Snap Hatası: '$1'. Hata Kodu: '$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Snap yükle" }, @@ -4045,9 +4035,6 @@ "message": "Üçüncü Taraf Hizmetleri ile paylaştığınız tüm bilgiler söz konusu Üçüncü Taraf Hizmetlerinin gizlilik politikalarına göre doğrudan üçüncü taraflar tarafından toplanacaktır. Daha fazla bilgi için lütfen üçüncü tarafların gizlilik politikalarına bakın.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Snaplerini yönet" - }, "snapsTermsOfUse": { "message": "Kullanım Şartları" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Hizmet şartları" }, - "termsOfUse": { - "message": "kullanım şartları" - }, "termsOfUseAgreeText": { "message": " MetaMask ve tüm özelliklerini kullanımım için geçerli olan Kullanım Şartları bölümünü kabul ediyorum" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "bu koleksiyon" }, - "thisServiceIsExperimental": { - "message": "Hizmet deneyseldir. Bu özelliği etkinleştirerek OpenSea'nin $1 bölümünü kabul edersiniz.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Zaman" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "İşlem, $2 itibariyle $1 olarak artırılan tahmini gaz ücreti ile yeniden gönderildi" }, - "transactionSecurityCheck": { - "message": "Güvenlik uyarılarını etkinleştir" - }, - "transactionSecurityCheckDescription": { - "message": "İmzalanmamış işleme dahil olan riskleri ve onları imzalamadan önce imza taleplerini algılamak ve göstermek için üçüncü taraf API'lerini kullanırız. Bu hizmetler imzalanmamış işlem ve imza taleplerinize, hesap adresinize ve tercih ettiğiniz dile erişim sağlar." - }, "transactionSettings": { "message": "İşlem ayarları" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Opensea'de görüntüle" }, - "viewPortfolioDashboard": { - "message": "Portföy Kontrol Panelini görüntüle" - }, "viewinCustodianApp": { "message": "Saklayıcı kurum uygulamasında görüntüle" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index d17b243151f6..7153719548a4 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "Mở trên trình khám phá khối" }, - "openSea": { - "message": "OpenSea + Blockaid (Beta)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "Tạo tài khoản $1", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "Snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "Đã cài đặt" }, - "snapError": { - "message": "Lỗi Snap: \"$1\". Mã lỗi: \"$2\"", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "Cài đặt Snap" }, @@ -4045,9 +4035,6 @@ "message": "Mọi thông tin mà bạn chia sẻ với Dịch vụ bên thứ ba sẽ được Dịch vụ bên thứ ba đó thu thập trực tiếp theo chính sách quyền riêng tư của họ. Vui lòng tham khảo chính sách quyền riêng tư của họ để biết thêm thông tin.", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "Quản lý Snap" - }, "snapsTermsOfUse": { "message": "Điều khoản sử dụng" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "Điều khoản dịch vụ" }, - "termsOfUse": { - "message": "điều khoản sử dụng" - }, "termsOfUseAgreeText": { "message": " Tôi đồng ý với Điều khoản sử dụng được áp dụng cho việc tôi sử dụng MetaMask và tất cả các tính năng của MetaMask" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "bộ sưu tập này" }, - "thisServiceIsExperimental": { - "message": "Đây là dịch vụ thử nghiệm. Bằng cách bật tính năng này, bạn đồng ý với $1 của OpenSea.", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "Thời gian" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "Đã gửi lại giao dịch với mức phí gas ước tính tăng lên $1 lúc $2" }, - "transactionSecurityCheck": { - "message": "Bật cảnh báo bảo mật" - }, - "transactionSecurityCheckDescription": { - "message": "Chúng tôi sử dụng API của bên thứ ba để phát hiện và hiển thị những rủi ro liên quan đến các yêu cầu chữ ký và giao dịch chưa ký trước khi bạn ký. Những dịch vụ này sẽ có quyền truy cập vào các yêu cầu chữ ký và giao dịch chưa ký, địa chỉ tài khoản và ngôn ngữ yêu thích của bạn." - }, "transactionSettings": { "message": "Cài đặt giao dịch" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "Xem trên Opensea" }, - "viewPortfolioDashboard": { - "message": "Xem Trang tổng quan Danh mục đầu tư" - }, "viewinCustodianApp": { "message": "Xem trong ứng dụng lưu ký" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index d07fc2f81d7b..21045baf5182 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -2387,9 +2387,6 @@ "networkNameGoerli": { "message": "Goerli" }, - "networkNameOptimism": { - "message": "Optimism" - }, "networkNamePolygon": { "message": "Polygon" }, @@ -3022,9 +3019,6 @@ "openInBlockExplorer": { "message": "在区块浏览器上打开" }, - "openSea": { - "message": "OpenSea + Blockaid(测试版)" - }, "openSeaNew": { "message": "OpenSea" }, @@ -3919,7 +3913,7 @@ }, "snapCreateAccountTitle": { "message": "创建 $1 账户", - "description": "Title of the Create Snap Account Page, $1 is the text using a different color" + "description": "Title of the Create Account Snap Page, $1 is the text using a different color" }, "snapCreateAccountTitle2": { "message": "snap", @@ -3955,10 +3949,6 @@ "snapDetailsInstalled": { "message": "已安装" }, - "snapError": { - "message": "Snap错误:'$1'。错误代码:'$2'", - "description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code." - }, "snapInstall": { "message": "安装Snap" }, @@ -4045,9 +4035,6 @@ "message": "您与第三方服务分享的任何信息,将由这些第三方服务根据其隐私政策直接收集。请参阅其隐私政策以了解更多信息。", "description": "Second part of a message in popup modal displayed when installing a snap for the first time." }, - "snapsSettingsDescription": { - "message": "管理您的 Snaps" - }, "snapsTermsOfUse": { "message": "使用条款" }, @@ -4799,9 +4786,6 @@ "termsOfService": { "message": "服务条款" }, - "termsOfUse": { - "message": "使用条款" - }, "termsOfUseAgreeText": { "message": "我同意适用于我使用MetaMask及其所有功能的使用条款" }, @@ -4830,10 +4814,6 @@ "thisCollection": { "message": "这个收藏品" }, - "thisServiceIsExperimental": { - "message": "此服务是实验性的。启用此功能,即表示您同意OpenSea的$1。", - "description": "$1 is link to open sea terms of use" - }, "time": { "message": "时间" }, @@ -5049,12 +5029,6 @@ "transactionResubmitted": { "message": "已在 $2 重新提交交易,燃料费预计升至 $1" }, - "transactionSecurityCheck": { - "message": "启用安全提醒" - }, - "transactionSecurityCheckDescription": { - "message": "我们使用第三方 API 来检测和显示未签名交易和签名请求中涉及的风险,然后您再进行签名。这些服务将访问您的未签名交易和签名请求、您的账户地址以及首选语言。" - }, "transactionSettings": { "message": "交易设置" }, @@ -5273,9 +5247,6 @@ "viewOnOpensea": { "message": "在 Opensea 上查看" }, - "viewPortfolioDashboard": { - "message": "查看 Portfolio 控制面板" - }, "viewinCustodianApp": { "message": "在托管应用程序中查看" }, diff --git a/app/build-types/mmi/images/check-green-solid.svg b/app/build-types/mmi/images/check-green-solid.svg deleted file mode 100644 index 68c46196d1e7..000000000000 --- a/app/build-types/mmi/images/check-green-solid.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/build-types/mmi/images/close-red-solid.svg b/app/build-types/mmi/images/close-red-solid.svg deleted file mode 100644 index 728c70457a60..000000000000 --- a/app/build-types/mmi/images/close-red-solid.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/app/build-types/mmi/images/custodialWallet.svg b/app/build-types/mmi/images/custodialWallet.svg deleted file mode 100644 index 00f9bd3b0ca5..000000000000 --- a/app/build-types/mmi/images/custodialWallet.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/build-types/mmi/images/icon-128.png b/app/build-types/mmi/images/icon-128.png index b63d1cd4f16c..b71f4fa6fadc 100644 Binary files a/app/build-types/mmi/images/icon-128.png and b/app/build-types/mmi/images/icon-128.png differ diff --git a/app/build-types/mmi/images/icon-16.png b/app/build-types/mmi/images/icon-16.png index 86f9caddad96..f5e8dadf537c 100644 Binary files a/app/build-types/mmi/images/icon-16.png and b/app/build-types/mmi/images/icon-16.png differ diff --git a/app/build-types/mmi/images/icon-19.png b/app/build-types/mmi/images/icon-19.png index 4f63666de4f6..40cd13e87d79 100644 Binary files a/app/build-types/mmi/images/icon-19.png and b/app/build-types/mmi/images/icon-19.png differ diff --git a/app/build-types/mmi/images/icon-32.png b/app/build-types/mmi/images/icon-32.png index 5adeec8dbe28..c2813c7c9456 100644 Binary files a/app/build-types/mmi/images/icon-32.png and b/app/build-types/mmi/images/icon-32.png differ diff --git a/app/build-types/mmi/images/icon-38.png b/app/build-types/mmi/images/icon-38.png index 8a29d35574ab..abcd19c892f1 100644 Binary files a/app/build-types/mmi/images/icon-38.png and b/app/build-types/mmi/images/icon-38.png differ diff --git a/app/build-types/mmi/images/icon-48.png b/app/build-types/mmi/images/icon-48.png index a4c741d25626..ec3fe0204c01 100644 Binary files a/app/build-types/mmi/images/icon-48.png and b/app/build-types/mmi/images/icon-48.png differ diff --git a/app/build-types/mmi/images/icon-512.png b/app/build-types/mmi/images/icon-512.png index 4046713753ac..0b00612f5225 100644 Binary files a/app/build-types/mmi/images/icon-512.png and b/app/build-types/mmi/images/icon-512.png differ diff --git a/app/build-types/mmi/images/icon-64.png b/app/build-types/mmi/images/icon-64.png index 278183b9e47e..cfbd477017ed 100644 Binary files a/app/build-types/mmi/images/icon-64.png and b/app/build-types/mmi/images/icon-64.png differ diff --git a/app/build-types/mmi/images/icons/portfolio-dashboard.svg b/app/build-types/mmi/images/icons/portfolio-dashboard.svg deleted file mode 100644 index 3144f8995813..000000000000 --- a/app/build-types/mmi/images/icons/portfolio-dashboard.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/app/build-types/mmi/images/info-logo.png b/app/build-types/mmi/images/info-logo.png index 3b19236ab934..c55b394a72d6 100644 Binary files a/app/build-types/mmi/images/info-logo.png and b/app/build-types/mmi/images/info-logo.png differ diff --git a/app/build-types/mmi/images/jupiter.svg b/app/build-types/mmi/images/jupiter.svg deleted file mode 100644 index 6beef9c2f2e9..000000000000 --- a/app/build-types/mmi/images/jupiter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/build-types/mmi/images/mmi-mascot.json b/app/build-types/mmi/images/mmi-mascot.json deleted file mode 100644 index 013cec092c3b..000000000000 --- a/app/build-types/mmi/images/mmi-mascot.json +++ /dev/null @@ -1,322 +0,0 @@ -{ - "chunks": [ - { - "color": [182, 202, 252], - "faces": [ - [17, 33, 10], - [17, 18, 34], - [34, 33, 17], - [10, 6, 17], - [11, 15, 31], - [31, 18, 11], - [18, 12, 11], - [14, 16, 40], - [40, 41, 14], - [59, 5, 35], - [35, 79, 59], - [67, 63, 77], - [67, 77, 76], - [76, 68, 67], - [63, 67, 58], - [64, 68, 75], - [75, 37, 64], - [68, 64, 66], - [14, 41, 37], - [37, 15, 14], - [5, 59, 40], - [40, 16, 5] - ] - }, - { - "color": [109, 149, 249], - "faces": [ - [31, 24, 18], - [6, 5, 16], - [16, 17, 6], - [24, 32, 33], - [33, 34, 24], - [5, 4, 35], - [75, 68, 71], - [58, 67, 40], - [40, 59, 58], - [71, 76, 77], - [77, 78, 71] - ] - }, - { - "color": [44, 86, 221], - "faces": [ - [0, 1, 2], - [2, 3, 0], - [4, 5, 2], - [6, 3, 2], - [2, 5, 6], - [7, 8, 9], - [10, 3, 6], - [10, 50, 7], - [7, 3, 10], - [7, 9, 3], - [49, 0, 9], - [3, 9, 0], - [53, 54, 55], - [55, 56, 53], - [57, 56, 55], - [58, 59, 55], - [55, 54, 58], - [60, 61, 62], - [63, 58, 54], - [63, 60, 89], - [60, 63, 54], - [60, 54, 61], - [88, 61, 53], - [54, 53, 61], - [2, 1, 4], - [55, 59, 57] - ] - }, - { - "color": [47, 52, 59], - "faces": [ - [36, 15, 37], - [37, 38, 36], - [31, 39, 22], - [22, 21, 31], - [31, 15, 36], - [36, 39, 31], - [75, 69, 26], - [26, 80, 75], - [75, 80, 38], - [38, 37, 75], - [38, 80, 39], - [39, 36, 38], - [39, 80, 26], - [26, 22, 39] - ] - }, - { - "color": [245, 247, 249], - "faces": [ - [21, 20, 24], - [24, 31, 21], - [69, 71, 70], - [71, 69, 75] - ] - }, - { - "color": [219, 229, 254], - "faces": [ - [19, 20, 21], - [21, 22, 19], - [20, 19, 23], - [23, 24, 20], - [23, 25, 24], - [19, 22, 26], - [26, 27, 19], - [23, 28, 29], - [23, 29, 30], - [25, 23, 30], - [29, 51, 52], - [52, 30, 29], - [27, 26, 69], - [69, 70, 27], - [70, 71, 72], - [72, 27, 70], - [72, 71, 73], - [51, 74, 72], - [52, 51, 72], - [73, 52, 72], - [19, 27, 74], - [74, 28, 19], - [51, 29, 28], - [28, 74, 51], - [74, 27, 72], - [28, 23, 19] - ] - }, - { - "color": [73, 123, 248], - "faces": [ - [24, 34, 18], - [16, 13, 12], - [12, 17, 16], - [13, 16, 11], - [71, 68, 76], - [40, 67, 66], - [66, 65, 40], - [65, 64, 40] - ] - }, - { - "color": [47, 52, 59], - "faces": [ - [11, 12, 13], - [64, 65, 66] - ] - }, - { - "color": [109, 149, 249], - "faces": [ - [14, 15, 11], - [11, 16, 14], - [17, 12, 18], - [41, 64, 37], - [67, 68, 66] - ] - }, - { - "color": [109, 149, 249], - "faces": [ - [35, 4, 42], - [4, 1, 42], - [42, 43, 44], - [44, 35, 42], - [45, 43, 42], - [42, 10, 45], - [30, 32, 24], - [24, 25, 30], - [30, 33, 32], - [33, 30, 10], - [44, 43, 46], - [43, 45, 47], - [47, 46, 43], - [48, 47, 45], - [45, 30, 48], - [30, 45, 10], - [49, 42, 0], - [8, 7, 42], - [50, 42, 7], - [50, 10, 42], - [1, 0, 42], - [42, 9, 8], - [42, 49, 9], - [64, 41, 40], - [57, 59, 79], - [79, 81, 57], - [57, 81, 56], - [82, 79, 35], - [35, 44, 82], - [81, 79, 82], - [82, 83, 81], - [84, 63, 81], - [81, 83, 84], - [44, 46, 85], - [85, 82, 44], - [52, 73, 71], - [71, 78, 52], - [52, 78, 77], - [77, 63, 52], - [82, 85, 83], - [83, 85, 86], - [86, 84, 83], - [87, 52, 84], - [84, 86, 87], - [52, 63, 84], - [88, 53, 81], - [62, 81, 60], - [89, 60, 81], - [89, 81, 63], - [56, 81, 53], - [81, 62, 61], - [81, 61, 88], - [48, 87, 86], - [86, 47, 48], - [47, 86, 85], - [85, 46, 47], - [48, 30, 52], - [52, 87, 48] - ] - } - ], - "positions": [ - [111.0246, 52.6046, 46.2259], - [114.025, 87.6733, 58.9818], - [66.192, 80.898, 55.3943], - [72.1133, 35.4918, 30.8714], - [97.8045, 116.561, 73.9788], - [16.7623, 58.0109, 58.0782], - [52.6089, 30.3641, 42.5561], - [106.8814, 31.9455, 46.9133], - [113.4846, 38.6049, 49.1215], - [108.6633, 43.2332, 46.3154], - [101.2166, 15.9822, 46.3082], - [16.6605, -16.2883, 93.6187], - [40.775, -10.2288, 85.2764], - [23.9269, -2.5103, 86.7365], - [11.1691, -7.0037, 99.3776], - [9.5692, -34.3939, 141.672], - [12.596, 7.1655, 88.741], - [61.1809, 8.8142, 76.9968], - [39.7195, -28.9271, 88.9638], - [13.7962, -68.5757, 132.057], - [15.2674, -62.32, 129.688], - [14.8446, -52.6096, 140.113], - [12.8917, -49.7716, 144.741], - [35.6042, -71.758, 81.0639], - [47.4625, -68.6061, 63.3697], - [38.2486, -64.7302, 38.9099], - [-12.8917, -49.7716, 144.741], - [-13.7962, -68.5757, 132.057], - [17.8021, -71.758, 81.0639], - [19.1243, -69.0168, 49.4201], - [38.2486, -66.2756, 17.7762], - [12.8928, -36.7035, 141.672], - [109.284, -93.5899, 27.8243], - [122.118, -36.8894, 35.025], - [67.7668, -30.197, 78.4178], - [33.1807, 101.852, 25.3186], - [9.4063, -35.5898, 150.722], - [-9.5692, -34.3939, 141.672], - [-9.4063, -35.5898, 150.722], - [11.4565, -37.8994, 150.722], - [-12.596, 7.1655, 88.741], - [-11.1691, -7.0037, 99.3776], - [70.2365, 62.8362, -3.9475], - [47.2634, 54.294, -27.4148], - [28.7302, 91.7311, -24.9726], - [69.1676, 6.5862, -12.7757], - [28.7302, 49.1003, -48.3596], - [31.903, 5.692, -47.822], - [35.0758, -34.4329, -16.2809], - [115.2841, 48.6815, 48.6841], - [110.8428, 28.4821, 49.1762], - [-19.1243, -69.0168, 49.4201], - [-38.2486, -66.2756, 17.7762], - [-111.0246, 52.6046, 46.2259], - [-72.1133, 35.4918, 30.8714], - [-66.192, 80.898, 55.3943], - [-114.025, 87.6733, 58.9818], - [-97.8045, 116.561, 73.9788], - [-52.6089, 30.3641, 42.5561], - [-16.7623, 58.0109, 58.0782], - [-106.8814, 31.9455, 46.9133], - [-108.6633, 43.2332, 46.3154], - [-113.4846, 38.6049, 49.1215], - [-101.2166, 15.9822, 46.3082], - [-16.6605, -16.2883, 93.6187], - [-23.9269, -2.5103, 86.7365], - [-40.775, -10.2288, 85.2764], - [-61.1809, 8.8142, 76.9968], - [-39.7195, -28.9271, 88.9638], - [-14.8446, -52.6096, 140.113], - [-15.2674, -62.32, 129.688], - [-47.4625, -68.6061, 63.3697], - [-35.6042, -71.758, 81.0639], - [-38.2486, -64.7302, 38.9099], - [-17.8021, -71.758, 81.0639], - [-12.8928, -36.7035, 141.672], - [-67.7668, -30.197, 78.4178], - [-122.118, -36.8894, 35.025], - [-109.284, -93.5899, 27.8243], - [-33.1807, 101.852, 25.3186], - [-11.4565, -37.8994, 150.722], - [-70.2365, 62.8362, -3.9475], - [-28.7302, 91.7311, -24.9726], - [-47.2634, 54.294, -27.4148], - [-69.1676, 6.5862, -12.7757], - [-28.7302, 49.1003, -48.3596], - [-31.903, 5.692, -47.822], - [-35.0758, -34.4329, -16.2809], - [-115.2841, 48.6815, 48.6841], - [-110.8428, 28.4821, 49.1762] - ] -} diff --git a/app/build-types/mmi/images/mmi-swaps.svg b/app/build-types/mmi/images/mmi-swaps.svg deleted file mode 100644 index 6199bcb94956..000000000000 --- a/app/build-types/mmi/images/mmi-swaps.svg +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/build-types/mmi/images/portfolio.svg b/app/build-types/mmi/images/portfolio.svg deleted file mode 100644 index 296366a8c7c8..000000000000 --- a/app/build-types/mmi/images/portfolio.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/build-types/mmi/mmi-mascot.json b/app/build-types/mmi/mmi-mascot.json new file mode 100644 index 000000000000..f28a25f5e28e --- /dev/null +++ b/app/build-types/mmi/mmi-mascot.json @@ -0,0 +1,824 @@ +{ + "chunks": [ + { + "faces": [ + [0, 1, 2], + [2, 3, 0], + [4, 5, 2], + [6, 3, 2], + [2, 5, 6], + [7, 8, 9], + [10, 3, 6], + [10, 50, 7], + [7, 3, 10], + [7, 9, 3], + [49, 0, 9], + [3, 9, 0], + [2, 1, 4] + ], + "name": "left ear", + "gradient": "left-ear-gradient" + }, + { + "faces": [ + [53, 54, 55], + [55, 56, 53], + [57, 56, 55], + [58, 59, 55], + [55, 54, 58], + [60, 61, 62], + [63, 58, 54], + [63, 60, 89], + [60, 63, 54], + [60, 54, 61], + [88, 61, 53], + [54, 53, 61], + [55, 59, 57] + ], + "name": "right ear", + "gradient": "right-ear-gradient" + }, + { + "color": [22, 22, 22], + "faces": [[11, 12, 13]], + "name": "left eye" + }, + { + "color": [22, 22, 22], + "faces": [[64, 65, 66]], + "name": "right eye" + }, + { + "faces": [ + [14, 15, 11], + [11, 16, 14] + ], + "name": "left inner eye", + "gradient": "left-inner-eye-gradient" + }, + { + "faces": [[17, 12, 18]], + "name": "left outer eye", + "gradient": "left-outer-eye-gradient" + }, + { + "faces": [[41, 64, 37]], + "name": "right lower inner eye", + "gradient": "right-inner-eye-gradient" + }, + { + "faces": [[67, 68, 66]], + "name": "right outer eye", + "gradient": "right-outer-eye-gradient" + }, + { + "color": [215, 193, 179], + "faces": [ + [19, 20, 21], + [21, 22, 19], + [20, 19, 23], + [23, 24, 20], + [23, 25, 24], + [19, 22, 26], + [26, 27, 19], + [23, 28, 29], + [23, 29, 30], + [25, 23, 30], + [29, 51, 52], + [52, 30, 29], + [27, 26, 69], + [69, 70, 27], + [70, 71, 72], + [72, 27, 70], + [72, 71, 73], + [51, 74, 72], + [52, 51, 72], + [73, 52, 72], + [19, 27, 74], + [74, 28, 19], + [51, 29, 28], + [28, 74, 51], + [74, 27, 72], + [28, 23, 19] + ], + "name": "lower chin" + }, + { + "color": [215, 193, 179], + "faces": [ + [21, 20, 24], + [24, 31, 21] + ], + "name": "left lower snout" + }, + { + "color": [215, 193, 179], + "faces": [ + [69, 71, 70], + [71, 69, 75] + ], + "name": "right lower snout" + }, + { + "color": [109, 149, 249], + "faces": [[31, 24, 18]], + "name": "left upper snout" + }, + { + "faces": [ + [6, 5, 16], + [16, 17, 6] + ], + "name": "left forehead", + "gradient": "left-forehead-gradient" + }, + { + "faces": [ + [24, 32, 33], + [33, 34, 24] + ], + "name": "left lower cheek", + "gradient": "left-lower-cheek-gradient" + }, + { + "faces": [[5, 4, 35]], + "name": "left top ear", + "gradient": "left-top-ear-gradient" + }, + { + "color": [109, 149, 249], + "faces": [[75, 68, 71]], + "name": "right upper snout" + }, + { + "faces": [ + [58, 67, 40], + [40, 59, 58] + ], + "name": "right forhead", + "gradient": "right-forehead-gradient" + }, + { + "faces": [ + [71, 76, 77], + [77, 78, 71] + ], + "name": "right lower cheek", + "gradient": "right-lower-cheek-gradient" + }, + { + "faces": [[24, 34, 18]], + "name": "left middle cheek", + "gradient": "left-middle-cheek-gradient" + }, + { + "color": [73, 123, 248], + "faces": [ + [16, 13, 12], + [12, 17, 16], + [13, 16, 11] + ], + "name": "left above eye" + }, + { + "faces": [[71, 68, 76]], + "name": "right middle cheek", + "gradient": "right-middle-cheek-gradient" + }, + { + "color": [73, 123, 248], + "faces": [ + [40, 67, 66], + [66, 65, 40], + [65, 64, 40] + ], + "name": "right above eye" + }, + { + "color": [22, 22, 22], + "faces": [ + [36, 15, 37], + [37, 38, 36], + [31, 39, 22], + [22, 21, 31], + [31, 15, 36], + [36, 39, 31], + [75, 69, 26], + [26, 80, 75], + [75, 80, 38], + [38, 37, 75], + [38, 80, 39], + [39, 36, 38], + [39, 80, 26], + [26, 22, 39] + ], + "name": "nose" + }, + { + "faces": [ + [17, 33, 10], + [17, 18, 34], + [34, 33, 17], + [10, 6, 17] + ], + "name": "left upper cheek", + "gradient": "left-upper-cheek-gradient" + }, + { + "faces": [ + [11, 15, 31], + [31, 18, 11], + [18, 12, 11] + ], + "name": "left below eye", + "gradient": "left-below-eye-gradient" + }, + { + "faces": [ + [14, 16, 40], + [40, 41, 14], + [59, 5, 35], + [35, 79, 59], + [14, 41, 37], + [37, 15, 14], + [5, 59, 40], + [40, 16, 5] + ], + "name": "forehead", + "gradient": "forehead-gradient" + }, + { + "faces": [ + [67, 63, 77], + [67, 77, 76], + [76, 68, 67], + [63, 67, 58] + ], + "name": "right upper cheek", + "gradient": "right-upper-cheek-gradient" + }, + { + "faces": [ + [64, 68, 75], + [75, 37, 64], + [68, 64, 66] + ], + "name": "right below eye", + "gradient": "right-below-eye-gradient" + }, + { + "faces": [ + [35, 4, 42], + [4, 1, 42], + [42, 43, 44], + [44, 35, 42], + [45, 43, 42], + [42, 10, 45], + [30, 32, 24], + [24, 25, 30], + [30, 33, 32], + [33, 30, 10], + [44, 43, 46], + [43, 45, 47], + [47, 46, 43], + [48, 47, 45], + [45, 30, 48], + [30, 45, 10], + [49, 42, 0], + [8, 7, 42], + [50, 42, 7], + [50, 10, 42], + [1, 0, 42], + [42, 9, 8], + [42, 49, 9], + [79, 81, 57], + [57, 81, 56], + [82, 79, 35], + [35, 44, 82], + [81, 79, 82], + [82, 83, 81], + [84, 63, 81], + [81, 83, 84], + [44, 46, 85], + [85, 82, 44], + [52, 73, 71], + [71, 78, 52], + [52, 78, 77], + [77, 63, 52], + [82, 85, 83], + [83, 85, 86], + [86, 84, 83], + [87, 52, 84], + [84, 86, 87], + [52, 63, 84], + [88, 53, 81], + [62, 81, 60], + [89, 60, 81], + [89, 81, 63], + [56, 81, 53], + [81, 62, 61], + [81, 61, 88], + [48, 87, 86], + [86, 47, 48], + [47, 86, 85], + [85, 46, 47], + [48, 30, 52], + [52, 87, 48] + ], + "name": "back", + "gradient": "back-gradient" + }, + { + "faces": [[57, 59, 79]], + "name": "right top ear", + "gradient": "right-top-ear-gradient" + }, + { + "faces": [[64, 41, 40]], + "name": "right inner upper eye", + "gradient": "right-inner-eye-gradient" + } + ], + "gradients": { + "left-inner-eye-gradient": { + "type": "linear", + "x1": "41.97721822541966%", + "y1": "67.79239690721649%", + "x2": "44.56654676258992%", + "y2": "67.79239690721649%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "0.5281", + "stop-color": "#6D95F9" + }, + { + "offset": "0.8987", + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "right-inner-eye-gradient": { + "type": "linear", + "x1": "56.72805755395684%", + "y1": "81.08904639175258%", + "x2": "56.72805755395684%", + "y2": "54.49574742268041%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "0.5281", + "stop-color": "#6D95F9" + }, + { + "offset": "0.8987", + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "left-middle-cheek-gradient": { + "type": "linear", + "x1": "25.107913669064747%", + "y1": "72.68865979381442%", + "x2": "25.107913669064747%", + "y2": "89.44690721649484%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#497BF8" + }, + { + "offset": "0.3363", + "stop-color": "#497BF8" + } + ] + }, + "right-middle-cheek-gradient": { + "type": "linear", + "x1": "74.89208633093526%", + "y1": "51.32938144329896%", + "x2": "74.89208633093526%", + "y2": "94.76301546391753%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "0.3363", + "stop-color": "#497BF8" + } + ] + }, + "right-forehead-gradient": { + "type": "linear", + "x1": "67.00671462829736%", + "y1": "30.13930412371134%", + "x2": "67.00671462829736%", + "y2": "54.49561855670103%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "left-forehead-gradient": { + "type": "linear", + "x1": "32.99340527577938%", + "y1": "30.13930412371134%", + "x2": "32.99340527577938%", + "y2": "54.49561855670103%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "right-top-ear-gradient": { + "type": "linear", + "x1": "95.056858513189448%", + "y1": "15.06958762886598%", + "x2": "57.31654676258992%", + "y2": "15.06958762886598%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "left-top-ear-gradient": { + "type": "linear", + "x1": "4.943141486810552%", + "y1": "15.06958762886598%", + "x2": "42.68345323741008%", + "y2": "15.06958762886598%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "left-lower-cheek-gradient": { + "type": "linear", + "x1": "15.103956834532372%", + "y1": "72.6889175257732%", + "x2": "15.103956834532372%", + "y2": "96.03221649484537%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#B6CAFC" + } + ] + }, + "right-lower-cheek-gradient": { + "type": "linear", + "x1": "84.91570743405276%", + "y1": "72.6889175257732%", + "x2": "84.91570743405276%", + "y2": "96.03221649484537%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#B6CAFC" + } + ] + }, + "right-outer-eye-gradient": { + "type": "linear", + "x1": "68.7720623501199%", + "y1": "63.14909793814433%", + "x2": "78.03057553956835%", + "y2": "63.14909793814433%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "0.5281", + "stop-color": "#6D95F9" + }, + { + "offset": "0.8987", + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "left-outer-eye-gradient": { + "type": "linear", + "x1": "21.969424460431654%", + "y1": "63.14909793814433%", + "x2": "31.227937649880094%", + "y2": "63.14909793814433%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "0.5281", + "stop-color": "#6D95F9" + }, + { + "offset": "0.8987", + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "left-ear-gradient": { + "type": "linear", + "x1": "50%", + "y1": "30%", + "x2": "4%", + "y2": "4%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "0.4286", + "stop-color": "#2C56DD" + }, + { + "offset": "0.62", + "stop-color": "#2C56DD" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "right-ear-gradient": { + "type": "linear", + "x1": "50%", + "y1": "30%", + "x2": "96%", + "y2": "4%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "0.4286", + "stop-color": "#2C56DD" + }, + { + "offset": "0.62", + "stop-color": "#2C56DD" + }, + { + "offset": "1", + "stop-color": "#6D95F9" + } + ] + }, + "left-below-eye-gradient": { + "type": "linear", + "x1": "30.914028776978412%", + "y1": "72.83646907216496%", + "x2": "44.56654676258992%", + "y2": "72.83646907216496%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#B6CAFC" + } + ] + }, + "right-below-eye-gradient": { + "type": "linear", + "x1": "55.43345323741007%", + "y1": "72.83646907216496%", + "x2": "69.12517985611511%", + "y2": "72.83646907216496%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#B6CAFC" + } + ] + }, + "left-upper-cheek-gradient": { + "type": "linear", + "x1": "16.02589928057554%", + "y1": "43.35154639175258%", + "x2": "16.02589928057554%", + "y2": "72.85773195876288%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#B6CAFC" + }, + { + "offset": "1", + "stop-color": "#B6CAFC" + } + ] + }, + "right-upper-cheek-gradient": { + "type": "linear", + "x1": "83.99364508393285%", + "y1": "43.35154639175258%", + "x2": "83.99364508393285%", + "y2": "72.85773195876288%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#B6CAFC" + }, + { + "offset": "1", + "stop-color": "#B6CAFC" + } + ] + }, + "forehead-gradient": { + "type": "linear", + "x1": "50%", + "y1": "12.790180412371136%", + "x2": "50%", + "y2": "81.08904639175258%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#B6CAFC" + }, + { + "offset": "1", + "stop-color": "#B6CAFC" + } + ] + }, + "back-gradient": { + "type": "linear", + "x1": "50%", + "y1": "12.790180412371136%", + "x2": "50%", + "y2": "81.08904639175258%", + "gradientUnits": "userSpaceOnUse", + "stops": [ + { + "stop-color": "#6D95F9" + }, + { + "offset": "1", + "stop-color": "#5C5CE0" + } + ] + } + }, + "positions": [ + [111.024597, 52.604599, 46.225899], + [114.025002, 87.673302, 58.9818], + [66.192001, 80.898003, 55.394299], + [72.113297, 35.491798, 30.871401], + [97.804497, 116.560997, 73.978798], + [16.7623, 58.010899, 58.078201], + [52.608898, 30.3641, 42.556099], + [106.881401, 31.945499, 46.9133], + [113.484596, 38.6049, 49.121498], + [108.6633, 43.2332, 46.315399], + [101.216599, 15.9822, 46.308201], + [16.6605, -16.2883, 93.618698], + [40.775002, -10.2288, 85.276398], + [23.926901, -2.5103, 86.736504], + [11.1691, -7.0037, 99.377602], + [9.5692, -34.393902, 141.671997], + [12.596, 7.1655, 88.740997], + [61.180901, 8.8142, 76.996803], + [39.719501, -28.927099, 88.963799], + [13.7962, -68.575699, 132.057007], + [15.2674, -62.32, 129.688004], + [14.8446, -52.6096, 140.113007], + [12.8917, -49.771599, 144.740997], + [35.604198, -71.758003, 81.063904], + [47.462502, -68.606102, 63.369701], + [38.2486, -64.730202, 38.909901], + [-12.8917, -49.771599, 144.740997], + [-13.7962, -68.575699, 132.057007], + [17.802099, -71.758003, 81.063904], + [19.1243, -69.0168, 49.420101], + [38.2486, -66.275597, 17.776199], + [12.8928, -36.703499, 141.671997], + [109.283997, -93.589897, 27.824301], + [122.117996, -36.8894, 35.025002], + [67.7668, -30.197001, 78.417801], + [33.180698, 101.851997, 25.3186], + [9.4063, -35.589802, 150.722], + [-9.5692, -34.393902, 141.671997], + [-9.4063, -35.589802, 150.722], + [11.4565, -37.899399, 150.722], + [-12.596, 7.1655, 88.740997], + [-11.1691, -7.0037, 99.377602], + [70.236504, 62.836201, -3.9475], + [47.263401, 54.293999, -27.414801], + [28.7302, 91.731102, -24.972601], + [69.167603, 6.5862, -12.7757], + [28.7302, 49.1003, -48.3596], + [31.903, 5.692, -47.821999], + [35.075802, -34.432899, -16.280899], + [115.284103, 48.681499, 48.684101], + [110.842796, 28.4821, 49.176201], + [-19.1243, -69.0168, 49.420101], + [-38.2486, -66.275597, 17.776199], + [-111.024597, 52.604599, 46.225899], + [-72.113297, 35.491798, 30.871401], + [-66.192001, 80.898003, 55.394299], + [-114.025002, 87.673302, 58.9818], + [-97.804497, 116.560997, 73.978798], + [-52.608898, 30.3641, 42.556099], + [-16.7623, 58.010899, 58.078201], + [-106.881401, 31.945499, 46.9133], + [-108.6633, 43.2332, 46.315399], + [-113.484596, 38.6049, 49.121498], + [-101.216599, 15.9822, 46.308201], + [-16.6605, -16.2883, 93.618698], + [-23.926901, -2.5103, 86.736504], + [-40.775002, -10.2288, 85.276398], + [-61.180901, 8.8142, 76.996803], + [-39.719501, -28.927099, 88.963799], + [-14.8446, -52.6096, 140.113007], + [-15.2674, -62.32, 129.688004], + [-47.462502, -68.606102, 63.369701], + [-35.604198, -71.758003, 81.063904], + [-38.2486, -64.730202, 38.909901], + [-17.802099, -71.758003, 81.063904], + [-12.8928, -36.703499, 141.671997], + [-67.7668, -30.197001, 78.417801], + [-122.117996, -36.8894, 35.025002], + [-109.283997, -93.589897, 27.824301], + [-33.180698, 101.851997, 25.3186], + [-11.4565, -37.899399, 150.722], + [-70.236504, 62.836201, -3.9475], + [-28.7302, 91.731102, -24.972601], + [-47.263401, 54.293999, -27.414801], + [-69.167603, 6.5862, -12.7757], + [-28.7302, 49.1003, -48.3596], + [-31.903, 5.692, -47.821999], + [-35.075802, -34.432899, -16.280899], + [-115.284103, 48.681499, 48.684101], + [-110.842796, 28.4821, 49.176201] + ] +} diff --git a/app/images/onboarding-mmi-pin-browser.svg b/app/images/onboarding-mmi-pin-browser.svg new file mode 100644 index 000000000000..93ca2b06b256 --- /dev/null +++ b/app/images/onboarding-mmi-pin-browser.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/token-list-buy-background.png b/app/images/token-list-buy-background.png new file mode 100644 index 000000000000..655669316397 Binary files /dev/null and b/app/images/token-list-buy-background.png differ diff --git a/app/images/token-list-nfts-background.png b/app/images/token-list-nfts-background.png new file mode 100644 index 000000000000..fd07c1c6f335 Binary files /dev/null and b/app/images/token-list-nfts-background.png differ diff --git a/app/images/token-list-receive-background.png b/app/images/token-list-receive-background.png new file mode 100644 index 000000000000..70f0944fc7e0 Binary files /dev/null and b/app/images/token-list-receive-background.png differ diff --git a/app/scripts/background.js b/app/scripts/background.js index 4ae4ecbbce87..469d1cc7ba0b 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -114,9 +114,6 @@ const ONE_SECOND_IN_MILLISECONDS = 1_000; // Timeout for initializing phishing warning page. const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS; -const ACK_KEEP_ALIVE_MESSAGE = 'ACK_KEEP_ALIVE_MESSAGE'; -const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE'; - ///: BEGIN:ONLY_INCLUDE_IN(desktop) const OVERRIDE_ORIGIN = { EXTENSION: 'EXTENSION', @@ -208,6 +205,12 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { connectExternal(...args); }); +function saveTimestamp() { + const timestamp = new Date().toISOString(); + + browser.storage.session.set({ timestamp }); +} + /** * @typedef {import('../../shared/constants/transaction').TransactionMeta} TransactionMeta */ @@ -233,7 +236,6 @@ browser.runtime.onConnectExternal.addListener(async (...args) => { * @property {object} providerConfig - The current selected network provider. * @property {string} providerConfig.rpcUrl - The address for the RPC API, if using an RPC API. * @property {string} providerConfig.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks. - * @property {string} networkId - The stringified number of the current network ID. * @property {string} networkStatus - Either "unknown", "available", "unavailable", or "blocked", depending on the status of the currently selected network. * @property {object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values. * @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string. @@ -279,6 +281,13 @@ async function initialize() { let isFirstMetaMaskControllerSetup; if (isManifestV3) { + // Save the timestamp immediately and then every `SAVE_TIMESTAMP_INTERVAL` + // miliseconds. This keeps the service worker alive. + const SAVE_TIMESTAMP_INTERVAL_MS = 2 * 1000; + + saveTimestamp(); + setInterval(saveTimestamp, SAVE_TIMESTAMP_INTERVAL_MS); + const sessionData = await browser.storage.session.get([ 'isFirstMetaMaskControllerSetup', ]); @@ -300,6 +309,7 @@ async function initialize() { } await sendReadyMessageToTabs(); log.info('MetaMask initialization complete.'); + resolveInitialization(); } catch (error) { rejectInitialization(error); @@ -601,20 +611,6 @@ export function setupController( controller.isClientOpen = true; controller.setupTrustedCommunication(portStream, remotePort.sender); - if (isManifestV3) { - // If we get a WORKER_KEEP_ALIVE message, we respond with an ACK - remotePort.onMessage.addListener((message) => { - if (message.name === WORKER_KEEP_ALIVE_MESSAGE) { - // To test un-comment this line and wait for 1 minute. An error should be shown on MetaMask UI. - remotePort.postMessage({ name: ACK_KEEP_ALIVE_MESSAGE }); - - controller.appStateController.setServiceWorkerLastActiveTime( - Date.now(), - ); - } - }); - } - if (processName === ENVIRONMENT_TYPE_POPUP) { popupIsOpen = true; endOfStream(portStream, () => { @@ -806,9 +802,8 @@ export function setupController( ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation: - controller.approvalController.accept(id, false); - break; case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountRemoval: + case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect: controller.approvalController.accept(id, false); break; ///: END:ONLY_INCLUDE_IN @@ -900,15 +895,18 @@ const addAppInstalledEvent = () => { }; // On first install, open a new tab with MetaMask -browser.runtime.onInstalled.addListener(({ reason }) => { +async function onInstall() { + const storeAlreadyExisted = Boolean(await localStore.get()); + // If the store doesn't exist, then this is the first time running this script, + // and is therefore an install if ( - reason === 'install' && + !storeAlreadyExisted && !(process.env.METAMASK_DEBUG || process.env.IN_TEST) ) { addAppInstalledEvent(); platform.openExtensionInBrowser(); } -}); +} function setupSentryGetStateGlobal(store) { global.stateHooks.getSentryAppState = function () { @@ -917,7 +915,8 @@ function setupSentryGetStateGlobal(store) { }; } -function initBackground() { +async function initBackground() { + await onInstall(); initialize().catch(log.error); } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 1ed6d60a68e5..360c6cd80249 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,12 +1,10 @@ -import pump from 'pump'; import { WindowPostMessageStream } from '@metamask/post-message-stream'; -import ObjectMultiplex from 'obj-multiplex'; -import browser from 'webextension-polyfill'; import PortStream from 'extension-port-stream'; +import ObjectMultiplex from 'obj-multiplex'; +import pump from 'pump'; import { obj as createThoughStream } from 'through2'; -import log from 'loglevel'; - -import { EXTENSION_MESSAGES, MESSAGE_TYPE } from '../../shared/constants/app'; +import browser from 'webextension-polyfill'; +import { EXTENSION_MESSAGES } from '../../shared/constants/app'; import { checkForLastError } from '../../shared/modules/browser-runtime.utils'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; import shouldInjectProvider from '../../shared/modules/provider-injection'; @@ -82,72 +80,6 @@ function injectScript(content) { } } -/** - * SERVICE WORKER LOGIC - */ - -const EXTENSION_CONTEXT_INVALIDATED_CHROMIUM_ERROR = - 'Extension context invalidated.'; - -const WORKER_KEEP_ALIVE_INTERVAL = 1000; -const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE'; -const TIME_45_MIN_IN_MS = 45 * 60 * 1000; - -/** - * Don't run the keep-worker-alive logic for JSON-RPC methods called on initial load. - * This is to prevent the service worker from being kept alive when accounts are not - * connected to the dapp or when the user is not interacting with the extension. - * The keep-alive logic should not work for non-dapp pages. - */ -const IGNORE_INIT_METHODS_FOR_KEEP_ALIVE = [ - MESSAGE_TYPE.GET_PROVIDER_STATE, - MESSAGE_TYPE.SEND_METADATA, -]; - -let keepAliveInterval; -let keepAliveTimer; - -/** - * Sending a message to the extension to receive will keep the service worker alive. - * - * If the extension is unloaded or reloaded during a session and the user attempts to send a - * message to the extension, an "Extension context invalidated." error will be thrown from - * chromium browsers. When this happens, prompt the user to reload the extension. Note: Handling - * this error is not supported in Firefox here. - */ -const sendMessageWorkerKeepAlive = () => { - browser.runtime - .sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }) - .catch((e) => { - e.message === EXTENSION_CONTEXT_INVALIDATED_CHROMIUM_ERROR - ? log.error(`Please refresh the page. MetaMask: ${e}`) - : log.error(`MetaMask: ${e}`); - }); -}; - -/** - * Running this method will ensure the service worker is kept alive for 45 minutes. - * The first message is sent immediately and subsequent messages are sent at an - * interval of WORKER_KEEP_ALIVE_INTERVAL. - */ -const runWorkerKeepAliveInterval = () => { - clearTimeout(keepAliveTimer); - - keepAliveTimer = setTimeout(() => { - clearInterval(keepAliveInterval); - }, TIME_45_MIN_IN_MS); - - clearInterval(keepAliveInterval); - - sendMessageWorkerKeepAlive(); - - keepAliveInterval = setInterval(() => { - if (browser.runtime.id) { - sendMessageWorkerKeepAlive(); - } - }, WORKER_KEEP_ALIVE_INTERVAL); -}; - /** * PHISHING STREAM LOGIC */ @@ -159,10 +91,6 @@ function setupPhishingPageStreams() { target: PHISHING_WARNING_PAGE, }); - if (isManifestV3) { - runWorkerKeepAliveInterval(); - } - // create and connect channel muxers // so we can handle the channels individually phishingPageMux = new ObjectMultiplex(); @@ -299,14 +227,6 @@ const setupPageStreams = () => { target: INPAGE, }); - if (isManifestV3) { - pageStream.on('data', ({ data: { method } }) => { - if (!IGNORE_INIT_METHODS_FOR_KEEP_ALIVE.includes(method)) { - runWorkerKeepAliveInterval(); - } - }); - } - // create and connect channel muxers // so we can handle the channels individually pageMux = new ObjectMultiplex(); @@ -381,14 +301,6 @@ const setupLegacyPageStreams = () => { target: LEGACY_INPAGE, }); - if (isManifestV3) { - legacyPageStream.on('data', ({ data: { method } }) => { - if (!IGNORE_INIT_METHODS_FOR_KEEP_ALIVE.includes(method)) { - runWorkerKeepAliveInterval(); - } - }); - } - legacyPageMux = new ObjectMultiplex(); legacyPageMux.setMaxListeners(25); diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 0d219d751e5b..853a02aa4171 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -62,7 +62,6 @@ export default class AppStateController extends EventEmitter { '0x5': true, '0x539': true, }, - serviceWorkerLastActiveTime: 0, }); this.timer = null; @@ -435,12 +434,6 @@ export default class AppStateController extends EventEmitter { return this.store.getState().currentPopupId; } - setServiceWorkerLastActiveTime(serviceWorkerLastActiveTime) { - this.store.updateState({ - serviceWorkerLastActiveTime, - }); - } - _requestApproval() { this._approvalRequestId = uuid(); diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index 17927cfcfbd2..2ba94dcaebd8 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -217,6 +217,7 @@ describe('DetectTokensController', function () { const tokenListMessenger = new ControllerMessenger().getRestricted({ name: 'TokenListController', + allowedEvents: ['TokenListController:stateChange'], }); tokenListController = new TokenListController({ chainId: toHex(1), @@ -249,6 +250,11 @@ describe('DetectTokensController', function () { networkControllerMessenger, 'NetworkController:stateChange', ), + onTokenListStateChange: (listener) => + tokenListMessenger.subscribe( + `${tokenListController.name}:stateChange`, + listener, + ), }); assetsContractController = new AssetsContractController({ @@ -363,6 +369,7 @@ describe('DetectTokensController', function () { aggregators: undefined, image: undefined, isERC721: undefined, + name: undefined, }, ]); @@ -384,6 +391,7 @@ describe('DetectTokensController', function () { aggregators: undefined, image: undefined, isERC721: undefined, + name: undefined, }, ]); }); @@ -417,6 +425,7 @@ describe('DetectTokensController', function () { aggregators: undefined, image: undefined, isERC721: undefined, + name: undefined, }, ]); const tokenAddressToAdd = erc20ContractAddresses[1]; @@ -435,6 +444,7 @@ describe('DetectTokensController', function () { aggregators: undefined, image: undefined, isERC721: undefined, + name: undefined, }, { address: toChecksumHexAddress(tokenAddressToAdd), @@ -443,6 +453,7 @@ describe('DetectTokensController', function () { aggregators: undefined, image: undefined, isERC721: undefined, + name: undefined, }, ]); }); diff --git a/app/scripts/controllers/ens/ens.js b/app/scripts/controllers/ens/ens.js index 4f8de4a12a1f..62779bccaf8d 100644 --- a/app/scripts/controllers/ens/ens.js +++ b/app/scripts/controllers/ens/ens.js @@ -1,17 +1,18 @@ import { Web3Provider } from '@ethersproject/providers'; import ensNetworkMap from 'ethereum-ens-network-map'; -import { NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP } from '../../../../shared/constants/network'; +import { CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP } from '../../../../shared/constants/network'; export default class Ens { - static getNetworkEnsSupport(network) { - return Boolean(ensNetworkMap[network]); + static getChainEnsSupport(chainId) { + return Boolean(ensNetworkMap[parseInt(chainId, 16).toString()]); } - constructor({ network, provider } = {}) { - const networkName = NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP[network]; - const ensAddress = ensNetworkMap[network]; + constructor({ chainId, provider } = {}) { + const networkName = CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP[chainId]; + const chainIdInt = parseInt(chainId, 16); + const ensAddress = ensNetworkMap[chainIdInt.toString()]; const ethProvider = new Web3Provider(provider, { - chainId: parseInt(network, 10), + chainId: chainIdInt, name: networkName, ensAddress, }); diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js index 47363a1562e6..efea49e85567 100644 --- a/app/scripts/controllers/ens/index.js +++ b/app/scripts/controllers/ens/index.js @@ -1,7 +1,6 @@ import punycode from 'punycode/punycode'; import { ObservableStore } from '@metamask/obs-store'; import log from 'loglevel'; -import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../../../shared/constants/network'; import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; import Ens from './ens'; @@ -17,10 +16,9 @@ export default class EnsController { this._ens = ens; if (!this._ens) { const chainId = getCurrentChainId(); - const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId]; - if (Ens.getNetworkEnsSupport(network)) { + if (Ens.getChainEnsSupport(chainId)) { this._ens = new Ens({ - network, + chainId, provider, }); } @@ -35,10 +33,9 @@ export default class EnsController { onNetworkDidChange(() => { this.store.putState(initState); const chainId = getCurrentChainId(); - const network = CHAIN_ID_TO_NETWORK_ID_MAP[chainId]; - if (Ens.getNetworkEnsSupport(network)) { + if (Ens.getChainEnsSupport(chainId)) { this._ens = new Ens({ - network, + chainId, provider, }); } else { diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index a72e64219df4..64a3f927a1ff 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -194,7 +194,7 @@ export default class MetaMetricsController { // tracked if the event isn't progressed within that amount of time. if (isManifestV3) { /* eslint-disable no-undef */ - this.extension.alarms.getAll((alarms) => { + this.extension.alarms.getAll().then((alarms) => { const hasAlarm = checkAlarmExists( alarms, METAMETRICS_FINALIZE_EVENT_FRAGMENT_ALARM, @@ -756,6 +756,13 @@ export default class MetaMetricsController { : null; ///: END:ONLY_INCLUDE_IN const { traits, previousUserTraits } = this.store.getState(); + let securityProvider; + if (metamaskState.securityAlertsEnabled) { + securityProvider = 'blockaid'; + } + if (metamaskState.transactionSecurityCheckEnabled) { + securityProvider = 'opensea'; + } /** @type {MetaMetricsTraits} */ const currentTraits = { [MetaMetricsUserTrait.AddressBookEntries]: sum( @@ -799,8 +806,9 @@ export default class MetaMetricsController { [MetaMetricsUserTrait.MmiAccountAddress]: mmiAccountAddress, [MetaMetricsUserTrait.MmiIsCustodian]: Boolean(mmiAccountAddress), ///: END:ONLY_INCLUDE_IN - [MetaMetricsUserTrait.SecurityProviders]: - metamaskState.transactionSecurityCheckEnabled ? ['opensea'] : [], + [MetaMetricsUserTrait.SecurityProviders]: securityProvider + ? [securityProvider] + : [], ///: BEGIN:ONLY_INCLUDE_IN(petnames) [MetaMetricsUserTrait.PetnameAddressCount]: this._getPetnameAddressCount(metamaskState), diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 3532e3b15de3..6579f1fb7766 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -1025,6 +1025,7 @@ describe('MetaMetricsController', function () { ledgerTransportType: 'web-hid', openSeaEnabled: true, useNftDetection: false, + securityAlertsEnabled: true, theme: 'default', useTokenDetection: true, desktopEnabled: false, @@ -1083,7 +1084,7 @@ describe('MetaMetricsController', function () { [MetaMetricsUserTrait.Theme]: 'default', [MetaMetricsUserTrait.TokenDetectionEnabled]: true, [MetaMetricsUserTrait.DesktopEnabled]: false, - [MetaMetricsUserTrait.SecurityProviders]: [], + [MetaMetricsUserTrait.SecurityProviders]: ['blockaid'], ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) [MetaMetricsUserTrait.MmiExtensionId]: 'testid', [MetaMetricsUserTrait.MmiAccountAddress]: null, diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index d6e980e1be95..04affcf2c5be 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -1,7 +1,6 @@ import EventEmitter from 'events'; import log from 'loglevel'; import { captureException } from '@sentry/browser'; -import { isEqual } from 'lodash'; import { CUSTODIAN_TYPES } from '@metamask-institutional/custody-keyring'; import { updateCustodianTransactions, @@ -11,17 +10,10 @@ import { REFRESH_TOKEN_CHANGE_EVENT, INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, } from '@metamask-institutional/sdk'; -import { - handleMmiPortfolio, - setDashboardCookie, -} from '@metamask-institutional/portfolio-dashboard'; +import { handleMmiPortfolio } 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 { CONNECT_HARDWARE_ROUTE } from '../../../ui/helpers/constants/routes'; import { getPermissionBackgroundApiMethods } from './permissions'; export default class MMIController extends EventEmitter { @@ -63,17 +55,6 @@ export default class MMIController extends EventEmitter { }); } - 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()), - ); - this.signatureController.hub.on( 'personal_sign:signed', async ({ signature, messageId }) => { @@ -461,8 +442,8 @@ export default class MMIController extends EventEmitter { } // Based on a custodian name, get all the tokens associated with that custodian - async getCustodianJWTList(custodianName) { - console.log('getCustodianJWTList', custodianName); + async getCustodianJWTList(custodianEnvName) { + console.log('getCustodianJWTList', custodianEnvName); const { identities } = this.preferencesController.store.getState(); @@ -474,7 +455,9 @@ export default class MMIController extends EventEmitter { const { custodians } = mmiConfiguration; - const custodian = custodians.find((item) => item.name === custodianName); + const custodian = custodians.find( + (item) => item.envName === custodianEnvName, + ); if (!custodian) { return []; @@ -499,9 +482,11 @@ export default class MMIController extends EventEmitter { if ( !custodyAccountDetails || - custodyAccountDetails.custodianName !== custodianName + custodyAccountDetails.custodianName !== custodianEnvName ) { - log.debug(`${address} does not belong to ${custodianName} keyring`); + log.debug( + `${address} does not belong to ${custodianEnvName} keyring`, + ); continue; } @@ -584,20 +569,6 @@ export default class MMIController extends EventEmitter { }); } - 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 newUnsignedMessage(msgParams, req, version) { // The code path triggered by deferSetAsSigned: true is for custodial accounts const accountDetails = this.custodyController.getAccountDetails( @@ -673,12 +644,6 @@ export default class MMIController extends EventEmitter { return true; } - async handleMmiOpenSwaps(origin, address, chainId) { - await this.setAccountAndNetwork(origin, address, chainId); - this.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE); - return true; - } - async handleMmiOpenAddHardwareWallet() { await this.appStateController.getUnlockPromise(true); this.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE); diff --git a/app/scripts/controllers/mmi-controller.test.js b/app/scripts/controllers/mmi-controller.test.js index f8c06d62981d..7adc37044a34 100644 --- a/app/scripts/controllers/mmi-controller.test.js +++ b/app/scripts/controllers/mmi-controller.test.js @@ -41,7 +41,6 @@ describe('MMIController', function () { type: 'rinkeby', }, getCurrentChainId: jest.fn(), - getNetworkId: jest.fn(), onNetworkStateChange: jest.fn(), }), signatureController: new SignatureController({ diff --git a/app/scripts/controllers/permissions/specifications.test.js b/app/scripts/controllers/permissions/specifications.test.js index 892ee3cadaec..eb64a6a4c7cf 100644 --- a/app/scripts/controllers/permissions/specifications.test.js +++ b/app/scripts/controllers/permissions/specifications.test.js @@ -16,7 +16,7 @@ describe('PermissionController specifications', () => { describe('caveat specifications', () => { it('getCaveatSpecifications returns the expected specifications object', () => { const caveatSpecifications = getCaveatSpecifications({}); - expect(Object.keys(caveatSpecifications)).toHaveLength(8); + expect(Object.keys(caveatSpecifications)).toHaveLength(9); expect( caveatSpecifications[CaveatTypes.restrictReturnedAccounts].type, ).toStrictEqual(CaveatTypes.restrictReturnedAccounts); @@ -42,6 +42,9 @@ describe('PermissionController specifications', () => { expect(caveatSpecifications.snapIds.type).toStrictEqual( SnapCaveatType.SnapIds, ); + expect(caveatSpecifications.keyringOrigin.type).toStrictEqual( + SnapCaveatType.KeyringOrigin, + ); }); describe('restrictReturnedAccounts', () => { diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index ac209b576480..908e3d985855 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -106,6 +106,9 @@ export default class PreferencesController { snapsAddSnapAccountModalDismissed: false, ///: END:ONLY_INCLUDE_IN isLineaMainnetReleased: false, + ///: BEGIN:ONLY_INCLUDE_IN(petnames) + useExternalNameSources: true, + ///: END:ONLY_INCLUDE_IN ...opts.initState, }; @@ -261,6 +264,19 @@ export default class PreferencesController { } ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(petnames) + /** + * Setter for the `useExternalNameSources` property + * + * @param {boolean} useExternalNameSources - Whether or not to use external name providers in the name controller. + */ + setUseExternalNameSources(useExternalNameSources) { + this.store.updateState({ + useExternalNameSources, + }); + } + ///: END:ONLY_INCLUDE_IN + /** * Setter for the `advancedGasFee` property * diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index 106871aa5d71..a46730c8c9ab 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -419,4 +419,21 @@ describe('preferences controller', () => { }); }); }); + + ///: BEGIN:ONLY_INCLUDE_IN(petnames) + describe('setUseExternalNameSources', () => { + it('should default to true', () => { + expect( + preferencesController.store.getState().useExternalNameSources, + ).toStrictEqual(true); + }); + + it('should set the useExternalNameSources property in state', () => { + preferencesController.setUseExternalNameSources(false); + expect( + preferencesController.store.getState().useExternalNameSources, + ).toStrictEqual(false); + }); + }); + ///: END:ONLY_INCLUDE_IN }); diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 1e9fe2c44eb6..775a02506517 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -155,17 +155,20 @@ export default class SwapsController { this.indexOfNewestCallInFlight = 0; this.ethersProvider = new Web3Provider(provider); - this._currentNetworkId = networkController.state.networkId; + this._currentChainId = networkController.state.providerConfig.chainId; onNetworkStateChange(() => { - const { networkId, networksMetadata, selectedNetworkClientId } = - networkController.state; + const { + networksMetadata, + selectedNetworkClientId, + providerConfig: { chainId }, + } = networkController.state; const selectedNetworkStatus = networksMetadata[selectedNetworkClientId]?.status; if ( selectedNetworkStatus === NetworkStatus.Available && - networkId !== this._currentNetworkId + chainId !== this._currentChainId ) { - this._currentNetworkId = networkId; + this._currentChainId = chainId; this.ethersProvider = new Web3Provider(provider); } }); diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index 56f5f5614758..44245fcb28db 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -5,11 +5,7 @@ import { BigNumber } from '@ethersproject/bignumber'; import { mapValues } from 'lodash'; import BigNumberjs from 'bignumber.js'; import { NetworkType } from '@metamask/controller-utils'; -import { - CHAIN_IDS, - NETWORK_IDS, - NetworkStatus, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, NetworkStatus } from '../../../shared/constants/network'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import { createTestProviderTools } from '../../../test/stub/provider'; import { SECOND } from '../../../shared/constants/time'; @@ -102,7 +98,6 @@ const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({ function getMockNetworkController() { return { state: { - networkId: NETWORK_IDS.GOERLI, selectedNetworkClientId: NetworkType.goerli, networksMetadata: { [NetworkType.goerli]: { @@ -110,6 +105,9 @@ function getMockNetworkController() { status: NetworkStatus.Available, }, }, + providerConfig: { + chainId: CHAIN_IDS.GOERLI, + }, }, }; } @@ -230,7 +228,6 @@ describe('SwapsController', function () { const currentEthersInstance = swapsController.ethersProvider; networkController.state = { - networkId: NETWORK_IDS.MAINNET, selectedNetworkClientId: NetworkType.mainnet, networksMetadata: { [NetworkType.mainnet]: { @@ -238,6 +235,9 @@ describe('SwapsController', function () { status: NetworkStatus.Available, }, }, + providerConfig: { + chainId: CHAIN_IDS.MAINNET, + }, }; networkStateChangeListener(); @@ -268,7 +268,6 @@ describe('SwapsController', function () { const currentEthersInstance = swapsController.ethersProvider; networkController.state = { - networkId: null, selectedNetworkClientId: NetworkType.goerli, networksMetadata: { [NetworkType.goerli]: { @@ -276,6 +275,9 @@ describe('SwapsController', function () { status: NetworkStatus.Unknown, }, }, + providerConfig: { + chainId: CHAIN_IDS.GOERLI, + }, }; networkStateChangeListener(); @@ -306,7 +308,6 @@ describe('SwapsController', function () { const currentEthersInstance = swapsController.ethersProvider; networkController.state = { - networkId: NETWORK_IDS.GOERLI, selectedNetworkClientId: NetworkType.goerli, networksMetadata: { [NetworkType.goerli]: { @@ -314,6 +315,9 @@ describe('SwapsController', function () { status: NetworkStatus.Available, }, }, + providerConfig: { + chainId: CHAIN_IDS.GOERLI, + }, }; networkStateChangeListener(); @@ -733,14 +737,14 @@ describe('SwapsController', function () { gasEstimateWithRefund: '0xb8cae', savings: { fee: '-0.061067', - metaMaskFee: '0.5050505050505050505', + metaMaskFee: '0.50505050505050505050505050505050505', performance: '6', - total: '5.4338824949494949495', - medianMetaMaskFee: '0.44444444444444444444', + total: '5.43388249494949494949494949494949495', + medianMetaMaskFee: '0.444444444444444444444444444444444444', }, ethFee: '0.113536', overallValueOfQuote: '49.886464', - metaMaskFeeInEth: '0.5050505050505050505', + metaMaskFeeInEth: '0.50505050505050505050505050505050505', ethValueOfTokens: '50', }); assert.strictEqual( @@ -798,15 +802,15 @@ describe('SwapsController', function () { gasEstimateWithRefund: '0xb8cae', savings: { fee: '-0.061067', - metaMaskFee: '0.5050505050505050505', + metaMaskFee: '0.50505050505050505050505050505050505', performance: '6', - total: '5.4338824949494949495', - medianMetaMaskFee: '0.44444444444444444444', + total: '5.43388249494949494949494949494949495', + medianMetaMaskFee: '0.444444444444444444444444444444444444', }, ethFee: '0.113822', multiLayerL1TradeFeeTotal: '0x0103c18816d4e8', overallValueOfQuote: '49.886178', - metaMaskFeeInEth: '0.5050505050505050505', + metaMaskFeeInEth: '0.50505050505050505050505050505050505', ethValueOfTokens: '50', }); assert.strictEqual( diff --git a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts index ee53f04d3741..d850616b2e06 100644 --- a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts +++ b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts @@ -98,7 +98,6 @@ const EXPECTED_NORMALISED_TRANSACTION_BASE = { chainId: undefined, hash: ETHERSCAN_TRANSACTION_SUCCESS_MOCK.hash, id: ID_MOCK, - metamaskNetworkId: undefined, status: TransactionStatus.confirmed, time: 1543596356000, txParams: { @@ -158,7 +157,6 @@ describe('EtherscanRemoteTransactionSource', () => { expect( new EtherscanRemoteTransactionSource().isSupportedNetwork( CHAIN_IDS.MAINNET, - '1', ), ).toBe(true); }); @@ -167,7 +165,6 @@ describe('EtherscanRemoteTransactionSource', () => { expect( new EtherscanRemoteTransactionSource().isSupportedNetwork( CHAIN_IDS.LOCALHOST, - '1', ), ).toBe(false); }); diff --git a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts index 0fe179d59b6c..f0ac784c060b 100644 --- a/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts +++ b/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts @@ -43,7 +43,7 @@ export class EtherscanRemoteTransactionSource this.#includeTokenTransfers = includeTokenTransfers ?? true; } - isSupportedNetwork(chainId: Hex, _networkId: string): boolean { + isSupportedNetwork(chainId: Hex): boolean { return Object.keys(ETHERSCAN_SUPPORTED_NETWORKS).includes(chainId); } @@ -68,19 +68,11 @@ export class EtherscanRemoteTransactionSource await Promise.all([transactionPromise, tokenTransactionPromise]); const transactions = etherscanTransactions.result.map((tx) => - this.#normalizeTransaction( - tx, - request.currentNetworkId, - request.currentChainId, - ), + this.#normalizeTransaction(tx, request.currentChainId), ); const tokenTransactions = etherscanTokenTransactions.result.map((tx) => - this.#normalizeTokenTransaction( - tx, - request.currentNetworkId, - request.currentChainId, - ), + this.#normalizeTokenTransaction(tx, request.currentChainId), ); return [...transactions, ...tokenTransactions]; @@ -88,14 +80,9 @@ export class EtherscanRemoteTransactionSource #normalizeTransaction( txMeta: EtherscanTransactionMeta, - currentNetworkId: string, currentChainId: Hex, ): TransactionMeta { - const base = this.#normalizeTransactionBase( - txMeta, - currentNetworkId, - currentChainId, - ); + const base = this.#normalizeTransactionBase(txMeta, currentChainId); return { ...base, @@ -113,14 +100,9 @@ export class EtherscanRemoteTransactionSource #normalizeTokenTransaction( txMeta: EtherscanTokenTransactionMeta, - currentNetworkId: string, currentChainId: Hex, ): TransactionMeta { - const base = this.#normalizeTransactionBase( - txMeta, - currentNetworkId, - currentChainId, - ); + const base = this.#normalizeTransactionBase(txMeta, currentChainId); return { ...base, @@ -129,7 +111,6 @@ export class EtherscanRemoteTransactionSource #normalizeTransactionBase( txMeta: EtherscanTransactionMetaBase, - currentNetworkId: string, currentChainId: Hex, ): TransactionMeta { const time = parseInt(txMeta.timeStamp, 10) * 1000; @@ -139,7 +120,6 @@ export class EtherscanRemoteTransactionSource chainId: currentChainId, hash: txMeta.hash, id: uuid(), - metamaskNetworkId: currentNetworkId, status: TransactionStatus.confirmed, time, txParams: { diff --git a/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts b/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts index f20675215885..b04977b74157 100644 --- a/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts +++ b/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts @@ -19,7 +19,6 @@ const NETWORK_STATE_MOCK: NetworkState = { chainId: '0x1', type: NetworkType.mainnet, }, - networkId: '1', } as unknown as NetworkState; const ADDERSS_MOCK = '0x1'; @@ -139,7 +138,6 @@ describe('IncomingTransactionHelper', () => { expect(remoteTransactionSource.fetchTransactions).toHaveBeenCalledWith({ address: ADDERSS_MOCK, currentChainId: NETWORK_STATE_MOCK.providerConfig.chainId, - currentNetworkId: NETWORK_STATE_MOCK.networkId, fromBlock: expect.any(Number), limit: CONTROLLER_ARGS_MOCK.transactionLimit, }); diff --git a/app/scripts/controllers/transactions/IncomingTransactionHelper.ts b/app/scripts/controllers/transactions/IncomingTransactionHelper.ts index 3acb6372637f..9cad8e4d0948 100644 --- a/app/scripts/controllers/transactions/IncomingTransactionHelper.ts +++ b/app/scripts/controllers/transactions/IncomingTransactionHelper.ts @@ -117,7 +117,6 @@ export class IncomingTransactionHelper { const fromBlock = this.#getFromBlock(latestBlockNumber); const address = this.#getCurrentAccount(); const currentChainId = this.#getCurrentChainId(); - const currentNetworkId = this.#getCurrentNetworkId(); let remoteTransactions = []; @@ -126,7 +125,6 @@ export class IncomingTransactionHelper { await this.#remoteTransactionSource.fetchTransactions({ address, currentChainId, - currentNetworkId, fromBlock, limit: this.#transactionLimit, }); @@ -262,12 +260,9 @@ export class IncomingTransactionHelper { #canStart(): boolean { const isEnabled = this.#isEnabled(); const currentChainId = this.#getCurrentChainId(); - const currentNetworkId = this.#getCurrentNetworkId(); - const isSupportedNetwork = this.#remoteTransactionSource.isSupportedNetwork( - currentChainId, - currentNetworkId, - ); + const isSupportedNetwork = + this.#remoteTransactionSource.isSupportedNetwork(currentChainId); return isEnabled && isSupportedNetwork; } @@ -275,8 +270,4 @@ export class IncomingTransactionHelper { #getCurrentChainId(): Hex { return this.#getNetworkState().providerConfig.chainId; } - - #getCurrentNetworkId(): string { - return this.#getNetworkState().networkId as string; - } } diff --git a/app/scripts/controllers/transactions/README.md b/app/scripts/controllers/transactions/README.md index 09ba59e806e3..c872bf951963 100644 --- a/app/scripts/controllers/transactions/README.md +++ b/app/scripts/controllers/transactions/README.md @@ -30,7 +30,8 @@ txMeta = { "id": 2828415030114568, // unique id for this txMeta used for look ups "time": 1524094064821, // time of creation "status": "confirmed", - "metamaskNetworkId": "1524091532133", //the network id for the transaction + "chainId": "0x1", // the chain id for the transaction + "metamaskNetworkId": "1524091532133", // Deprecated: the network id for the transaction. Use chainId instead "loadingDefaults": false, // used to tell the ui when we are done calculating gas defaults "txParams": { // the txParams object "from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", @@ -45,6 +46,7 @@ txMeta = { "id": 2828415030114568, "time": 1524094064821, "status": "unapproved", + "chainId": "0x1", "metamaskNetworkId": "1524091532133", "loadingDefaults": true, "txParams": { diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 5aee2965ea6c..81e16ec5e78a 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -20,9 +20,7 @@ import { import { TransactionStatus, TransactionType, - TokenStandard, TransactionEnvelopeType, - TransactionMetaMetricsEvent, TransactionApprovalAmountType, } from '../../../../shared/constants/transaction'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; @@ -50,22 +48,14 @@ import { NetworkStatus, } from '../../../../shared/constants/network'; import { - determineTransactionAssetType, determineTransactionContractCode, determineTransactionType, isEIP1559Transaction, } from '../../../../shared/modules/transaction.utils'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; -///: BEGIN:ONLY_INCLUDE_IN(blockaid) -import { - BlockaidReason, - BlockaidResultType, -} from '../../../../shared/constants/security-provider'; -///: END:ONLY_INCLUDE_IN import { calcGasTotal, getSwapsTokensReceivedFromTxMeta, - TRANSACTION_ENVELOPE_TYPE_NAMES, } from '../../../../shared/lib/transactions-controller-utils'; import { Numeric } from '../../../../shared/modules/Numeric'; import TransactionStateManager from './tx-state-manager'; @@ -98,8 +88,6 @@ const VALID_UNAPPROVED_TRANSACTION_TYPES = [ * @typedef {import('../../../../shared/constants/gas').TxGasFees} TxGasFees */ -const METRICS_STATUS_FAILED = 'failed on-chain'; - /** * @typedef {object} CustomGasSettings * @property {string} [gas] - The gas limit to use for the transaction @@ -127,7 +115,6 @@ const METRICS_STATUS_FAILED = 'failed on-chain'; * * @param {object} opts * @param {object} opts.initState - initial transaction list default is an empty array - * @param {Function} opts.getNetworkId - Get the current network ID. * @param {Function} opts.getNetworkStatus - Get the current network status. * @param {Function} opts.getNetworkState - Get the network state. * @param {Function} opts.onNetworkStateChange - Subscribe to network state change events. @@ -144,7 +131,6 @@ const METRICS_STATUS_FAILED = 'failed on-chain'; export default class TransactionController extends EventEmitter { constructor(opts) { super(); - this.getNetworkId = opts.getNetworkId; this.getNetworkStatus = opts.getNetworkStatus; this._getNetworkState = opts.getNetworkState; this._getCurrentChainId = opts.getCurrentChainId; @@ -159,17 +145,13 @@ export default class TransactionController extends EventEmitter { this.blockTracker = opts.blockTracker; this.signEthTx = opts.signTransaction; this.inProcessOfSigning = new Set(); - this._trackMetaMetricsEvent = opts.trackMetaMetricsEvent; this._getParticipateInMetrics = opts.getParticipateInMetrics; this._getEIP1559GasFeeEstimates = opts.getEIP1559GasFeeEstimates; - this.createEventFragment = opts.createEventFragment; - this.updateEventFragment = opts.updateEventFragment; - this.finalizeEventFragment = opts.finalizeEventFragment; - this.getEventFragmentById = opts.getEventFragmentById; - this.getDeviceModel = opts.getDeviceModel; - this.getAccountType = opts.getAccountType; - this.getTokenStandardAndDetails = opts.getTokenStandardAndDetails; this.securityProviderRequest = opts.securityProviderRequest; + this.getSelectedAddress = opts.getSelectedAddress; + this.getAccountType = opts.getAccountType; + this.getDeviceModel = opts.getDeviceModel; + this.snapAndHardwareMessenger = opts.snapAndHardwareMessenger; this.messagingSystem = opts.messenger; this._hasCompletedOnboarding = opts.hasCompletedOnboarding; @@ -186,7 +168,6 @@ export default class TransactionController extends EventEmitter { this.txStateManager = new TransactionStateManager({ initState: opts.initState, txHistoryLimit: opts.txHistoryLimit, - getNetworkId: this.getNetworkId, getNetworkStatus: this.getNetworkStatus, getCurrentChainId: opts.getCurrentChainId, }); @@ -345,7 +326,7 @@ export default class TransactionController extends EventEmitter { this._requestTransactionApproval(txMeta, { shouldShowRequest: false, }).catch((error) => { - if (error.code === errorCodes.provider.userRejectedRequest) { + if (error?.code === errorCodes.provider.userRejectedRequest) { return; } log.error('Error during persisted transaction approval', error); @@ -724,25 +705,9 @@ export default class TransactionController extends EventEmitter { this.txStateManager.setTxStatusConfirmed(txId); this._markNonceDuplicatesDropped(txId); - const { submittedTime } = txMeta; - const metricsParams = { gas_used: gasUsed }; - - if (submittedTime) { - metricsParams.completion_time = - this._getTransactionCompletionTime(submittedTime); - } - - if (txReceipt.status === '0x0') { - metricsParams.status = METRICS_STATUS_FAILED; - // metricsParams.error = TODO: figure out a way to get the on-chain failure reason - } - - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - undefined, - metricsParams, - ); + this.emit('transaction-finalized', { + transactionMeta: txMeta, + }); this.txStateManager.updateTransaction( txMeta, @@ -773,28 +738,6 @@ export default class TransactionController extends EventEmitter { this.txStateManager.updateTransaction(txMeta, 'transactions#setTxHash'); } - /** - * Convenience method for the UI to easily create event fragments when the - * fragment does not exist in state. - * - * @param {number} transactionId - The transaction id to create the event - * fragment for - * @param {valueOf} event - event type to create - * @param {string} actionId - actionId passed from UI - */ - async createTransactionEventFragment(transactionId, event, actionId) { - const txMeta = this.txStateManager.getTransaction(transactionId); - const { properties, sensitiveProperties } = - await this._buildEventFragmentProperties(txMeta); - this._createTransactionEventFragment( - txMeta, - event, - properties, - sensitiveProperties, - actionId, - ); - } - startIncomingTransactionPolling() { this.incomingTransactionHelper.start(); } @@ -874,26 +817,13 @@ export default class TransactionController extends EventEmitter { // For 'rpc' we need to use the same basic configuration as mainnet, since // we only support EVM compatible chains, and then override the - // name, chainId and networkId properties. This is done using the + // name, and chainId properties. This is done using the // `forCustomChain` static method on the Common class. const chainId = parseInt(this._getCurrentChainId(), 16); - const networkStatus = this.getNetworkStatus(); - const networkId = this.getNetworkId(); return Common.custom({ name, chainId, - // It is improbable for a transaction to be signed while the network - // is loading for two reasons. - // 1. Pending, unconfirmed transactions are wiped on network change - // 2. The UI is unusable (loading indicator) when network is loading. - // setting the networkId to 0 is for type safety and to explicity lead - // the transaction to failing if a user is able to get to this branch - // on a custom network that requires valid network id. I have not ran - // into this limitation on any network I have attempted, even when - // hardcoding networkId to 'loading'. - networkId: - networkStatus === NetworkStatus.Available ? parseInt(networkId, 10) : 0, hardfork, }); } @@ -968,25 +898,9 @@ export default class TransactionController extends EventEmitter { this.txStateManager.setTxStatusConfirmed(txId); this._markNonceDuplicatesDropped(txId); - const { submittedTime } = txMeta; - const metricsParams = { gas_used: gasUsed }; - - if (submittedTime) { - metricsParams.completion_time = - this._getTransactionCompletionTime(submittedTime); - } - - if (txReceipt.status === '0x0') { - metricsParams.status = METRICS_STATUS_FAILED; - // metricsParams.error = TODO: figure out a way to get the on-chain failure reason - } - - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - undefined, - metricsParams, - ); + this.emit('transaction-finalized', { + transactionMeta: txMeta, + }); this.txStateManager.updateTransaction( txMeta, @@ -1313,8 +1227,8 @@ export default class TransactionController extends EventEmitter { * * @param {number} txId - the tx's Id * @param {string} rawTx - the hex string of the serialized signed transaction + * @param {string} actionId - actionId passed from UI * @returns {Promise} - * @param {number} actionId - actionId passed from UI */ async _publishTransaction(txId, rawTx, actionId) { const txMeta = this.txStateManager.getTransaction(txId); @@ -1342,11 +1256,10 @@ export default class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.submitted, + this.emit('transaction-submitted', { + transactionMeta: txMeta, actionId, - ); + }); } /** @@ -1794,7 +1707,9 @@ export default class TransactionController extends EventEmitter { ), ); case TransactionStatus.failed: - throw cleanErrorStack(ethErrors.rpc.internal(finalTxMeta.err.message)); + throw cleanErrorStack( + ethErrors.rpc.internal(finalTxMeta.error.message), + ); default: throw cleanErrorStack( ethErrors.rpc.internal( @@ -1868,11 +1783,10 @@ export default class TransactionController extends EventEmitter { // sign transaction const rawTx = await this._signTransaction(txId); await this._publishTransaction(txId, rawTx, actionId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, + this.emit('transaction-approved', { + transactionMeta: txMeta, actionId, - ); + }); // must set transaction to submitted/failed before releasing lock nonceLock.releaseLock(); } catch (err) { @@ -1903,11 +1817,10 @@ export default class TransactionController extends EventEmitter { async _cancelTransaction(txId, actionId) { const txMeta = this.txStateManager.getTransaction(txId); this.txStateManager.setTxStatusRejected(txId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.rejected, + this.emit('transaction-rejected', { + transactionMeta: txMeta, actionId, - ); + }); } /** maps methods for convenience*/ @@ -2171,7 +2084,7 @@ export default class TransactionController extends EventEmitter { _trackSwapsMetrics(txMeta, approvalTxMeta) { if (this._getParticipateInMetrics() && txMeta.swapMetaData) { if (txMeta.txReceipt.status === '0x0') { - this._trackMetaMetricsEvent({ + this.emit('transaction-swap-failed', { event: 'Swap Failed', sensitiveProperties: { ...txMeta.swapMetaData }, category: MetaMetricsEventCategory.Swaps, @@ -2207,7 +2120,7 @@ export default class TransactionController extends EventEmitter { approvalTxMeta, ); - this._trackMetaMetricsEvent({ + this.emit('transaction-swap-finalized', { event: MetaMetricsEventName.SwapCompleted, category: MetaMetricsEventCategory.Swaps, sensitiveProperties: { @@ -2257,483 +2170,6 @@ export default class TransactionController extends EventEmitter { return null; } - /** - * The allowance amount in relation to the balance for that specific token - * - * @param {string} transactionApprovalAmountType - The transaction approval amount type - * @param {string} dappProposedTokenAmount - The dapp proposed token amount - * @param {string} currentTokenBalance - The balance of the token that is being send - */ - _allowanceAmountInRelationToTokenBalance( - transactionApprovalAmountType, - dappProposedTokenAmount, - currentTokenBalance, - ) { - if ( - (transactionApprovalAmountType === TransactionApprovalAmountType.custom || - transactionApprovalAmountType === - TransactionApprovalAmountType.dappProposed) && - dappProposedTokenAmount && - currentTokenBalance - ) { - return `${new BigNumber(dappProposedTokenAmount, 16) - .div(currentTokenBalance, 10) - .times(100) - .round(2)}`; - } - return null; - } - - async _buildEventFragmentProperties(txMeta, extraParams) { - const { - type, - time, - status, - chainId, - origin: referrer, - txParams: { - gasPrice, - gas: gasLimit, - maxFeePerGas, - maxPriorityFeePerGas, - estimateSuggested, - estimateUsed, - }, - defaultGasEstimates, - originalType, - replacedById, - metamaskNetworkId: network, - customTokenAmount, - dappProposedTokenAmount, - currentTokenBalance, - originalApprovalAmount, - finalApprovalAmount, - contractMethodName, - securityProviderResponse, - ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - securityAlertResponse, - ///: END:ONLY_INCLUDE_IN - } = txMeta; - - const source = referrer === ORIGIN_METAMASK ? 'user' : 'dapp'; - - const { assetType, tokenStandard } = await determineTransactionAssetType( - txMeta, - this.query, - this.getTokenStandardAndDetails, - ); - - const gasParams = {}; - - if (isEIP1559Transaction(txMeta)) { - gasParams.max_fee_per_gas = maxFeePerGas; - gasParams.max_priority_fee_per_gas = maxPriorityFeePerGas; - } else { - gasParams.gas_price = gasPrice; - } - - if (defaultGasEstimates) { - const { estimateType } = defaultGasEstimates; - if (estimateType) { - gasParams.default_estimate = estimateType; - let defaultMaxFeePerGas = txMeta.defaultGasEstimates.maxFeePerGas; - let defaultMaxPriorityFeePerGas = - txMeta.defaultGasEstimates.maxPriorityFeePerGas; - - if ( - [ - GasRecommendations.low, - GasRecommendations.medium, - GasRecommendations.high, - ].includes(estimateType) - ) { - const { gasFeeEstimates } = await this._getEIP1559GasFeeEstimates(); - if (gasFeeEstimates?.[estimateType]?.suggestedMaxFeePerGas) { - defaultMaxFeePerGas = - gasFeeEstimates[estimateType]?.suggestedMaxFeePerGas; - gasParams.default_max_fee_per_gas = defaultMaxFeePerGas; - } - if (gasFeeEstimates?.[estimateType]?.suggestedMaxPriorityFeePerGas) { - defaultMaxPriorityFeePerGas = - gasFeeEstimates[estimateType]?.suggestedMaxPriorityFeePerGas; - gasParams.default_max_priority_fee_per_gas = - defaultMaxPriorityFeePerGas; - } - } - } - - if (txMeta.defaultGasEstimates.gas) { - gasParams.default_gas = txMeta.defaultGasEstimates.gas; - } - if (txMeta.defaultGasEstimates.gasPrice) { - gasParams.default_gas_price = txMeta.defaultGasEstimates.gasPrice; - } - } - - if (estimateSuggested) { - gasParams.estimate_suggested = estimateSuggested; - } - - if (estimateUsed) { - gasParams.estimate_used = estimateUsed; - } - - if (extraParams?.gas_used) { - gasParams.gas_used = extraParams.gas_used; - } - - const gasParamsInGwei = this._getGasValuesInGWEI(gasParams); - - let eip1559Version = '0'; - if (txMeta.txParams.maxFeePerGas) { - eip1559Version = '2'; - } - - const contractInteractionTypes = [ - TransactionType.contractInteraction, - TransactionType.tokenMethodApprove, - TransactionType.tokenMethodSafeTransferFrom, - TransactionType.tokenMethodSetApprovalForAll, - TransactionType.tokenMethodTransfer, - TransactionType.tokenMethodTransferFrom, - TransactionType.smart, - TransactionType.swap, - TransactionType.swapApproval, - ].includes(type); - - const contractMethodNames = { - APPROVE: 'Approve', - }; - - let transactionApprovalAmountType; - let transactionContractMethod; - let transactionApprovalAmountVsProposedRatio; - let transactionApprovalAmountVsBalanceRatio; - let transactionType = TransactionType.simpleSend; - if (type === TransactionType.cancel) { - transactionType = TransactionType.cancel; - } else if (type === TransactionType.retry) { - transactionType = originalType; - } else if (type === TransactionType.deployContract) { - transactionType = TransactionType.deployContract; - } else if (contractInteractionTypes) { - transactionType = TransactionType.contractInteraction; - transactionContractMethod = contractMethodName; - if ( - transactionContractMethod === contractMethodNames.APPROVE && - tokenStandard === TokenStandard.ERC20 - ) { - if (dappProposedTokenAmount === '0' || customTokenAmount === '0') { - transactionApprovalAmountType = TransactionApprovalAmountType.revoke; - } else if ( - customTokenAmount && - customTokenAmount !== dappProposedTokenAmount - ) { - transactionApprovalAmountType = TransactionApprovalAmountType.custom; - } else if (dappProposedTokenAmount) { - transactionApprovalAmountType = - TransactionApprovalAmountType.dappProposed; - } - transactionApprovalAmountVsProposedRatio = - this._allowanceAmountInRelationToDappProposedValue( - transactionApprovalAmountType, - originalApprovalAmount, - finalApprovalAmount, - ); - transactionApprovalAmountVsBalanceRatio = - this._allowanceAmountInRelationToTokenBalance( - transactionApprovalAmountType, - dappProposedTokenAmount, - currentTokenBalance, - ); - } - } - - const replacedTxMeta = this._getTransaction(replacedById); - - const TRANSACTION_REPLACEMENT_METHODS = { - RETRY: TransactionType.retry, - CANCEL: TransactionType.cancel, - SAME_NONCE: 'other', - }; - - let transactionReplaced; - if (extraParams?.dropped) { - transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.SAME_NONCE; - if (replacedTxMeta?.type === TransactionType.cancel) { - transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.CANCEL; - } else if (replacedTxMeta?.type === TransactionType.retry) { - transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.RETRY; - } - } - - let uiCustomizations; - - ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - if (securityAlertResponse?.result_type === BlockaidResultType.Failed) { - uiCustomizations = ['security_alert_failed']; - } else { - ///: END:ONLY_INCLUDE_IN - // eslint-disable-next-line no-lonely-if - if (securityProviderResponse?.flagAsDangerous === 1) { - uiCustomizations = ['flagged_as_malicious']; - } else if (securityProviderResponse?.flagAsDangerous === 2) { - uiCustomizations = ['flagged_as_safety_unknown']; - } else { - uiCustomizations = null; - } - ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - } - ///: END:ONLY_INCLUDE_IN - - /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ - let properties = { - chain_id: chainId, - referrer, - source, - status, - network, - eip_1559_version: eip1559Version, - gas_edit_type: 'none', - gas_edit_attempted: 'none', - account_type: await this.getAccountType(this.getSelectedAddress()), - device_model: await this.getDeviceModel(this.getSelectedAddress()), - asset_type: assetType, - token_standard: tokenStandard, - transaction_type: transactionType, - transaction_speed_up: type === TransactionType.retry, - ui_customizations: uiCustomizations, - ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - security_alert_response: - securityAlertResponse?.result_type ?? BlockaidResultType.NotApplicable, - security_alert_reason: - securityAlertResponse?.reason ?? BlockaidReason.notApplicable, - ///: END:ONLY_INCLUDE_IN - }; - - if (transactionContractMethod === contractMethodNames.APPROVE) { - properties = { - ...properties, - transaction_approval_amount_type: transactionApprovalAmountType, - }; - } - - let sensitiveProperties = { - transaction_envelope_type: isEIP1559Transaction(txMeta) - ? TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET - : TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - first_seen: time, - gas_limit: gasLimit, - transaction_contract_method: transactionContractMethod, - transaction_replaced: transactionReplaced, - ...extraParams, - ...gasParamsInGwei, - }; - - if (transactionContractMethod === contractMethodNames.APPROVE) { - sensitiveProperties = { - ...sensitiveProperties, - transaction_approval_amount_vs_balance_ratio: - transactionApprovalAmountVsBalanceRatio, - transaction_approval_amount_vs_proposed_ratio: - transactionApprovalAmountVsProposedRatio, - }; - } - - return { properties, sensitiveProperties }; - } - - /** - * Helper method that checks for the presence of an existing fragment by id - * appropriate for the type of event that triggered fragment creation. If the - * appropriate fragment exists, then nothing is done. If it does not exist a - * new event fragment is created with the appropriate payload. - * - * @param {TransactionMeta} txMeta - Transaction meta object - * @param {TransactionMetaMetricsEvent} event - The event type that - * triggered fragment creation - * @param {object} properties - properties to include in the fragment - * @param {object} [sensitiveProperties] - sensitive properties to include in - * @param {object} [actionId] - actionId passed from UI - * the fragment - */ - _createTransactionEventFragment( - txMeta, - event, - properties, - sensitiveProperties, - actionId, - ) { - const isSubmitted = [ - TransactionMetaMetricsEvent.finalized, - TransactionMetaMetricsEvent.submitted, - ].includes(event); - const uniqueIdentifier = `transaction-${ - isSubmitted ? 'submitted' : 'added' - }-${txMeta.id}`; - - const fragment = this.getEventFragmentById(uniqueIdentifier); - if (typeof fragment !== 'undefined') { - return; - } - - switch (event) { - // When a transaction is added to the controller, we know that the user - // will be presented with a confirmation screen. The user will then - // either confirm or reject that transaction. Each has an associated - // event we want to track. While we don't necessarily need an event - // fragment to model this, having one allows us to record additional - // properties onto the event from the UI. For example, when the user - // edits the transactions gas params we can record that property and - // then get analytics on the number of transactions in which gas edits - // occur. - case TransactionMetaMetricsEvent.added: - this.createEventFragment({ - category: MetaMetricsEventCategory.Transactions, - initialEvent: TransactionMetaMetricsEvent.added, - successEvent: TransactionMetaMetricsEvent.approved, - failureEvent: TransactionMetaMetricsEvent.rejected, - properties, - sensitiveProperties, - persist: true, - uniqueIdentifier, - actionId, - }); - break; - // If for some reason an approval or rejection occurs without the added - // fragment existing in memory, we create the added fragment but without - // the initialEvent firing. This is to prevent possible duplication of - // events. A good example why this might occur is if the user had - // unapproved transactions in memory when updating to the version that - // includes this change. A migration would have also helped here but this - // implementation hardens against other possible bugs where a fragment - // does not exist. - case TransactionMetaMetricsEvent.approved: - case TransactionMetaMetricsEvent.rejected: - this.createEventFragment({ - category: MetaMetricsEventCategory.Transactions, - successEvent: TransactionMetaMetricsEvent.approved, - failureEvent: TransactionMetaMetricsEvent.rejected, - properties, - sensitiveProperties, - persist: true, - uniqueIdentifier, - actionId, - }); - break; - // When a transaction is submitted it will always result in updating - // to a finalized state (dropped, failed, confirmed) -- eventually. - // However having a fragment started at this stage allows augmenting - // analytics data with user interactions such as speeding up and - // canceling the transactions. From this controllers perspective a new - // transaction with a new id is generated for speed up and cancel - // transactions, but from the UI we could augment the previous ID with - // supplemental data to show user intent. Such as when they open the - // cancel UI but don't submit. We can record that this happened and add - // properties to the transaction event. - case TransactionMetaMetricsEvent.submitted: - this.createEventFragment({ - category: MetaMetricsEventCategory.Transactions, - initialEvent: TransactionMetaMetricsEvent.submitted, - successEvent: TransactionMetaMetricsEvent.finalized, - properties, - sensitiveProperties, - persist: true, - uniqueIdentifier, - actionId, - }); - break; - // If for some reason a transaction is finalized without the submitted - // fragment existing in memory, we create the submitted fragment but - // without the initialEvent firing. This is to prevent possible - // duplication of events. A good example why this might occur is if th - // user had pending transactions in memory when updating to the version - // that includes this change. A migration would have also helped here but - // this implementation hardens against other possible bugs where a - // fragment does not exist. - case TransactionMetaMetricsEvent.finalized: - this.createEventFragment({ - category: MetaMetricsEventCategory.Transactions, - successEvent: TransactionMetaMetricsEvent.finalized, - properties, - sensitiveProperties, - persist: true, - uniqueIdentifier, - actionId, - }); - break; - default: - break; - } - } - - /** - * Extracts relevant properties from a transaction meta - * object and uses them to create and send metrics for various transaction - * events. - * - * @param {object} txMeta - the txMeta object - * @param {TransactionMetaMetricsEvent} event - the name of the transaction event - * @param {string} actionId - actionId passed from UI - * @param {object} extraParams - optional props and values to include in sensitiveProperties - */ - async _trackTransactionMetricsEvent( - txMeta, - event, - actionId, - extraParams = {}, - ) { - if (!txMeta) { - return; - } - const { properties, sensitiveProperties } = - await this._buildEventFragmentProperties(txMeta, extraParams); - - // Create event fragments for event types that spawn fragments, and ensure - // existence of fragments for event types that act upon them. - this._createTransactionEventFragment( - txMeta, - event, - properties, - sensitiveProperties, - actionId, - ); - - let id; - - switch (event) { - // If the user approves a transaction, finalize the transaction added - // event fragment. - case TransactionMetaMetricsEvent.approved: - id = `transaction-added-${txMeta.id}`; - this.updateEventFragment(id, { properties, sensitiveProperties }); - this.finalizeEventFragment(id); - break; - // If the user rejects a transaction, finalize the transaction added - // event fragment. with the abandoned flag set. - case TransactionMetaMetricsEvent.rejected: - id = `transaction-added-${txMeta.id}`; - this.updateEventFragment(id, { properties, sensitiveProperties }); - this.finalizeEventFragment(id, { - abandoned: true, - }); - break; - // When a transaction is finalized, also finalize the transaction - // submitted event fragment. - case TransactionMetaMetricsEvent.finalized: - id = `transaction-submitted-${txMeta.id}`; - this.updateEventFragment(id, { properties, sensitiveProperties }); - this.finalizeEventFragment(`transaction-submitted-${txMeta.id}`); - break; - default: - break; - } - } - - _getTransactionCompletionTime(submittedTime) { - return Math.round((Date.now() - submittedTime) / 1000).toString(); - } - _getGasValuesInGWEI(gasParams) { const gasValuesInGwei = {}; for (const param in gasParams) { @@ -2749,27 +2185,19 @@ export default class TransactionController extends EventEmitter { _failTransaction(txId, error, actionId) { this.txStateManager.setTxStatusFailed(txId, error); const txMeta = this.txStateManager.getTransaction(txId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, + this.emit('transaction-finalized', { actionId, - { - error: error.message, - }, - ); + error: error.message, + transactionMeta: txMeta, + }); } _dropTransaction(txId) { this.txStateManager.setTxStatusDropped(txId); const txMeta = this.txStateManager.getTransaction(txId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - undefined, - { - dropped: true, - }, - ); + this.emit('transaction-dropped', { + transactionMeta: txMeta, + }); } /** @@ -2781,11 +2209,10 @@ export default class TransactionController extends EventEmitter { _addTransaction(txMeta) { this.txStateManager.addTransaction(txMeta); this.emit(`${txMeta.id}:unapproved`, txMeta); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - txMeta.actionId, - ); + this.emit('transaction-added', { + transactionMeta: txMeta, + actionId: txMeta.actionId, + }); } _onIncomingTransactions({ added: transactions }) { diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index 046ed4bae3ad..e90bf6a19a32 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -8,26 +8,14 @@ import { ApprovalType } from '@metamask/controller-utils'; import sinon from 'sinon'; import { errorCodes, ethErrors } from 'eth-rpc-errors'; -import { - BlockaidReason, - BlockaidResultType, -} from '../../../../shared/constants/security-provider'; import { createTestProviderTools, getTestAccounts, } from '../../../../test/stub/provider'; -import mockEstimates from '../../../../test/data/mock-estimates.json'; -import { - MetaMetricsEventCategory, - MetaMetricsTransactionEventSource, -} from '../../../../shared/constants/metametrics'; import { TransactionStatus, TransactionType, TransactionEnvelopeType, - TransactionMetaMetricsEvent, - AssetType, - TokenStandard, } from '../../../../shared/constants/transaction'; import { @@ -36,7 +24,6 @@ import { } from '../../../../shared/constants/gas'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { NetworkStatus } from '../../../../shared/constants/network'; -import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; import TxGasUtil from './tx-gas-utils'; import * as IncomingTransactionHelperClass from './IncomingTransactionHelper'; import TransactionController from '.'; @@ -48,7 +35,6 @@ const currentNetworkStatus = NetworkStatus.Available; const providerConfig = { type: 'goerli', }; -const actionId = 'DUMMY_ACTION_ID'; const VALID_ADDRESS = '0x0000000000000000000000000000000000000000'; const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001'; @@ -71,7 +57,6 @@ describe('Transaction Controller', function () { provider, providerResultStub, fromAccount, - fragmentExists, networkStatusStore, preferencesStore, getCurrentChainId, @@ -82,7 +67,6 @@ describe('Transaction Controller', function () { incomingTransactionHelperEventMock; beforeEach(function () { - fragmentExists = false; providerResultStub = { // 1 gwei eth_gasPrice: '0x0de0b6b3a7640000', @@ -131,7 +115,6 @@ describe('Transaction Controller', function () { getGasPrice() { return '0xee6b2800'; }, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => networkStatusStore.getState(), onNetworkStateChange: (listener) => networkStatusStore.subscribe(listener), @@ -147,15 +130,7 @@ describe('Transaction Controller', function () { getPermittedAccounts: () => undefined, getCurrentChainId, getParticipateInMetrics: () => false, - trackMetaMetricsEvent: () => undefined, - createEventFragment: () => undefined, - updateEventFragment: () => undefined, - finalizeEventFragment: () => undefined, - getEventFragmentById: () => - fragmentExists === false ? undefined : { id: 0 }, getEIP1559GasFeeEstimates: () => undefined, - getAccountType: () => 'MetaMask', - getDeviceModel: () => 'N/A', securityProviderRequest: () => undefined, preferencesStore, messenger: messengerMock, @@ -199,7 +174,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -209,7 +184,7 @@ describe('Transaction Controller', function () { { id: 2, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -219,7 +194,7 @@ describe('Transaction Controller', function () { { id: 3, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -238,7 +213,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -248,7 +223,7 @@ describe('Transaction Controller', function () { { id: 2, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -258,7 +233,7 @@ describe('Transaction Controller', function () { { id: 3, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -282,63 +257,63 @@ describe('Transaction Controller', function () { { id: 0, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 1, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 2, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 3, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 4, status: TransactionStatus.rejected, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 5, status: TransactionStatus.approved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 6, status: TransactionStatus.signed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 7, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, { id: 8, status: TransactionStatus.failed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, @@ -370,7 +345,7 @@ describe('Transaction Controller', function () { txMeta = { status: TransactionStatus.unapproved, id: 1, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }; @@ -405,10 +380,7 @@ describe('Transaction Controller', function () { })); assert.ok('id' in txMeta, 'should have a id'); assert.ok('time' in txMeta, 'should have a time stamp'); - assert.ok( - 'metamaskNetworkId' in txMeta, - 'should have a metamaskNetworkId', - ); + assert.ok('chainId' in txMeta, 'should have a chainId'); assert.ok('txParams' in txMeta, 'should have a txParams'); assert.ok('history' in txMeta, 'should have a history'); assert.equal( @@ -1237,7 +1209,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1287,7 +1259,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1337,7 +1309,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1387,7 +1359,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1441,7 +1413,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1558,7 +1530,7 @@ describe('Transaction Controller', function () { { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -1637,7 +1609,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams, history: [{}], }, @@ -1803,7 +1775,7 @@ describe('Transaction Controller', function () { { status: TransactionStatus.unapproved, id: 1, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { from: VALID_ADDRESS_TWO, @@ -1826,7 +1798,7 @@ describe('Transaction Controller', function () { { status: TransactionStatus.unapproved, id: 2, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { from: VALID_ADDRESS_TWO, @@ -1845,7 +1817,7 @@ describe('Transaction Controller', function () { }); describe('_publishTransaction', function () { - let hash, txMeta, trackTransactionMetricsEventSpy; + let hash, txMeta; beforeEach(function () { hash = @@ -1858,17 +1830,9 @@ describe('Transaction Controller', function () { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }; providerResultStub.eth_sendRawTransaction = hash; - trackTransactionMetricsEventSpy = sinon.spy( - txController, - '_trackTransactionMetricsEvent', - ); - }); - - afterEach(function () { - trackTransactionMetricsEventSpy.restore(); }); it('should publish a tx, updates the rawTx when provided a one', async function () { @@ -1896,22 +1860,6 @@ describe('Transaction Controller', function () { ); assert.equal(publishedTx.status, TransactionStatus.submitted); }); - - it('should call _trackTransactionMetricsEvent with the correct params', async function () { - const rawTx = - '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c'; - txController.txStateManager.addTransaction(txMeta); - await txController._publishTransaction(txMeta.id, rawTx); - assert.equal(trackTransactionMetricsEventSpy.callCount, 1); - assert.deepEqual( - trackTransactionMetricsEventSpy.getCall(0).args[0], - txMeta, - ); - assert.equal( - trackTransactionMetricsEventSpy.getCall(0).args[1], - TransactionMetaMetricsEvent.submitted, - ); - }); }); describe('#_markNonceDuplicatesDropped', function () { @@ -1920,7 +1868,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1931,7 +1879,7 @@ describe('Transaction Controller', function () { { id: 2, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1942,7 +1890,7 @@ describe('Transaction Controller', function () { { id: 3, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1953,7 +1901,7 @@ describe('Transaction Controller', function () { { id: 4, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1964,7 +1912,7 @@ describe('Transaction Controller', function () { { id: 5, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1975,7 +1923,7 @@ describe('Transaction Controller', function () { { id: 6, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -1986,7 +1934,7 @@ describe('Transaction Controller', function () { { id: 7, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, history: [{}], txParams: { to: VALID_ADDRESS_TWO, @@ -2018,7 +1966,7 @@ describe('Transaction Controller', function () { { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2027,7 +1975,7 @@ describe('Transaction Controller', function () { { id: 2, status: TransactionStatus.rejected, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2037,7 +1985,7 @@ describe('Transaction Controller', function () { { id: 3, status: TransactionStatus.approved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2047,7 +1995,7 @@ describe('Transaction Controller', function () { { id: 4, status: TransactionStatus.signed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2057,7 +2005,7 @@ describe('Transaction Controller', function () { { id: 5, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2067,7 +2015,7 @@ describe('Transaction Controller', function () { { id: 6, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2077,7 +2025,7 @@ describe('Transaction Controller', function () { { id: 7, status: TransactionStatus.failed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -2104,917 +2052,6 @@ describe('Transaction Controller', function () { }); }); - describe('#_trackTransactionMetricsEvent', function () { - let trackMetaMetricsEventSpy; - let createEventFragmentSpy; - let finalizeEventFragmentSpy; - - beforeEach(function () { - trackMetaMetricsEventSpy = sinon.spy( - txController, - '_trackMetaMetricsEvent', - ); - - createEventFragmentSpy = sinon.spy(txController, 'createEventFragment'); - - finalizeEventFragmentSpy = sinon.spy( - txController, - 'finalizeEventFragment', - ); - - sinon - .stub(txController, '_getEIP1559GasFeeEstimates') - .resolves(mockEstimates['fee-market']); - }); - - afterEach(function () { - trackMetaMetricsEventSpy.restore(); - createEventFragmentSpy.restore(); - finalizeEventFragmentSpy.restore(); - }); - - describe('On transaction created by the user', function () { - let txMeta; - - before(function () { - txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: ORIGIN_METAMASK, - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - defaultGasEstimates: { - gas: '0x7b0d', - gasPrice: '0x77359400', - }, - securityProviderResponse: { - flagAsDangerous: 0, - }, - }; - }); - - it('should create an event fragment when transaction added', async function () { - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: ORIGIN_METAMASK, - source: MetaMetricsTransactionEventSource.User, - transaction_type: TransactionType.simpleSend, - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('Should finalize the transaction added fragment as abandoned if user rejects transaction', async function () { - fragmentExists = true; - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.rejected, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], { - abandoned: true, - }); - }); - - it('Should finalize the transaction added fragment if user approves transaction', async function () { - fragmentExists = true; - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[1], - undefined, - ); - }); - - it('should create an event fragment when transaction is submitted', async function () { - const expectedPayload = { - actionId, - initialEvent: 'Transaction Submitted', - successEvent: 'Transaction Finalized', - uniqueIdentifier: 'transaction-submitted-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: ORIGIN_METAMASK, - source: MetaMetricsTransactionEventSource.User, - transaction_type: TransactionType.simpleSend, - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.submitted, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('Should finalize the transaction submitted fragment when transaction finalizes', async function () { - fragmentExists = true; - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-submitted-1', - ); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[1], - undefined, - ); - }); - }); - - describe('On transaction suggested by dapp', function () { - let txMeta; - before(function () { - txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - defaultGasEstimates: { - gas: '0x7b0d', - gasPrice: '0x77359400', - }, - securityProviderResponse: { - flagAsDangerous: 0, - }, - }; - }); - - it('should create an event fragment when transaction added', async function () { - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('Should finalize the transaction added fragment as abandoned if user rejects transaction', async function () { - fragmentExists = true; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.rejected, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], { - abandoned: true, - }); - }); - - it('Should finalize the transaction added fragment if user approves transaction', async function () { - fragmentExists = true; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[1], - undefined, - ); - }); - - it('should create an event fragment when transaction is submitted', async function () { - const expectedPayload = { - actionId, - initialEvent: 'Transaction Submitted', - successEvent: 'Transaction Finalized', - uniqueIdentifier: 'transaction-submitted-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - default_gas: '0.000031501', - default_gas_price: '2', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.submitted, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('Should finalize the transaction submitted fragment when transaction finalizes', async function () { - fragmentExists = true; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.finalized, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 0); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-submitted-1', - ); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[1], - undefined, - ); - }); - }); - - it('should create missing fragments when events happen out of order or are missing', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityProviderResponse: { - flagAsDangerous: 0, - }, - securityAlertResponse: { - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - }, - }; - - const expectedPayload = { - actionId, - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - category: MetaMetricsEventCategory.Transactions, - persist: true, - properties: { - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, - actionId, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - assert.equal(finalizeEventFragmentSpy.callCount, 1); - assert.deepEqual( - finalizeEventFragmentSpy.getCall(0).args[0], - 'transaction-added-1', - ); - assert.deepEqual(finalizeEventFragmentSpy.getCall(0).args[1], undefined); - }); - - it('should call _trackMetaMetricsEvent with the correct payload (extra params)', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityProviderResponse: { - flagAsDangerous: 0, - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('should call _trackMetaMetricsEvent with the correct payload when blockaid verification fails', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityAlertResponse: { - result_type: BlockaidResultType.Failed, - reason: 'some error', - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - status: 'unapproved', - transaction_type: TransactionType.simpleSend, - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: ['security_alert_failed'], - security_alert_reason: 'some error', - security_alert_response: BlockaidResultType.Failed, - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('should call _trackMetaMetricsEvent with the correct payload (extra params) when flagAsDangerous is malicious', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityProviderResponse: { - flagAsDangerous: 1, - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: ['flagged_as_malicious'], - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('should call _trackMetaMetricsEvent with the correct payload (extra params) when flagAsDangerous is unknown', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - securityProviderResponse: { - flagAsDangerous: 2, - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - chain_id: '0x5', - eip_1559_version: '0', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: ['flagged_as_safety_unknown'], - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - gas_price: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - - it('should call _trackMetaMetricsEvent with the correct payload (EIP-1559)', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - maxFeePerGas: '0x77359400', - maxPriorityFeePerGas: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - estimateSuggested: GasRecommendations.medium, - estimateUsed: GasRecommendations.high, - }, - type: TransactionType.simpleSend, - origin: 'other', - chainId: currentChainId, - time: 1624408066355, - metamaskNetworkId: currentNetworkId, - defaultGasEstimates: { - estimateType: 'medium', - maxFeePerGas: '0x77359400', - maxPriorityFeePerGas: '0x77359400', - }, - securityProviderResponse: { - flagAsDangerous: 0, - }, - }; - const expectedPayload = { - actionId, - initialEvent: 'Transaction Added', - successEvent: 'Transaction Approved', - failureEvent: 'Transaction Rejected', - uniqueIdentifier: 'transaction-added-1', - persist: true, - category: MetaMetricsEventCategory.Transactions, - properties: { - chain_id: '0x5', - eip_1559_version: '2', - gas_edit_attempted: 'none', - gas_edit_type: 'none', - network: '5', - referrer: 'other', - source: MetaMetricsTransactionEventSource.Dapp, - transaction_type: TransactionType.simpleSend, - account_type: 'MetaMask', - asset_type: AssetType.native, - token_standard: TokenStandard.none, - device_model: 'N/A', - transaction_speed_up: false, - ui_customizations: null, - security_alert_reason: BlockaidReason.notApplicable, - security_alert_response: BlockaidResultType.NotApplicable, - status: 'unapproved', - }, - sensitiveProperties: { - baz: 3.0, - foo: 'bar', - max_fee_per_gas: '2', - max_priority_fee_per_gas: '2', - gas_limit: '0x7b0d', - transaction_contract_method: undefined, - transaction_replaced: undefined, - first_seen: 1624408066355, - transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET, - estimate_suggested: GasRecommendations.medium, - estimate_used: GasRecommendations.high, - default_estimate: 'medium', - default_max_fee_per_gas: '70', - default_max_priority_fee_per_gas: '7', - }, - }; - - await txController._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.added, - actionId, - { - baz: 3.0, - foo: 'bar', - }, - ); - assert.equal(createEventFragmentSpy.callCount, 1); - assert.equal(finalizeEventFragmentSpy.callCount, 0); - assert.deepEqual( - createEventFragmentSpy.getCall(0).args[0], - expectedPayload, - ); - }); - }); - - describe('#_getTransactionCompletionTime', function () { - let nowStub; - - beforeEach(function () { - nowStub = sinon.stub(Date, 'now').returns(1625782016341); - }); - - afterEach(function () { - nowStub.restore(); - }); - - it('calculates completion time (one)', function () { - const submittedTime = 1625781997397; - const result = txController._getTransactionCompletionTime(submittedTime); - assert.equal(result, '19'); - }); - - it('calculates completion time (two)', function () { - const submittedTime = 1625781995397; - const result = txController._getTransactionCompletionTime(submittedTime); - assert.equal(result, '21'); - }); - }); - describe('#_getGasValuesInGWEI', function () { it('converts gas values in hex GWEi to dec GWEI (EIP-1559)', function () { const params = { @@ -3066,7 +2103,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { gasLimit: '0x001', gasPrice: '0x002', @@ -3106,7 +2143,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { maxPriorityFeePerGas: '0x003', to: VALID_ADDRESS, @@ -3124,7 +2161,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '3', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { maxPriorityFeePerGas: '0x003', maxFeePerGas: '0x004', @@ -3147,7 +2184,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '4', status: TransactionStatus.dropped, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { maxPriorityFeePerGas: '0x007', maxFeePerGas: '0x008', @@ -3193,7 +2230,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { gas: '0x001', gasPrice: '0x002', @@ -3231,7 +2268,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { gas: '0x001', gasPrice: '0x002', @@ -3262,7 +2299,7 @@ describe('Transaction Controller', function () { txStateManager.addTransaction({ id: '3', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { gas: '0x001', gasPrice: '0x002', @@ -3312,7 +2349,7 @@ describe('Transaction Controller', function () { id: firstTxId, origin: ORIGIN_METAMASK, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -3324,7 +2361,7 @@ describe('Transaction Controller', function () { id: secondTxId, origin: ORIGIN_METAMASK, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, @@ -3369,7 +2406,7 @@ describe('Transaction Controller', function () { id: txId, origin: ORIGIN_METAMASK, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS_TWO, diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index c2a4dc56ea14..37f1eaf59d26 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -5,7 +5,6 @@ import { values, keyBy, mapValues, omitBy, pickBy, sortBy } from 'lodash'; import { v1 as uuid } from 'uuid'; import { TransactionStatus } from '../../../../shared/constants/transaction'; import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; -import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { NetworkStatus } from '../../../../shared/constants/network'; import { @@ -55,14 +54,12 @@ export const ERROR_SUBMITTING = * transactions list keyed by id * @param {number} [opts.txHistoryLimit] - limit for how many finished * transactions can hang around in state - * @param {Function} opts.getNetworkId - Get the current network Id. * @param {Function} opts.getNetworkStatus - Get the current network status. */ export default class TransactionStateManager extends EventEmitter { constructor({ initState, txHistoryLimit, - getNetworkId, getNetworkStatus, getCurrentChainId, }) { @@ -73,7 +70,6 @@ export default class TransactionStateManager extends EventEmitter { ...initState, }); this.txHistoryLimit = txHistoryLimit; - this.getNetworkId = getNetworkId; this.getNetworkStatus = getNetworkStatus; this.getCurrentChainId = getCurrentChainId; } @@ -90,7 +86,6 @@ export default class TransactionStateManager extends EventEmitter { * @returns {TransactionMeta} the default txMeta object */ generateTxMeta(opts = {}) { - const networkId = this.getNetworkId(); const networkStatus = this.getNetworkStatus(); const chainId = this.getCurrentChainId(); if (networkStatus !== NetworkStatus.Available) { @@ -133,7 +128,6 @@ export default class TransactionStateManager extends EventEmitter { id: uuid(), time: new Date().getTime(), status: TransactionStatus.unapproved, - metamaskNetworkId: networkId, originalGasEstimate: opts.txParams?.gas, userEditedGasLimit: false, chainId, @@ -155,12 +149,11 @@ export default class TransactionStateManager extends EventEmitter { */ getUnapprovedTxList() { const chainId = this.getCurrentChainId(); - const networkId = this.getNetworkId(); return pickBy( this.store.getState().transactions, (transaction) => transaction.status === TransactionStatus.unapproved && - transactionMatchesNetwork(transaction, chainId, networkId), + transaction.chainId === chainId, ); } @@ -279,9 +272,8 @@ export default class TransactionStateManager extends EventEmitter { const txsToDelete = transactions .reverse() .filter((tx) => { - const { nonce, from } = tx.txParams; - const { chainId, metamaskNetworkId, status } = tx; - const key = `${nonce}-${chainId ?? metamaskNetworkId}-${from}`; + const { chainId, status, txParams } = tx; + const key = `${txParams.nonce}-${chainId}-${txParams.from}`; if (nonceNetworkSet.has(key)) { return false; } else if ( @@ -419,7 +411,6 @@ export default class TransactionStateManager extends EventEmitter { limit, } = {}) { const chainId = this.getCurrentChainId(); - const networkId = this.getNetworkId(); // searchCriteria is an object that might have values that aren't predicate // methods. When providing any other value type (string, number, etc), we // consider this shorthand for "check the value at key for strict equality @@ -444,12 +435,7 @@ export default class TransactionStateManager extends EventEmitter { // matching transactions that are sorted by time. const filteredTransactions = sortBy( pickBy(transactionsToFilter, (transaction) => { - // default matchesCriteria to the value of transactionMatchesNetwork - // when filterToCurrentNetwork is true. - if ( - filterToCurrentNetwork && - transactionMatchesNetwork(transaction, chainId, networkId) === false - ) { + if (filterToCurrentNetwork && transaction.chainId !== chainId) { return false; } // iterate over the predicateMethods keys to check if the transaction @@ -582,16 +568,16 @@ export default class TransactionStateManager extends EventEmitter { * the error on the TransactionMeta object. * * @param {number} txId - the target TransactionMeta's Id - * @param {Error} err - error object + * @param {Error} error - error object */ - setTxStatusFailed(txId, err) { - const error = err || new Error('Internal metamask failure'); + setTxStatusFailed(txId, error) { + const err = error || new Error('Internal metamask failure'); const txMeta = this.getTransaction(txId); - txMeta.err = { - message: error.message?.toString() || error.toString(), - rpc: error.value, - stack: error.stack, + txMeta.error = { + message: err.message?.toString() || err.toString(), + rpc: err.value, + stack: err.stack, }; this._updateTransactionHistory( txMeta, @@ -611,7 +597,6 @@ export default class TransactionStateManager extends EventEmitter { // network only tx const { transactions } = this.store.getState(); const chainId = this.getCurrentChainId(); - const networkId = this.getNetworkId(); // Update state this.store.updateState({ @@ -619,7 +604,7 @@ export default class TransactionStateManager extends EventEmitter { transactions, (transaction) => transaction.txParams.from === address && - transactionMatchesNetwork(transaction, chainId, networkId), + transaction.chainId === chainId, ), }); } diff --git a/app/scripts/controllers/transactions/tx-state-manager.test.js b/app/scripts/controllers/transactions/tx-state-manager.test.js index a3b420e10b84..51b27dfd61f2 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.test.js +++ b/app/scripts/controllers/transactions/tx-state-manager.test.js @@ -4,11 +4,7 @@ import { TransactionStatus, TransactionType, } from '../../../../shared/constants/transaction'; -import { - CHAIN_IDS, - NETWORK_IDS, - NetworkStatus, -} from '../../../../shared/constants/network'; +import { CHAIN_IDS, NetworkStatus } from '../../../../shared/constants/network'; import { GAS_LIMITS } from '../../../../shared/constants/gas'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import TxStateManager, { ERROR_SUBMITTING } from './tx-state-manager'; @@ -48,10 +44,9 @@ function generateTransactions( } describe('TransactionStateManager', function () { let txStateManager; - const currentNetworkId = NETWORK_IDS.GOERLI; const currentNetworkStatus = NetworkStatus.Available; const currentChainId = CHAIN_IDS.MAINNET; - const otherNetworkId = '2'; + const otherChainId = '0x2'; beforeEach(function () { txStateManager = new TxStateManager({ @@ -59,7 +54,6 @@ describe('TransactionStateManager', function () { transactions: {}, }, txHistoryLimit: 10, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => currentNetworkStatus, getCurrentChainId: () => currentChainId, }); @@ -70,7 +64,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -88,7 +82,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -112,7 +106,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -129,7 +123,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -158,7 +152,7 @@ describe('TransactionStateManager', function () { it('should return a full list of transactions', function () { const submittedTx = { id: 0, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 0, txParams: { from: '0xAddress', @@ -170,7 +164,7 @@ describe('TransactionStateManager', function () { const confirmedTx = { id: 3, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 3, txParams: { from: '0xAddress', @@ -187,7 +181,6 @@ describe('TransactionStateManager', function () { [confirmedTx.id]: confirmedTx, }, }, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => currentNetworkStatus, getCurrentChainId: () => currentChainId, }); @@ -198,7 +191,7 @@ describe('TransactionStateManager', function () { it('should return a list of transactions, limited by N unique nonces when there are NO duplicates', function () { const submittedTx0 = { id: 0, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 0, txParams: { from: '0xAddress', @@ -210,7 +203,7 @@ describe('TransactionStateManager', function () { const unapprovedTx1 = { id: 1, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 1, txParams: { from: '0xAddress', @@ -222,7 +215,7 @@ describe('TransactionStateManager', function () { const approvedTx2 = { id: 2, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 2, txParams: { from: '0xAddress', @@ -234,7 +227,7 @@ describe('TransactionStateManager', function () { const confirmedTx3 = { id: 3, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 3, txParams: { from: '0xAddress', @@ -253,7 +246,6 @@ describe('TransactionStateManager', function () { [confirmedTx3.id]: confirmedTx3, }, }, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => currentNetworkStatus, getCurrentChainId: () => currentChainId, }); @@ -267,7 +259,7 @@ describe('TransactionStateManager', function () { it('should return a list of transactions, limited by N unique nonces when there ARE duplicates', function () { const submittedTx0 = { id: 0, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 0, txParams: { from: '0xAddress', @@ -278,7 +270,7 @@ describe('TransactionStateManager', function () { }; const submittedTx0Dupe = { id: 1, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 0, txParams: { from: '0xAddress', @@ -290,7 +282,6 @@ describe('TransactionStateManager', function () { const unapprovedTx1 = { id: 2, - metamaskNetworkId: currentNetworkId, chainId: currentChainId, time: 1, txParams: { @@ -303,7 +294,7 @@ describe('TransactionStateManager', function () { const approvedTx2 = { id: 3, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 2, txParams: { from: '0xAddress', @@ -314,7 +305,6 @@ describe('TransactionStateManager', function () { }; const approvedTx2Dupe = { id: 4, - metamaskNetworkId: currentNetworkId, chainId: currentChainId, time: 2, txParams: { @@ -327,7 +317,7 @@ describe('TransactionStateManager', function () { const failedTx3 = { id: 5, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, time: 3, txParams: { from: '0xAddress', @@ -338,7 +328,6 @@ describe('TransactionStateManager', function () { }; const failedTx3Dupe = { id: 6, - metamaskNetworkId: currentNetworkId, chainId: currentChainId, time: 3, txParams: { @@ -363,7 +352,6 @@ describe('TransactionStateManager', function () { [failedTx3Dupe.id]: failedTx3Dupe, }, }, - getNetworkId: () => currentNetworkId, getNetworkStatus: () => currentNetworkStatus, getCurrentChainId: () => currentChainId, }); @@ -382,61 +370,61 @@ describe('TransactionStateManager', function () { id: 0, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 1, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 2, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 3, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 4, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 5, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 6, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 7, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 8, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 9, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, ]; txMetas.forEach((txMeta) => txStateManager.addTransaction(txMeta)); @@ -503,7 +491,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -533,7 +521,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { ...validTxParams, [key]: value, @@ -554,7 +542,7 @@ describe('TransactionStateManager', function () { const tx = { id: 1, status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -563,7 +551,7 @@ describe('TransactionStateManager', function () { const tx2 = { id: 2, status: TransactionStatus.confirmed, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -762,7 +750,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -771,7 +759,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -799,7 +787,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: validTxParams, }); @@ -830,7 +818,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS, @@ -906,7 +894,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS, @@ -925,7 +913,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS, @@ -967,7 +955,7 @@ describe('TransactionStateManager', function () { assert.equal(result.history[1][1].value.message, ERROR_SUBMITTING); assert.equal(result.history[1][2].op, 'add'); - assert.equal(result.history[1][2].path, '/err'); + assert.equal(result.history[1][2].path, '/error'); assert.equal( result.history[1][2].value.message, 'Invalid transaction envelope type: specified type "0x0" but including maxFeePerGas and maxPriorityFeePerGas requires type: "0x2"', @@ -987,7 +975,7 @@ describe('TransactionStateManager', function () { const txMeta = { id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: VALID_ADDRESS_TWO, to: VALID_ADDRESS, @@ -1013,7 +1001,7 @@ describe('TransactionStateManager', function () { 'transactions:tx-state-manager#fail - add error', ); assert.equal(result.history[1][0].op, 'add'); - assert.equal(result.history[1][0].path, '/err'); + assert.equal(result.history[1][0].path, '/error'); assert.equal( result.history[1][0].value.message, 'Testing tx status failed with arbitrary error', @@ -1034,7 +1022,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -1043,7 +1031,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -1061,7 +1049,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '1', status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -1070,7 +1058,7 @@ describe('TransactionStateManager', function () { txStateManager.addTransaction({ id: '2', status: TransactionStatus.confirmed, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { to: VALID_ADDRESS, from: VALID_ADDRESS, @@ -1097,19 +1085,19 @@ describe('TransactionStateManager', function () { id: 0, status: TransactionStatus.unapproved, txParams: { from: specificAddress, to: otherAddress }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 1, status: TransactionStatus.confirmed, txParams: { from: otherAddress, to: specificAddress }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 2, status: TransactionStatus.confirmed, txParams: { from: otherAddress, to: specificAddress }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, ]; txMetas.forEach((txMeta) => txStateManager.addTransaction(txMeta)); @@ -1133,19 +1121,19 @@ describe('TransactionStateManager', function () { id: 0, status: TransactionStatus.unapproved, txParams: { from: specificAddress, to: otherAddress }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 1, status: TransactionStatus.confirmed, txParams: { from: specificAddress, to: otherAddress }, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, }, { id: 2, status: TransactionStatus.confirmed, txParams: { from: specificAddress, to: otherAddress }, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, }, ]; @@ -1158,7 +1146,7 @@ describe('TransactionStateManager', function () { .filter((txMeta) => txMeta.txParams.from === specificAddress); const txFromOtherNetworks = txStateManager .getTransactions({ filterToCurrentNetwork: false }) - .filter((txMeta) => txMeta.metamaskNetworkId === otherNetworkId); + .filter((txMeta) => txMeta.chainId === otherChainId); assert.equal(txsFromCurrentNetworkAndAddress.length, 0); assert.equal(txFromOtherNetworks.length, 2); @@ -1328,25 +1316,25 @@ describe('TransactionStateManager', function () { id: 0, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 1, status: TransactionStatus.unapproved, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, }, { id: 2, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, }, { id: 3, status: TransactionStatus.confirmed, txParams: { from: VALID_ADDRESS, to: VALID_ADDRESS_TWO }, - metamaskNetworkId: otherNetworkId, + chainId: otherChainId, }, ]; diff --git a/app/scripts/controllers/transactions/types.ts b/app/scripts/controllers/transactions/types.ts index e59205e1b654..9f956896f71f 100644 --- a/app/scripts/controllers/transactions/types.ts +++ b/app/scripts/controllers/transactions/types.ts @@ -20,11 +20,6 @@ export interface RemoteTransactionSourceRequest { */ currentChainId: Hex; - /** - * The networkId of the current network. - */ - currentNetworkId: string; - /** * Block number to start fetching transactions from. */ @@ -41,7 +36,7 @@ export interface RemoteTransactionSourceRequest { * Used by the IncomingTransactionHelper to retrieve remote transaction data. */ export interface RemoteTransactionSource { - isSupportedNetwork: (chainId: Hex, networkId: string) => boolean; + isSupportedNetwork: (chainId: Hex) => boolean; fetchTransactions: ( request: RemoteTransactionSourceRequest, diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 92ae8d28658b..d00a0542db03 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -32,6 +32,7 @@ cleanContextForImports(); /* eslint-disable import/first */ import log from 'loglevel'; +import { v4 as uuid } from 'uuid'; import { WindowPostMessageStream } from '@metamask/post-message-stream'; import { initializeProvider } from '@metamask/providers/dist/initializeInpageProvider'; import shouldInjectProvider from '../../shared/modules/provider-injection'; @@ -59,5 +60,11 @@ if (shouldInjectProvider()) { connectionStream: metamaskStream, logger: log, shouldShimWeb3: true, + providerInfo: { + uuid: uuid(), + name: process.env.METAMASK_BUILD_NAME, + icon: process.env.METAMASK_BUILD_ICON, + rdns: process.env.METAMASK_BUILD_APP_ID, + }, }); } diff --git a/app/scripts/lib/AddressBookPetnamesBridge.test.ts b/app/scripts/lib/AddressBookPetnamesBridge.test.ts new file mode 100644 index 000000000000..3d7ab4ee58af --- /dev/null +++ b/app/scripts/lib/AddressBookPetnamesBridge.test.ts @@ -0,0 +1,266 @@ +import { NameController, NameType } from '@metamask/name-controller'; +import { AddressBookController } from '@metamask/address-book-controller'; +import { + AddressBookPetnamesBridge, + AddressBookPetnamesBridgeMessenger, +} from './AddressBookPetnamesBridge'; + +const ADDRESS_MOCK = '0xabc'; +const NAME_MOCK = 'testName'; +const NAME_2_MOCK = 'testName2'; +const CHAIN_ID_MOCK = '0x1'; + +function createAddressBookControllerMock( + state: any = {}, +): jest.Mocked { + return { + state: { + addressBook: state, + }, + set: jest.fn(), + delete: jest.fn(), + subscribe: jest.fn(), + } as any; +} + +function createNameControllerMock( + state: any = {}, +): jest.Mocked { + return { + state: { + names: { + ethereumAddress: state, + }, + }, + setName: jest.fn(), + } as any; +} + +function createMessengerMock(): jest.Mocked { + return { + subscribe: jest.fn(), + } as any; +} + +describe('AddressBookPetnamesBridge', () => { + let addressBookControllerDefault; + let nameControllerDefault; + let messengerDefault; + let options: any; + + beforeEach(() => { + jest.resetAllMocks(); + + addressBookControllerDefault = createAddressBookControllerMock(); + nameControllerDefault = createNameControllerMock(); + messengerDefault = createMessengerMock(); + + options = { + addressBookController: addressBookControllerDefault, + nameController: nameControllerDefault, + messenger: messengerDefault, + }; + }); + + describe('NameController', () => { + it('adds entry when address book entry added', () => { + new AddressBookPetnamesBridge(options).init(); + + addressBookControllerDefault.subscribe.mock.calls[0][0]({ + addressBook: { + [CHAIN_ID_MOCK]: { + [ADDRESS_MOCK]: { + address: ADDRESS_MOCK, + name: NAME_MOCK, + chainId: CHAIN_ID_MOCK, + isEns: true, + } as any, + }, + }, + }); + + expect(nameControllerDefault.setName).toHaveBeenCalledTimes(1); + expect(nameControllerDefault.setName).toHaveBeenCalledWith({ + value: ADDRESS_MOCK, + type: NameType.ETHEREUM_ADDRESS, + name: NAME_MOCK, + sourceId: 'ens', + variation: CHAIN_ID_MOCK, + }); + }); + + it('updates entry when address book entry is updated', () => { + const nameController = createNameControllerMock({ + [ADDRESS_MOCK]: { + [CHAIN_ID_MOCK]: { + name: NAME_MOCK, + sourceId: null, + proposedNames: {}, + }, + }, + }); + + new AddressBookPetnamesBridge({ + ...options, + nameController, + }).init(); + + addressBookControllerDefault.subscribe.mock.calls[0][0]({ + addressBook: { + [CHAIN_ID_MOCK]: { + [ADDRESS_MOCK]: { + address: ADDRESS_MOCK, + name: NAME_2_MOCK, + chainId: CHAIN_ID_MOCK, + isEns: false, + } as any, + }, + }, + }); + + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith({ + value: ADDRESS_MOCK, + type: NameType.ETHEREUM_ADDRESS, + name: NAME_2_MOCK, + sourceId: undefined, + variation: CHAIN_ID_MOCK, + }); + }); + + it('deletes entry when address book entry is deleted', () => { + const nameController = createNameControllerMock({ + [ADDRESS_MOCK]: { + [CHAIN_ID_MOCK]: { + name: NAME_MOCK, + sourceId: null, + proposedNames: {}, + } as any, + }, + }); + + new AddressBookPetnamesBridge({ + ...options, + nameController, + }).init(); + + addressBookControllerDefault.subscribe.mock.calls[0][0]({ + addressBook: {}, + }); + + expect(nameController.setName).toHaveBeenCalledTimes(1); + expect(nameController.setName).toHaveBeenCalledWith({ + value: ADDRESS_MOCK, + type: NameType.ETHEREUM_ADDRESS, + name: null, + sourceId: undefined, + variation: CHAIN_ID_MOCK, + }); + }); + }); + + describe('AddressBookController', () => { + it('adds entry when petname added', () => { + new AddressBookPetnamesBridge(options).init(); + + messengerDefault.subscribe.mock.calls[0][1]( + { + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_MOCK]: { + [CHAIN_ID_MOCK]: { + name: NAME_MOCK, + sourceId: null, + proposedNames: {}, + }, + }, + }, + }, + }, + undefined, + ); + + expect(addressBookControllerDefault.set).toHaveBeenCalledTimes(1); + expect(addressBookControllerDefault.set).toHaveBeenCalledWith( + ADDRESS_MOCK, + NAME_MOCK, + CHAIN_ID_MOCK, + ); + }); + + it('updates entry when petname updated', () => { + const addressBookController = createAddressBookControllerMock({ + [CHAIN_ID_MOCK]: { + [ADDRESS_MOCK]: { + address: ADDRESS_MOCK, + name: NAME_MOCK, + chainId: CHAIN_ID_MOCK, + isEns: false, + } as any, + }, + }); + + new AddressBookPetnamesBridge({ + ...options, + addressBookController, + }).init(); + + messengerDefault.subscribe.mock.calls[0][1]( + { + names: { + [NameType.ETHEREUM_ADDRESS]: { + [ADDRESS_MOCK]: { + [CHAIN_ID_MOCK]: { + name: NAME_2_MOCK, + sourceId: null, + proposedNames: {}, + }, + }, + }, + }, + }, + undefined, + ); + + expect(addressBookController.set).toHaveBeenCalledTimes(1); + expect(addressBookController.set).toHaveBeenCalledWith( + ADDRESS_MOCK, + NAME_2_MOCK, + CHAIN_ID_MOCK, + ); + }); + + it('deletes entry when petname deleted', () => { + const addressBookController = createAddressBookControllerMock({ + [CHAIN_ID_MOCK]: { + [ADDRESS_MOCK]: { + address: ADDRESS_MOCK, + name: NAME_MOCK, + chainId: CHAIN_ID_MOCK, + isEns: false, + } as any, + }, + }); + + new AddressBookPetnamesBridge({ + ...options, + addressBookController, + }).init(); + + messengerDefault.subscribe.mock.calls[0][1]( + { + names: { + [NameType.ETHEREUM_ADDRESS]: {}, + }, + }, + undefined, + ); + + expect(addressBookController.delete).toHaveBeenCalledTimes(1); + expect(addressBookController.delete).toHaveBeenCalledWith( + CHAIN_ID_MOCK, + ADDRESS_MOCK, + ); + }); + }); +}); diff --git a/app/scripts/lib/AddressBookPetnamesBridge.ts b/app/scripts/lib/AddressBookPetnamesBridge.ts new file mode 100644 index 000000000000..94f2edb18fc3 --- /dev/null +++ b/app/scripts/lib/AddressBookPetnamesBridge.ts @@ -0,0 +1,247 @@ +import { + AddressBookController, + AddressBookState, +} from '@metamask/address-book-controller'; +import { RestrictedControllerMessenger } from '@metamask/base-controller'; +import { + NameController, + NameControllerState, + NameStateChange, + NameType, +} from '@metamask/name-controller'; +import { cloneDeep } from 'lodash'; +import log from 'loglevel'; + +type Entry = { + address: string; + name: string; + chainId: string; + isEns: boolean; +}; + +type AllowedEvents = NameStateChange; + +export type AddressBookPetnamesBridgeMessenger = RestrictedControllerMessenger< + 'AddressBookPetnamesBridge', + never, + AllowedEvents, + never, + AllowedEvents['type'] +>; + +export class AddressBookPetnamesBridge { + #addressBookController: AddressBookController; + + #addressBookState: AddressBookState; + + #nameController: NameController; + + #nameState: NameControllerState; + + #messenger: AddressBookPetnamesBridgeMessenger; + + #updating: boolean; + + constructor({ + addressBookController, + nameController, + messenger, + }: { + addressBookController: AddressBookController; + nameController: NameController; + messenger: AddressBookPetnamesBridgeMessenger; + }) { + this.#addressBookController = addressBookController; + this.#addressBookState = addressBookController.state; + this.#nameController = nameController; + this.#nameState = nameController.state; + this.#messenger = messenger; + this.#updating = false; + } + + init() { + this.#addressBookController.subscribe((state) => { + try { + this.#onAddressBookStateChange(state); + } catch (error) { + log.debug( + 'Error synchronising address book update with petnames', + error, + ); + } + }); + + this.#messenger.subscribe('NameController:stateChange', (state) => { + try { + this.#onPetnameStateChange(state); + } catch (error) { + log.debug('Error synchronising petname update with petnames', error); + } + }); + } + + #onPetnameStateChange(newState: NameControllerState) { + if (this.#updating) { + return; + } + + this.#updating = true; + + const newEntries = this.#getPetnameEntries(newState); + const oldEntries = this.#getAddressBookEntries(this.#addressBookState); + + const { added, updated, deleted } = this.#groupEntries( + oldEntries, + newEntries, + ); + + for (const entry of [...added, ...updated]) { + this.#addressBookController.set( + entry.address, + entry.name, + entry.chainId as any, + ); + + log.debug('Updated address book following petname update', entry); + } + + for (const entry of deleted) { + this.#addressBookController.delete(entry.chainId as any, entry.address); + log.debug('Removed address book entry following petname removal', entry); + } + + this.#addressBookState = cloneDeep(this.#addressBookController.state); + this.#nameState = cloneDeep(newState); + this.#updating = false; + } + + #onAddressBookStateChange(newState: AddressBookState) { + if (this.#updating) { + return; + } + + this.#updating = true; + + const newEntries = this.#getAddressBookEntries(newState); + const oldEntries = this.#getPetnameEntries(this.#nameState); + + const { added, updated, deleted } = this.#groupEntries( + oldEntries, + newEntries, + ); + + for (const entry of [...added, ...updated]) { + this.#nameController.setName({ + value: entry.address, + type: NameType.ETHEREUM_ADDRESS, + name: entry.name, + sourceId: entry.isEns ? 'ens' : undefined, + variation: entry.chainId, + }); + + log.debug('Updated petname following address book update', entry); + } + + for (const entry of deleted) { + this.#nameController.setName({ + value: entry.address, + type: NameType.ETHEREUM_ADDRESS, + name: null, + variation: entry.chainId, + }); + + log.debug('Removed petname following address book removal', entry); + } + + this.#addressBookState = cloneDeep(newState); + this.#nameState = cloneDeep(this.#nameController.state); + this.#updating = false; + } + + #groupEntries(oldEntries: Entry[], newEntries: Entry[]) { + const added = newEntries.filter( + (newEntry) => + !oldEntries.some( + (oldEntry) => + oldEntry.address === newEntry.address && + oldEntry.chainId === newEntry.chainId, + ), + ); + + const updated = newEntries.filter((newEntry) => + oldEntries.some( + (oldEntry) => + oldEntry.address === newEntry.address && + oldEntry.chainId === newEntry.chainId && + oldEntry.name !== newEntry.name, + ), + ); + + const deleted = oldEntries.filter( + (oldEntry) => + !newEntries.some( + (newEntry) => + newEntry.address === oldEntry.address && + newEntry.chainId === oldEntry.chainId, + ), + ); + + return { added, updated, deleted }; + } + + #getPetnameEntries(state: NameControllerState): Entry[] { + const entries: Entry[] = []; + + for (const address of Object.keys(state.names.ethereumAddress)) { + const addressEntries = state.names.ethereumAddress[address]; + + for (const chainId of Object.keys(addressEntries)) { + const entry = state.names.ethereumAddress[address][chainId as any]; + const normalizedAddress = address.toLowerCase(); + const normalizedChainId = chainId.toLowerCase(); + const { sourceId, name } = entry; + + if (!name?.length) { + continue; + } + + entries.push({ + address: normalizedAddress, + name, + chainId: normalizedChainId, + isEns: sourceId === 'ens', + }); + } + } + + return entries; + } + + #getAddressBookEntries(state: AddressBookState): Entry[] { + const entries: Entry[] = []; + + for (const chainId of Object.keys(state.addressBook)) { + const chainEntries = state.addressBook[chainId as any]; + + for (const address of Object.keys(chainEntries)) { + const entry = state.addressBook[chainId as any][address]; + const normalizedAddress = address.toLowerCase(); + const normalizedChainId = chainId.toLowerCase(); + const { name, isEns } = entry; + + if (!name?.length || !normalizedAddress?.length) { + continue; + } + + entries.push({ + address: normalizedAddress, + name, + chainId: normalizedChainId, + isEns, + }); + } + } + + return entries; + } +} diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index bd9e4b357998..825994c391ec 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -1,16 +1,15 @@ -import { errorCodes } from 'eth-rpc-errors'; import { detectSIWE } from '@metamask/controller-utils'; +import { errorCodes } from 'eth-rpc-errors'; import { isValidAddress } from 'ethereumjs-util'; - import { MESSAGE_TYPE, ORIGIN_METAMASK } from '../../../shared/constants/app'; -import { TransactionStatus } from '../../../shared/constants/transaction'; -import { SECOND } from '../../../shared/constants/time'; - import { MetaMetricsEventCategory, MetaMetricsEventName, MetaMetricsEventUiCustomization, } from '../../../shared/constants/metametrics'; +import { SECOND } from '../../../shared/constants/time'; +import { TransactionStatus } from '../../../shared/constants/transaction'; + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) import { BlockaidReason, @@ -18,6 +17,8 @@ import { } from '../../../shared/constants/security-provider'; ///: END:ONLY_INCLUDE_IN +import { getSnapAndHardwareInfoForMetrics } from './snap-keyring/metrics'; + /** * These types determine how the method tracking middleware handles incoming * requests based on the method name. There are three options right now but @@ -117,6 +118,10 @@ const rateLimitTimeouts = {}; * @param {number} [opts.rateLimitSeconds] - number of seconds to wait before * allowing another set of events to be tracked. * @param opts.securityProviderRequest + * @param {Function} opts.getSelectedAddress + * @param {Function} opts.getAccountType + * @param {Function} opts.getDeviceModel + * @param {RestrictedControllerMessenger} opts.snapAndHardwareMessenger * @returns {Function} */ export default function createRPCMethodTrackingMiddleware({ @@ -124,6 +129,10 @@ export default function createRPCMethodTrackingMiddleware({ getMetricsState, rateLimitSeconds = 60 * 5, securityProviderRequest, + getSelectedAddress, + getAccountType, + getDeviceModel, + snapAndHardwareMessenger, }) { return async function rpcMethodTrackingMiddleware( /** @type {any} */ req, @@ -197,6 +206,16 @@ export default function createRPCMethodTrackingMiddleware({ req.securityAlertResponse?.reason ?? BlockaidReason.notApplicable; ///: END:ONLY_INCLUDE_IN + const snapAndHardwareInfo = await getSnapAndHardwareInfoForMetrics( + getSelectedAddress, + getAccountType, + getDeviceModel, + snapAndHardwareMessenger, + ); + + // merge the snapAndHardwareInfo into eventProperties + Object.assign(eventProperties, snapAndHardwareInfo); + const msgData = { msgParams: { ...paramsExamplePassword, @@ -273,6 +292,13 @@ export default function createRPCMethodTrackingMiddleware({ eventProperties.error = res.error; } else if (res.error?.code === errorCodes.provider.userRejectedRequest) { event = eventType.REJECTED; + } else if ( + res.error?.code === errorCodes.rpc.internal && + res.error?.message === 'Request rejected by user or snap.' + ) { + // The signature was approved in MetaMask but rejected in the snap + event = eventType.REJECTED; + eventProperties.status = res.error.message; } else { event = eventType.APPROVED; } diff --git a/app/scripts/lib/keyring-snaps-permissions.test.ts b/app/scripts/lib/keyring-snaps-permissions.test.ts new file mode 100644 index 000000000000..c2707914ecbd --- /dev/null +++ b/app/scripts/lib/keyring-snaps-permissions.test.ts @@ -0,0 +1,98 @@ +import { + SubjectMetadataController, + SubjectType, +} from '@metamask/permission-controller'; +import { KeyringRpcMethod } from '@metamask/keyring-api'; +import { + isProtocolAllowed, + keyringSnapPermissionsBuilder, +} from './keyring-snaps-permissions'; + +describe('keyringSnapPermissionsBuilder', () => { + const mockController = new SubjectMetadataController({ + subjectCacheLimit: 100, + messenger: { + registerActionHandler: jest.fn(), + publish: jest.fn(), + } as any, + state: {}, + }); + mockController.addSubjectMetadata({ + origin: 'https://some-dapp.com', + subjectType: SubjectType.Website, + }); + + it('returns the methods metamask can call', () => { + const permissions = keyringSnapPermissionsBuilder(mockController); + expect(permissions('metamask')).toStrictEqual([ + KeyringRpcMethod.ListAccounts, + KeyringRpcMethod.GetAccount, + KeyringRpcMethod.FilterAccountChains, + KeyringRpcMethod.DeleteAccount, + KeyringRpcMethod.ListRequests, + KeyringRpcMethod.GetRequest, + KeyringRpcMethod.SubmitRequest, + KeyringRpcMethod.RejectRequest, + ]); + }); + + it('returns the methods a known origin can call', () => { + const permissions = keyringSnapPermissionsBuilder(mockController); + expect(permissions('https://some-dapp.com')).toStrictEqual([ + KeyringRpcMethod.ListAccounts, + KeyringRpcMethod.GetAccount, + KeyringRpcMethod.CreateAccount, + KeyringRpcMethod.FilterAccountChains, + KeyringRpcMethod.UpdateAccount, + KeyringRpcMethod.DeleteAccount, + KeyringRpcMethod.ExportAccount, + KeyringRpcMethod.ListRequests, + KeyringRpcMethod.GetRequest, + KeyringRpcMethod.ApproveRequest, + KeyringRpcMethod.RejectRequest, + ]); + }); + + it('returns the methods an unknown origin can call', () => { + const permissions = keyringSnapPermissionsBuilder(mockController); + expect(permissions('https://some-other-dapp.com')).toStrictEqual([]); + }); + + it.each([ + '', + 'null', + 'sftp://some-dapp.com', + 'http://some-dapp.com', + '0', + undefined, + null, + true, + false, + 1, + 0, + -1, + ])('"%s" cannot call any methods', (origin) => { + const permissions = keyringSnapPermissionsBuilder(mockController); + expect(permissions(origin as any)).toStrictEqual([]); + }); +}); + +describe('isProtocolAllowed', () => { + it.each([ + ['http://some-dapp.com', true], + ['https://some-dapp.com', true], + ['sftp://some-dapp.com', false], + ['', false], + ['null', false], + ['0', false], + [undefined, false], + [null, false], + [true, false], + [false, false], + [1, false], + [0, false], + [-1, false], + ])('"%s" cannot call any methods', (origin: any, expected: boolean) => { + expect(isProtocolAllowed(origin)).toBe(expected); + }); +}); diff --git a/app/scripts/lib/keyring-snaps-permissions.ts b/app/scripts/lib/keyring-snaps-permissions.ts new file mode 100644 index 000000000000..d6df442e8966 --- /dev/null +++ b/app/scripts/lib/keyring-snaps-permissions.ts @@ -0,0 +1,86 @@ +import { + SubjectType, + SubjectMetadataController, +} from '@metamask/permission-controller'; +import { KeyringRpcMethod } from '@metamask/keyring-api'; + +/** + * List of keyring methods MetaMask can call. + */ +const METAMASK_ALLOWED_METHODS: string[] = [ + KeyringRpcMethod.ListAccounts, + KeyringRpcMethod.GetAccount, + KeyringRpcMethod.FilterAccountChains, + KeyringRpcMethod.DeleteAccount, + KeyringRpcMethod.ListRequests, + KeyringRpcMethod.GetRequest, + KeyringRpcMethod.SubmitRequest, + KeyringRpcMethod.RejectRequest, +]; + +/** + * List of keyring methods a dapp can call. + */ +const WEBSITE_ALLOWED_METHODS: string[] = [ + KeyringRpcMethod.ListAccounts, + KeyringRpcMethod.GetAccount, + KeyringRpcMethod.CreateAccount, + KeyringRpcMethod.FilterAccountChains, + KeyringRpcMethod.UpdateAccount, + KeyringRpcMethod.DeleteAccount, + KeyringRpcMethod.ExportAccount, + KeyringRpcMethod.ListRequests, + KeyringRpcMethod.GetRequest, + KeyringRpcMethod.ApproveRequest, + KeyringRpcMethod.RejectRequest, +]; + +/** + * List of allowed protocols. On Flask, HTTP is also allowed for testing. + */ +const ALLOWED_PROTOCOLS: string[] = [ + 'https:', + ///: BEGIN:ONLY_INCLUDE_IN(build-flask) + 'http:', + ///: END:ONLY_INCLUDE_IN +]; + +/** + * Checks if the protocol of the origin is allowed. + * + * @param origin - The origin to check. + * @returns `true` if the protocol of the origin is allowed, `false` otherwise. + */ +export function isProtocolAllowed(origin: string): boolean { + try { + const url = new URL(origin); + return ALLOWED_PROTOCOLS.includes(url.protocol); + } catch (error) { + return false; + } +} + +/** + * Builds a function that returns the list of keyring methods an origin can + * call. + * + * @param controller - Reference to the `SubjectMetadataController`. + * @returns A function that returns the list of keyring methods an origin can + * call. + */ +export function keyringSnapPermissionsBuilder( + controller: SubjectMetadataController, +): (origin: string) => string[] { + return (origin: string) => { + if (origin === 'metamask') { + return METAMASK_ALLOWED_METHODS; + } + + const originMetadata = controller.getSubjectMetadata(origin); + if (originMetadata?.subjectType === SubjectType.Website) { + return isProtocolAllowed(origin) ? WEBSITE_ALLOWED_METHODS : []; + } + + return []; + }; +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/index.js b/app/scripts/lib/rpc-method-middleware/handlers/index.js index 126da76f9de4..a4c02fa71d66 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/index.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/index.js @@ -11,7 +11,6 @@ import watchAsset from './watch-asset'; import mmiSupported from './institutional/mmi-supported'; import mmiAuthenticate from './institutional/mmi-authenticate'; import mmiPortfolio from './institutional/mmi-portfolio'; -import mmiOpenSwaps from './institutional/mmi-open-swaps'; import mmiCheckIfTokenIsPresent from './institutional/mmi-check-if-token-is-present'; import mmiSetAccountAndNetwork from './institutional/mmi-set-account-and-network'; import mmiOpenAddHardwareWallet from './institutional/mmi-open-add-hardware-wallet'; @@ -30,7 +29,6 @@ const handlers = [ mmiAuthenticate, mmiSupported, mmiPortfolio, - mmiOpenSwaps, mmiCheckIfTokenIsPresent, mmiSetAccountAndNetwork, mmiOpenAddHardwareWallet, diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-swaps.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-swaps.js deleted file mode 100644 index f0cdbd026c24..000000000000 --- a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-swaps.js +++ /dev/null @@ -1,68 +0,0 @@ -import { ethErrors } from 'eth-rpc-errors'; -import { RPC_ALLOWED_ORIGINS } from '@metamask-institutional/rpc-allowlist'; -import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; - -const mmiOpenSwaps = { - methodNames: [MESSAGE_TYPE.MMI_OPEN_SWAPS], - implementation: mmiOpenSwapsHandler, - hookNames: { - handleMmiOpenSwaps: true, - }, -}; -export default mmiOpenSwaps; - -/** - * @typedef {object} MmiOpenSwapsOptions - * @property {Function} handleMmiOpenSwaps - The metmaskinsititutional_open_swaps method implementation. - */ - -/** - * @typedef {object} MmiOpenSwapsParam - * @property {string} service - The service to which we are authenticating, e.g. 'codefi-compliance' - * @property {object} token - The token used to authenticate - */ - -/** - * @param {import('json-rpc-engine').JsonRpcRequest} req - The JSON-RPC request object. - * @param {import('json-rpc-engine').JsonRpcResponse} res - The JSON-RPC response object. - * @param {Function} _next - The json-rpc-engine 'next' callback. - * @param {Function} end - The json-rpc-engine 'end' callback. - * @param {WatchAssetOptions} options - */ -async function mmiOpenSwapsHandler( - req, - res, - _next, - end, - { handleMmiOpenSwaps }, -) { - try { - let validUrl = false; - // if (!RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].includes(req.origin)) { - RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].forEach((regexp) => { - // eslint-disable-next-line require-unicode-regexp - if (regexp.test(req.origin)) { - validUrl = true; - } - }); - // eslint-disable-next-line no-negated-condition - if (!validUrl) { - throw new Error('Unauthorized'); - } - - if (!req.params?.[0] || typeof req.params[0] !== 'object') { - return end( - ethErrors.rpc.invalidParams({ - message: `Expected single, object parameter. Received:\n${JSON.stringify( - req.params, - )}`, - }), - ); - } - const { address, network } = req.params[0]; - res.result = await handleMmiOpenSwaps(req.origin, address, network); - return end(); - } catch (error) { - return end(error); - } -} 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 2f7fb5d7ab5b..6bce7d7dca18 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js @@ -40,6 +40,7 @@ async function watchAssetHandler( const { params: { options: asset, type }, origin, + networkClientId, } = req; const { tokenId } = asset; @@ -56,7 +57,7 @@ async function watchAssetHandler( ); } - await handleWatchAssetRequest(asset, type, origin); + await handleWatchAssetRequest({ asset, type, origin, networkClientId }); res.result = true; return end(); } catch (error) { 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 index 5af3ad1bd937..eebe8a470ead 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.test.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.test.js @@ -21,6 +21,7 @@ describe('watchAssetHandler', () => { type: ERC721, }, origin: 'testOrigin', + networkClientId: 'networkClientId1', }; const res = { @@ -31,11 +32,12 @@ describe('watchAssetHandler', () => { handleWatchAssetRequest: mockHandleWatchAssetRequest, }); - expect(mockHandleWatchAssetRequest).toHaveBeenCalledWith( - req.params.options, - req.params.type, - req.origin, - ); + expect(mockHandleWatchAssetRequest).toHaveBeenCalledWith({ + asset: req.params.options, + type: req.params.type, + origin: req.origin, + networkClientId: req.networkClientId, + }); expect(res.result).toStrictEqual(true); expect(mockEnd).toHaveBeenCalledWith(); }); @@ -51,6 +53,7 @@ describe('watchAssetHandler', () => { type: ERC20, }, origin: 'testOrigin', + networkClientId: 'networkClientId1', }; const res = { @@ -61,11 +64,12 @@ describe('watchAssetHandler', () => { handleWatchAssetRequest: mockHandleWatchAssetRequest, }); - expect(mockHandleWatchAssetRequest).toHaveBeenCalledWith( - req.params.options, - req.params.type, - req.origin, - ); + expect(mockHandleWatchAssetRequest).toHaveBeenCalledWith({ + asset: req.params.options, + type: req.params.type, + origin: req.origin, + networkClientId: req.networkClientId, + }); expect(res.result).toStrictEqual(true); expect(mockEnd).toHaveBeenCalledWith(); }); diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 2b908647deea..e17409291b79 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -69,7 +69,6 @@ export const SENTRY_BACKGROUND_STATE = { qrHardware: true, recoveryPhraseReminderHasBeenShown: true, recoveryPhraseReminderLastShown: true, - serviceWorkerLastActiveTime: true, showBetaHeader: true, showProductTour: true, showTestnetMessageInDropdown: true, @@ -128,7 +127,6 @@ export const SENTRY_BACKGROUND_STATE = { }, NetworkController: { networkConfigurations: false, - networkId: true, networksMetadata: true, providerConfig: { chainId: true, @@ -155,6 +153,12 @@ export const SENTRY_BACKGROUND_STATE = { onboardingTabs: false, seedPhraseBackedUp: true, }, + PPOMController: { + chainStatus: true, + securityAlertsEnabled: false, + storageMetadata: [], + versionInfo: [], + }, PermissionController: { subjects: false, }, @@ -320,6 +324,10 @@ export const SENTRY_UI_STATE = { pendingTokens: false, welcomeScreenSeen: true, useSafeChainsListValidation: true, + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + addSnapAccountEnabled: false, + snapsAddSnapAccountModalDismissed: false, + ///: END:ONLY_INCLUDE_IN }, unconnectedAccount: true, }; diff --git a/app/scripts/lib/snap-keyring/metrics.test.ts b/app/scripts/lib/snap-keyring/metrics.test.ts new file mode 100644 index 000000000000..af817abefff2 --- /dev/null +++ b/app/scripts/lib/snap-keyring/metrics.test.ts @@ -0,0 +1,103 @@ +import { getSnapAndHardwareInfoForMetrics } from './metrics'; + +describe('getSnapAndHardwareInfoForMetrics', () => { + let getSelectedAddress: jest.Mock; + let getAccountType: jest.Mock; + let getDeviceModel: jest.Mock; + let messenger; + + beforeEach(() => { + getSelectedAddress = jest.fn(); + getAccountType = jest.fn(); + getDeviceModel = jest.fn(); + messenger = { + call: jest.fn(), + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return an empty object if no messenger is provided', async () => { + const result = await getSnapAndHardwareInfoForMetrics( + getSelectedAddress, + getAccountType, + getDeviceModel, + // @ts-expect-error - We're testing the case where messenger is null + null, + ); + expect(result).toEqual({}); + }); + + it('should call the appropriate functions with the correct arguments when the keyring exposes the listAccounts method', async () => { + getSelectedAddress.mockReturnValue('0x123'); + getAccountType.mockResolvedValue('accountType'); + getDeviceModel.mockResolvedValue('deviceModel'); + messenger.call + .mockResolvedValueOnce({ + listAccounts: () => [ + { address: '0x123', metadata: { snap: { id: 'snapId' } } }, + ], + }) + .mockResolvedValueOnce({ id: 'snapId', version: 'snapVersion' }); + + const result = await getSnapAndHardwareInfoForMetrics( + getSelectedAddress, + getAccountType, + getDeviceModel, + messenger, + ); + + expect(getSelectedAddress).toHaveBeenCalled(); + expect(getAccountType).toHaveBeenCalledWith('0x123'); + expect(getDeviceModel).toHaveBeenCalledWith('0x123'); + expect(messenger.call).toHaveBeenCalledWith( + 'KeyringController:getKeyringForAccount', + '0x123', + ); + expect(messenger.call).toHaveBeenCalledWith('SnapController:get', 'snapId'); + expect(result).toEqual({ + account_type: 'accountType', + device_model: 'deviceModel', + account_hardware_type: undefined, + account_snap_type: 'snapId', + account_snap_version: 'snapVersion', + }); + }); + + it('should call the appropriate functions with the correct arguments when the keyring does not have the listAccounts method', async () => { + getSelectedAddress.mockReturnValue('0x123'); + getAccountType.mockResolvedValue('accountType'); + getDeviceModel.mockResolvedValue('deviceModel'); + messenger.call + .mockResolvedValueOnce({}) + .mockResolvedValueOnce({ id: 'snapId', version: 'snapVersion' }); + + const result = await getSnapAndHardwareInfoForMetrics( + getSelectedAddress, + getAccountType, + getDeviceModel, + messenger, + ); + + expect(getSelectedAddress).toHaveBeenCalled(); + expect(getAccountType).toHaveBeenCalledWith('0x123'); + expect(getDeviceModel).toHaveBeenCalledWith('0x123'); + expect(messenger.call).toHaveBeenCalledWith( + 'KeyringController:getKeyringForAccount', + '0x123', + ); + expect(messenger.call).toHaveBeenCalledWith( + 'SnapController:get', + undefined, + ); + expect(result).toEqual({ + account_type: 'accountType', + device_model: 'deviceModel', + account_hardware_type: undefined, + account_snap_type: 'snapId', + account_snap_version: 'snapVersion', + }); + }); +}); diff --git a/app/scripts/lib/snap-keyring/metrics.ts b/app/scripts/lib/snap-keyring/metrics.ts new file mode 100644 index 000000000000..9036b8529435 --- /dev/null +++ b/app/scripts/lib/snap-keyring/metrics.ts @@ -0,0 +1,74 @@ +import { RestrictedControllerMessenger } from '@metamask/base-controller'; +import { KeyringControllerGetKeyringForAccountAction } from '@metamask/keyring-controller'; +import { GetSnap } from '@metamask/snaps-controllers'; +import { Snap } from '@metamask/snaps-utils'; + +type AllowedActions = GetSnap | KeyringControllerGetKeyringForAccountAction; + +export type SnapAndHardwareMessenger = RestrictedControllerMessenger< + 'SnapAndHardwareMessenger', + AllowedActions, + never, + AllowedActions['type'], + never +>; + +export async function getSnapAndHardwareInfoForMetrics( + getSelectedAddress: () => string, + getAccountType: (address: string) => Promise, + getDeviceModel: (address: string) => Promise, + messenger: SnapAndHardwareMessenger, +) { + // If it's coming from a unit test, there's no messenger + // Will fix this in a future PR and add proper unit tests + if (!messenger) { + return {}; + } + + const selectedAddress = getSelectedAddress(); + + const keyring: any = await getKeyringForAccount(selectedAddress); + + const account = await getAccountFromAddress(selectedAddress); + + const snap: Snap = (await messenger.call( + 'SnapController:get', + account?.metadata.snap.id, + )) as Snap; + + async function getAccountFromAddress(address: string) { + const lowercaseAddress = address.toLowerCase(); + + if (keyring.listAccounts) { + const accounts = await keyring.listAccounts(); + return accounts.find( + (_account: any) => _account.address.toLowerCase() === lowercaseAddress, + ); + } + + return undefined; + } + + async function getHardwareWalletType() { + if (keyring?.type?.includes('Hardware')) { + return keyring.type; + } + + return undefined; + } + + async function getKeyringForAccount(address: string) { + return await messenger.call( + 'KeyringController:getKeyringForAccount', + address, + ); + } + + return { + account_type: await getAccountType(selectedAddress), + device_model: await getDeviceModel(selectedAddress), + account_hardware_type: await getHardwareWalletType(), + account_snap_type: snap?.id, + account_snap_version: snap?.version, + }; +} diff --git a/app/scripts/lib/snap-keyring/snap-keyring.ts b/app/scripts/lib/snap-keyring/snap-keyring.ts index 26ab295cffe1..92c1de692cc8 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.ts @@ -5,9 +5,13 @@ import type { ResultComponent, } from '@metamask/approval-controller'; import type { KeyringController } from '@metamask/keyring-controller'; +import { PhishingController } from '@metamask/phishing-controller'; +import browser from 'webextension-polyfill'; import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; import { t } from '../../translate'; import MetamaskController from '../../metamask-controller'; +import PreferencesController from '../../controllers/preferences'; +import { isBlockedUrl } from './utils/isBlockedUrl'; /** * Get the addresses of the accounts managed by a given Snap. @@ -29,7 +33,9 @@ export const getAccountsBySnapId = async ( * * @param getSnapController - A function that retrieves the Snap Controller instance. * @param getApprovalController - A function that retrieves the Approval Controller instance. - * @param getCoreKeyringController - A function that retrieves the Core Keyring Controller instance. + * @param getKeyringController - A function that retrieves the Keyring Controller instance. + * @param getPreferencesController - A function that retrieves the Preferences Controller instance. + * @param getPhishingController - A function that retrieves the Phishing Controller instance * @param removeAccountHelper - A function to help remove an account based on its address. * @returns The constructed SnapKeyring builder instance with the following methods: * - `saveState`: Persists all keyrings in the keyring controller. @@ -39,20 +45,45 @@ export const getAccountsBySnapId = async ( export const snapKeyringBuilder = ( getSnapController: () => SnapController, getApprovalController: () => ApprovalController, - getCoreKeyringController: () => KeyringController, + getKeyringController: () => KeyringController, + getPreferencesController: () => PreferencesController, + getPhishingController: () => PhishingController, removeAccountHelper: (address: string) => Promise, ) => { const builder = (() => { return new SnapKeyring(getSnapController() as any, { addressExists: async (address) => { - const addresses = await getCoreKeyringController().getAccounts(); + const addresses = await getKeyringController().getAccounts(); return addresses.includes(address.toLowerCase()); }, + redirectUser: async (snapId: string, url: string, message: string) => { + // Either url or message must be defined + if (url.length > 0 || message.length > 0) { + const isBlocked = await isBlockedUrl(url, getPhishingController()); + + const confirmationResult: boolean = + (await getApprovalController().addAndShowApprovalRequest({ + origin: snapId, + requestData: { url, message, isBlockedUrl: isBlocked }, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect, + })) as boolean; + + if (confirmationResult && url.length > 0) { + browser.tabs.create({ url }); + } else { + console.log('User refused snap account redirection to:', url); + } + } else { + console.log( + 'Error occurred when redirecting snap account. url or message must be defined', + ); + } + }, saveState: async () => { - await getCoreKeyringController().persistAllKeyrings(); + await getKeyringController().persistAllKeyrings(); }, addAccount: async ( - _address: string, + address: string, origin: string, handleUserInput: (accepted: boolean) => Promise, ) => { @@ -75,7 +106,8 @@ export const snapKeyringBuilder = ( if (confirmationResult) { try { await handleUserInput(confirmationResult); - await getCoreKeyringController().persistAllKeyrings(); + await getKeyringController().persistAllKeyrings(); + getPreferencesController().setSelectedAddress(address); await getApprovalController().success({ message: t('snapAccountCreated') ?? 'Your account is ready!', header: [snapAuthorshipHeader], @@ -127,7 +159,7 @@ export const snapKeyringBuilder = ( try { await removeAccountHelper(address); await handleUserInput(confirmationResult); - await getCoreKeyringController().persistAllKeyrings(); + await getKeyringController().persistAllKeyrings(); await getApprovalController().success({ message: t('snapAccountRemoved') ?? 'Account removed', header: [snapAuthorshipHeader], diff --git a/app/scripts/lib/snap-keyring/utils/isBlockedUrl.test.ts b/app/scripts/lib/snap-keyring/utils/isBlockedUrl.test.ts new file mode 100644 index 000000000000..0ac199636d67 --- /dev/null +++ b/app/scripts/lib/snap-keyring/utils/isBlockedUrl.test.ts @@ -0,0 +1,38 @@ +import { ListNames, PhishingController } from '@metamask/phishing-controller'; +import { isBlockedUrl } from './isBlockedUrl'; + +describe('isBlockedUrl', () => { + const phishingController = new PhishingController( + {}, + { + phishingLists: [ + { + blocklist: ['https://metamask.test'], + allowlist: [], + fuzzylist: [], + tolerance: 0, + version: 1, + lastUpdated: 0, + name: ListNames.MetaMask, + }, + ], + }, + ); + + it.each([ + ['http://metamask.io', false], + ['https://metamask.io', false], + ['https://metamask.test', true], + ['sftp://metamask.io', true], + ['', true], + ['1', true], + [undefined, true], + [null, true], + [1, true], + [0, true], + [-1, true], + ])('"%s" is blocked: %s', async (url: any, expected: boolean) => { + const result = await isBlockedUrl(url, phishingController); + expect(result).toEqual(expected); + }); +}); diff --git a/app/scripts/lib/snap-keyring/utils/isBlockedUrl.ts b/app/scripts/lib/snap-keyring/utils/isBlockedUrl.ts new file mode 100644 index 000000000000..8a4f527a1bf7 --- /dev/null +++ b/app/scripts/lib/snap-keyring/utils/isBlockedUrl.ts @@ -0,0 +1,32 @@ +import { PhishingController } from '@metamask/phishing-controller'; +import { isProtocolAllowed } from '../../keyring-snaps-permissions'; + +/** + * Checks whether a given URL is blocked due to not using HTTPS or being + * recognized as a phishing URL. + * + * @param url - The URL to check. + * @param phishingController - An instance of PhishingController to verify + * against known phishing URLs. + * @returns Returns a promise which resolves to `true` if the URL is blocked + * either due to using an insecure protocol (not HTTPS) or being recognized as + * a phishing URL. Otherwise, resolves to `false`. + */ +export const isBlockedUrl = async ( + url: string, + phishingController: PhishingController, +): Promise => { + try { + // check if the URL is HTTPS + if (!isProtocolAllowed(url)) { + return true; + } + + // check if the url is in the phishing list + await phishingController.maybeUpdateState(); + return phishingController.test(url).result; + } catch (error) { + console.error('Invalid URL passed into snap-keyring:', error); + return false; + } +}; diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index a9c42df7a659..9a1b37f6a4ca 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -17,8 +17,6 @@ export function setupMultiplex(connectionStream) { * https://github.com/MetaMask/object-multiplex/blob/280385401de84f57ef57054d92cfeb8361ef2680/src/ObjectMultiplex.ts#L63 */ mux.ignoreStream(EXTENSION_MESSAGES.CONNECTION_READY); - mux.ignoreStream('ACK_KEEP_ALIVE_MESSAGE'); - mux.ignoreStream('WORKER_KEEP_ALIVE_MESSAGE'); pump(connectionStream, mux, connectionStream, (err) => { if (err) { console.error(err); diff --git a/app/scripts/lib/transaction-metrics.test.ts b/app/scripts/lib/transaction-metrics.test.ts new file mode 100644 index 000000000000..8634e3201465 --- /dev/null +++ b/app/scripts/lib/transaction-metrics.test.ts @@ -0,0 +1,673 @@ +import { Provider } from '@metamask/network-controller'; +import { + createTestProviderTools, + getTestAccounts, +} from '../../../test/stub/provider'; +import { ORIGIN_METAMASK } from '../../../shared/constants/app'; +import { + TransactionType, + TransactionStatus, + AssetType, + TokenStandard, + TransactionMetaMetricsEvent, +} from '../../../shared/constants/transaction'; +import { + MetaMetricsTransactionEventSource, + MetaMetricsEventCategory, +} from '../../../shared/constants/metametrics'; +import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../shared/lib/transactions-controller-utils'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import { BlockaidReason } from '../../../shared/constants/security-provider'; +///: END:ONLY_INCLUDE_IN(blockaid) +import { + handleTransactionAdded, + handleTransactionApproved, + handleTransactionDropped, + handleTransactionFinalized, + handleTransactionRejected, + handleTransactionSubmitted, + METRICS_STATUS_FAILED, +} from './transaction-metrics'; + +const providerResultStub = { + eth_getCode: '0x123', +}; +const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + networkId: '5', + chainId: '5', +}); + +jest.mock('./snap-keyring/metrics', () => { + return { + getSnapAndHardwareInfoForMetrics: jest.fn().mockResolvedValue({ + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + }), + }; +}); + +const mockTransactionMetricsRequest = { + createEventFragment: jest.fn(), + finalizeEventFragment: jest.fn(), + getEventFragmentById: jest.fn(), + updateEventFragment: jest.fn(), + getAccountType: jest.fn(), + getDeviceModel: jest.fn(), + getEIP1559GasFeeEstimates: jest.fn(), + getSelectedAddress: jest.fn(), + getTokenStandardAndDetails: jest.fn(), + getTransaction: jest.fn(), + snapAndHardwareMessenger: jest.fn() as any, + provider: provider as Provider, +}; + +describe('Transaction metrics', () => { + let fromAccount, + mockChainId, + mockNetworkId, + mockTransactionMeta, + mockActionId; + + beforeEach(() => { + fromAccount = getTestAccounts()[0]; + mockChainId = '5'; + mockNetworkId = '5'; + mockActionId = '2'; + mockTransactionMeta = { + id: '1', + status: TransactionStatus.unapproved, + txParams: { + from: fromAccount.address, + to: '0x1678a085c290ebd122dc42cba69373b5953b831d', + gasPrice: '0x77359400', + gas: '0x7b0d', + nonce: '0x4b', + }, + type: TransactionType.simpleSend, + origin: ORIGIN_METAMASK, + chainId: mockChainId, + time: 1624408066355, + metamaskNetworkId: mockNetworkId, + defaultGasEstimates: { + gas: '0x7b0d', + gasPrice: '0x77359400', + }, + securityProviderResponse: { + flagAsDangerous: 0, + }, + }; + + jest.clearAllMocks(); + }); + + describe('handleTransactionAdded', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionAdded(mockTransactionMetricsRequest, {} as any); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + }); + + it('should create event fragment', async () => { + await handleTransactionAdded(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta as any, + actionId: mockActionId, + }); + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + failureEvent: TransactionMetaMetricsEvent.rejected, + initialEvent: TransactionMetaMetricsEvent.added, + successEvent: TransactionMetaMetricsEvent.approved, + uniqueIdentifier: 'transaction-added-1', + persist: true, + properties: { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }, + sensitiveProperties: { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }, + }); + }); + }); + + describe('handleTransactionApproved', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionApproved(mockTransactionMetricsRequest, {} as any); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + + it('should create, update, finalize event fragment', async () => { + await handleTransactionApproved(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta as any, + actionId: mockActionId, + }); + + const expectedUniqueId = 'transaction-added-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + }); + + describe('handleTransactionFinalized', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionFinalized( + mockTransactionMetricsRequest, + {} as any, + ); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + + it('should create, update, finalize event fragment', async () => { + mockTransactionMeta.txReceipt = { + gasUsed: '0x123', + status: '0x0', + }; + mockTransactionMeta.submittedTime = 123; + + await handleTransactionFinalized(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + completion_time: expect.any(String), + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + gas_used: '0.000000291', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + status: METRICS_STATUS_FAILED, + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + + it('should append error to event properties', async () => { + const mockErrorMessage = 'Unexpected error'; + + await handleTransactionFinalized(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta, + actionId: mockActionId, + error: mockErrorMessage, + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + error: mockErrorMessage, + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + }); + + describe('handleTransactionDropped', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionDropped(mockTransactionMetricsRequest, {} as any); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + + it('should create, update, finalize event fragment', async () => { + await handleTransactionDropped(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-submitted-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + dropped: true, + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: 'other', + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId); + }); + }); + + describe('handleTransactionRejected', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionRejected(mockTransactionMetricsRequest, {} as any); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + + it('should create, update, finalize event fragment', async () => { + await handleTransactionRejected(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta, + actionId: mockActionId, + } as any); + + const expectedUniqueId = 'transaction-added-1'; + const expectedProperties = { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }; + + const expectedSensitiveProperties = { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }; + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + uniqueIdentifier: expectedUniqueId, + persist: true, + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }); + + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.updateEventFragment).toBeCalledWith( + expectedUniqueId, + { + properties: expectedProperties, + sensitiveProperties: expectedSensitiveProperties, + }, + ); + + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledTimes(1); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).toBeCalledWith(expectedUniqueId, { + abandoned: true, + }); + }); + }); + + describe('handleTransactionSubmitted', () => { + it('should return if transaction meta is not defined', async () => { + await handleTransactionSubmitted( + mockTransactionMetricsRequest, + {} as any, + ); + expect( + mockTransactionMetricsRequest.createEventFragment, + ).not.toBeCalled(); + }); + + it('should only create event fragment', async () => { + await handleTransactionSubmitted(mockTransactionMetricsRequest, { + transactionMeta: mockTransactionMeta as any, + actionId: mockActionId, + }); + + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledTimes( + 1, + ); + expect(mockTransactionMetricsRequest.createEventFragment).toBeCalledWith({ + actionId: mockActionId, + category: MetaMetricsEventCategory.Transactions, + initialEvent: TransactionMetaMetricsEvent.submitted, + successEvent: TransactionMetaMetricsEvent.finalized, + uniqueIdentifier: 'transaction-submitted-1', + persist: true, + properties: { + account_snap_type: 'snaptype', + account_snap_version: 'snapversion', + account_type: undefined, + asset_type: AssetType.native, + chain_id: mockChainId, + device_model: undefined, + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + network: mockNetworkId, + referrer: ORIGIN_METAMASK, + security_alert_reason: BlockaidReason.notApplicable, + security_alert_response: BlockaidReason.notApplicable, + source: MetaMetricsTransactionEventSource.User, + status: 'unapproved', + token_standard: TokenStandard.none, + transaction_speed_up: false, + transaction_type: TransactionType.simpleSend, + ui_customizations: null, + }, + sensitiveProperties: { + default_gas: '0.000031501', + default_gas_price: '2', + first_seen: 1624408066355, + gas_limit: '0x7b0d', + gas_price: '2', + transaction_contract_method: undefined, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + transaction_replaced: undefined, + }, + }); + + expect( + mockTransactionMetricsRequest.updateEventFragment, + ).not.toBeCalled(); + expect( + mockTransactionMetricsRequest.finalizeEventFragment, + ).not.toBeCalled(); + }); + }); +}); diff --git a/app/scripts/lib/transaction-metrics.ts b/app/scripts/lib/transaction-metrics.ts new file mode 100644 index 000000000000..01e149bc45a6 --- /dev/null +++ b/app/scripts/lib/transaction-metrics.ts @@ -0,0 +1,912 @@ +import { isHexString } from 'ethereumjs-util'; +import EthQuery from 'eth-query'; +import { BigNumber } from 'bignumber.js'; +import type { Provider } from '@metamask/network-controller'; +import { FetchGasFeeEstimateOptions } from '@metamask/gas-fee-controller'; + +import { ORIGIN_METAMASK } from '../../../shared/constants/app'; +import { + determineTransactionAssetType, + isEIP1559Transaction, +} from '../../../shared/modules/transaction.utils'; +import { hexWEIToDecGWEI } from '../../../shared/modules/conversion.utils'; +import { + TransactionType, + TokenStandard, + TransactionApprovalAmountType, + TransactionMetaMetricsEvent, + TransactionMeta, +} from '../../../shared/constants/transaction'; +import { + MetaMetricsEventCategory, + MetaMetricsEventFragment, + MetaMetricsPageObject, + MetaMetricsReferrerObject, +} from '../../../shared/constants/metametrics'; +import { GasRecommendations } from '../../../shared/constants/gas'; +import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../shared/lib/transactions-controller-utils'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import { + BlockaidReason, + BlockaidResultType, +} from '../../../shared/constants/security-provider'; +///: END:ONLY_INCLUDE_IN +import { + getSnapAndHardwareInfoForMetrics, + type SnapAndHardwareMessenger, +} from './snap-keyring/metrics'; + +export const METRICS_STATUS_FAILED = 'failed on-chain'; + +export type TransactionMetricsRequest = { + createEventFragment: ( + options: MetaMetricsEventFragment, + ) => MetaMetricsEventFragment; + finalizeEventFragment: ( + fragmentId: string, + options?: { + abandoned?: boolean; + page?: MetaMetricsPageObject; + referrer?: MetaMetricsReferrerObject; + }, + ) => void; + getEventFragmentById: (fragmentId: string) => MetaMetricsEventFragment; + updateEventFragment: ( + fragmentId: string, + payload: Partial, + ) => void; + getAccountType: ( + address: string, + ) => Promise<'hardware' | 'imported' | 'MetaMask'>; + getDeviceModel: ( + address: string, + ) => Promise<'ledger' | 'lattice' | 'N/A' | string>; + // According to the type GasFeeState returned from getEIP1559GasFeeEstimates + // doesn't include some properties used in buildEventFragmentProperties, + // hence returning any here to avoid type errors. + getEIP1559GasFeeEstimates(options?: FetchGasFeeEstimateOptions): Promise; + getSelectedAddress: () => string; + getTokenStandardAndDetails: () => { + decimals?: string; + balance?: string; + symbol?: string; + standard?: TokenStandard; + }; + getTransaction: (transactionId: string) => TransactionMeta; + snapAndHardwareMessenger: SnapAndHardwareMessenger; + provider: Provider; +}; + +type TransactionEventPayload = { + transactionMeta: TransactionMeta; + actionId?: string; + error?: string; +}; + +/** + * This function is called when a transaction is added to the controller. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionAdded = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + const { properties, sensitiveProperties } = + await buildEventFragmentProperties({ + transactionEventPayload, + transactionMetricsRequest, + }); + + createTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.added, + transactionEventPayload, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); +}; + +/** + * This function is called when a transaction is approved by the user. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionApproved = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + + await createUpdateFinalizeTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.approved, + transactionEventPayload, + transactionMetricsRequest, + }); +}; + +/** + * This function is called when a transaction is finalized. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + * @param transactionEventPayload.error - The error message if the transaction failed + */ +export const handleTransactionFinalized = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + + const extraParams = {} as Record; + if (transactionEventPayload.error) { + // This is a failed transaction + extraParams.error = transactionEventPayload.error; + } else { + const { transactionMeta } = transactionEventPayload; + const { txReceipt } = transactionMeta; + + extraParams.gas_used = txReceipt.gasUsed; + + const { submittedTime } = transactionMeta; + + if (submittedTime) { + extraParams.completion_time = getTransactionCompletionTime(submittedTime); + } + + if (txReceipt.status === '0x0') { + extraParams.status = METRICS_STATUS_FAILED; + } + } + + await createUpdateFinalizeTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.finalized, + extraParams, + transactionEventPayload, + transactionMetricsRequest, + }); +}; + +/** + * This function is called when a transaction is dropped. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionDropped = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + + const extraParams = { + dropped: true, + }; + + await createUpdateFinalizeTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.finalized, + extraParams, + transactionEventPayload, + transactionMetricsRequest, + }); +}; + +/** + * This function is called when a transaction is rejected by the user. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionRejected = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + + await createUpdateFinalizeTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.rejected, + transactionEventPayload, + transactionMetricsRequest, + }); +}; + +/** + * This function is called when a transaction is submitted to the network. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param transactionEventPayload - The event payload + * @param transactionEventPayload.transactionMeta - The transaction meta object + */ +export const handleTransactionSubmitted = async ( + transactionMetricsRequest: TransactionMetricsRequest, + transactionEventPayload: TransactionEventPayload, +) => { + if (!transactionEventPayload.transactionMeta) { + return; + } + const { properties, sensitiveProperties } = + await buildEventFragmentProperties({ + transactionEventPayload, + transactionMetricsRequest, + }); + + createTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.submitted, + transactionEventPayload, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); +}; + +/** + * UI needs this specific create function in order to be sure that event fragment exists when updating transaction gas values. + * + * @param transactionMetricsRequest - Contains controller actions needed to create/update/finalize event fragments + * @param eventPayload - The event payload + * @param eventPayload.actionId - The action id of the transaction + * @param eventPayload.transactionId - The transaction id + */ +export const createTransactionEventFragmentWithTxId = async ( + transactionMetricsRequest: TransactionMetricsRequest, + { + transactionId, + actionId, + }: { + transactionId: string; + actionId: string; + }, +) => { + const transactionMeta = + transactionMetricsRequest.getTransaction(transactionId); + + transactionMeta.actionId = actionId; + + const { properties, sensitiveProperties } = + await buildEventFragmentProperties({ + transactionEventPayload: { + transactionMeta, + }, + transactionMetricsRequest, + }); + createTransactionEventFragment({ + eventName: TransactionMetaMetricsEvent.approved, + transactionEventPayload: { + actionId: transactionMeta.actionId, + transactionMeta, + }, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); +}; + +function createTransactionEventFragment({ + eventName, + transactionEventPayload: { transactionMeta, actionId }, + transactionMetricsRequest, + payload, +}: { + eventName: TransactionMetaMetricsEvent; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; + payload: any; +}) { + if ( + hasFragment( + transactionMetricsRequest.getEventFragmentById, + eventName, + transactionMeta, + ) + ) { + return; + } + + const uniqueIdentifier = getUniqueId(eventName, transactionMeta.id); + + switch (eventName) { + // When a transaction is added to the controller, we know that the user + // will be presented with a confirmation screen. The user will then + // either confirm or reject that transaction. Each has an associated + // event we want to track. While we don't necessarily need an event + // fragment to model this, having one allows us to record additional + // properties onto the event from the UI. For example, when the user + // edits the transactions gas params we can record that property and + // then get analytics on the number of transactions in which gas edits + // occur. + case TransactionMetaMetricsEvent.added: + transactionMetricsRequest.createEventFragment({ + category: MetaMetricsEventCategory.Transactions, + initialEvent: TransactionMetaMetricsEvent.added, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + actionId, + uniqueIdentifier, + persist: true, + }); + break; + // If for some reason an approval or rejection occurs without the added + // fragment existing in memory, we create the added fragment but without + // the initialEvent firing. This is to prevent possible duplication of + // events. A good example why this might occur is if the user had + // unapproved transactions in memory when updating to the version that + // includes this change. A migration would have also helped here but this + // implementation hardens against other possible bugs where a fragment + // does not exist. + case TransactionMetaMetricsEvent.approved: + case TransactionMetaMetricsEvent.rejected: + transactionMetricsRequest.createEventFragment({ + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.approved, + failureEvent: TransactionMetaMetricsEvent.rejected, + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + actionId, + uniqueIdentifier, + persist: true, + }); + break; + // When a transaction is submitted it will always result in updating + // to a finalized state (dropped, failed, confirmed) -- eventually. + // However having a fragment started at this stage allows augmenting + // analytics data with user interactions such as speeding up and + // canceling the transactions. From this controllers perspective a new + // transaction with a new id is generated for speed up and cancel + // transactions, but from the UI we could augment the previous ID with + // supplemental data to show user intent. Such as when they open the + // cancel UI but don't submit. We can record that this happened and add + // properties to the transaction event. + case TransactionMetaMetricsEvent.submitted: + transactionMetricsRequest.createEventFragment({ + category: MetaMetricsEventCategory.Transactions, + initialEvent: TransactionMetaMetricsEvent.submitted, + successEvent: TransactionMetaMetricsEvent.finalized, + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + actionId, + uniqueIdentifier, + persist: true, + }); + break; + // If for some reason a transaction is finalized without the submitted + // fragment existing in memory, we create the submitted fragment but + // without the initialEvent firing. This is to prevent possible + // duplication of events. A good example why this might occur is if th + // user had pending transactions in memory when updating to the version + // that includes this change. A migration would have also helped here but + // this implementation hardens against other possible bugs where a + // fragment does not exist. + case TransactionMetaMetricsEvent.finalized: + transactionMetricsRequest.createEventFragment({ + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + actionId, + uniqueIdentifier, + persist: true, + }); + break; + default: + break; + } +} + +function updateTransactionEventFragment({ + eventName, + transactionEventPayload: { transactionMeta }, + transactionMetricsRequest, + payload, +}: { + eventName: TransactionMetaMetricsEvent; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; + payload: any; +}) { + const uniqueId = getUniqueId(eventName, transactionMeta.id); + + switch (eventName) { + case TransactionMetaMetricsEvent.approved: + transactionMetricsRequest.updateEventFragment(uniqueId, { + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + }); + break; + + case TransactionMetaMetricsEvent.rejected: + transactionMetricsRequest.updateEventFragment(uniqueId, { + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + }); + break; + + case TransactionMetaMetricsEvent.finalized: + transactionMetricsRequest.updateEventFragment(uniqueId, { + properties: payload.properties, + sensitiveProperties: payload.sensitiveProperties, + }); + break; + default: + break; + } +} + +function finalizeTransactionEventFragment({ + eventName, + transactionMetricsRequest, + transactionEventPayload: { transactionMeta }, +}: { + eventName: TransactionMetaMetricsEvent; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; +}) { + const uniqueId = getUniqueId(eventName, transactionMeta.id); + + switch (eventName) { + case TransactionMetaMetricsEvent.approved: + transactionMetricsRequest.finalizeEventFragment(uniqueId); + break; + + case TransactionMetaMetricsEvent.rejected: + transactionMetricsRequest.finalizeEventFragment(uniqueId, { + abandoned: true, + }); + break; + + case TransactionMetaMetricsEvent.finalized: + transactionMetricsRequest.finalizeEventFragment(uniqueId); + break; + default: + break; + } +} + +async function createUpdateFinalizeTransactionEventFragment({ + eventName, + transactionEventPayload, + transactionMetricsRequest, + extraParams = {}, +}: { + eventName: TransactionMetaMetricsEvent; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; + extraParams?: Record; +}) { + const { properties, sensitiveProperties } = + await buildEventFragmentProperties({ + transactionEventPayload, + transactionMetricsRequest, + extraParams, + }); + + createTransactionEventFragment({ + eventName, + transactionEventPayload, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); + + updateTransactionEventFragment({ + eventName, + transactionEventPayload, + transactionMetricsRequest, + payload: { + properties, + sensitiveProperties, + }, + }); + + finalizeTransactionEventFragment({ + eventName, + transactionEventPayload, + transactionMetricsRequest, + }); +} + +function hasFragment( + getEventFragmentById: (arg0: string) => any, + eventName: TransactionMetaMetricsEvent, + transactionMeta: TransactionMeta, +) { + const uniqueId = getUniqueId(eventName, transactionMeta.id); + const fragment = getEventFragmentById(uniqueId); + return typeof fragment !== 'undefined'; +} + +function getUniqueId( + eventName: TransactionMetaMetricsEvent, + transactionId: string, +) { + const isSubmitted = [ + TransactionMetaMetricsEvent.finalized, + TransactionMetaMetricsEvent.submitted, + ].includes(eventName); + const uniqueIdentifier = `transaction-${ + isSubmitted ? 'submitted' : 'added' + }-${transactionId}`; + + return uniqueIdentifier; +} + +async function buildEventFragmentProperties({ + transactionEventPayload: { transactionMeta }, + transactionMetricsRequest, + extraParams = {}, +}: { + extraParams?: Record; + transactionEventPayload: TransactionEventPayload; + transactionMetricsRequest: TransactionMetricsRequest; +}) { + const { + type, + time, + status, + chainId, + origin: referrer, + txParams: { + gasPrice, + gas: gasLimit, + maxFeePerGas, + maxPriorityFeePerGas, + estimateSuggested, + estimateUsed, + }, + defaultGasEstimates, + originalType, + replacedById, + metamaskNetworkId: network, + customTokenAmount, + dappProposedTokenAmount, + currentTokenBalance, + originalApprovalAmount, + finalApprovalAmount, + contractMethodName, + securityProviderResponse, + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + securityAlertResponse, + ///: END:ONLY_INCLUDE_IN + } = transactionMeta; + + const query = new EthQuery(transactionMetricsRequest.provider); + const source = referrer === ORIGIN_METAMASK ? 'user' : 'dapp'; + + const { assetType, tokenStandard } = await determineTransactionAssetType( + transactionMeta, + query, + transactionMetricsRequest.getTokenStandardAndDetails, + ); + + const gasParams = {} as Record; + + if (isEIP1559Transaction(transactionMeta)) { + gasParams.max_fee_per_gas = maxFeePerGas; + gasParams.max_priority_fee_per_gas = maxPriorityFeePerGas; + } else { + gasParams.gas_price = gasPrice; + } + + if (defaultGasEstimates) { + const { estimateType } = defaultGasEstimates; + if (estimateType) { + gasParams.default_estimate = estimateType; + let defaultMaxFeePerGas = + transactionMeta.defaultGasEstimates.maxFeePerGas; + let defaultMaxPriorityFeePerGas = + transactionMeta.defaultGasEstimates.maxPriorityFeePerGas; + + if ( + [ + GasRecommendations.low, + GasRecommendations.medium, + GasRecommendations.high, + ].includes(estimateType) + ) { + const { gasFeeEstimates } = + await transactionMetricsRequest.getEIP1559GasFeeEstimates(); + if (gasFeeEstimates?.[estimateType]?.suggestedMaxFeePerGas) { + defaultMaxFeePerGas = + gasFeeEstimates[estimateType]?.suggestedMaxFeePerGas; + gasParams.default_max_fee_per_gas = defaultMaxFeePerGas; + } + if (gasFeeEstimates?.[estimateType]?.suggestedMaxPriorityFeePerGas) { + defaultMaxPriorityFeePerGas = + gasFeeEstimates[estimateType]?.suggestedMaxPriorityFeePerGas; + gasParams.default_max_priority_fee_per_gas = + defaultMaxPriorityFeePerGas; + } + } + } + + if (transactionMeta.defaultGasEstimates.gas) { + gasParams.default_gas = transactionMeta.defaultGasEstimates.gas; + } + if (transactionMeta.defaultGasEstimates.gasPrice) { + gasParams.default_gas_price = + transactionMeta.defaultGasEstimates.gasPrice; + } + } + + if (estimateSuggested) { + gasParams.estimate_suggested = estimateSuggested; + } + + if (estimateUsed) { + gasParams.estimate_used = estimateUsed; + } + + if (extraParams?.gas_used) { + gasParams.gas_used = extraParams.gas_used; + } + + const gasParamsInGwei = getGasValuesInGWEI(gasParams); + + let eip1559Version = '0'; + if (transactionMeta.txParams.maxFeePerGas) { + eip1559Version = '2'; + } + + const contractInteractionTypes = [ + TransactionType.contractInteraction, + TransactionType.tokenMethodApprove, + TransactionType.tokenMethodSafeTransferFrom, + TransactionType.tokenMethodSetApprovalForAll, + TransactionType.tokenMethodTransfer, + TransactionType.tokenMethodTransferFrom, + TransactionType.smart, + TransactionType.swap, + TransactionType.swapApproval, + ].includes(type); + + const contractMethodNames = { + APPROVE: 'Approve', + }; + + let transactionApprovalAmountType; + let transactionContractMethod; + let transactionApprovalAmountVsProposedRatio; + let transactionApprovalAmountVsBalanceRatio; + let transactionType = TransactionType.simpleSend; + if (type === TransactionType.cancel) { + transactionType = TransactionType.cancel; + } else if (type === TransactionType.retry) { + transactionType = originalType; + } else if (type === TransactionType.deployContract) { + transactionType = TransactionType.deployContract; + } else if (contractInteractionTypes) { + transactionType = TransactionType.contractInteraction; + transactionContractMethod = contractMethodName; + if ( + transactionContractMethod === contractMethodNames.APPROVE && + tokenStandard === TokenStandard.ERC20 + ) { + if (dappProposedTokenAmount === '0' || customTokenAmount === '0') { + transactionApprovalAmountType = TransactionApprovalAmountType.revoke; + } else if ( + customTokenAmount && + customTokenAmount !== dappProposedTokenAmount + ) { + transactionApprovalAmountType = TransactionApprovalAmountType.custom; + } else if (dappProposedTokenAmount) { + transactionApprovalAmountType = + TransactionApprovalAmountType.dappProposed; + } + transactionApprovalAmountVsProposedRatio = + allowanceAmountInRelationToDappProposedValue( + transactionApprovalAmountType, + originalApprovalAmount, + finalApprovalAmount, + ); + transactionApprovalAmountVsBalanceRatio = + allowanceAmountInRelationToTokenBalance( + transactionApprovalAmountType, + dappProposedTokenAmount, + currentTokenBalance, + ); + } + } + + const replacedTransactionMeta = transactionMetricsRequest.getTransaction( + replacedById as string, + ); + + const TRANSACTION_REPLACEMENT_METHODS = { + RETRY: TransactionType.retry, + CANCEL: TransactionType.cancel, + SAME_NONCE: 'other', + }; + + let transactionReplaced; + if (extraParams?.dropped) { + transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.SAME_NONCE; + if (replacedTransactionMeta?.type === TransactionType.cancel) { + transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.CANCEL; + } else if (replacedTransactionMeta?.type === TransactionType.retry) { + transactionReplaced = TRANSACTION_REPLACEMENT_METHODS.RETRY; + } + } + + let uiCustomizations; + + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + if (securityAlertResponse?.result_type === BlockaidResultType.Failed) { + uiCustomizations = ['security_alert_failed']; + } else { + ///: END:ONLY_INCLUDE_IN + // eslint-disable-next-line no-lonely-if + if (securityProviderResponse?.flagAsDangerous === 1) { + uiCustomizations = ['flagged_as_malicious']; + } else if (securityProviderResponse?.flagAsDangerous === 2) { + uiCustomizations = ['flagged_as_safety_unknown']; + } else { + uiCustomizations = null; + } + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + } + ///: END:ONLY_INCLUDE_IN + + /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ + let properties = { + chain_id: chainId, + referrer, + source, + status, + network, + eip_1559_version: eip1559Version, + gas_edit_type: 'none', + gas_edit_attempted: 'none', + account_type: await transactionMetricsRequest.getAccountType( + transactionMetricsRequest.getSelectedAddress(), + ), + device_model: await transactionMetricsRequest.getDeviceModel( + transactionMetricsRequest.getSelectedAddress(), + ), + asset_type: assetType, + token_standard: tokenStandard, + transaction_type: transactionType, + transaction_speed_up: type === TransactionType.retry, + ui_customizations: uiCustomizations, + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + security_alert_response: + securityAlertResponse?.result_type ?? BlockaidResultType.NotApplicable, + security_alert_reason: + securityAlertResponse?.reason ?? BlockaidReason.notApplicable, + ///: END:ONLY_INCLUDE_IN + } as Record; + + const snapAndHardwareInfo = await getSnapAndHardwareInfoForMetrics( + transactionMetricsRequest.getSelectedAddress, + transactionMetricsRequest.getAccountType, + transactionMetricsRequest.getDeviceModel, + transactionMetricsRequest.snapAndHardwareMessenger, + ); + Object.assign(properties, snapAndHardwareInfo); + + if (transactionContractMethod === contractMethodNames.APPROVE) { + properties = { + ...properties, + transaction_approval_amount_type: transactionApprovalAmountType, + }; + } + + let sensitiveProperties = { + transaction_envelope_type: isEIP1559Transaction(transactionMeta) + ? TRANSACTION_ENVELOPE_TYPE_NAMES.FEE_MARKET + : TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + first_seen: time, + gas_limit: gasLimit, + transaction_contract_method: transactionContractMethod, + transaction_replaced: transactionReplaced, + ...extraParams, + ...gasParamsInGwei, + } as Record; + + if (transactionContractMethod === contractMethodNames.APPROVE) { + sensitiveProperties = { + ...sensitiveProperties, + transaction_approval_amount_vs_balance_ratio: + transactionApprovalAmountVsBalanceRatio, + transaction_approval_amount_vs_proposed_ratio: + transactionApprovalAmountVsProposedRatio, + }; + } + + return { properties, sensitiveProperties }; +} + +function getGasValuesInGWEI(gasParams: Record) { + const gasValuesInGwei = {} as Record; + for (const param in gasParams) { + if (isHexString(gasParams[param])) { + gasValuesInGwei[param] = hexWEIToDecGWEI(gasParams[param]); + } else { + gasValuesInGwei[param] = gasParams[param]; + } + } + return gasValuesInGwei; +} + +function getTransactionCompletionTime(submittedTime: number) { + return Math.round((Date.now() - submittedTime) / 1000).toString(); +} + +/** + * The allowance amount in relation to the dapp proposed amount for specific token + * + * @param transactionApprovalAmountType - The transaction approval amount type + * @param originalApprovalAmount - The original approval amount is the originally dapp proposed token amount + * @param finalApprovalAmount - The final approval amount is the chosen amount which will be the same as the + * originally dapp proposed token amount if the user does not edit the amount or will be a custom token amount set by the user + */ +function allowanceAmountInRelationToDappProposedValue( + transactionApprovalAmountType?: TransactionApprovalAmountType, + originalApprovalAmount?: string, + finalApprovalAmount?: string, +) { + if ( + transactionApprovalAmountType === TransactionApprovalAmountType.custom && + originalApprovalAmount && + finalApprovalAmount + ) { + return `${new BigNumber(originalApprovalAmount, 10) + .div(finalApprovalAmount, 10) + .times(100) + .round(2)}`; + } + return null; +} + +/** + * The allowance amount in relation to the balance for that specific token + * + * @param transactionApprovalAmountType - The transaction approval amount type + * @param dappProposedTokenAmount - The dapp proposed token amount + * @param currentTokenBalance - The balance of the token that is being send + */ +function allowanceAmountInRelationToTokenBalance( + transactionApprovalAmountType?: TransactionApprovalAmountType, + dappProposedTokenAmount?: string, + currentTokenBalance?: string, +) { + if ( + (transactionApprovalAmountType === TransactionApprovalAmountType.custom || + transactionApprovalAmountType === + TransactionApprovalAmountType.dappProposed) && + dappProposedTokenAmount && + currentTokenBalance + ) { + return `${new BigNumber(dappProposedTokenAmount, 16) + .div(currentTokenBalance, 10) + .times(100) + .round(2)}`; + } + return null; +} diff --git a/app/scripts/lib/util.test.js b/app/scripts/lib/util.test.js index 3b70d9be7a73..906beff93ee5 100644 --- a/app/scripts/lib/util.test.js +++ b/app/scripts/lib/util.test.js @@ -272,7 +272,6 @@ describe('app utils', () => { origin: 'other', chainId: '0x5', time: 1624408066355, - metamaskNetworkId: '5', hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7', r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e', s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff', @@ -317,7 +316,6 @@ describe('app utils', () => { origin: 'other', chainId: '0x5', time: 1624408066355, - metamaskNetworkId: '5', hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7', r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e', s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff', diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index 51b69a12008f..cee3455bfe5c 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -177,7 +177,7 @@ describe('MetaMaskController', function () { ]), Promise.resolve(1).then(() => { keyringControllerState1 = JSON.stringify( - metamaskController.coreKeyringController.state, + metamaskController.keyringController.state, ); metamaskController.importAccountWithStrategy('privateKey', [ importPrivkey, @@ -185,7 +185,7 @@ describe('MetaMaskController', function () { }), Promise.resolve(2).then(() => { keyringControllerState2 = JSON.stringify( - metamaskController.coreKeyringController.state, + metamaskController.keyringController.state, ); }), ]); @@ -244,11 +244,41 @@ describe('MetaMaskController', function () { ); const [token1, token2] = await Promise.all([ - metamaskController.getApi().addToken(address, symbol, decimals), - metamaskController.getApi().addToken(address, symbol, decimals), + metamaskController.getApi().addToken({ address, symbol, decimals }), + metamaskController.getApi().addToken({ address, symbol, decimals }), ]); assert.deepEqual(token1, token2); }); + + it('networkClientId is used when provided', async function () { + const supportsInterfaceStub = sinon + .stub() + .returns(Promise.resolve(false)); + sinon + .stub(metamaskController.tokensController, '_createEthersContract') + .callsFake(() => + Promise.resolve({ supportsInterface: supportsInterfaceStub }), + ); + sinon + .stub(metamaskController.tokensController, 'getNetworkClientById') + .callsFake(() => ({ + configuration: { + chainId: '0xa', + }, + })); + + await metamaskController.getApi().addToken({ + address, + symbol, + decimals, + networkClientId: 'networkClientId1', + }); + assert.strictEqual( + metamaskController.tokensController.getNetworkClientById.getCall(0) + .args[0], + 'networkClientId1', + ); + }); }); describe('#removePermissionsFor', function () { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 27ebd056f8f3..45dc581b7649 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -9,6 +9,8 @@ import { debounce, ///: BEGIN:ONLY_INCLUDE_IN(snaps) throttle, + memoize, + wrap, ///: END:ONLY_INCLUDE_IN } from 'lodash'; import { keyringBuilderFactory } from '@metamask/eth-keyring-controller'; @@ -47,17 +49,15 @@ import { GasFeeController } from '@metamask/gas-fee-controller'; import { PermissionController, PermissionsRequestNotFoundError, -} from '@metamask/permission-controller'; -import { SubjectMetadataController, SubjectType, -} from '@metamask/subject-metadata-controller'; +} from '@metamask/permission-controller'; import SmartTransactionsController from '@metamask/smart-transactions-controller'; import { SelectedNetworkController, createSelectedNetworkMiddleware, } from '@metamask/selected-network-controller'; -import { LoggingController } from '@metamask/logging-controller'; +import { LoggingController, LogType } from '@metamask/logging-controller'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { encrypt, decrypt } from '@metamask/browser-passworder'; @@ -141,9 +141,6 @@ 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, @@ -156,6 +153,7 @@ import { MetaMetricsEventCategory, MetaMetricsEventName, } from '../../shared/constants/metametrics'; +import { LOG_EVENT } from '../../shared/constants/logs'; import { getTokenIdParam, @@ -167,10 +165,23 @@ import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens'; import { getTokenValueParam } from '../../shared/lib/metamask-controller-utils'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { hexToDecimal } from '../../shared/modules/conversion.utils'; -import { ACTION_QUEUE_METRICS_E2E_TEST } from '../../shared/constants/test-flags'; +import { convertNetworkId } from '../../shared/modules/network.utils'; +import { + handleTransactionAdded, + handleTransactionApproved, + handleTransactionFinalized, + handleTransactionDropped, + handleTransactionRejected, + handleTransactionSubmitted, + createTransactionEventFragmentWithTxId, +} from './lib/transaction-metrics'; +///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) +import { keyringSnapPermissionsBuilder } from './lib/keyring-snaps-permissions'; +///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(petnames) import { SnapsNameProvider } from './lib/SnapsNameProvider'; +import { AddressBookPetnamesBridge } from './lib/AddressBookPetnamesBridge'; ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(blockaid) @@ -286,6 +297,13 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger = new ControllerMessenger(); + this.loggingController = new LoggingController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'LoggingController', + }), + state: initState.LoggingController, + }); + // instance of a class that wraps the extension's storage local API. this.localStoreApiWrapper = opts.localStore; @@ -306,8 +324,18 @@ export default class MetamaskController extends EventEmitter { this.createVaultMutex = new Mutex(); this.extension.runtime.onInstalled.addListener((details) => { - if (details.reason === 'update' && version === '8.1.0') { - this.platform.openExtensionInBrowser(); + if (details.reason === 'update') { + if (version === '8.1.0') { + this.platform.openExtensionInBrowser(); + } + this.loggingController.add({ + type: LogType.GenericLog, + data: { + event: LOG_EVENT.VERSION_UPDATE, + previousVersion: details.previousVersion, + version, + }, + }); } }); @@ -351,13 +379,6 @@ export default class MetamaskController extends EventEmitter { ], }); - this.loggingController = new LoggingController({ - messenger: this.controllerMessenger.getRestricted({ - name: 'LoggingController', - }), - state: initState.LoggingController, - }); - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) this.mmiConfigurationController = new MmiConfigurationController({ initState: initState.MmiConfigurationController, @@ -424,6 +445,14 @@ export default class MetamaskController extends EventEmitter { this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker; + // TODO: Delete when ready to remove `networkVersion` from provider object + this.deprecatedNetworkId = null; + this.updateDeprecatedNetworkId(); + networkControllerMessenger.subscribe( + 'NetworkController:networkDidChange', + () => this.updateDeprecatedNetworkId(), + ); + const tokenListMessenger = this.controllerMessenger.getRestricted({ name: 'TokenListController', allowedEvents: [ @@ -453,25 +482,6 @@ export default class MetamaskController extends EventEmitter { networkConfigurations: this.networkController.state.networkConfigurations, }); - const tokensControllerMessenger = this.controllerMessenger.getRestricted({ - name: 'TokensController', - allowedActions: ['ApprovalController:addRequest'], - allowedEvents: ['NetworkController:stateChange'], - }); - this.tokensController = new TokensController({ - messenger: tokensControllerMessenger, - chainId: this.networkController.state.providerConfig.chainId, - onPreferencesStateChange: this.preferencesController.store.subscribe.bind( - this.preferencesController.store, - ), - onNetworkStateChange: networkControllerMessenger.subscribe.bind( - networkControllerMessenger, - 'NetworkController:stateChange', - ), - config: { provider: this.provider }, - state: initState.TokensController, - }); - this.assetsContractController = new AssetsContractController( { chainId: this.networkController.state.providerConfig.chainId, @@ -493,6 +503,9 @@ export default class MetamaskController extends EventEmitter { return cb(networkState); }, ), + getNetworkClientById: this.networkController.getNetworkClientById.bind( + this.networkController, + ), }, { provider: this.provider, @@ -500,6 +513,36 @@ export default class MetamaskController extends EventEmitter { initState.AssetsContractController, ); + const tokensControllerMessenger = this.controllerMessenger.getRestricted({ + name: 'TokensController', + allowedActions: ['ApprovalController:addRequest'], + allowedEvents: ['NetworkController:stateChange'], + }); + this.tokensController = new TokensController({ + messenger: tokensControllerMessenger, + chainId: this.networkController.state.providerConfig.chainId, + onPreferencesStateChange: this.preferencesController.store.subscribe.bind( + this.preferencesController.store, + ), + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), + onTokenListStateChange: (listener) => + this.controllerMessenger.subscribe( + `${this.tokenListController.name}:stateChange`, + listener, + ), + getNetworkClientById: this.networkController.getNetworkClientById.bind( + this.networkController, + ), + getERC20TokenName: this.assetsContractController.getERC20TokenName.bind( + this.assetsContractController, + ), + config: { provider: this.provider }, + state: initState.TokensController, + }); + const nftControllerMessenger = this.controllerMessenger.getRestricted({ name: 'NftController', allowedActions: [`${this.approvalController.name}:addRequest`], @@ -551,6 +594,9 @@ export default class MetamaskController extends EventEmitter { source, }, }), + getNetworkClientById: this.networkController.getNetworkClientById.bind( + this.networkController, + ), }, {}, initState.NftController, @@ -708,15 +754,9 @@ export default class MetamaskController extends EventEmitter { 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, + allAnnouncements: UI_NOTIFICATIONS, state: initState.AnnouncementController, }); @@ -724,38 +764,36 @@ export default class MetamaskController extends EventEmitter { this.tokenRatesController = new TokenRatesController( { chainId: this.networkController.state.providerConfig.chainId, + ticker: this.networkController.state.providerConfig.ticker, + selectedAddress: this.preferencesController.getSelectedAddress(), onTokensStateChange: (listener) => this.tokensController.subscribe(listener), - onCurrencyRateStateChange: (listener) => - this.controllerMessenger.subscribe( - `${this.currencyRateController.name}:stateChange`, - listener, - ), onNetworkStateChange: networkControllerMessenger.subscribe.bind( networkControllerMessenger, 'NetworkController:stateChange', ), + onPreferencesStateChange: + this.preferencesController.store.subscribe.bind( + this.preferencesController.store, + ), }, - { - disabled: - !this.preferencesController.store.getState().useCurrencyRateCheck, - }, + {}, initState.TokenRatesController, ); + if (this.preferencesController.store.getState().useCurrencyRateCheck) { + this.tokenRatesController.start(); + } + this.preferencesController.store.subscribe( previousValueComparator((prevState, currState) => { const { useCurrencyRateCheck: prevUseCurrencyRateCheck } = prevState; const { useCurrencyRateCheck: currUseCurrencyRateCheck } = currState; if (currUseCurrencyRateCheck && !prevUseCurrencyRateCheck) { this.currencyRateController.start(); - this.tokenRatesController.configure( - { disabled: false }, - false, - false, - ); + this.tokenRatesController.start(); } else if (!currUseCurrencyRateCheck && prevUseCurrencyRateCheck) { this.currencyRateController.stop(); - this.tokenRatesController.configure({ disabled: true }, false, false); + this.tokenRatesController.stop(); } }, this.preferencesController.store.getState()), ); @@ -857,13 +895,17 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) const getSnapController = () => this.snapController; const getApprovalController = () => this.approvalController; - const getCoreKeyringController = () => this.coreKeyringController; + const getKeyringController = () => this.keyringController; + const getPreferencesController = () => this.preferencesController; + const getPhishingController = () => this.phishingController; additionalKeyrings.push( snapKeyringBuilder( getSnapController, getApprovalController, - getCoreKeyringController, + getKeyringController, + getPreferencesController, + getPhishingController, (address) => this.removeAccount(address), ), ); @@ -892,7 +934,7 @@ export default class MetamaskController extends EventEmitter { ], }); - this.coreKeyringController = new KeyringController({ + this.keyringController = new KeyringController({ keyringBuilders: additionalKeyrings, state: initState.KeyringController, encryptor: opts.encryptor || undefined, @@ -949,8 +991,8 @@ export default class MetamaskController extends EventEmitter { permissionSpecifications: { ...getPermissionSpecifications({ getIdentities, - getAllAccounts: this.coreKeyringController.getAccounts.bind( - this.coreKeyringController, + getAllAccounts: this.keyringController.getAccounts.bind( + this.keyringController, ), captureKeyringTypesWithMissingIdentities: ( identities = {}, @@ -961,7 +1003,7 @@ export default class MetamaskController extends EventEmitter { ); const keyringTypesWithMissingIdentities = accountsMissingIdentities.map((address) => - this.coreKeyringController.getAccountKeyringType(address), + this.keyringController.getAccountKeyringType(address), ); const identitiesCount = Object.keys(identities || {}).length; @@ -1228,7 +1270,6 @@ export default class MetamaskController extends EventEmitter { ), getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind(this), - getNetworkId: () => this.networkController.state.networkId, getNetworkStatus: () => this.networkController.state.networksMetadata?.[ this.networkController.state.selectedNetworkClientId @@ -1240,44 +1281,24 @@ export default class MetamaskController extends EventEmitter { networkControllerMessenger.subscribe( 'NetworkController:stateChange', () => listener(), - ({ networkId }) => networkId, + (state) => state.providerConfig.chainId, ); }, getCurrentChainId: () => this.networkController.state.providerConfig.chainId, preferencesStore: this.preferencesController.store, txHistoryLimit: 60, - signTransaction: this.coreKeyringController.signTransaction.bind( - this.coreKeyringController, + signTransaction: this.keyringController.signTransaction.bind( + this.keyringController, ), provider: this.provider, blockTracker: this.blockTracker, - createEventFragment: this.metaMetricsController.createEventFragment.bind( - this.metaMetricsController, - ), - updateEventFragment: this.metaMetricsController.updateEventFragment.bind( - this.metaMetricsController, - ), - finalizeEventFragment: - this.metaMetricsController.finalizeEventFragment.bind( - this.metaMetricsController, - ), - getEventFragmentById: - this.metaMetricsController.getEventFragmentById.bind( - this.metaMetricsController, - ), - trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind( - this.metaMetricsController, - ), getParticipateInMetrics: () => this.metaMetricsController.state.participateInMetaMetrics, getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController), getExternalPendingTransactions: this.getExternalPendingTransactions.bind(this), - getAccountType: this.getAccountType.bind(this), - getDeviceModel: this.getDeviceModel.bind(this), - getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this), securityProviderRequest: this.securityProviderRequest.bind(this), ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) transactionUpdateController: this.transactionUpdateController, @@ -1292,6 +1313,39 @@ export default class MetamaskController extends EventEmitter { }), }); + const transactionMetricsRequest = this.getTransactionMetricsRequest(); + + this.txController.on( + 'transaction-added', + handleTransactionAdded.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-approved', + handleTransactionApproved.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-dropped', + handleTransactionDropped.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-finalized', + handleTransactionFinalized.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-rejected', + handleTransactionRejected.bind(null, transactionMetricsRequest), + ); + this.txController.on( + 'transaction-submitted', + handleTransactionSubmitted.bind(null, transactionMetricsRequest), + ); + this.txController.on('transaction-swap-failed', (payload) => + this.metaMetricsController.trackEvent(payload), + ); + this.txController.on('transaction-swap-finalized', (payload) => + this.metaMetricsController.trackEvent(payload), + ); + this.txController.on(`tx:status-update`, async (txId, status) => { if ( status === TransactionStatus.confirmed || @@ -1400,7 +1454,7 @@ export default class MetamaskController extends EventEmitter { `${this.approvalController.name}:addRequest`, `${this.approvalController.name}:acceptRequest`, `${this.approvalController.name}:rejectRequest`, - `${this.coreKeyringController.name}:decryptMessage`, + `${this.keyringController.name}:decryptMessage`, ], }), metricsEvent: this.metaMetricsController.trackEvent.bind( @@ -1418,13 +1472,12 @@ export default class MetamaskController extends EventEmitter { ], }), getEncryptionPublicKey: - this.coreKeyringController.getEncryptionPublicKey.bind( - this.coreKeyringController, - ), - getAccountKeyringType: - this.coreKeyringController.getAccountKeyringType.bind( - this.coreKeyringController, + this.keyringController.getEncryptionPublicKey.bind( + this.keyringController, ), + getAccountKeyringType: this.keyringController.getAccountKeyringType.bind( + this.keyringController, + ), getState: this.getState.bind(this), metricsEvent: this.metaMetricsController.trackEvent.bind( this.metaMetricsController, @@ -1436,9 +1489,10 @@ export default class MetamaskController extends EventEmitter { name: 'SignatureController', allowedActions: [ `${this.approvalController.name}:addRequest`, - `${this.coreKeyringController.name}:signMessage`, - `${this.coreKeyringController.name}:signPersonalMessage`, - `${this.coreKeyringController.name}:signTypedMessage`, + `${this.keyringController.name}:signMessage`, + `${this.keyringController.name}:signPersonalMessage`, + `${this.keyringController.name}:signTypedMessage`, + `${this.loggingController.name}:add`, ], }), isEthSignEnabled: () => @@ -1467,7 +1521,7 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) this.mmiController = new MMIController({ mmiConfigurationController: this.mmiConfigurationController, - keyringController: this.coreKeyringController, + keyringController: this.keyringController, txController: this.txController, securityProviderRequest: this.securityProviderRequest.bind(this), preferencesController: this.preferencesController, @@ -1537,6 +1591,9 @@ export default class MetamaskController extends EventEmitter { ); ///: BEGIN:ONLY_INCLUDE_IN(petnames) + const isExternalNameSourcesEnabled = () => + this.preferencesController.store.getState().useExternalNameSources; + this.nameController = new NameController({ messenger: this.controllerMessenger.getRestricted({ name: 'NameController', @@ -1548,9 +1605,9 @@ export default class MetamaskController extends EventEmitter { this.ensController, ), }), - new EtherscanNameProvider({}), - new TokenNameProvider({}), - new LensNameProvider(), + new EtherscanNameProvider({ isEnabled: isExternalNameSourcesEnabled }), + new TokenNameProvider({ isEnabled: isExternalNameSourcesEnabled }), + new LensNameProvider({ isEnabled: isExternalNameSourcesEnabled }), new SnapsNameProvider({ messenger: this.controllerMessenger.getRestricted({ name: 'SnapsNameProvider', @@ -1565,6 +1622,15 @@ export default class MetamaskController extends EventEmitter { ], state: initState.NameController, }); + + new AddressBookPetnamesBridge({ + addressBookController: this.addressBookController, + nameController: this.nameController, + messenger: this.controllerMessenger.getRestricted({ + name: 'AddressBookPetnamesBridge', + allowedEvents: ['NameController:stateChange'], + }), + }).init(); ///: END:ONLY_INCLUDE_IN this.txController.on('newSwapApproval', (txMeta) => { @@ -1595,25 +1661,6 @@ export default class MetamaskController extends EventEmitter { }, ); - if (isManifestV3 && globalThis.isFirstTimeProfileLoaded === undefined) { - const { serviceWorkerLastActiveTime } = - this.appStateController.store.getState(); - const metametricsPayload = { - category: MetaMetricsEventCategory.ServiceWorkers, - event: MetaMetricsEventName.ServiceWorkerRestarted, - properties: { - service_worker_restarted_time: - Date.now() - serviceWorkerLastActiveTime, - }, - }; - - try { - this.metaMetricsController.trackEvent(metametricsPayload); - } catch (e) { - log.warn('Failed to track service worker restart metric:', e); - } - } - this.metamaskMiddleware = createMetamaskMiddleware({ static: { eth_syncing: false, @@ -1733,7 +1780,7 @@ export default class MetamaskController extends EventEmitter { AppStateController: this.appStateController.store, AppMetadataController: this.appMetadataController.store, TransactionController: this.txController.store, - KeyringController: this.coreKeyringController, + KeyringController: this.keyringController, PreferencesController: this.preferencesController.store, MetaMetricsController: this.metaMetricsController.store, AddressBookController: this.addressBookController, @@ -1785,7 +1832,7 @@ export default class MetamaskController extends EventEmitter { AppMetadataController: this.appMetadataController.store, NetworkController: this.networkController, CachedBalancesController: this.cachedBalancesController.store, - KeyringController: this.coreKeyringController, + KeyringController: this.keyringController, PreferencesController: this.preferencesController.store, MetaMetricsController: this.metaMetricsController.store, AddressBookController: this.addressBookController, @@ -1889,6 +1936,48 @@ export default class MetamaskController extends EventEmitter { checkForMultipleVersionsRunning(); } + getTransactionMetricsRequest() { + const controllerActions = { + // Metametrics Actions + createEventFragment: this.metaMetricsController.createEventFragment.bind( + this.metaMetricsController, + ), + finalizeEventFragment: + this.metaMetricsController.finalizeEventFragment.bind( + this.metaMetricsController, + ), + getEventFragmentById: + this.metaMetricsController.getEventFragmentById.bind( + this.metaMetricsController, + ), + updateEventFragment: this.metaMetricsController.updateEventFragment.bind( + this.metaMetricsController, + ), + // Other dependencies + getAccountType: this.getAccountType.bind(this), + getDeviceModel: this.getDeviceModel.bind(this), + getEIP1559GasFeeEstimates: + this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController), + getSelectedAddress: () => + this.preferencesController.store.getState().selectedAddress, + getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this), + getTransaction: this.txController.txStateManager.getTransaction.bind( + this.txController, + ), + }; + return { + ...controllerActions, + snapAndHardwareMessenger: this.controllerMessenger.getRestricted({ + name: 'SnapAndHardwareMessenger', + allowedActions: [ + 'KeyringController:getKeyringForAccount', + 'SnapController:get', + ], + }), + provider: this.provider, + }; + } + triggerNetworkrequests() { this.accountTracker.start(); this.txController.startIncomingTransactionPolling(); @@ -1932,11 +2021,11 @@ export default class MetamaskController extends EventEmitter { * @returns {SnapKeyring} */ async getSnapKeyring() { - let [snapKeyring] = this.coreKeyringController.getKeyringsByType( + let [snapKeyring] = this.keyringController.getKeyringsByType( KeyringType.snap, ); if (!snapKeyring) { - snapKeyring = await this.coreKeyringController.addNewKeyring( + snapKeyring = await this.keyringController.addNewKeyring( KeyringType.snap, ); } @@ -1947,22 +2036,30 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(snaps) /** - * Tracks snaps export usage. Note: This function is throttled to 1 call per 60 seconds. + * Tracks snaps export usage. + * Note: This function is throttled to 1 call per 60 seconds per snap id + handler combination. * * @param {string} snapId - The ID of the snap the handler is being triggered on. * @param {string} handler - The handler to trigger on the snap for the request. */ - _trackSnapExportUsage = throttle( - (snapId, handler) => - this.metaMetricsController.trackEvent({ - event: MetaMetricsEventName.SnapExportUsed, - category: MetaMetricsEventCategory.Snaps, - properties: { - snap_id: snapId, - export: handler, - }, - }), - SECOND * 60, + _trackSnapExportUsage = wrap( + memoize( + () => + throttle( + (snapId, handler) => + this.metaMetricsController.trackEvent({ + event: MetaMetricsEventName.SnapExportUsed, + category: MetaMetricsEventCategory.Snaps, + properties: { + snap_id: snapId, + export: handler, + }, + }), + SECOND * 60, + ), + (snapId, handler) => `${snapId}${handler}`, + ), + (getFunc, ...args) => getFunc(...args)(...args), ); /** @@ -2207,7 +2304,12 @@ export default class MetamaskController extends EventEmitter { }, []); this.dismissNotifications(notificationIds); + }, + ); + this.controllerMessenger.subscribe( + `${this.snapController.name}:snapUninstalled`, + (truncatedSnap) => { this.metaMetricsController.trackEvent({ event: MetaMetricsEventName.SnapUninstalled, category: MetaMetricsEventCategory.Snaps, @@ -2230,28 +2332,27 @@ export default class MetamaskController extends EventEmitter { createPublicConfigStore() { // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore(); - const { networkController } = this; - // setup memStore subscription hooks - this.on('update', updatePublicConfigStore); - updatePublicConfigStore(this.getState()); + const selectPublicState = (chainId, { isUnlocked }) => { + return { + isUnlocked, + chainId, + networkVersion: this.deprecatedNetworkId ?? 'loading', + }; + }; - function updatePublicConfigStore(memState) { + const updatePublicConfigStore = (memState) => { const networkStatus = memState.networksMetadata[memState.selectedNetworkClientId]?.status; - const { chainId } = networkController.state.providerConfig; + const { chainId } = this.networkController.state.providerConfig; if (networkStatus === NetworkStatus.Available) { publicConfigStore.putState(selectPublicState(chainId, memState)); } - } + }; - function selectPublicState(chainId, { isUnlocked, networkId }) { - return { - isUnlocked, - chainId, - networkVersion: networkId ?? 'loading', - }; - } + // setup memStore subscription hooks + this.on('update', updatePublicConfigStore); + updatePublicConfigStore(this.getState()); return publicConfigStore; } @@ -2265,26 +2366,64 @@ export default class MetamaskController extends EventEmitter { async getProviderState(origin) { return { isUnlocked: this.isUnlocked(), - ...this.getProviderNetworkState(), accounts: await this.getPermittedAccounts(origin), + ...this.getProviderNetworkState(), }; } /** * Gets network state relevant for external providers. * - * @param {object} [memState] - The MetaMask memState. If not provided, - * this function will retrieve the most recent state. * @returns {object} An object with relevant network state properties. */ - getProviderNetworkState(memState) { - const { networkId } = memState || this.getState(); + getProviderNetworkState() { return { chainId: this.networkController.state.providerConfig.chainId, - networkVersion: networkId ?? 'loading', + networkVersion: this.deprecatedNetworkId ?? 'loading', }; } + /** + * TODO: Delete when ready to remove `networkVersion` from provider object + * Updates the `deprecatedNetworkId` value + */ + async updateDeprecatedNetworkId() { + try { + this.deprecatedNetworkId = await this.deprecatedGetNetworkId(); + } catch (error) { + console.error(error); + this.deprecatedNetworkId = null; + } + this._notifyChainChange(); + } + + /** + * TODO: Delete when ready to remove `networkVersion` from provider object + * Gets current networkId as returned by `net_version` + * + * @returns {string} The networkId for the current network or null on failure + * @throws Will throw if there is a problem getting the network version + */ + async deprecatedGetNetworkId() { + const ethQuery = this.controllerMessenger.call( + 'NetworkController:getEthQuery', + ); + + if (!ethQuery) { + throw new Error('Provider has not been initialized'); + } + + return new Promise((resolve, reject) => { + ethQuery.sendAsync({ method: 'net_version' }, (error, result) => { + if (error) { + reject(error); + } else { + resolve(convertNetworkId(result)); + } + }); + }); + } + //============================================================================= // EXPOSED TO THE UI SUBSYSTEM //============================================================================= @@ -2295,7 +2434,7 @@ export default class MetamaskController extends EventEmitter { * @returns {object} status */ getState() { - const { vault } = this.coreKeyringController.state; + const { vault } = this.keyringController.state; const isInitialized = Boolean(vault); const flatState = this.memStore.getFlatState(); @@ -2331,7 +2470,7 @@ export default class MetamaskController extends EventEmitter { addressBookController, alertController, appStateController, - coreKeyringController, + keyringController, nftController, nftDetectionController, currencyRateController, @@ -2351,6 +2490,7 @@ export default class MetamaskController extends EventEmitter { assetsContractController, backup, approvalController, + phishingController, } = this; return { @@ -2404,6 +2544,12 @@ export default class MetamaskController extends EventEmitter { preferencesController, ), ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(petnames) + setUseExternalNameSources: + preferencesController.setUseExternalNameSources.bind( + preferencesController, + ), + ///: END:ONLY_INCLUDE_IN setIpfsGateway: preferencesController.setIpfsGateway.bind( preferencesController, ), @@ -2457,17 +2603,15 @@ export default class MetamaskController extends EventEmitter { // qr hardware devices submitQRHardwareCryptoHDKey: - coreKeyringController.submitQRCryptoHDKey.bind(coreKeyringController), + keyringController.submitQRCryptoHDKey.bind(keyringController), submitQRHardwareCryptoAccount: - coreKeyringController.submitQRCryptoAccount.bind(coreKeyringController), - cancelSyncQRHardware: coreKeyringController.cancelQRSynchronization.bind( - coreKeyringController, - ), - submitQRHardwareSignature: coreKeyringController.submitQRSignature.bind( - coreKeyringController, - ), + keyringController.submitQRCryptoAccount.bind(keyringController), + cancelSyncQRHardware: + keyringController.cancelQRSynchronization.bind(keyringController), + submitQRHardwareSignature: + keyringController.submitQRSignature.bind(keyringController), cancelQRHardwareSignRequest: - coreKeyringController.cancelQRSignRequest.bind(coreKeyringController), + keyringController.cancelQRSignRequest.bind(keyringController), // vault management submitPassword: this.submitPassword.bind(this), @@ -2629,7 +2773,10 @@ export default class MetamaskController extends EventEmitter { addTransactionAndWaitForPublish: this.addTransactionAndWaitForPublish.bind(this), createTransactionEventFragment: - txController.createTransactionEventFragment.bind(txController), + createTransactionEventFragmentWithTxId.bind( + null, + this.getTransactionMetricsRequest(), + ), getTransactions: txController.getTransactions.bind(txController), updateEditableParams: @@ -2748,10 +2895,6 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(snaps) // snaps - removeSnapError: this.controllerMessenger.call.bind( - this.controllerMessenger, - 'SnapController:removeSnapError', - ), disableSnap: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:disable', @@ -2772,6 +2915,11 @@ export default class MetamaskController extends EventEmitter { dismissNotifications: this.dismissNotifications.bind(this), markNotificationsAsRead: this.markNotificationsAsRead.bind(this), updateCaveat: this.updateCaveat.bind(this), + getPhishingResult: async (website) => { + await phishingController.maybeUpdateState(); + + return phishingController.test(website); + }, ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) updateSnapRegistry: this.preferencesController.updateSnapRegistry.bind( @@ -2950,7 +3098,7 @@ export default class MetamaskController extends EventEmitter { async exportAccount(address, password) { await this.verifyPassword(password); - return this.coreKeyringController.exportAccount(password, address); + return this.keyringController.exportAccount(password, address); } async getTokenStandardAndDetails(address, userAddress, tokenId) { @@ -3047,11 +3195,11 @@ export default class MetamaskController extends EventEmitter { async createNewVaultAndKeychain(password) { const releaseLock = await this.createVaultMutex.acquire(); try { - const vault = await this.coreKeyringController.createNewVaultAndKeychain( + const vault = await this.keyringController.createNewVaultAndKeychain( password, ); - const accounts = await this.coreKeyringController.getAccounts(); + const accounts = await this.keyringController.getAccounts(); this.preferencesController.setAddresses(accounts); this.selectFirstIdentity(); @@ -3098,13 +3246,13 @@ export default class MetamaskController extends EventEmitter { this.txController.txStateManager.clearUnapprovedTxs(); // create new vault - const vault = await this.coreKeyringController.createNewVaultAndRestore( + const vault = await this.keyringController.createNewVaultAndRestore( password, this._convertMnemonicToWordlistIndices(seedPhraseAsBuffer), ); const ethQuery = new EthQuery(this.provider); - accounts = await this.coreKeyringController.getAccounts(); + accounts = await this.keyringController.getAccounts(); lastBalance = await this.getBalance( accounts[accounts.length - 1], ethQuery, @@ -3113,15 +3261,15 @@ export default class MetamaskController extends EventEmitter { // seek out the first zero balance while (lastBalance !== '0x0') { const { addedAccountAddress } = - await this.coreKeyringController.addNewAccount(accounts.length); - accounts = await this.coreKeyringController.getAccounts(); + await this.keyringController.addNewAccount(accounts.length); + accounts = await this.keyringController.getAccounts(); lastBalance = await this.getBalance(addedAccountAddress, ethQuery); } // remove extra zero balance account potentially created from seeking ahead if (accounts.length > 1 && lastBalance === '0x0') { await this.removeAccount(accounts[accounts.length - 1]); - accounts = await this.coreKeyringController.getAccounts(); + accounts = await this.keyringController.getAccounts(); } // This must be set as soon as possible to communicate to the @@ -3201,7 +3349,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} password - The user's password */ async submitPassword(password) { - await this.coreKeyringController.submitPassword(password); + await this.keyringController.submitPassword(password); ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) this.mmiController.onSubmitPassword(); @@ -3259,7 +3407,7 @@ export default class MetamaskController extends EventEmitter { const { loginToken, loginSalt } = await this.extension.storage.session.get(['loginToken', 'loginSalt']); if (loginToken && loginSalt) { - const { vault } = this.coreKeyringController.state; + const { vault } = this.keyringController.state; const jsonVault = JSON.parse(vault); @@ -3271,10 +3419,7 @@ export default class MetamaskController extends EventEmitter { return; } - await this.coreKeyringController.submitEncryptionKey( - loginToken, - loginSalt, - ); + await this.keyringController.submitEncryptionKey(loginToken, loginSalt); } } catch (e) { // If somehow this login token doesn't work properly, @@ -3294,7 +3439,7 @@ export default class MetamaskController extends EventEmitter { * @param {string} password - The user's password */ async verifyPassword(password) { - await this.coreKeyringController.verifyPassword(password); + await this.keyringController.verifyPassword(password); } /** @@ -3317,7 +3462,7 @@ export default class MetamaskController extends EventEmitter { * Gets the mnemonic of the user's primary keyring. */ getPrimaryKeyringMnemonic() { - const [keyring] = this.coreKeyringController.getKeyringsByType( + const [keyring] = this.keyringController.getKeyringsByType( KeyringType.hdKeyTree, ); if (!keyring.mnemonic) { @@ -3332,8 +3477,7 @@ export default class MetamaskController extends EventEmitter { const custodyType = this.custodyController.getCustodyTypeByAddress( toChecksumHexAddress(address), ); - const keyring = - this.coreKeyringController.getKeyringsByType(custodyType)[0]; + const keyring = this.keyringController.getKeyringsByType(custodyType)[0]; return keyring?.getAccountDetails(address) ? keyring : undefined; } ///: END:ONLY_INCLUDE_IN @@ -3370,11 +3514,9 @@ export default class MetamaskController extends EventEmitter { 'MetamaskController:getKeyringForDevice - Unknown device', ); } - let [keyring] = await this.coreKeyringController.getKeyringsByType( - keyringName, - ); + let [keyring] = await this.keyringController.getKeyringsByType(keyringName); if (!keyring) { - keyring = await this.coreKeyringController.addNewKeyring(keyringName); + keyring = await this.keyringController.addNewKeyring(keyringName); } if (hdPath && keyring.setHdPath) { keyring.setHdPath(hdPath); @@ -3428,7 +3570,7 @@ export default class MetamaskController extends EventEmitter { // Merge with existing accounts // and make sure addresses are not repeated - const oldAccounts = await this.coreKeyringController.getAccounts(); + const oldAccounts = await this.keyringController.getAccounts(); const accountsToTrack = [ ...new Set( oldAccounts.concat(accounts.map((a) => a.address.toLowerCase())), @@ -3464,13 +3606,13 @@ export default class MetamaskController extends EventEmitter { /** * Retrieves the keyring for the selected address and using the .type returns - * a subtype for the account. Either 'hardware', 'imported' or 'MetaMask'. + * a subtype for the account. Either 'hardware', 'imported', 'snap', or 'MetaMask'. * * @param {string} address - Address to retrieve keyring for - * @returns {'hardware' | 'imported' | 'MetaMask'} + * @returns {'hardware' | 'imported' | 'snap' | 'MetaMask'} */ async getAccountType(address) { - const keyringType = await this.coreKeyringController.getAccountKeyringType( + const keyringType = await this.keyringController.getAccountKeyringType( address, ); switch (keyringType) { @@ -3481,6 +3623,8 @@ export default class MetamaskController extends EventEmitter { return 'hardware'; case KeyringType.imported: return 'imported'; + case KeyringType.snap: + return 'snap'; default: return 'MetaMask'; } @@ -3489,15 +3633,13 @@ export default class MetamaskController extends EventEmitter { /** * Retrieves the keyring for the selected address and using the .type * determines if a more specific name for the device is available. Returns - * 'N/A' for non hardware wallets. + * undefined for non hardware wallets. * * @param {string} address - Address to retrieve keyring for - * @returns {'ledger' | 'lattice' | 'N/A' | string} + * @returns {'ledger' | 'lattice' | string | undefined} */ async getDeviceModel(address) { - const keyring = await this.coreKeyringController.getKeyringForAccount( - address, - ); + const keyring = await this.keyringController.getKeyringForAccount(address); switch (keyring.type) { case KeyringType.trezor: return keyring.getModel(); @@ -3510,7 +3652,7 @@ export default class MetamaskController extends EventEmitter { // TODO: get model after lattice keyring exposes method return HardwareDeviceNames.lattice; default: - return 'N/A'; + return undefined; } } @@ -3544,11 +3686,11 @@ export default class MetamaskController extends EventEmitter { const keyring = await this.getKeyringForDevice(deviceName, hdPath); keyring.setAccountToUnlock(index); - const oldAccounts = await this.coreKeyringController.getAccounts(); - const keyState = await this.coreKeyringController.addNewAccountForKeyring( + const oldAccounts = await this.keyringController.getAccounts(); + const keyState = await this.keyringController.addNewAccountForKeyring( keyring, ); - const newAccounts = await this.coreKeyringController.getAccounts(); + const newAccounts = await this.keyringController.getAccounts(); this.preferencesController.setAddresses(newAccounts); newAccounts.forEach((address) => { if (!oldAccounts.includes(address)) { @@ -3581,17 +3723,11 @@ export default class MetamaskController extends EventEmitter { * @returns {Promise} The address of the newly-created account. */ async addNewAccount(accountCount) { - const isActionMetricsQueueE2ETest = - this.appStateController.store.getState()[ACTION_QUEUE_METRICS_E2E_TEST]; - - if (process.env.IN_TEST && isActionMetricsQueueE2ETest) { - await new Promise((resolve) => setTimeout(resolve, 5_000)); - } - - const oldAccounts = await this.coreKeyringController.getAccounts(); + const oldAccounts = await this.keyringController.getAccounts(); - const { addedAccountAddress } = - await this.coreKeyringController.addNewAccount(accountCount); + const { addedAccountAddress } = await this.keyringController.addNewAccount( + accountCount, + ); if (!oldAccounts.includes(addedAccountAddress)) { this.preferencesController.setSelectedAddress(addedAccountAddress); @@ -3612,7 +3748,7 @@ export default class MetamaskController extends EventEmitter { */ async verifySeedPhrase() { return this._convertEnglishWordlistIndicesToCodepoints( - await this.coreKeyringController.verifySeedPhrase(), + await this.keyringController.verifySeedPhrase(), ); } @@ -3693,11 +3829,9 @@ export default class MetamaskController extends EventEmitter { this.custodyController.removeAccount(address); ///: END:ONLY_INCLUDE_IN(build-mmi) - const keyring = await this.coreKeyringController.getKeyringForAccount( - address, - ); + const keyring = await this.keyringController.getKeyringForAccount(address); // Remove account from the keyring - await this.coreKeyringController.removeAccount(address); + await this.keyringController.removeAccount(address); const updatedKeyringAccounts = keyring ? await keyring.getAccounts() : {}; if (updatedKeyringAccounts?.length === 0) { keyring.destroy?.(); @@ -3716,10 +3850,7 @@ export default class MetamaskController extends EventEmitter { */ async importAccountWithStrategy(strategy, args) { const { importedAccountAddress } = - await this.coreKeyringController.importAccountWithStrategy( - strategy, - args, - ); + await this.keyringController.importAccountWithStrategy(strategy, args); // set new account as selected this.preferencesController.setSelectedAddress(importedAccountAddress); } @@ -3848,10 +3979,14 @@ export default class MetamaskController extends EventEmitter { }); } - handleWatchAssetRequest = (asset, type, origin) => { + handleWatchAssetRequest = ({ asset, type, origin, networkClientId }) => { switch (type) { case ERC20: - return this.tokensController.watchAsset(asset, type); + return this.tokensController.watchAsset({ + asset, + type, + networkClientId, + }); case ERC721: case ERC1155: return this.nftController.watchNft(asset, type, origin); @@ -4224,6 +4359,18 @@ export default class MetamaskController extends EventEmitter { this.metaMetricsController.store, ), securityProviderRequest: this.securityProviderRequest.bind(this), + getSelectedAddress: this.preferencesController.getSelectedAddress.bind( + this.preferencesController, + ), + getAccountType: this.getAccountType.bind(this), + getDeviceModel: this.getDeviceModel.bind(this), + snapAndHardwareMessenger: this.controllerMessenger.getRestricted({ + name: 'SnapAndHardwareMessenger', + allowedActions: [ + 'KeyringController:getKeyringForAccount', + 'SnapController:get', + ], + }), }), ); @@ -4347,9 +4494,6 @@ export default class MetamaskController extends EventEmitter { handleMmiDashboardData: this.mmiController.handleMmiDashboardData.bind( this.mmiController, ), - handleMmiOpenSwaps: this.mmiController.handleMmiOpenSwaps.bind( - this.mmiController, - ), handleMmiSetAccountAndNetwork: this.mmiController.setAccountAndNetwork.bind(this.mmiController), handleMmiOpenAddHardwareWallet: @@ -4386,6 +4530,24 @@ export default class MetamaskController extends EventEmitter { 'SnapController:install', origin, ), + ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + hasPermission: this.permissionController.hasPermission.bind( + this.permissionController, + ), + getSnap: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:get', + ), + handleSnapRpcRequest: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapController:handleRequest', + ), + getAllowedKeyringMethods: keyringSnapPermissionsBuilder( + this.subjectMetadataController, + ), + ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(snaps) }), ); ///: END:ONLY_INCLUDE_IN @@ -4634,10 +4796,7 @@ export default class MetamaskController extends EventEmitter { */ _onStateUpdate(newState) { this.isClientOpenAndUnlocked = newState.isUnlocked && this._isClientOpen; - this.notifyAllConnections({ - method: NOTIFICATION_NAMES.chainChanged, - params: this.getProviderNetworkState(newState), - }); + this._notifyChainChange(); } // misc @@ -4655,7 +4814,7 @@ export default class MetamaskController extends EventEmitter { * @returns {boolean} Whether the extension is unlocked. */ isUnlocked() { - return this.coreKeyringController.state.isUnlocked; + return this.keyringController.state.isUnlocked; } //============================================================================= @@ -4845,7 +5004,7 @@ export default class MetamaskController extends EventEmitter { * Locks MetaMask */ setLocked() { - const [trezorKeyring] = this.coreKeyringController.getKeyringsByType( + const [trezorKeyring] = this.keyringController.getKeyringsByType( KeyringType.trezor, ); if (trezorKeyring) { @@ -4856,7 +5015,7 @@ export default class MetamaskController extends EventEmitter { this.clearLoginArtifacts(); } - return this.coreKeyringController.setLocked(); + return this.keyringController.setLocked(); } removePermissionsFor = (subjects) => { @@ -4990,4 +5149,11 @@ export default class MetamaskController extends EventEmitter { this.permissionLogController.updateAccountsHistory(origin, newAccounts); } + + _notifyChainChange() { + this.notifyAllConnections({ + method: NOTIFICATION_NAMES.chainChanged, + params: this.getProviderNetworkState(), + }); + } } diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 40ad6d240527..9f71713a7323 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -16,12 +16,14 @@ import { } from '@metamask/phishing-controller'; import { NetworkType } from '@metamask/controller-utils'; import { ControllerMessenger } from '@metamask/base-controller'; +import { LoggingController, LogType } from '@metamask/logging-controller'; import { TransactionStatus } from '../../shared/constants/transaction'; import createTxMeta from '../../test/lib/createTxMeta'; import { NETWORK_TYPES } from '../../shared/constants/network'; import { createTestProviderTools } from '../../test/stub/provider'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; import { KeyringType } from '../../shared/constants/keyring'; +import { LOG_EVENT } from '../../shared/constants/logs'; import { deferredPromise } from './lib/util'; import TransactionController from './controllers/transactions'; import MetaMaskController from './metamask-controller'; @@ -47,7 +49,7 @@ const browserPolyfillMock = { }, }, alarms: { - getAll: jest.fn(), + getAll: jest.fn(() => Promise.resolve([])), create: jest.fn(), clear: jest.fn(), onAlarm: { @@ -103,7 +105,7 @@ jest.mock('../../shared/modules/mv3.utils', () => ({ }, })); -const currentNetworkId = '5'; +const currentChainId = '0x5'; const DEFAULT_LABEL = 'Account 1'; const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'; @@ -318,11 +320,11 @@ describe('MetaMaskController', () => { }); jest.spyOn( - metamaskController.coreKeyringController, + metamaskController.keyringController, 'createNewVaultAndKeychain', ); jest.spyOn( - metamaskController.coreKeyringController, + metamaskController.keyringController, 'createNewVaultAndRestore', ); jest @@ -337,6 +339,64 @@ describe('MetaMaskController', () => { }); }); + describe('on new version install', () => { + const mockOnInstalledEventDetails = { + reason: 'update', + previousVersion: '1.0.0', + }; + browserPolyfillMock.runtime.onInstalled.addListener.mockImplementation( + (handler) => { + handler(mockOnInstalledEventDetails); + }, + ); + + it('should details with LoggingController', async () => { + const mockVersion = '1.3.7'; + const mockGetVersionInfo = jest.fn().mockReturnValue(mockVersion); + + jest.spyOn(LoggingController.prototype, 'add'); + + const localController = new MetaMaskController({ + initLangCode: 'en_US', + platform: { + getVersion: mockGetVersionInfo, + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + }); + + expect(localController.loggingController.add).toHaveBeenCalledTimes(1); + expect(localController.loggingController.add).toHaveBeenCalledWith({ + type: LogType.GenericLog, + data: { + event: LOG_EVENT.VERSION_UPDATE, + previousVersion: mockOnInstalledEventDetails.previousVersion, + version: mockVersion, + }, + }); + }); + + it('should openExtensionInBrowser if version is 8.1.0', () => { + const mockVersion = '8.1.0'; + const mockGetVersionInfo = jest.fn().mockReturnValue(mockVersion); + + const openExtensionInBrowserMock = jest.fn(); + + // eslint-disable-next-line no-new + new MetaMaskController({ + initLangCode: 'en_US', + platform: { + getVersion: mockGetVersionInfo, + openExtensionInBrowser: openExtensionInBrowserMock, + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + }); + + expect(openExtensionInBrowserMock).toHaveBeenCalledTimes(1); + }); + }); + describe('#importAccountWithStrategy', () => { const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'; @@ -349,9 +409,9 @@ describe('MetaMaskController', () => { ]); }); - it('adds private key to keyrings in core KeyringController', async () => { + it('adds private key to keyrings in KeyringController', async () => { const simpleKeyrings = - metamaskController.coreKeyringController.getKeyringsByType( + metamaskController.keyringController.getKeyringsByType( KeyringType.imported, ); const pubAddressHexArr = await simpleKeyrings[0].getAccounts(); @@ -366,7 +426,7 @@ describe('MetaMaskController', () => { it('adds 1 account', async () => { const keyringAccounts = - await metamaskController.coreKeyringController.getAccounts(); + await metamaskController.keyringController.getAccounts(); expect(keyringAccounts[keyringAccounts.length - 1]).toStrictEqual( '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', ); @@ -386,7 +446,7 @@ describe('MetaMaskController', () => { metamaskController.preferencesController.store.getState().identities, ); const addresses = - await metamaskController.coreKeyringController.getAccounts(); + await metamaskController.keyringController.getAccounts(); identities.forEach((identity) => { expect(addresses).toContain(identity); @@ -400,15 +460,15 @@ describe('MetaMaskController', () => { describe('setLocked', () => { it('should lock KeyringController', async () => { - jest.spyOn(metamaskController.coreKeyringController, 'setLocked'); + jest.spyOn(metamaskController.keyringController, 'setLocked'); await metamaskController.setLocked(); expect( - metamaskController.coreKeyringController.setLocked, + metamaskController.keyringController.setLocked, ).toHaveBeenCalled(); expect( - metamaskController.coreKeyringController.state.isUnlocked, + metamaskController.keyringController.state.isUnlocked, ).toStrictEqual(false); }); }); @@ -440,7 +500,7 @@ describe('MetaMaskController', () => { await metamaskController.createNewVaultAndRestore(password, TEST_SEED); expect( - metamaskController.coreKeyringController.createNewVaultAndRestore, + metamaskController.keyringController.createNewVaultAndRestore, ).toHaveBeenCalledTimes(2); }); @@ -626,31 +686,31 @@ describe('MetaMaskController', () => { }); it('should add the Trezor Hardware keyring', async () => { - jest.spyOn(metamaskController.coreKeyringController, 'addNewKeyring'); + jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); await metamaskController .connectHardware(HardwareDeviceNames.trezor, 0) .catch(() => null); const keyrings = - await metamaskController.coreKeyringController.getKeyringsByType( + await metamaskController.keyringController.getKeyringsByType( KeyringType.trezor, ); expect( - metamaskController.coreKeyringController.addNewKeyring, + metamaskController.keyringController.addNewKeyring, ).toHaveBeenCalledWith(KeyringType.trezor); expect(keyrings).toHaveLength(1); }); it('should add the Ledger Hardware keyring', async () => { - jest.spyOn(metamaskController.coreKeyringController, 'addNewKeyring'); + jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); await metamaskController .connectHardware(HardwareDeviceNames.ledger, 0) .catch(() => null); const keyrings = - await metamaskController.coreKeyringController.getKeyringsByType( + await metamaskController.keyringController.getKeyringsByType( KeyringType.ledger, ); expect( - metamaskController.coreKeyringController.addNewKeyring, + metamaskController.keyringController.addNewKeyring, ).toHaveBeenCalledWith(KeyringType.ledger); expect(keyrings).toHaveLength(1); }); @@ -672,7 +732,7 @@ describe('MetaMaskController', () => { mnemonic: uint8ArrayMnemonic, }; jest - .spyOn(metamaskController.coreKeyringController, 'getKeyringsByType') + .spyOn(metamaskController.keyringController, 'getKeyringsByType') .mockReturnValue([mockHDKeyring]); const recoveredMnemonic = @@ -720,7 +780,7 @@ describe('MetaMaskController', () => { .catch(() => null); await metamaskController.forgetDevice(HardwareDeviceNames.trezor); const keyrings = - await metamaskController.coreKeyringController.getKeyringsByType( + await metamaskController.keyringController.getKeyringsByType( KeyringType.trezor, ); @@ -733,20 +793,20 @@ describe('MetaMaskController', () => { describe('unlockHardwareWalletAccount', () => { const accountToUnlock = 10; beforeEach(async () => { - await metamaskController.coreKeyringController.createNewVaultAndRestore( + await metamaskController.keyringController.createNewVaultAndRestore( 'password', TEST_SEED, ); jest.spyOn(window, 'open').mockReturnValue(); jest .spyOn( - metamaskController.coreKeyringController, + metamaskController.keyringController, 'addNewAccountForKeyring', ) .mockReturnValue('0x123'); jest - .spyOn(metamaskController.coreKeyringController, 'getAccounts') + .spyOn(metamaskController.keyringController, 'getAccounts') .mockResolvedValueOnce(['0x1']) .mockResolvedValueOnce(['0x2']) .mockResolvedValueOnce(['0x3']); @@ -769,7 +829,7 @@ describe('MetaMaskController', () => { it('should set unlockedAccount in the keyring', async () => { const keyrings = - await metamaskController.coreKeyringController.getKeyringsByType( + await metamaskController.keyringController.getKeyringsByType( KeyringType.trezor, ); expect(keyrings[0].unlockedAccount).toStrictEqual(accountToUnlock); @@ -777,13 +837,13 @@ describe('MetaMaskController', () => { it('should call keyringController.addNewAccount', async () => { expect( - metamaskController.coreKeyringController.addNewAccountForKeyring, + metamaskController.keyringController.addNewAccountForKeyring, ).toHaveBeenCalledTimes(1); }); it('should call keyringController.getAccounts', async () => { expect( - metamaskController.coreKeyringController.getAccounts, + metamaskController.keyringController.getAccounts, ).toHaveBeenCalledTimes(2); }); @@ -825,42 +885,39 @@ describe('MetaMaskController', () => { await metamaskController.createNewVaultAndKeychain('password'); await metamaskController.addNewAccount(1); const getAccounts = - await metamaskController.coreKeyringController.getAccounts(); + await metamaskController.keyringController.getAccounts(); expect(getAccounts).toHaveLength(2); }); }); describe('#resetAccount', () => { - it('wipes transactions from only the correct network id and with the selected address', async () => { + it('wipes transactions from only the correct chain id and with the selected address', async function () { jest .spyOn(metamaskController.preferencesController, 'getSelectedAddress') .mockReturnValue('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'); - jest - .spyOn(metamaskController.txController.txStateManager, 'getNetworkId') - .mockReturnValue(42); metamaskController.txController.txStateManager._addTransactionsToState([ createTxMeta({ id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, }), createTxMeta({ id: 1, status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, }), createTxMeta({ id: 2, status: TransactionStatus.rejected, - metamaskNetworkId: '32', + chainId: '0x32', }), createTxMeta({ id: 3, status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, + chainId: currentChainId, txParams: { from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4' }, }), ]); @@ -884,17 +941,14 @@ describe('MetaMaskController', () => { destroy: jest.fn(), }; jest - .spyOn(metamaskController.coreKeyringController, 'removeAccount') + .spyOn(metamaskController.keyringController, 'removeAccount') .mockReturnValue(); jest .spyOn(metamaskController, 'removeAllAccountPermissions') .mockReturnValue(); jest - .spyOn( - metamaskController.coreKeyringController, - 'getKeyringForAccount', - ) + .spyOn(metamaskController.keyringController, 'getKeyringForAccount') .mockResolvedValue(mockKeyring); ret = await metamaskController.removeAccount(addressToRemove); @@ -902,7 +956,7 @@ describe('MetaMaskController', () => { it('should call keyringController.removeAccount', async () => { expect( - metamaskController.coreKeyringController.removeAccount, + metamaskController.keyringController.removeAccount, ).toHaveBeenCalledWith(addressToRemove); }); it('should call metamaskController.removeAllAccountPermissions', async () => { @@ -913,9 +967,9 @@ describe('MetaMaskController', () => { it('should return address', async () => { expect(ret).toStrictEqual('0x1'); }); - it('should call coreKeyringController.getKeyringForAccount', async () => { + it('should call keyringController.getKeyringForAccount', async () => { expect( - metamaskController.coreKeyringController.getKeyringForAccount, + metamaskController.keyringController.getKeyringForAccount, ).toHaveBeenCalledWith(addressToRemove); }); it('should call keyring.destroy', async () => { diff --git a/app/scripts/migrations/069.js b/app/scripts/migrations/069.js index 2f07770e9665..00b0385e4366 100644 --- a/app/scripts/migrations/069.js +++ b/app/scripts/migrations/069.js @@ -1,4 +1,4 @@ -import { SubjectType } from '@metamask/subject-metadata-controller'; +import { SubjectType } from '@metamask/permission-controller'; import { cloneDeep } from 'lodash'; const version = 69; diff --git a/app/scripts/migrations/069.test.js b/app/scripts/migrations/069.test.js index 84200a08f5c1..f737154af8a8 100644 --- a/app/scripts/migrations/069.test.js +++ b/app/scripts/migrations/069.test.js @@ -1,4 +1,4 @@ -import { SubjectType } from '@metamask/subject-metadata-controller'; +import { SubjectType } from '@metamask/permission-controller'; import migration69 from './069'; describe('migration #69', () => { diff --git a/app/scripts/migrations/100.test.ts b/app/scripts/migrations/100.test.ts new file mode 100644 index 000000000000..15d52131b153 --- /dev/null +++ b/app/scripts/migrations/100.test.ts @@ -0,0 +1,259 @@ +import { migrate, version } from './100'; + +const oldVersion = 99; + +describe('migration #100', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if no address book state', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('does nothing if no address book entries', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: {}, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('adds name entries', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: { + '0x1': { + '0xc0ffee254729296a45a3885639AC7E10F9d54979': { + name: 'TestName1', + isEns: false, + }, + '0xc0ffee254729296a45a3885639AC7E10F9d54978': { + name: 'TestName2', + isEns: true, + }, + }, + '0x2': { + '0xc0ffee254729296a45a3885639AC7E10F9d54977': { + name: 'TestName3', + isEns: false, + }, + '0xc0ffee254729296a45a3885639AC7E10F9d54978': { + name: 'TestName4', + isEns: false, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54979': { + '0x1': { + name: 'TestName1', + sourceId: null, + proposedNames: {}, + }, + }, + '0xc0ffee254729296a45a3885639ac7e10f9d54978': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + '0x2': { + name: 'TestName4', + sourceId: null, + proposedNames: {}, + }, + }, + '0xc0ffee254729296a45a3885639ac7e10f9d54977': { + '0x2': { + name: 'TestName3', + sourceId: null, + proposedNames: {}, + }, + }, + }, + }, + }, + }); + }); + + it('keeps existing name entries', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: { + '0x1': { + '0xc0ffee254729296a45a3885639AC7E10F9d54979': { + name: 'TestName1', + isEns: false, + }, + }, + }, + }, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54978': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54979': { + '0x1': { + name: 'TestName1', + sourceId: null, + proposedNames: {}, + }, + }, + '0xc0ffee254729296a45a3885639ac7e10f9d54978': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }); + }); + + it('ignores address book entry if existing petname', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: { + '0x1': { + '0xc0ffee254729296a45a3885639AC7E10F9d54979': { + name: 'TestName1', + isEns: false, + }, + }, + }, + }, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54979': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + ethereumAddress: { + '0xc0ffee254729296a45a3885639ac7e10f9d54979': { + '0x1': { + name: 'TestName2', + sourceId: 'ens', + proposedNames: {}, + }, + }, + }, + }, + }, + }); + }); + + it('ignores address book entry if no name or address', async () => { + const oldState = { + OtherController: {}, + AddressBookController: { + addressBook: { + '0x1': { + '': { + name: 'TestName1', + isEns: false, + }, + '0xc0ffee254729296a45a3885639AC7E10F9d54979': { + name: '', + isEns: false, + }, + }, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual({ + ...oldState, + NameController: { + names: { + ethereumAddress: {}, + }, + }, + }); + }); +}); diff --git a/app/scripts/migrations/100.ts b/app/scripts/migrations/100.ts new file mode 100644 index 000000000000..c9b4a99afceb --- /dev/null +++ b/app/scripts/migrations/100.ts @@ -0,0 +1,70 @@ +import { cloneDeep, isEmpty } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 100; + +/** + * Copy all entries from AddressBookController to NameController. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + const addressBook = state?.AddressBookController?.addressBook ?? {}; + const names = state?.NameController?.names?.ethereumAddress ?? {}; + + if (isEmpty(Object.keys(addressBook))) { + return; + } + + for (const chainId of Object.keys(addressBook)) { + const chainAddressBook = addressBook[chainId]; + + for (const address of Object.keys(chainAddressBook)) { + const addressBookEntry = chainAddressBook[address]; + const normalizedAddress = address.toLowerCase(); + const nameEntry = names[normalizedAddress] ?? {}; + const nameChainEntry = nameEntry[chainId] ?? {}; + + // Ignore if petname already set, or if address book entry is missing name or address. + if ( + nameChainEntry.name?.length || + !addressBookEntry.name?.length || + !normalizedAddress?.length + ) { + continue; + } + + names[normalizedAddress] = nameEntry; + + nameEntry[chainId] = { + name: addressBookEntry.name, + sourceId: addressBookEntry.isEns ? 'ens' : null, + proposedNames: {}, + }; + } + } + + state.NameController = { + ...state.NameController, + names: { + ethereumAddress: names, + }, + }; +} diff --git a/app/scripts/migrations/101.test.js b/app/scripts/migrations/101.test.js new file mode 100644 index 000000000000..5130c23ed4b6 --- /dev/null +++ b/app/scripts/migrations/101.test.js @@ -0,0 +1,112 @@ +import { migrate, version } from './101'; + +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + +jest.mock('uuid', () => { + const actual = jest.requireActual('uuid'); + + return { + ...actual, + v4: jest.fn(), + }; +}); + +describe('migration #101', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should update the version metadata', async () => { + const oldStorage = { + meta: { + version: 100, + }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ + version, + }); + }); + + it('should return state unaltered if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 100, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should capture an exception if there is no network controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 100, + }, + data: oldData, + }; + + await migrate(oldStorage); + + expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error(`typeof state.NetworkController is undefined`), + ); + }); + + it('should return state unaltered if there is no network controller networkId state', async () => { + const oldData = { + other: 'data', + NetworkController: { + selectedNetworkClientId: 'networkClientId1', + }, + }; + const oldStorage = { + meta: { + version: 100, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should delete the networkId state', async () => { + const oldData = { + other: 'data', + NetworkController: { + selectedNetworkClientId: 'networkClientId1', + networkId: '1337', + }, + }; + const oldStorage = { + meta: { + version: 100, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + other: 'data', + NetworkController: { + selectedNetworkClientId: 'networkClientId1', + }, + }); + }); +}); diff --git a/app/scripts/migrations/101.ts b/app/scripts/migrations/101.ts new file mode 100644 index 000000000000..bd474212e694 --- /dev/null +++ b/app/scripts/migrations/101.ts @@ -0,0 +1,52 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 101; + +/** + * Remove network controller `networkId` state. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if ( + hasProperty(state, 'NetworkController') && + isObject(state.NetworkController) && + hasProperty(state.NetworkController, 'networkId') + ) { + const networkControllerState = state.NetworkController; + delete networkControllerState.networkId; + + return { + ...state, + NetworkController: networkControllerState, + }; + } + if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `typeof state.NetworkController is ${typeof state.NetworkController}`, + ), + ); + } + + return state; +} diff --git a/app/scripts/migrations/102.test.ts b/app/scripts/migrations/102.test.ts new file mode 100644 index 000000000000..894c875d524c --- /dev/null +++ b/app/scripts/migrations/102.test.ts @@ -0,0 +1,118 @@ +import { migrate, version } from './102'; + +const oldVersion = 101; +describe('migration #102', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('handles missing TransactionController', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('handles empty transactions', async () => { + const oldState = { + TransactionController: { + transactions: {}, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('handles missing state', async () => { + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: {}, + }); + + expect(transformedState.data).toEqual({}); + }); + + it('adds `error` property in the transaction by copying `err` and deleting it afterwards', async () => { + const oldState = { + TransactionController: { + transactions: { + tx1: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + err: { + message: 'nonce too high', + rpc: 'rpc_error', + stack: 'stacktrace', + }, + otherProp: 'value', + }, + tx2: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + otherProp: 'value', + }, + tx3: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + err: { + message: 'mocked error', + rpc: 'rpc_error', + stack: 'stacktrace', + }, + otherProp: 'value', + }, + }, + }, + }; + const oldStorage = { + meta: { version: oldVersion }, + data: oldState, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toEqual({ + TransactionController: { + transactions: { + tx1: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + error: { + message: 'nonce too high', + rpc: 'rpc_error', + stack: 'stacktrace', + }, + otherProp: 'value', + }, + tx2: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + otherProp: 'value', + }, + tx3: { + to: '0x9ef57335bc7d5b6cbc06dca6064a604b75e09ace', + error: { + message: 'mocked error', + rpc: 'rpc_error', + stack: 'stacktrace', + }, + otherProp: 'value', + }, + }, + }, + }); + }); +}); diff --git a/app/scripts/migrations/102.ts b/app/scripts/migrations/102.ts new file mode 100644 index 000000000000..820e67605251 --- /dev/null +++ b/app/scripts/migrations/102.ts @@ -0,0 +1,57 @@ +import { cloneDeep, isEmpty } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 102; + +/** + * The core TransactionController uses `error` to log transaction error information. + * For the sake of standardization and minimizing code maintenance, `err` is renamed as part of the unification of the Transaction Controller effort. + * This migration adds an `error` property by copying the old `err` and deleting it afterwards. + * + * @param originalVersionedData + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + const transactionControllerState = state?.TransactionController || {}; + const transactions = transactionControllerState?.transactions || {}; + + if (isEmpty(transactions)) { + return; + } + + const newTxs = Object.keys(transactions).reduce( + (txs: { [key: string]: any }, txId) => { + // Clone the transaction + const transaction = cloneDeep(transactions[txId]); + + // Check if 'err' exists before assigning it to 'error' + if (transaction?.err) { + transaction.error = transaction.err; + delete transaction.err; + } + + return { + ...txs, + [txId]: transaction, + }; + }, + {}, + ); + + state.TransactionController = { + ...transactionControllerState, + transactions: newTxs, + }; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index e70a1aa6a4e9..ca592106da0f 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -106,6 +106,9 @@ import * as m096 from './096'; import * as m097 from './097'; import * as m098 from './098'; import * as m099 from './099'; +import * as m100 from './100'; +import * as m101 from './101'; +import * as m102 from './102'; const migrations = [ m002, @@ -209,5 +212,8 @@ const migrations = [ m097, m098, m099, + m100, + m101, + m102, ]; export default migrations; diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 55f5a29e9e9a..3b39404ce2b1 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -198,13 +198,13 @@ export default class ExtensionPlatform { let message = t( 'notificationTransactionFailedMessage', nonce, - errorMessage || txMeta.err.message, + errorMessage || txMeta.error.message, ); ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) if (isNaN(nonce)) { message = t( 'notificationTransactionFailedMessageMMI', - errorMessage || txMeta.err.message, + errorMessage || txMeta.error.message, ); } ///: END:ONLY_INCLUDE_IN diff --git a/app/scripts/platforms/extension.test.js b/app/scripts/platforms/extension.test.js index 1016efef21a7..14ec2e66b40b 100644 --- a/app/scripts/platforms/extension.test.js +++ b/app/scripts/platforms/extension.test.js @@ -129,7 +129,7 @@ describe('extension platform', () => { it('should show failed transaction with nonce', async () => { const txMeta = { txParams: { nonce: '0x1' }, - err: { message: 'Error message' }, + error: { message: 'Error message' }, }; const extensionPlatform = new ExtensionPlatform(); const showNotificationSpy = jest.spyOn( @@ -141,7 +141,7 @@ describe('extension platform', () => { expect(showNotificationSpy).toHaveBeenCalledWith( 'Failed transaction', - `Transaction 1 failed! ${txMeta.err.message}`, + `Transaction 1 failed! ${txMeta.error.message}`, ); }); @@ -149,7 +149,7 @@ describe('extension platform', () => { const errorMessage = 'Test error message'; const txMeta = { txParams: { nonce: '0x1' }, - err: { message: 'Error message' }, + error: { message: 'Error message' }, }; const extensionPlatform = new ExtensionPlatform(); const showNotificationSpy = jest.spyOn( @@ -168,7 +168,7 @@ describe('extension platform', () => { it('should show failed transaction without nonce', async () => { const txMeta = { txParams: {}, - err: { message: 'Error message' }, + error: { message: 'Error message' }, }; const extensionPlatform = new ExtensionPlatform(); const showNotificationSpy = jest.spyOn( @@ -180,7 +180,7 @@ describe('extension platform', () => { expect(showNotificationSpy).toHaveBeenCalledWith( 'Failed transaction', - `Transaction failed! ${txMeta.err.message}`, + `Transaction failed! ${txMeta.error.message}`, ); }); }); diff --git a/app/scripts/ui.js b/app/scripts/ui.js index df85aa2f957d..bda3e4dcf9de 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -38,70 +38,12 @@ const container = document.getElementById('app-content'); const ONE_SECOND_IN_MILLISECONDS = 1_000; -// Service Worker Keep Alive Message Constants -const WORKER_KEEP_ALIVE_INTERVAL = ONE_SECOND_IN_MILLISECONDS; -const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE'; -const ACK_KEEP_ALIVE_WAIT_TIME = 60_000; // 1 minute -const ACK_KEEP_ALIVE_MESSAGE = 'ACK_KEEP_ALIVE_MESSAGE'; - // Timeout for initializing phishing warning page. const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS; const PHISHING_WARNING_SW_STORAGE_KEY = 'phishing-warning-sw-registered'; -let lastMessageReceivedTimestamp = Date.now(); - let extensionPort; -let ackTimeoutToDisplayError; - -/* - * As long as UI is open it will keep sending messages to service worker - * In service worker as this message is received - * if service worker is inactive it is reactivated and script re-loaded - * Time has been kept to 1000ms but can be reduced for even faster re-activation of service worker - */ -if (isManifestV3) { - // Checking for SW aliveness (or stuckness) flow - // 1. Check if we have an extensionPort, if yes - // 2a. Send a keep alive message to the background via extensionPort - // 2b. Add a listener to it (if not already added) - // 3a. Set a timeout to check if we have received an ACK from background - // 3b. If we have not received an ACK within ACK_KEEP_ALIVE_WAIT_TIME, - // we know the background is stuck or dead - // 4. If we recieve an ACK_KEEP_ALIVE_MESSAGE from the service worker, we know it is alive - - const ackKeepAliveListener = (message) => { - if (message.name === ACK_KEEP_ALIVE_MESSAGE) { - lastMessageReceivedTimestamp = Date.now(); - clearTimeout(ackTimeoutToDisplayError); - } - }; - - const keepAliveInterval = setInterval(() => { - browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); - - if (extensionPort !== null && extensionPort !== undefined) { - extensionPort.postMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); - - if (extensionPort.onMessage.hasListener(ackKeepAliveListener) === false) { - extensionPort.onMessage.addListener(ackKeepAliveListener); - } - } - - ackTimeoutToDisplayError = setTimeout(() => { - if ( - Date.now() - lastMessageReceivedTimestamp > - ACK_KEEP_ALIVE_WAIT_TIME - ) { - clearInterval(keepAliveInterval); - displayCriticalError( - 'somethingIsWrong', - new Error("Something's gone wrong. Try reloading the page."), - ); - } - }, ACK_KEEP_ALIVE_WAIT_TIME); - }, WORKER_KEEP_ALIVE_INTERVAL); -} start().catch(log.error); @@ -244,10 +186,6 @@ async function start() { resetExtensionStreamAndListeners, ); - // message below will try to activate service worker - // in MV3 is likely that reason of stream closing is service worker going in-active - browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); - extensionPort = browser.runtime.connect({ name: windowType }); connectionStream = new PortStream(extensionPort); extensionPort.onMessage.addListener(messageListener); diff --git a/builds.yml b/builds.yml index c096b51509ec..4c3bd3b01bb2 100644 --- a/builds.yml +++ b/builds.yml @@ -17,6 +17,8 @@ buildTypes: features: - build-main - snaps + - keyring-snaps + - blockaid # Additional env variables that are specific to this build env: - INFURA_PROD_PROJECT_ID @@ -25,7 +27,9 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_PROD_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/2.0.1/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.0.0/index.html + - KEYRING_SNAPS_REGISTRY_URL: https://metamask.github.io/keyring-snaps-registry/prod/registry.json + - KEYRING_SNAPS_AVAILABILITY_DATE: 02 Nov 2023 15:00:00 GMT # Main build uses the default browser manifest manifestOverrides: false @@ -58,11 +62,12 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/2.0.1/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.0.0/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID - SEGMENT_WRITE_KEY_REF: SEGMENT_FLASK_WRITE_KEY + - KEYRING_SNAPS_REGISTRY_URL: https://metamask.github.io/keyring-snaps-registry/dev/registry.json isPrerelease: true manifestOverrides: ./app/build-types/flask/manifest/ @@ -77,11 +82,12 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/2.0.1/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.0.0/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID - SEGMENT_WRITE_KEY_REF: SEGMENT_FLASK_WRITE_KEY + - KEYRING_SNAPS_REGISTRY_URL: https://metamask.github.io/keyring-snaps-registry/prod/registry.json isPrerelease: true manifestOverrides: ./app/build-types/desktop/manifest/ @@ -96,7 +102,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_MMI_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/2.0.1/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.consensys.io/3.0.0/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://mmi-support.zendesk.com/hc/en-us - SUPPORT_REQUEST_LINK: https://mmi-support.zendesk.com/hc/en-us/requests/new @@ -156,8 +162,6 @@ features: dest: images - ./{app,shared,ui}/**/flask/** keyring-snaps: - env: - - KEYRING_SNAPS_REGISTRY_URL: https://metamask.github.io/keyring-snaps-registry/prod/registry.json assets: - ./{app,shared,ui}/**/keyring-snaps/** @@ -231,6 +235,12 @@ env: # Modified in /development/build/scripts.js:@setEnvironmentVariables - METAMASK_BUILD_TYPE # Modified in /development/build/scripts.js:@setEnvironmentVariables + - METAMASK_BUILD_NAME + # Modified in /development/build/scripts.js:@setEnvironmentVariables + - METAMASK_BUILD_APP_ID + # Modified in /development/build/scripts.js:@setEnvironmentVariables + - METAMASK_BUILD_ICON + # Modified in /development/build/scripts.js:@setEnvironmentVariables - NODE_ENV # Defined by node itself # For the purposes of the build system we define it as empty below diff --git a/development/build/manifest.js b/development/build/manifest.js index d9f0f71ca1ff..a12f1c455558 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -1,7 +1,7 @@ const { promises: fs } = require('fs'); const path = require('path'); const childProcess = require('child_process'); -const { mergeWith, cloneDeep, capitalize } = require('lodash'); +const { mergeWith, cloneDeep } = require('lodash'); const baseManifest = process.env.ENABLE_MV3 ? require('../../app/manifest/v3/_base.json') @@ -10,7 +10,7 @@ const { loadBuildTypesConfig } = require('../lib/build-type'); const { TASKS, ENVIRONMENT } = require('./constants'); const { createTask, composeSeries } = require('./task'); -const { getEnvironment } = require('./utils'); +const { getEnvironment, getBuildName } = require('./utils'); module.exports = createManifestTasks; @@ -124,10 +124,6 @@ function createManifestTasks({ return; } - const mv3Str = process.env.ENABLE_MV3 ? ' MV3' : ''; - const lavamoatStr = applyLavaMoat ? ' lavamoat' : ''; - const snowStr = shouldIncludeSnow ? ' snow' : ''; - // Get the first 8 characters of the git revision id const gitRevisionStr = childProcess .execSync('git rev-parse HEAD') @@ -135,12 +131,13 @@ function createManifestTasks({ .trim() .substring(0, 8); - const buildName = - buildType === 'mmi' - ? `MetaMask Institutional ${mv3Str}` - : `MetaMask ${capitalize(buildType)}${mv3Str}${lavamoatStr}${snowStr}`; - - manifest.name = buildName; + manifest.name = getBuildName({ + environment, + buildType, + applyLavaMoat, + shouldIncludeSnow, + shouldIncludeMV3: process.env.ENABLE_MV3, + }); manifest.description = `${environment} build from git id: ${gitRevisionStr}`; } diff --git a/development/build/scripts.js b/development/build/scripts.js index fa50a925c09f..f33d509991e0 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -40,6 +40,9 @@ const { getEnvironment, logError, wrapAgainstScuttling, + getBuildName, + getBuildAppId, + getBuildIcon, } = require('./utils'); const { @@ -1181,6 +1184,16 @@ async function setEnvironmentVariables({ testing, }), METAMASK_DEBUG: devMode || variables.getMaybe('METAMASK_DEBUG') === true, + METAMASK_BUILD_NAME: getBuildName({ + environment, + buildType, + }), + METAMASK_BUILD_APP_ID: getBuildAppId({ + buildType, + }), + METAMASK_BUILD_ICON: getBuildIcon({ + buildType, + }), METAMASK_ENVIRONMENT: environment, METAMASK_VERSION: version, METAMASK_BUILD_TYPE: buildType, diff --git a/development/build/utils.js b/development/build/utils.js index 9b5bd58e3738..2f814b753014 100644 --- a/development/build/utils.js +++ b/development/build/utils.js @@ -1,8 +1,18 @@ const path = require('path'); +const { readFileSync } = require('fs'); const semver = require('semver'); +const { capitalize } = require('lodash'); const { loadBuildTypesConfig } = require('../lib/build-type'); const { BUILD_TARGETS, ENVIRONMENT } = require('./constants'); +const BUILD_TYPES_TO_SVG_LOGO_PATH = { + main: './app/images/logo/metamask-fox.svg', + beta: './app/build-types/beta/images/logo/metamask-fox.svg', + flask: './app/build-types/flask/images/logo/metamask-fox.svg', + mmi: './app/build-types/mmi/images/logo/mmi-logo.svg', + desktop: './app/build-types/desktop/images/logo/metamask-fox.svg', +}; + /** * Returns whether the current build is a development build or not. * @@ -218,8 +228,69 @@ function getPathInsideNodeModules(packageName, pathToFiles) { return targetPath; } +/** + * Get the name for the current build. + * + * @param {object} options - The build options. + * @param {string} options.buildType - The build type of the current build. + * @param {boolean} options.applyLavaMoat - Flag if lavamoat was applied. + * @param {boolean} options.shouldIncludeSnow - Flag if snow should be included in the build name. + * @param {boolean} options.shouldIncludeMV3 - Flag if mv3 should be included in the build name. + * @param options.environment + * @returns {string} The build name. + */ +function getBuildName({ + environment, + buildType, + applyLavaMoat, + shouldIncludeSnow, + shouldIncludeMV3, +}) { + if (environment === ENVIRONMENT.PRODUCTION) { + return 'MetaMask'; + } + + const mv3Str = shouldIncludeMV3 ? ' MV3' : ''; + const lavamoatStr = applyLavaMoat ? ' lavamoat' : ''; + const snowStr = shouldIncludeSnow ? ' snow' : ''; + + return buildType === 'mmi' + ? `MetaMask Institutional${mv3Str}` + : `MetaMask ${capitalize(buildType)}${mv3Str}${lavamoatStr}${snowStr}`; +} + +/** + * Get the app ID for the current build. Should be valid reverse FQDN. + * + * @param {object} options - The build options. + * @param {string} options.buildType - The build type of the current build. + * @returns {string} The build app ID. + */ +function getBuildAppId({ buildType }) { + const baseDomain = 'io.metamask'; + return buildType === 'main' ? baseDomain : `${baseDomain}.${buildType}`; +} + +/** + * Get the image data uri for the svg icon for the current build. + * + * @param {object} options - The build options. + * @param {string} options.buildType - The build type of the current build. + * @returns {string} The image data uri for the icon. + */ +function getBuildIcon({ buildType }) { + const svgLogoPath = + BUILD_TYPES_TO_SVG_LOGO_PATH[buildType] || + BUILD_TYPES_TO_SVG_LOGO_PATH.main; + const svg = readFileSync(svgLogoPath, 'utf8'); + return `data:image/svg+xml,${encodeURIComponent(svg)}`; +} + module.exports = { getBrowserVersionMap, + getBuildName, + getBuildAppId, + getBuildIcon, getEnvironment, isDevBuild, isTestBuild, diff --git a/development/gource-viz.sh b/development/gource-viz.sh index daacbdfe182a..96ab2cee4350 100755 --- a/development/gource-viz.sh +++ b/development/gource-viz.sh @@ -4,7 +4,7 @@ set -x gource \ --seconds-per-day .025 \ --user-scale 1.5 \ - --default-user-image "./images/icon-512.png" \ + --default-user-image "./app/images/icon-512.png" \ --viewport 1280x720 \ --auto-skip-seconds .05 \ --multi-sampling \ @@ -21,4 +21,13 @@ gource \ --title "MetaMask Development History" \ --output-ppm-stream - \ --output-framerate 30 \ - | ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4 + | \ +ffmpeg \ + -y \ + -r 30 \ + -f image2pipe \ + -vcodec ppm \ + -i \ + - \ + -b:v 65536K \ + metamask-dev-history.mp4 diff --git a/development/lib/retry.js b/development/lib/retry.js index ea469958b221..2b38aaf486a0 100644 --- a/development/lib/retry.js +++ b/development/lib/retry.js @@ -14,8 +14,10 @@ * @param {string} args.retryUntilFailure - Retries until the function fails. * @param {Function} functionToRetry - The function that is run and tested for * failure. - * @returns {Promise} a promise that either resolves to null if - * the function is successful or is rejected with rejectionMessage otherwise. + * @returns {Promise<* | null | Error>} a promise that either resolves with one of the following: + * - If successful, resolves with the return value of functionToRetry. + * - If functionToRetry fails while retryUntilFailure is true, resolves with null. + * - Otherwise it is rejected with rejectionMessage. */ async function retry( { @@ -33,14 +35,14 @@ async function retry( } try { - await functionToRetry(); + const result = await functionToRetry(); if (!retryUntilFailure) { - return; + return result; } } catch (error) { console.error(error); if (retryUntilFailure) { - return; + return null; } } finally { attempts += 1; diff --git a/development/ts-migration-dashboard/files-to-convert.json b/development/ts-migration-dashboard/files-to-convert.json index 003db164cfec..0a11ad6fba86 100644 --- a/development/ts-migration-dashboard/files-to-convert.json +++ b/development/ts-migration-dashboard/files-to-convert.json @@ -334,7 +334,6 @@ "ui/components/app/account-menu/account-menu.container.js", "ui/components/app/account-menu/account-menu.test.js", "ui/components/app/account-menu/index.js", - "ui/components/app/account-menu/keyring-label.js", "ui/components/app/add-network/add-network.js", "ui/components/app/add-network/add-network.stories.js", "ui/components/app/add-network/add-network.test.js", diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 137d438d31e5..eefdcc00c4ea 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -5,11 +5,6 @@ "regeneratorRuntime": "write" } }, - "@babel/runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@download/blockies": { "globals": { "document.createElement": true @@ -717,8 +712,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -743,6 +768,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -757,16 +783,16 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, - "@metamask/assets-controllers>abort-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/assets-controllers>@metamask/utils": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, - "eth-query": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true, @@ -785,21 +811,36 @@ "TextEncoder": true }, "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, "nock>debug": true, "semver": true, "superstruct": true } }, - "@metamask/assets-controllers>@metamask/rpc-errors": { + "@metamask/assets-controllers>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, - "@metamask/assets-controllers>abort-controller": { + "@metamask/assets-controllers>@metamask/utils": { "globals": { - "AbortController": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/assets-controllers>multiformats": { @@ -1462,13 +1503,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1636,7 +1707,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1645,67 +1716,18 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/message-manager>@metamask/utils": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "eth-sig-util>tweetnacl": true, "eth-sig-util>tweetnacl-util": true } }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": { - "packages": { - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/message-manager>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1893,11 +1915,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -1930,6 +1982,25 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/rpc-methods": { "packages": { "@metamask/key-tree": true, @@ -2000,6 +2071,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2022,12 +2094,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2052,6 +2154,7 @@ "@metamask/snaps-ui": { "packages": { "@metamask/snaps-ui>@metamask/utils": true, + "@metamask/snaps-ui>is-svg": true, "superstruct": true } }, @@ -2068,6 +2171,20 @@ "superstruct": true } }, + "@metamask/snaps-ui>is-svg": { + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser": true + } + }, + "@metamask/snaps-ui>is-svg>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils": { "globals": { "TextDecoder": true, @@ -2082,10 +2199,11 @@ "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/permission-controller": true, + "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>is-svg": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, "browserify>buffer": true, @@ -2115,20 +2233,6 @@ "luxon": true } }, - "@metamask/snaps-utils>is-svg": { - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-utils>is-svg>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils>rfdc": { "packages": { "browserify>buffer": true @@ -2145,16 +2249,6 @@ "semver": true } }, - "@metamask/subject-metadata-controller": { - "packages": { - "@metamask/subject-metadata-controller>@metamask/base-controller": true - } - }, - "@metamask/subject-metadata-controller>@metamask/base-controller": { - "packages": { - "immer": true - } - }, "@metamask/utils": { "globals": { "TextDecoder": true, @@ -2377,6 +2471,11 @@ "browserify>process": true } }, + "@storybook/addon-knobs>qs": { + "packages": { + "string.prototype.matchall>side-channel": true + } + }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -2898,6 +2997,15 @@ "define": true } }, + "brfs>static-module>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, "browserify>assert": { "globals": { "Buffer": true @@ -3176,8 +3284,8 @@ }, "browserify>url": { "packages": { - "browserify>punycode": true, - "browserify>querystring-es3": true + "@storybook/addon-knobs>qs": true, + "browserify>punycode": true } }, "browserify>util": { @@ -3910,8 +4018,8 @@ }, "ethjs-contract>babel-runtime": { "packages": { - "@babel/runtime>regenerator-runtime": true, - "ethjs-contract>babel-runtime>core-js": true + "ethjs-contract>babel-runtime>core-js": true, + "ethjs-contract>babel-runtime>regenerator-runtime": true } }, "ethjs-contract>babel-runtime>core-js": { @@ -3924,6 +4032,11 @@ "setTimeout": true } }, + "ethjs-contract>babel-runtime>regenerator-runtime": { + "globals": { + "regeneratorRuntime": "write" + } + }, "ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, @@ -4727,6 +4840,13 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "string.prototype.matchall>side-channel": { + "packages": { + "brfs>static-module>object-inspect": true, + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true + } + }, "superstruct": { "globals": { "console.warn": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index 5f3e7b1e6a65..c8bc5a296298 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -5,11 +5,6 @@ "regeneratorRuntime": "write" } }, - "@babel/runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@download/blockies": { "globals": { "document.createElement": true @@ -717,8 +712,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -743,6 +768,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -757,16 +783,16 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, - "@metamask/assets-controllers>abort-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/assets-controllers>@metamask/utils": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, - "eth-query": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true, @@ -785,21 +811,36 @@ "TextEncoder": true }, "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, "nock>debug": true, "semver": true, "superstruct": true } }, - "@metamask/assets-controllers>@metamask/rpc-errors": { + "@metamask/assets-controllers>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, - "@metamask/assets-controllers>abort-controller": { + "@metamask/assets-controllers>@metamask/utils": { "globals": { - "AbortController": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/assets-controllers>multiformats": { @@ -1184,15 +1225,14 @@ }, "@metamask/eth-snap-keyring": { "globals": { - "console.error": true, - "console.log": true + "console.error": true }, "packages": { "@ethereumjs/tx": true, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, - "@metamask/eth-snap-keyring>@metamask/keyring-api": true, "@metamask/eth-snap-keyring>@metamask/utils": true, "@metamask/eth-snap-keyring>uuid": true, + "@metamask/keyring-api": true, "superstruct": true, "webpack>events": true } @@ -1201,39 +1241,14 @@ "packages": { "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "eth-sig-util>tweetnacl": true, "eth-sig-util>tweetnacl-util": true } }, - "@metamask/eth-snap-keyring>@metamask/keyring-api": { - "packages": { - "@metamask/eth-snap-keyring>@metamask/keyring-api>@metamask/utils": true, - "@metamask/eth-snap-keyring>@metamask/keyring-api>uuid": true, - "superstruct": true - } - }, - "@metamask/eth-snap-keyring>@metamask/keyring-api>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/eth-snap-keyring>@metamask/keyring-api>uuid": { - "globals": { - "crypto": true - } - }, "@metamask/eth-snap-keyring>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1603,13 +1618,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1693,6 +1738,31 @@ "TextEncoder": true } }, + "@metamask/keyring-api": { + "packages": { + "@metamask/keyring-api>@metamask/utils": true, + "@metamask/keyring-api>uuid": true, + "superstruct": true + } + }, + "@metamask/keyring-api>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/keyring-api>uuid": { + "globals": { + "crypto": true + } + }, "@metamask/keyring-controller": { "packages": { "@metamask/base-controller": true, @@ -1777,7 +1847,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1786,67 +1856,18 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/message-manager>@metamask/utils": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "eth-sig-util>tweetnacl": true, "eth-sig-util>tweetnacl-util": true } }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": { - "packages": { - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/message-manager>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2041,11 +2062,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2088,6 +2139,25 @@ "readable-stream": true } }, + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/rate-limit-controller": { "globals": { "setTimeout": true @@ -2167,6 +2237,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2189,12 +2260,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2434,6 +2535,7 @@ "@metamask/snaps-ui": { "packages": { "@metamask/snaps-ui>@metamask/utils": true, + "@metamask/snaps-ui>is-svg": true, "superstruct": true } }, @@ -2450,6 +2552,20 @@ "superstruct": true } }, + "@metamask/snaps-ui>is-svg": { + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser": true + } + }, + "@metamask/snaps-ui>is-svg>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils": { "globals": { "TextDecoder": true, @@ -2464,10 +2580,11 @@ "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/permission-controller": true, + "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>is-svg": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, "browserify>buffer": true, @@ -2517,20 +2634,6 @@ "luxon": true } }, - "@metamask/snaps-utils>is-svg": { - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-utils>is-svg>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils>rfdc": { "packages": { "browserify>buffer": true @@ -2547,16 +2650,6 @@ "semver": true } }, - "@metamask/subject-metadata-controller": { - "packages": { - "@metamask/subject-metadata-controller>@metamask/base-controller": true - } - }, - "@metamask/subject-metadata-controller>@metamask/base-controller": { - "packages": { - "immer": true - } - }, "@metamask/utils": { "globals": { "TextDecoder": true, @@ -2779,6 +2872,11 @@ "browserify>process": true } }, + "@storybook/addon-knobs>qs": { + "packages": { + "string.prototype.matchall>side-channel": true + } + }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -3300,6 +3398,15 @@ "define": true } }, + "brfs>static-module>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, "browserify>assert": { "globals": { "Buffer": true @@ -3578,8 +3685,8 @@ }, "browserify>url": { "packages": { - "browserify>punycode": true, - "browserify>querystring-es3": true + "@storybook/addon-knobs>qs": true, + "browserify>punycode": true } }, "browserify>util": { @@ -4312,8 +4419,8 @@ }, "ethjs-contract>babel-runtime": { "packages": { - "@babel/runtime>regenerator-runtime": true, - "ethjs-contract>babel-runtime>core-js": true + "ethjs-contract>babel-runtime>core-js": true, + "ethjs-contract>babel-runtime>regenerator-runtime": true } }, "ethjs-contract>babel-runtime>core-js": { @@ -4326,6 +4433,11 @@ "setTimeout": true } }, + "ethjs-contract>babel-runtime>regenerator-runtime": { + "globals": { + "regeneratorRuntime": "write" + } + }, "ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, @@ -5261,6 +5373,13 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "string.prototype.matchall>side-channel": { + "packages": { + "brfs>static-module>object-inspect": true, + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true + } + }, "superstruct": { "globals": { "console.warn": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 62ee4d7636f5..4aa556327197 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -5,11 +5,6 @@ "regeneratorRuntime": "write" } }, - "@babel/runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@download/blockies": { "globals": { "document.createElement": true @@ -717,8 +712,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -743,6 +768,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -757,16 +783,16 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, - "@metamask/assets-controllers>abort-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/assets-controllers>@metamask/utils": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, - "eth-query": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true, @@ -785,21 +811,36 @@ "TextEncoder": true }, "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, "nock>debug": true, "semver": true, "superstruct": true } }, - "@metamask/assets-controllers>@metamask/rpc-errors": { + "@metamask/assets-controllers>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, - "@metamask/assets-controllers>abort-controller": { + "@metamask/assets-controllers>@metamask/utils": { "globals": { - "AbortController": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/assets-controllers>multiformats": { @@ -1184,15 +1225,14 @@ }, "@metamask/eth-snap-keyring": { "globals": { - "console.error": true, - "console.log": true + "console.error": true }, "packages": { "@ethereumjs/tx": true, "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, - "@metamask/eth-snap-keyring>@metamask/keyring-api": true, "@metamask/eth-snap-keyring>@metamask/utils": true, "@metamask/eth-snap-keyring>uuid": true, + "@metamask/keyring-api": true, "superstruct": true, "webpack>events": true } @@ -1201,39 +1241,14 @@ "packages": { "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, "@metamask/eth-snap-keyring>@metamask/utils": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "eth-sig-util>tweetnacl": true, "eth-sig-util>tweetnacl-util": true } }, - "@metamask/eth-snap-keyring>@metamask/keyring-api": { - "packages": { - "@metamask/eth-snap-keyring>@metamask/keyring-api>@metamask/utils": true, - "@metamask/eth-snap-keyring>@metamask/keyring-api>uuid": true, - "superstruct": true - } - }, - "@metamask/eth-snap-keyring>@metamask/keyring-api>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/eth-snap-keyring>@metamask/keyring-api>uuid": { - "globals": { - "crypto": true - } - }, "@metamask/eth-snap-keyring>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1603,13 +1618,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1693,6 +1738,31 @@ "TextEncoder": true } }, + "@metamask/keyring-api": { + "packages": { + "@metamask/keyring-api>@metamask/utils": true, + "@metamask/keyring-api>uuid": true, + "superstruct": true + } + }, + "@metamask/keyring-api>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/keyring-api>uuid": { + "globals": { + "crypto": true + } + }, "@metamask/keyring-controller": { "packages": { "@metamask/base-controller": true, @@ -1777,7 +1847,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1786,67 +1856,18 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/message-manager>@metamask/utils": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "eth-sig-util>tweetnacl": true, "eth-sig-util>tweetnacl-util": true } }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": { - "packages": { - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/message-manager>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2041,11 +2062,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2060,13 +2111,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/ppom-validator>@metamask/controller-utils": true, "@metamask/ppom-validator>elliptic": true, "await-semaphore": true, "browserify>buffer": true, "eth-query>json-rpc-random-id": true } }, + "@metamask/ppom-validator>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -2104,6 +2185,25 @@ "readable-stream": true } }, + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/rate-limit-controller": { "globals": { "setTimeout": true @@ -2183,6 +2283,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2205,12 +2306,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2450,6 +2581,7 @@ "@metamask/snaps-ui": { "packages": { "@metamask/snaps-ui>@metamask/utils": true, + "@metamask/snaps-ui>is-svg": true, "superstruct": true } }, @@ -2466,6 +2598,20 @@ "superstruct": true } }, + "@metamask/snaps-ui>is-svg": { + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser": true + } + }, + "@metamask/snaps-ui>is-svg>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils": { "globals": { "TextDecoder": true, @@ -2480,10 +2626,11 @@ "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/permission-controller": true, + "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>is-svg": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, "browserify>buffer": true, @@ -2533,20 +2680,6 @@ "luxon": true } }, - "@metamask/snaps-utils>is-svg": { - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-utils>is-svg>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils>rfdc": { "packages": { "browserify>buffer": true @@ -2563,16 +2696,6 @@ "semver": true } }, - "@metamask/subject-metadata-controller": { - "packages": { - "@metamask/subject-metadata-controller>@metamask/base-controller": true - } - }, - "@metamask/subject-metadata-controller>@metamask/base-controller": { - "packages": { - "immer": true - } - }, "@metamask/utils": { "globals": { "TextDecoder": true, @@ -2795,6 +2918,11 @@ "browserify>process": true } }, + "@storybook/addon-knobs>qs": { + "packages": { + "string.prototype.matchall>side-channel": true + } + }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -3316,6 +3444,15 @@ "define": true } }, + "brfs>static-module>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, "browserify>assert": { "globals": { "Buffer": true @@ -3594,8 +3731,8 @@ }, "browserify>url": { "packages": { - "browserify>punycode": true, - "browserify>querystring-es3": true + "@storybook/addon-knobs>qs": true, + "browserify>punycode": true } }, "browserify>util": { @@ -4328,8 +4465,8 @@ }, "ethjs-contract>babel-runtime": { "packages": { - "@babel/runtime>regenerator-runtime": true, - "ethjs-contract>babel-runtime>core-js": true + "ethjs-contract>babel-runtime>core-js": true, + "ethjs-contract>babel-runtime>regenerator-runtime": true } }, "ethjs-contract>babel-runtime>core-js": { @@ -4342,6 +4479,11 @@ "setTimeout": true } }, + "ethjs-contract>babel-runtime>regenerator-runtime": { + "globals": { + "regeneratorRuntime": "write" + } + }, "ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, @@ -5277,6 +5419,13 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "string.prototype.matchall>side-channel": { + "packages": { + "brfs>static-module>object-inspect": true, + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true + } + }, "superstruct": { "globals": { "console.warn": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 3620f072010a..35b1492ddca5 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -5,11 +5,6 @@ "regeneratorRuntime": "write" } }, - "@babel/runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@download/blockies": { "globals": { "document.createElement": true @@ -717,8 +712,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -743,6 +768,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -757,16 +783,16 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, - "@metamask/assets-controllers>abort-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/assets-controllers>@metamask/utils": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, - "eth-query": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true, @@ -785,21 +811,36 @@ "TextEncoder": true }, "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, "nock>debug": true, "semver": true, "superstruct": true } }, - "@metamask/assets-controllers>@metamask/rpc-errors": { + "@metamask/assets-controllers>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, - "@metamask/assets-controllers>abort-controller": { + "@metamask/assets-controllers>@metamask/utils": { "globals": { - "AbortController": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/assets-controllers>multiformats": { @@ -1111,6 +1152,50 @@ "koa>content-disposition>safe-buffer": true } }, + "@metamask/eth-snap-keyring": { + "globals": { + "console.error": true + }, + "packages": { + "@ethereumjs/tx": true, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "@metamask/eth-snap-keyring>uuid": true, + "@metamask/keyring-api": true, + "superstruct": true, + "webpack>events": true + } + }, + "@metamask/eth-snap-keyring>@metamask/eth-sig-util": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/eth-snap-keyring>@metamask/utils": true, + "browserify>buffer": true, + "eth-sig-util>ethereumjs-util>ethjs-util": true, + "eth-sig-util>tweetnacl": true, + "eth-sig-util>tweetnacl-util": true + } + }, + "@metamask/eth-snap-keyring>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/eth-snap-keyring>uuid": { + "globals": { + "crypto": true + } + }, "@metamask/eth-token-tracker": { "globals": { "console.warn": true @@ -1462,13 +1547,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1552,6 +1667,31 @@ "TextEncoder": true } }, + "@metamask/keyring-api": { + "packages": { + "@metamask/keyring-api>@metamask/utils": true, + "@metamask/keyring-api>uuid": true, + "superstruct": true + } + }, + "@metamask/keyring-api>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/keyring-api>uuid": { + "globals": { + "crypto": true + } + }, "@metamask/keyring-controller": { "packages": { "@metamask/base-controller": true, @@ -1636,7 +1776,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1645,67 +1785,18 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/message-manager>@metamask/utils": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "eth-sig-util>tweetnacl": true, "eth-sig-util>tweetnacl-util": true } }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": { - "packages": { - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/message-manager>@metamask/utils": { "globals": { "TextDecoder": true, @@ -1900,16 +1991,92 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true } }, + "@metamask/ppom-validator": { + "globals": { + "URL": true, + "clearInterval": true, + "console.error": true, + "setInterval": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/ppom-validator>@metamask/controller-utils": true, + "@metamask/ppom-validator>elliptic": true, + "await-semaphore": true, + "browserify>buffer": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/ppom-validator>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/ppom-validator>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/ppom-validator>elliptic": { "packages": { "@metamask/ppom-validator>elliptic>brorand": true, @@ -1947,6 +2114,25 @@ "readable-stream": true } }, + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/rate-limit-controller": { "globals": { "setTimeout": true @@ -2026,6 +2212,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2048,12 +2235,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2293,6 +2510,7 @@ "@metamask/snaps-ui": { "packages": { "@metamask/snaps-ui>@metamask/utils": true, + "@metamask/snaps-ui>is-svg": true, "superstruct": true } }, @@ -2309,6 +2527,20 @@ "superstruct": true } }, + "@metamask/snaps-ui>is-svg": { + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser": true + } + }, + "@metamask/snaps-ui>is-svg>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils": { "globals": { "TextDecoder": true, @@ -2323,10 +2555,11 @@ "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/permission-controller": true, + "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>is-svg": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, "browserify>buffer": true, @@ -2376,20 +2609,6 @@ "luxon": true } }, - "@metamask/snaps-utils>is-svg": { - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-utils>is-svg>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils>rfdc": { "packages": { "browserify>buffer": true @@ -2406,16 +2625,6 @@ "semver": true } }, - "@metamask/subject-metadata-controller": { - "packages": { - "@metamask/subject-metadata-controller>@metamask/base-controller": true - } - }, - "@metamask/subject-metadata-controller>@metamask/base-controller": { - "packages": { - "immer": true - } - }, "@metamask/utils": { "globals": { "TextDecoder": true, @@ -2638,6 +2847,11 @@ "browserify>process": true } }, + "@storybook/addon-knobs>qs": { + "packages": { + "string.prototype.matchall>side-channel": true + } + }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -3159,6 +3373,15 @@ "define": true } }, + "brfs>static-module>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, "browserify>assert": { "globals": { "Buffer": true @@ -3437,8 +3660,8 @@ }, "browserify>url": { "packages": { - "browserify>punycode": true, - "browserify>querystring-es3": true + "@storybook/addon-knobs>qs": true, + "browserify>punycode": true } }, "browserify>util": { @@ -4171,8 +4394,8 @@ }, "ethjs-contract>babel-runtime": { "packages": { - "@babel/runtime>regenerator-runtime": true, - "ethjs-contract>babel-runtime>core-js": true + "ethjs-contract>babel-runtime>core-js": true, + "ethjs-contract>babel-runtime>regenerator-runtime": true } }, "ethjs-contract>babel-runtime>core-js": { @@ -4185,6 +4408,11 @@ "setTimeout": true } }, + "ethjs-contract>babel-runtime>regenerator-runtime": { + "globals": { + "regeneratorRuntime": "write" + } + }, "ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, @@ -5120,6 +5348,13 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "string.prototype.matchall>side-channel": { + "packages": { + "brfs>static-module>object-inspect": true, + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true + } + }, "superstruct": { "globals": { "console.warn": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index da52999e2f72..8c7ffc4c077d 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -5,11 +5,6 @@ "regeneratorRuntime": "write" } }, - "@babel/runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@download/blockies": { "globals": { "document.createElement": true @@ -857,8 +852,38 @@ }, "@metamask/address-book-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true + "@metamask/address-book-controller>@metamask/controller-utils": true, + "@metamask/base-controller": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/announcement-controller": { @@ -883,6 +908,7 @@ }, "@metamask/assets-controllers": { "globals": { + "AbortController": true, "Headers": true, "URL": true, "clearInterval": true, @@ -897,16 +923,16 @@ "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, - "@metamask/assets-controllers>@metamask/rpc-errors": true, - "@metamask/assets-controllers>abort-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, + "@metamask/assets-controllers>@metamask/utils": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, "@metamask/contract-metadata": true, "@metamask/controller-utils": true, + "@metamask/controller-utils>@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/utils": true, + "@metamask/providers>@metamask/rpc-errors": true, "eth-json-rpc-filters>async-mutex": true, - "eth-query": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true, @@ -925,21 +951,36 @@ "TextEncoder": true }, "packages": { + "@metamask/key-tree>@noble/hashes": true, "browserify>buffer": true, "nock>debug": true, "semver": true, "superstruct": true } }, - "@metamask/assets-controllers>@metamask/rpc-errors": { + "@metamask/assets-controllers>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true } }, - "@metamask/assets-controllers>abort-controller": { + "@metamask/assets-controllers>@metamask/utils": { "globals": { - "AbortController": true + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true } }, "@metamask/assets-controllers>multiformats": { @@ -1602,13 +1643,43 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/gas-fee-controller>@metamask/controller-utils": true, "eth-query": true, "ethereumjs-util": true, "ethjs>ethjs-unit": true, "uuid": true } }, + "@metamask/gas-fee-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/gas-fee-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, @@ -1776,7 +1847,7 @@ "@metamask/message-manager": { "packages": { "@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1785,67 +1856,18 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true, - "ethjs>ethjs-unit": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": true, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/message-manager>@metamask/utils": true, "browserify>buffer": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "eth-sig-util>tweetnacl": true, "eth-sig-util>tweetnacl-util": true } }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils": { - "packages": { - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/abi-utils>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/message-manager>@metamask/eth-sig-util>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/key-tree>@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, "@metamask/message-manager>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2040,11 +2062,41 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2087,6 +2139,25 @@ "readable-stream": true } }, + "@metamask/providers>@metamask/rpc-errors": { + "packages": { + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/providers>@metamask/rpc-errors>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/rate-limit-controller": { "globals": { "setTimeout": true @@ -2166,6 +2237,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/controller-utils": true, + "@metamask/logging-controller": true, "@metamask/message-manager": true, "browserify>buffer": true, "eth-rpc-errors": true, @@ -2188,12 +2260,42 @@ "@ethersproject/bignumber": true, "@ethersproject/providers": true, "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>bignumber.js": true, "fast-json-patch": true, "lodash": true } }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2433,6 +2535,7 @@ "@metamask/snaps-ui": { "packages": { "@metamask/snaps-ui>@metamask/utils": true, + "@metamask/snaps-ui>is-svg": true, "superstruct": true } }, @@ -2449,6 +2552,20 @@ "superstruct": true } }, + "@metamask/snaps-ui>is-svg": { + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser": true + } + }, + "@metamask/snaps-ui>is-svg>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-ui>is-svg>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils": { "globals": { "TextDecoder": true, @@ -2463,10 +2580,11 @@ "@metamask/key-tree": true, "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/permission-controller": true, + "@metamask/snaps-ui>is-svg": true, "@metamask/snaps-utils>@metamask/utils": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, - "@metamask/snaps-utils>is-svg": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, "browserify>buffer": true, @@ -2516,20 +2634,6 @@ "luxon": true } }, - "@metamask/snaps-utils>is-svg": { - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-utils>is-svg>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-utils>is-svg>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils>rfdc": { "packages": { "browserify>buffer": true @@ -2546,16 +2650,6 @@ "semver": true } }, - "@metamask/subject-metadata-controller": { - "packages": { - "@metamask/subject-metadata-controller>@metamask/base-controller": true - } - }, - "@metamask/subject-metadata-controller>@metamask/base-controller": { - "packages": { - "immer": true - } - }, "@metamask/utils": { "globals": { "TextDecoder": true, @@ -2778,6 +2872,11 @@ "browserify>process": true } }, + "@storybook/addon-knobs>qs": { + "packages": { + "string.prototype.matchall>side-channel": true + } + }, "@truffle/codec": { "packages": { "@truffle/codec>@truffle/abi-utils": true, @@ -3299,6 +3398,15 @@ "define": true } }, + "brfs>static-module>object-inspect": { + "globals": { + "HTMLElement": true, + "WeakRef": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, "browserify>assert": { "globals": { "Buffer": true @@ -3577,8 +3685,8 @@ }, "browserify>url": { "packages": { - "browserify>punycode": true, - "browserify>querystring-es3": true + "@storybook/addon-knobs>qs": true, + "browserify>punycode": true } }, "browserify>util": { @@ -4311,8 +4419,8 @@ }, "ethjs-contract>babel-runtime": { "packages": { - "@babel/runtime>regenerator-runtime": true, - "ethjs-contract>babel-runtime>core-js": true + "ethjs-contract>babel-runtime>core-js": true, + "ethjs-contract>babel-runtime>regenerator-runtime": true } }, "ethjs-contract>babel-runtime>core-js": { @@ -4325,6 +4433,11 @@ "setTimeout": true } }, + "ethjs-contract>babel-runtime>regenerator-runtime": { + "globals": { + "regeneratorRuntime": "write" + } + }, "ethjs-contract>ethjs-abi": { "packages": { "bn.js": true, @@ -4959,33 +5072,9 @@ } }, "react-responsive-carousel": { - "globals": { - "HTMLElement": true, - "addEventListener": true, - "clearTimeout": true, - "console.warn": true, - "document": true, - "getComputedStyle": true, - "removeEventListener": true, - "setTimeout": true - }, - "packages": { - "classnames": true, - "react": true, - "react-dom": true, - "react-responsive-carousel>react-easy-swipe": true - } - }, - "react-responsive-carousel>react-easy-swipe": { "globals": { "addEventListener": true, - "define": true, - "document.addEventListener": true, - "document.removeEventListener": true - }, - "packages": { - "prop-types": true, - "react": true + "removeEventListener": true } }, "react-router-dom": { @@ -5260,6 +5349,13 @@ "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true } }, + "string.prototype.matchall>side-channel": { + "packages": { + "brfs>static-module>object-inspect": true, + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>get-intrinsic": true + } + }, "superstruct": { "globals": { "console.warn": true, diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index baacaffa92fa..f6fcc3efe20f 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -6,9 +6,46 @@ "process.emitWarning": true }, "packages": { + "@babel/code-frame>chalk": true, "lavamoat>@babel/highlight": true } }, + "@babel/code-frame>chalk": { + "globals": { + "process.env.TERM": true, + "process.platform": true + }, + "packages": { + "@babel/code-frame>chalk>ansi-styles": true, + "@babel/code-frame>chalk>escape-string-regexp": true, + "@babel/code-frame>chalk>supports-color": true + } + }, + "@babel/code-frame>chalk>ansi-styles": { + "packages": { + "@metamask/jazzicon>color>color-convert": true + } + }, + "@babel/code-frame>chalk>supports-color": { + "builtin": { + "os.release": true + }, + "globals": { + "process.env": true, + "process.platform": true, + "process.stderr": true, + "process.stdout": true, + "process.versions.node.split": true + }, + "packages": { + "@babel/code-frame>chalk>supports-color>has-flag": true + } + }, + "@babel/code-frame>chalk>supports-color>has-flag": { + "globals": { + "process.argv": true + } + }, "@babel/core": { "builtin": { "assert": true, @@ -40,6 +77,7 @@ "@babel/core>@babel/parser": true, "@babel/core>@babel/template": true, "@babel/core>@babel/types": true, + "@babel/core>convert-source-map": true, "@babel/core>gensync": true, "@babel/core>semver": true, "@babel/plugin-proposal-class-properties": true, @@ -52,8 +90,7 @@ "@babel/preset-typescript": true, "depcheck>@babel/traverse": true, "depcheck>json5": true, - "nock>debug": true, - "nyc>convert-source-map": true + "nock>debug": true } }, "@babel/core>@ampproject/remapping": { @@ -67,7 +104,8 @@ }, "@babel/core>@babel/generator": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@babel/core>@babel/generator>jsesc": true, @@ -112,11 +150,9 @@ "path.extname": true }, "packages": { + "@babel/core": true, "@babel/core>@babel/helper-module-transforms>@babel/helper-module-imports": true, "@babel/core>@babel/helper-module-transforms>@babel/helper-simple-access": true, - "@babel/core>@babel/template": true, - "@babel/core>@babel/types": true, - "depcheck>@babel/traverse": true, "depcheck>@babel/traverse>@babel/helper-environment-visitor": true, "depcheck>@babel/traverse>@babel/helper-split-export-declaration": true, "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true @@ -160,6 +196,14 @@ "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true } }, + "@babel/core>convert-source-map": { + "globals": { + "Buffer": true, + "atob": true, + "btoa": true, + "value": true + } + }, "@babel/core>semver": { "globals": { "console": true, @@ -180,13 +224,18 @@ "packages": { "@babel/core": true, "@babel/core>@babel/parser": true, + "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals": true, "@babel/eslint-parser>eslint-scope": true, "@babel/eslint-parser>eslint-visitor-keys": true, "@babel/eslint-parser>semver": true, "@babel/parser": true, "depcheck>@babel/parser": true, "eslint": true, - "lavamoat>lavamoat-tofu>@babel/parser": true, + "lavamoat>lavamoat-tofu>@babel/parser": true + } + }, + "@babel/eslint-parser>@nicolo-ribaudo/eslint-scope-5-internals": { + "packages": { "webpack>eslint-scope": true } }, @@ -217,27 +266,13 @@ "@babel/preset-env>@babel/helper-validator-option": true, "@babel/preset-env>@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": true, "@babel/preset-env>@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": true, - "@babel/preset-env>@babel/plugin-proposal-async-generator-functions": true, - "@babel/preset-env>@babel/plugin-proposal-class-properties": true, - "@babel/preset-env>@babel/plugin-proposal-class-static-block": true, - "@babel/preset-env>@babel/plugin-proposal-dynamic-import": true, - "@babel/preset-env>@babel/plugin-proposal-export-namespace-from": true, - "@babel/preset-env>@babel/plugin-proposal-json-strings": true, - "@babel/preset-env>@babel/plugin-proposal-logical-assignment-operators": true, - "@babel/preset-env>@babel/plugin-proposal-nullish-coalescing-operator": true, - "@babel/preset-env>@babel/plugin-proposal-numeric-separator": true, - "@babel/preset-env>@babel/plugin-proposal-object-rest-spread": true, - "@babel/preset-env>@babel/plugin-proposal-optional-catch-binding": true, - "@babel/preset-env>@babel/plugin-proposal-optional-chaining": true, - "@babel/preset-env>@babel/plugin-proposal-private-methods": true, - "@babel/preset-env>@babel/plugin-proposal-private-property-in-object": true, - "@babel/preset-env>@babel/plugin-proposal-unicode-property-regex": true, "@babel/preset-env>@babel/plugin-syntax-async-generators": true, "@babel/preset-env>@babel/plugin-syntax-class-properties": true, "@babel/preset-env>@babel/plugin-syntax-class-static-block": true, "@babel/preset-env>@babel/plugin-syntax-dynamic-import": true, "@babel/preset-env>@babel/plugin-syntax-export-namespace-from": true, "@babel/preset-env>@babel/plugin-syntax-import-assertions": true, + "@babel/preset-env>@babel/plugin-syntax-import-attributes": true, "@babel/preset-env>@babel/plugin-syntax-import-meta": true, "@babel/preset-env>@babel/plugin-syntax-json-strings": true, "@babel/preset-env>@babel/plugin-syntax-logical-assignment-operators": true, @@ -248,19 +283,27 @@ "@babel/preset-env>@babel/plugin-syntax-optional-chaining": true, "@babel/preset-env>@babel/plugin-syntax-private-property-in-object": true, "@babel/preset-env>@babel/plugin-syntax-top-level-await": true, + "@babel/preset-env>@babel/plugin-syntax-unicode-sets-regex": true, "@babel/preset-env>@babel/plugin-transform-arrow-functions": true, + "@babel/preset-env>@babel/plugin-transform-async-generator-functions": true, "@babel/preset-env>@babel/plugin-transform-async-to-generator": true, "@babel/preset-env>@babel/plugin-transform-block-scoped-functions": true, "@babel/preset-env>@babel/plugin-transform-block-scoping": true, + "@babel/preset-env>@babel/plugin-transform-class-properties": true, + "@babel/preset-env>@babel/plugin-transform-class-static-block": true, "@babel/preset-env>@babel/plugin-transform-classes": true, "@babel/preset-env>@babel/plugin-transform-computed-properties": true, "@babel/preset-env>@babel/plugin-transform-destructuring": true, "@babel/preset-env>@babel/plugin-transform-dotall-regex": true, "@babel/preset-env>@babel/plugin-transform-duplicate-keys": true, + "@babel/preset-env>@babel/plugin-transform-dynamic-import": true, "@babel/preset-env>@babel/plugin-transform-exponentiation-operator": true, + "@babel/preset-env>@babel/plugin-transform-export-namespace-from": true, "@babel/preset-env>@babel/plugin-transform-for-of": true, "@babel/preset-env>@babel/plugin-transform-function-name": true, + "@babel/preset-env>@babel/plugin-transform-json-strings": true, "@babel/preset-env>@babel/plugin-transform-literals": true, + "@babel/preset-env>@babel/plugin-transform-logical-assignment-operators": true, "@babel/preset-env>@babel/plugin-transform-member-expression-literals": true, "@babel/preset-env>@babel/plugin-transform-modules-amd": true, "@babel/preset-env>@babel/plugin-transform-modules-commonjs": true, @@ -268,8 +311,15 @@ "@babel/preset-env>@babel/plugin-transform-modules-umd": true, "@babel/preset-env>@babel/plugin-transform-named-capturing-groups-regex": true, "@babel/preset-env>@babel/plugin-transform-new-target": true, + "@babel/preset-env>@babel/plugin-transform-nullish-coalescing-operator": true, + "@babel/preset-env>@babel/plugin-transform-numeric-separator": true, + "@babel/preset-env>@babel/plugin-transform-object-rest-spread": true, "@babel/preset-env>@babel/plugin-transform-object-super": true, + "@babel/preset-env>@babel/plugin-transform-optional-catch-binding": true, + "@babel/preset-env>@babel/plugin-transform-optional-chaining": true, "@babel/preset-env>@babel/plugin-transform-parameters": true, + "@babel/preset-env>@babel/plugin-transform-private-methods": true, + "@babel/preset-env>@babel/plugin-transform-private-property-in-object": true, "@babel/preset-env>@babel/plugin-transform-property-literals": true, "@babel/preset-env>@babel/plugin-transform-regenerator": true, "@babel/preset-env>@babel/plugin-transform-reserved-words": true, @@ -279,7 +329,9 @@ "@babel/preset-env>@babel/plugin-transform-template-literals": true, "@babel/preset-env>@babel/plugin-transform-typeof-symbol": true, "@babel/preset-env>@babel/plugin-transform-unicode-escapes": true, + "@babel/preset-env>@babel/plugin-transform-unicode-property-regex": true, "@babel/preset-env>@babel/plugin-transform-unicode-regex": true, + "@babel/preset-env>@babel/plugin-transform-unicode-sets-regex": true, "@babel/preset-env>@babel/preset-modules": true, "@babel/preset-env>babel-plugin-polyfill-corejs2": true, "@babel/preset-env>babel-plugin-polyfill-corejs3": true, @@ -297,138 +349,10 @@ "packages": { "@babel/core": true, "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-proposal-optional-chaining": true, - "@babel/preset-env>@babel/plugin-transform-spread>@babel/helper-skip-transparent-expression-wrappers": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-async-generator-functions": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-async-generators": true, - "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator": true, - "depcheck>@babel/traverse>@babel/helper-environment-visitor": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-class-properties": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-proposal-private-methods>@babel/helper-create-class-features-plugin": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-class-static-block": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-proposal-private-methods>@babel/helper-create-class-features-plugin": true, - "@babel/preset-env>@babel/plugin-syntax-class-static-block": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-dynamic-import": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-dynamic-import": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-export-namespace-from": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-export-namespace-from": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-json-strings": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-json-strings": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-logical-assignment-operators": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-logical-assignment-operators": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-nullish-coalescing-operator": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-nullish-coalescing-operator": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-numeric-separator": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-numeric-separator": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-object-rest-spread": { - "packages": { - "@babel/core": true, - "@babel/core>@babel/helper-compilation-targets": true, - "@babel/preset-env>@babel/compat-data": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-object-rest-spread": true, - "@babel/preset-env>@babel/plugin-transform-parameters": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-optional-catch-binding": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-optional-catch-binding": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-optional-chaining": { - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-syntax-optional-chaining": true, + "@babel/preset-env>@babel/plugin-transform-optional-chaining": true, "@babel/preset-env>@babel/plugin-transform-spread>@babel/helper-skip-transparent-expression-wrappers": true } }, - "@babel/preset-env>@babel/plugin-proposal-private-methods": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-proposal-private-methods>@babel/helper-create-class-features-plugin": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-private-methods>@babel/helper-create-class-features-plugin": { - "globals": { - "console.warn": true - }, - "packages": { - "@babel/core": true, - "@babel/preset-env>@babel/plugin-proposal-private-methods>@babel/helper-create-class-features-plugin>semver": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-optimise-call-expression": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": true, - "@babel/preset-env>@babel/plugin-transform-spread>@babel/helper-skip-transparent-expression-wrappers": true, - "depcheck>@babel/traverse>@babel/helper-environment-visitor": true, - "depcheck>@babel/traverse>@babel/helper-function-name": true, - "depcheck>@babel/traverse>@babel/helper-split-export-declaration": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-private-methods>@babel/helper-create-class-features-plugin>semver": { - "globals": { - "console": true, - "process": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-private-property-in-object": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-proposal-private-methods>@babel/helper-create-class-features-plugin": true, - "@babel/preset-env>@babel/plugin-syntax-private-property-in-object": true, - "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true - } - }, - "@babel/preset-env>@babel/plugin-proposal-unicode-property-regex": { - "packages": { - "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true - } - }, "@babel/preset-env>@babel/plugin-syntax-async-generators": { "packages": { "@babel/preset-env>@babel/helper-plugin-utils": true @@ -459,6 +383,11 @@ "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-syntax-import-attributes": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true + } + }, "@babel/preset-env>@babel/plugin-syntax-import-meta": { "packages": { "@babel/preset-env>@babel/helper-plugin-utils": true @@ -509,11 +438,26 @@ "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-syntax-unicode-sets-regex": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true + } + }, "@babel/preset-env>@babel/plugin-transform-arrow-functions": { "packages": { "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-transform-async-generator-functions": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-async-generators": true, + "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator": true, + "depcheck>@babel/traverse>@babel/helper-environment-visitor": true + } + }, "@babel/preset-env>@babel/plugin-transform-async-to-generator": { "packages": { "@babel/core": true, @@ -525,7 +469,6 @@ "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator": { "packages": { "@babel/core": true, - "@babel/core>@babel/types": true, "@babel/preset-env>@babel/plugin-transform-async-to-generator>@babel/helper-remap-async-to-generator>@babel/helper-wrap-function": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, "depcheck>@babel/traverse>@babel/helper-environment-visitor": true @@ -550,6 +493,19 @@ "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-transform-class-properties": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true + } + }, + "@babel/preset-env>@babel/plugin-transform-class-static-block": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-class-static-block": true, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true + } + }, "@babel/preset-env>@babel/plugin-transform-classes": { "packages": { "@babel/core": true, @@ -576,11 +532,9 @@ }, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": { "packages": { - "@babel/core>@babel/template": true, - "@babel/core>@babel/types": true, + "@babel/core": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-optimise-call-expression": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": true, - "depcheck>@babel/traverse": true, "depcheck>@babel/traverse>@babel/helper-environment-visitor": true } }, @@ -612,7 +566,8 @@ "packages": { "@babel/core": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core": true + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>semver": true } }, "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core": { @@ -620,19 +575,19 @@ "characterClassItem.kind": true }, "packages": { + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>@babel/regjsgen": true, "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regenerate": true, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsgen": true, "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsparser": true, "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript": true, "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-value-ecmascript": true } }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regenerate": { + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>@babel/regjsgen": { "globals": { "define": true } }, - "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regjsgen": { + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>regenerate": { "globals": { "define": true } @@ -648,12 +603,24 @@ "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>regexpu-core>unicode-match-property-ecmascript>unicode-property-aliases-ecmascript": true } }, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin>semver": { + "globals": { + "console": true, + "process": true + } + }, "@babel/preset-env>@babel/plugin-transform-duplicate-keys": { "packages": { "@babel/core": true, "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-transform-dynamic-import": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-dynamic-import": true + } + }, "@babel/preset-env>@babel/plugin-transform-exponentiation-operator": { "packages": { "@babel/core": true, @@ -663,13 +630,14 @@ }, "@babel/preset-env>@babel/plugin-transform-exponentiation-operator>@babel/helper-builder-binary-assignment-operator-visitor": { "packages": { - "@babel/core>@babel/types": true, - "@babel/preset-env>@babel/plugin-transform-exponentiation-operator>@babel/helper-builder-binary-assignment-operator-visitor>@babel/helper-explode-assignable-expression": true + "@babel/core>@babel/types": true } }, - "@babel/preset-env>@babel/plugin-transform-exponentiation-operator>@babel/helper-builder-binary-assignment-operator-visitor>@babel/helper-explode-assignable-expression": { + "@babel/preset-env>@babel/plugin-transform-export-namespace-from": { "packages": { - "@babel/core>@babel/types": true + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-export-namespace-from": true } }, "@babel/preset-env>@babel/plugin-transform-for-of": { @@ -685,11 +653,24 @@ "depcheck>@babel/traverse>@babel/helper-function-name": true } }, + "@babel/preset-env>@babel/plugin-transform-json-strings": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-json-strings": true + } + }, "@babel/preset-env>@babel/plugin-transform-literals": { "packages": { "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-transform-logical-assignment-operators": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-logical-assignment-operators": true + } + }, "@babel/preset-env>@babel/plugin-transform-member-expression-literals": { "packages": { "@babel/core": true, @@ -746,6 +727,29 @@ "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-transform-nullish-coalescing-operator": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-nullish-coalescing-operator": true + } + }, + "@babel/preset-env>@babel/plugin-transform-numeric-separator": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-numeric-separator": true + } + }, + "@babel/preset-env>@babel/plugin-transform-object-rest-spread": { + "packages": { + "@babel/core": true, + "@babel/core>@babel/helper-compilation-targets": true, + "@babel/preset-env>@babel/compat-data": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-object-rest-spread": true, + "@babel/preset-env>@babel/plugin-transform-parameters": true + } + }, "@babel/preset-env>@babel/plugin-transform-object-super": { "packages": { "@babel/core": true, @@ -753,12 +757,63 @@ "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": true } }, + "@babel/preset-env>@babel/plugin-transform-optional-catch-binding": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-optional-catch-binding": true + } + }, + "@babel/preset-env>@babel/plugin-transform-optional-chaining": { + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-optional-chaining": true, + "@babel/preset-env>@babel/plugin-transform-spread>@babel/helper-skip-transparent-expression-wrappers": true + } + }, "@babel/preset-env>@babel/plugin-transform-parameters": { "packages": { "@babel/core": true, "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-transform-private-methods": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true + } + }, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": { + "globals": { + "console.warn": true + }, + "packages": { + "@babel/core": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-optimise-call-expression": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-replace-supers>@babel/helper-member-expression-to-functions": true, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin>semver": true, + "@babel/preset-env>@babel/plugin-transform-spread>@babel/helper-skip-transparent-expression-wrappers": true, + "depcheck>@babel/traverse>@babel/helper-environment-visitor": true, + "depcheck>@babel/traverse>@babel/helper-function-name": true, + "depcheck>@babel/traverse>@babel/helper-split-export-declaration": true + } + }, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin>semver": { + "globals": { + "console": true, + "process": true + } + }, + "@babel/preset-env>@babel/plugin-transform-private-property-in-object": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-syntax-private-property-in-object": true, + "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true + } + }, "@babel/preset-env>@babel/plugin-transform-property-literals": { "packages": { "@babel/core": true, @@ -828,18 +883,30 @@ "@babel/preset-env>@babel/helper-plugin-utils": true } }, + "@babel/preset-env>@babel/plugin-transform-unicode-property-regex": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true + } + }, "@babel/preset-env>@babel/plugin-transform-unicode-regex": { "packages": { "@babel/preset-env>@babel/helper-plugin-utils": true, "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true } }, + "@babel/preset-env>@babel/plugin-transform-unicode-sets-regex": { + "packages": { + "@babel/preset-env>@babel/helper-plugin-utils": true, + "@babel/preset-env>@babel/plugin-transform-dotall-regex>@babel/helper-create-regexp-features-plugin": true + } + }, "@babel/preset-env>babel-plugin-polyfill-corejs2": { "packages": { "@babel/core": true, "@babel/preset-env>@babel/compat-data": true, "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": true, - "@babel/preset-env>babel-plugin-polyfill-corejs2>semver": true + "@babel/preset-env>semver": true } }, "@babel/preset-env>babel-plugin-polyfill-corejs2>@babel/helper-define-polyfill-provider": { @@ -867,12 +934,6 @@ "setTimeout": true } }, - "@babel/preset-env>babel-plugin-polyfill-corejs2>semver": { - "globals": { - "console": true, - "process": true - } - }, "@babel/preset-env>babel-plugin-polyfill-corejs3": { "packages": { "@babel/core": true, @@ -957,8 +1018,8 @@ "packages": { "@babel/core": true, "@babel/preset-env>@babel/helper-plugin-utils": true, - "@babel/preset-env>@babel/plugin-proposal-private-methods>@babel/helper-create-class-features-plugin": true, "@babel/preset-env>@babel/plugin-transform-classes>@babel/helper-annotate-as-pure": true, + "@babel/preset-env>@babel/plugin-transform-private-methods>@babel/helper-create-class-features-plugin": true, "@babel/preset-typescript>@babel/plugin-transform-typescript>@babel/plugin-syntax-typescript": true } }, @@ -2135,31 +2196,7 @@ "setTimeout": true }, "packages": { - "del>rimraf>glob": true - } - }, - "del>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 + "nyc>glob": true } }, "depcheck>@babel/traverse": { @@ -2386,33 +2423,9 @@ "packages": { "brfs>resolve": true, "del>is-glob": true, - "eslint-import-resolver-typescript>glob": true, "eslint-plugin-import>tsconfig-paths": true, - "nock>debug": true - } - }, - "eslint-import-resolver-typescript>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 + "nock>debug": true, + "nyc>glob": true } }, "eslint-plugin-import": { @@ -5766,40 +5779,16 @@ "packages": { "eslint>glob-parent": true, "gulp>glob-watcher>is-negated-glob": true, - "gulp>vinyl-fs>glob-stream>glob": true, "gulp>vinyl-fs>glob-stream>ordered-read-streams": true, "gulp>vinyl-fs>glob-stream>pumpify": true, "gulp>vinyl-fs>glob-stream>to-absolute-glob": true, "gulp>vinyl-fs>glob-stream>unique-stream": true, + "nyc>glob": true, "react-markdown>unified>extend": true, "readable-stream": true, "vinyl>remove-trailing-separator": true } }, - "gulp>vinyl-fs>glob-stream>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>vinyl-fs>glob-stream>ordered-read-streams": { "builtin": { "util.inherits": true @@ -6447,6 +6436,30 @@ "util.promisify": true } }, + "nyc>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 + } + }, "nyc>glob>fs.realpath": { "builtin": { "fs.lstat": true, @@ -7981,31 +7994,7 @@ "setTimeout": true }, "packages": { - "stylelint>file-entry-cache>flat-cache>rimraf>glob": true - } - }, - "stylelint>file-entry-cache>flat-cache>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 + "nyc>glob": true } }, "stylelint>file-entry-cache>flat-cache>write": { diff --git a/package.json b/package.json index 4ff01e9f53aa..386fb28df474 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "11.1.0", + "version": "11.3.0", "private": true, "repository": { "type": "git", @@ -13,20 +13,21 @@ "start:mmi": "yarn start --build-type mmi", "start:lavamoat": "yarn build:dev dev --apply-lavamoat=true", "dist": "yarn build dist", + "dist:mv3": "ENABLE_MV3=true yarn build dist", "build": "yarn lavamoat:build", "build:dev": "node development/build/index.js", - "start:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build:dev testDev", - "start:test:flask": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 BLOCKAID_FILE_CDN=storage.googleapis.com/ppom-mock-cdn yarn build:dev testDev --build-type flask --apply-lavamoat=false --snow=false", - "start:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build:dev testDev", + "start:test": "BLOCKAID_FILE_CDN=static.metafi-dev.codefi.network/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev", + "start:test:flask": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --build-type flask --apply-lavamoat=false --snow=false", + "start:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev", "benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/benchmark.js", "mv3:stats:chrome": "SELENIUM_BROWSER=chrome ENABLE_MV3=true ts-node test/e2e/mv3-perf-stats/index.js", "user-actions-benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/user-actions-benchmark.js", "benchmark:firefox": "SELENIUM_BROWSER=firefox ts-node test/e2e/benchmark.js", - "build:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build test", - "build:test:flask": "BLOCKAID_FILE_CDN=storage.googleapis.com/ppom-mock-cdn yarn build test --build-type flask", + "build:test": "BLOCKAID_FILE_CDN=static.metafi-dev.codefi.network/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", + "build:test:flask": "yarn build test --build-type flask", "build:test:mmi": "yarn build test --build-type mmi", - "build:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build test", - "build:test:dev:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build:dev testDev --apply-lavamoat=false", + "build:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", + "build:test:dev:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --apply-lavamoat=false", "test": "yarn lint && yarn test:unit && yarn test:unit:jest", "dapp": "node development/static-server.js node_modules/@metamask/test-dapp/dist --port 8080", "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'", @@ -40,12 +41,11 @@ "test:e2e:chrome": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:e2e:chrome:mmi": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mmi", "test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps", - "test:e2e:chrome:mv3": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mv3", + "test:e2e:chrome:mv3": "ENABLE_MV3=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:e2e:chrome:rpc": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --rpc", "test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js", "test:e2e:firefox:snaps": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --snaps", "test:e2e:single": "node test/e2e/run-e2e-test.js", - "test:e2e:report": "node ./test/e2e/e2e-process-report.js && jrm ./test/test-results/e2e.xml \"./test/test-results/e2e/*.xml\"", "test:coverage:mocha": "node ./test/run-unit-tests.js --mocha --coverage", "test:coverage:jest": "node ./test/run-unit-tests.js --jestGlobal --coverage", "test:coverage:jest:dev": "node ./test/run-unit-tests.js --jestDev --coverage", @@ -103,12 +103,13 @@ "add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts", "check-pr-has-required-labels": "ts-node ./.github/scripts/check-pr-has-required-labels.ts", "close-release-bug-report-issue": "ts-node ./.github/scripts/close-release-bug-report-issue.ts", + "check-issue-template-and-add-labels": "ts-node ./.github/scripts/check-issue-template-and-add-labels.ts", "audit": "yarn npm audit --recursive --environment production --severity moderate" }, "resolutions": { "simple-update-notifier@^1.0.0": "^2.0.0", - "@babel/core": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", - "@babel/runtime": "patch:@babel/runtime@npm%3A7.18.9#./.yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch", + "@babel/core": "patch:@babel/core@npm%3A7.23.2#~/.yarn/patches/@babel-core-npm-7.23.2-b93f586907.patch", + "@babel/runtime": "patch:@babel/runtime@npm%3A7.23.2#~/.yarn/patches/@babel-runtime-npm-7.23.2-d013d6cf7e.patch", "@metamask/approval-controller": "^3.4.0", "@types/react": "^16.9.53", "analytics-node/axios": "^0.21.2", @@ -122,7 +123,6 @@ "ast-types": "^0.14.2", "x-default-browser": "^0.5.2", "web3-provider-engine/eth-json-rpc-filters": "^6.0.0", - "typescript@~4.4.0": "patch:typescript@npm:4.4.4#.yarn/patches/typescript-npm-4.4.4-3fedcc07a3.patch", "acorn@^7.0.0": "patch:acorn@npm:7.4.1#.yarn/patches/acorn-npm-7.4.1-f450b4646c.patch", "acorn@^7.4.1": "patch:acorn@npm:7.4.1#.yarn/patches/acorn-npm-7.4.1-f450b4646c.patch", "acorn@^7.1.1": "patch:acorn@npm:7.4.1#.yarn/patches/acorn-npm-7.4.1-f450b4646c.patch", @@ -205,12 +205,13 @@ "request@^2.88.2": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "lavamoat-core@npm:^14.4.1": "patch:lavamoat-core@npm%3A14.4.1#~/.yarn/patches/lavamoat-core-npm-14.4.1-c4e8bbb016.patch", - "@metamask/signature-controller@^6.0.0": "patch:@metamask/signature-controller@npm%3A6.0.0#~/.yarn/patches/@metamask-signature-controller-npm-6.0.0-90e8e479a9.patch", + "@metamask/signature-controller@^6.1.2": "patch:@metamask/signature-controller@npm%3A6.1.2#~/.yarn/patches/@metamask-signature-controller-npm-6.1.2-f60d8a4960.patch", "semver@7.3.7": "^7.5.4", - "semver@7.3.8": "^7.5.4" + "semver@7.3.8": "^7.5.4", + "@metamask/eth-keyring-controller@npm:^13.0.1": "patch:@metamask/eth-keyring-controller@npm%3A13.0.1#~/.yarn/patches/@metamask-eth-keyring-controller-npm-13.0.1-06ff83faad.patch" }, "dependencies": { - "@babel/runtime": "^7.18.9", + "@babel/runtime": "^7.23.2", "@blockaid/ppom_release": "^1.2.8", "@download/blockies": "^1.0.3", "@ensdomains/content-hash": "^2.5.6", @@ -226,28 +227,28 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@lavamoat/snow": "^1.5.0", "@material-ui/core": "^4.11.0", - "@metamask-institutional/custody-controller": "^0.2.12", - "@metamask-institutional/custody-keyring": "^1.0.2", - "@metamask-institutional/extension": "^0.3.5", - "@metamask-institutional/institutional-features": "^1.2.4", + "@metamask-institutional/custody-controller": "^0.2.15", + "@metamask-institutional/custody-keyring": "^1.0.4", + "@metamask-institutional/extension": "^0.3.8", + "@metamask-institutional/institutional-features": "^1.2.7", "@metamask-institutional/portfolio-dashboard": "^1.4.0", "@metamask-institutional/rpc-allowlist": "^1.0.0", - "@metamask-institutional/sdk": "^0.1.18", - "@metamask-institutional/transaction-update": "^0.1.27", + "@metamask-institutional/sdk": "^0.1.20", + "@metamask-institutional/transaction-update": "^0.1.30", "@metamask/address-book-controller": "^3.0.0", "@metamask/announcement-controller": "^4.0.0", "@metamask/approval-controller": "^3.4.0", - "@metamask/assets-controllers": "^9.2.0", + "@metamask/assets-controllers": "^16.0.0", "@metamask/base-controller": "^3.2.0", "@metamask/browser-passworder": "^4.1.0", "@metamask/contract-metadata": "^2.3.1", - "@metamask/controller-utils": "^4.2.0", + "@metamask/controller-utils": "^5.0.0", "@metamask/design-tokens": "^1.12.0", "@metamask/desktop": "^0.3.0", "@metamask/eth-json-rpc-middleware": "^11.0.0", "@metamask/eth-keyring-controller": "^13.0.1", "@metamask/eth-ledger-bridge-keyring": "^0.15.0", - "@metamask/eth-snap-keyring": "0.3.1", + "@metamask/eth-snap-keyring": "^1.0.0", "@metamask/eth-token-tracker": "^4.0.0", "@metamask/eth-trezor-keyring": "^1.1.0", "@metamask/etherscan-link": "^2.2.0", @@ -255,32 +256,32 @@ "@metamask/gas-fee-controller": "^6.0.1", "@metamask/jazzicon": "^2.0.0", "@metamask/key-tree": "^9.0.0", - "@metamask/keyring-controller": "^8.0.1", + "@metamask/keyring-api": "^1.0.0", + "@metamask/keyring-controller": "^8.0.3", "@metamask/logging-controller": "^1.0.1", - "@metamask/logo": "^3.1.1", + "@metamask/logo": "^3.1.2", "@metamask/message-manager": "^7.3.0", "@metamask/metamask-eth-abis": "^3.0.0", "@metamask/name-controller": "^3.0.0", - "@metamask/network-controller": "^12.2.0", + "@metamask/network-controller": "^14.0.0", "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", "@metamask/permission-controller": "^4.0.0", "@metamask/phishing-controller": "^6.0.0", "@metamask/post-message-stream": "^6.2.0", - "@metamask/ppom-validator": "^0.5.0", - "@metamask/providers": "^11.1.0", + "@metamask/ppom-validator": "^0.8.0", + "@metamask/providers": "^13.1.0", "@metamask/rate-limit-controller": "^3.0.0", - "@metamask/rpc-methods": "^2.0.0", + "@metamask/rpc-methods": "^3.0.0", "@metamask/safe-event-emitter": "^2.0.0", "@metamask/scure-bip39": "^2.0.3", - "@metamask/selected-network-controller": "^1.0.0", - "@metamask/signature-controller": "^6.0.0", - "@metamask/slip44": "^3.0.0", + "@metamask/selected-network-controller": "^2.0.0", + "@metamask/signature-controller": "^6.1.2", + "@metamask/slip44": "^3.1.0", "@metamask/smart-transactions-controller": "^4.0.0", - "@metamask/snaps-controllers": "^2.0.1", - "@metamask/snaps-ui": "^2.0.0", - "@metamask/snaps-utils": "^2.0.1", - "@metamask/subject-metadata-controller": "^2.0.0", + "@metamask/snaps-controllers": "^3.0.0", + "@metamask/snaps-ui": "^3.0.0", + "@metamask/snaps-utils": "^3.0.0", "@metamask/utils": "^5.0.0", "@ngraveio/bc-ur": "^1.1.6", "@popperjs/core": "^2.4.0", @@ -372,14 +373,14 @@ "devDependencies": { "@actions/core": "^1.10.0", "@actions/github": "^5.1.1", - "@babel/code-frame": "^7.12.13", - "@babel/core": "^7.21.5", - "@babel/eslint-parser": "^7.13.14", - "@babel/eslint-plugin": "^7.12.1", - "@babel/preset-env": "^7.5.5", - "@babel/preset-react": "^7.0.0", - "@babel/preset-typescript": "^7.16.7", - "@babel/register": "^7.5.5", + "@babel/code-frame": "^7.22.13", + "@babel/core": "^7.23.2", + "@babel/eslint-parser": "^7.22.15", + "@babel/eslint-plugin": "^7.22.10", + "@babel/preset-env": "^7.23.2", + "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.23.2", + "@babel/register": "^7.22.15", "@lavamoat/allow-scripts": "^2.3.1", "@lavamoat/lavapack": "^5.2.4", "@metamask/auto-changelog": "^2.1.0", @@ -390,23 +391,24 @@ "@metamask/eslint-config-typescript": "^9.0.1", "@metamask/forwarder": "^1.1.0", "@metamask/phishing-warning": "^2.1.0", - "@metamask/test-dapp": "^7.1.0", + "@metamask/test-dapp": "^7.2.0", "@sentry/cli": "^2.19.4", - "@storybook/addon-a11y": "^7.0.11", - "@storybook/addon-actions": "^7.0.11", - "@storybook/addon-essentials": "^7.0.11", + "@storybook/addon-a11y": "^7.4.6", + "@storybook/addon-actions": "^7.4.6", + "@storybook/addon-designs": "^7.0.5", + "@storybook/addon-essentials": "^7.4.6", "@storybook/addon-knobs": "^7.0.2", - "@storybook/addon-mdx-gfm": "^7.0.11", - "@storybook/addons": "^7.0.11", - "@storybook/api": "^7.0.11", - "@storybook/client-api": "^7.0.11", - "@storybook/components": "^7.0.11", - "@storybook/core-events": "^7.0.11", - "@storybook/react": "^7.0.11", - "@storybook/react-webpack5": "^7.0.11", + "@storybook/addon-mdx-gfm": "^7.4.6", + "@storybook/addons": "^7.4.6", + "@storybook/api": "^7.4.6", + "@storybook/client-api": "^7.4.6", + "@storybook/components": "^7.4.6", + "@storybook/core-events": "^7.4.6", + "@storybook/react": "^7.4.6", + "@storybook/react-webpack5": "^7.4.6", "@storybook/storybook-deployer": "^2.8.16", "@storybook/test-runner": "^0.10.0", - "@storybook/theming": "^7.0.11", + "@storybook/theming": "^7.4.6", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^10.4.8", "@testing-library/react-hooks": "^8.0.1", @@ -435,6 +437,7 @@ "@types/sinon": "^10.0.13", "@types/w3c-web-hid": "^1.0.3", "@types/watchify": "^3.11.1", + "@types/webextension-polyfill": "^0.10.4", "@types/yargs": "^17.0.8", "@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/parser": "^5.30.7", @@ -447,7 +450,7 @@ "browserify": "^17.0.0", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "chromedriver": "^116.0.0", + "chromedriver": "^118.0.1", "concurrently": "^7.6.0", "copy-webpack-plugin": "^6.0.3", "cross-spawn": "^7.0.3", @@ -471,7 +474,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.23.1", "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-storybook": "^0.6.12", + "eslint-plugin-storybook": "^0.6.15", "fake-indexeddb": "^4.0.1", "fancy-log": "^1.3.3", "fast-glob": "^3.2.2", @@ -502,7 +505,6 @@ "jest-environment-jsdom": "^29.1.2", "js-yaml": "^4.1.0", "jsdom": "^16.7.0", - "junit-report-merger": "^4.0.0", "koa": "^2.7.0", "lavamoat": "^7.1.2", "lavamoat-browserify": "^15.7.4", @@ -511,6 +513,7 @@ "loose-envify": "^1.4.0", "madge": "^6.1.0", "mocha": "^9.2.2", + "mocha-junit-reporter": "^2.2.1", "mockttp": "^2.6.0", "nock": "^13.2.9", "node-fetch": "^2.6.1", @@ -537,8 +540,8 @@ "source-map": "^0.7.2", "source-map-explorer": "^2.4.2", "squirrelly": "^9.0.0", - "storybook": "^7.0.11", - "storybook-dark-mode": "^3.0.0", + "storybook": "^7.4.6", + "storybook-dark-mode": "^3.0.1", "stream-browserify": "^3.0.0", "string.prototype.matchall": "^4.0.2", "style-loader": "^0.21.0", @@ -548,7 +551,7 @@ "through2": "^4.0.2", "ts-node": "^10.5.0", "ttest": "^2.1.1", - "typescript": "~4.4.0", + "typescript": "~4.5.0", "vinyl": "^2.2.1", "vinyl-buffer": "^1.0.1", "vinyl-source-stream": "^2.0.0", @@ -628,7 +631,8 @@ "@storybook/addon-knobs>core-js": false, "@storybook/manager-webpack5>core-js": false, "@storybook/react-webpack5>@storybook/preset-react-webpack>@pmmmwh/react-refresh-webpack-plugin>core-js-pure": false, - "ethjs-contract>babel-runtime>core-js": false + "ethjs-contract>babel-runtime>core-js": false, + "ts-node>@swc/core": false } }, "packageManager": "yarn@4.0.0-rc.48" diff --git a/privacy-snapshot.json b/privacy-snapshot.json new file mode 100644 index 000000000000..6685b0f680b0 --- /dev/null +++ b/privacy-snapshot.json @@ -0,0 +1,41 @@ +[ + "acl.execution.consensys.io", + "api.coingecko.com", + "api.lens.dev", + "api.segment.io", + "arbitrum-mainnet.infura.io", + "bafkreifvhjdf6ve4jfv6qytqtux5nd4nwnelioeiqx5x2ez5yrgrzk7ypi.ipfs.dweb.link", + "bafybeidxfmwycgzcp4v2togflpqh2gnibuexjy4m4qqwxp7nh3jx5zlh4y.ipfs.dweb.link", + "cdnjs.cloudflare.com", + "chainid.network", + "configuration.dev.metamask-institutional.io", + "configuration.metamask-institutional.io", + "connect.trezor.io", + "customnetwork.com", + "doesntexist.abc", + "etherscan.io", + "execution.consensys.io", + "fonts.gstatic.com", + "gas-api.metaswap.codefi.network", + "github.com", + "localhost:8545", + "mainnet.infura.io", + "metamask.github.io", + "min-api.cryptocompare.com", + "phishing-detection.metafi.codefi.network", + "portfolio.metamask.io", + "proxy.metafi.codefi.network", + "raw.githubusercontent.com", + "registry.npmjs.org", + "responsive-rpc.url", + "sentry.io", + "start.metamask.io", + "static.metafi-dev.codefi.network", + "static.metafi.codefi.network", + "swap.metaswap.codefi.network", + "test.metamask-phishing.io", + "token-api.metaswap.codefi.network", + "tx-insights.metaswap.codefi.network", + "unresponsive-rpc.url", + "www.4byte.directory" +] diff --git a/shared/constants/app.ts b/shared/constants/app.ts index 47bb37e4b5ae..60dd772ae1f7 100644 --- a/shared/constants/app.ts +++ b/shared/constants/app.ts @@ -81,6 +81,7 @@ export const SNAP_DIALOG_TYPES = { export const SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES = { confirmAccountCreation: 'snap_manageAccounts:confirmAccountCreation', confirmAccountRemoval: 'snap_manageAccounts:confirmAccountRemoval', + showSnapAccountRedirect: 'showSnapAccountRedirect', }; ///: END:ONLY_INCLUDE_IN diff --git a/shared/constants/copy.ts b/shared/constants/copy.ts new file mode 100644 index 000000000000..3f997f4f9998 --- /dev/null +++ b/shared/constants/copy.ts @@ -0,0 +1,3 @@ +export const COPY_OPTIONS = { + format: 'text/plain', +}; diff --git a/shared/constants/logs.ts b/shared/constants/logs.ts new file mode 100644 index 000000000000..1965a4cc0cd0 --- /dev/null +++ b/shared/constants/logs.ts @@ -0,0 +1,4 @@ +// This constant used for event property while logging events by LoggingController +export const LOG_EVENT = { + VERSION_UPDATE: 'Extension version update', +}; diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index e9b26f6c6712..dc58a3b7acf7 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -149,6 +149,10 @@ export type MetaMetricsEventOptions = { }; export type MetaMetricsEventFragment = { + /** + * The action ID of transaction metadata object. + */ + actionId?: string; /** * The event name to fire when the fragment is closed in an affirmative action. */ @@ -558,7 +562,7 @@ export enum MetaMetricsEventName { ProviderMethodCalled = 'Provider Method Called', PublicAddressCopied = 'Public Address Copied', QuoteError = 'Quote Error', - ServiceWorkerRestarted = 'Service Worker Restarted', + SettingsUpdated = 'Settings Updated', SignatureApproved = 'Signature Approved', SignatureFailed = 'Signature Failed', SignatureRejected = 'Signature Rejected', @@ -676,7 +680,6 @@ export enum MetaMetricsEventCategory { Petnames = 'Petnames', Phishing = 'Phishing', Retention = 'Retention', - ServiceWorkers = 'service_workers', Settings = 'Settings', Snaps = 'Snaps', Swaps = 'Swaps', diff --git a/shared/constants/network.ts b/shared/constants/network.ts index db5ca5477740..56564fdc32a1 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -108,24 +108,6 @@ export const NETWORK_NAMES = { HOMESTEAD: 'homestead', }; -/** - * The Network ID for our builtin networks. This is the decimal equivalent of - * the chain id for the network, but is expresssed as a string. Many moons ago - * the decision was made on the extension team to expressly use chainId with - * hex encoding over network id. Consider that when accessing this object. Note - * for cross product purposes: alignment with mobile on this matter has not - * been fully achieved, thus it is possible for some dependencies to still - * ask for or require network id. - */ -export const NETWORK_IDS = { - MAINNET: '1', - GOERLI: '5', - LOCALHOST: '1337', - SEPOLIA: '11155111', - LINEA_GOERLI: '59140', - LINEA_MAINNET: '59144', -} as const; - /** * An object containing all of the chain ids for networks both built in and * those that we have added custom code to support our feature set. @@ -181,7 +163,7 @@ export const POLYGON_DISPLAY_NAME = 'Polygon'; export const AVALANCHE_DISPLAY_NAME = 'Avalanche Network C-Chain'; export const ARBITRUM_DISPLAY_NAME = 'Arbitrum One'; export const BNB_DISPLAY_NAME = 'BNB Chain'; -export const OPTIMISM_DISPLAY_NAME = 'Optimism'; +export const OPTIMISM_DISPLAY_NAME = 'OP Mainnet'; export const FANTOM_DISPLAY_NAME = 'Fantom Opera'; export const HARMONY_DISPLAY_NAME = 'Harmony Mainnet Shard 0'; export const PALM_DISPLAY_NAME = 'Palm'; @@ -296,35 +278,29 @@ export const TEST_NETWORK_TICKER_MAP: { */ export const BUILT_IN_NETWORKS = { [NETWORK_TYPES.GOERLI]: { - networkId: NETWORK_IDS.GOERLI, chainId: CHAIN_IDS.GOERLI, ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], blockExplorerUrl: `https://${NETWORK_TYPES.GOERLI}.etherscan.io`, }, [NETWORK_TYPES.SEPOLIA]: { - networkId: NETWORK_IDS.SEPOLIA, chainId: CHAIN_IDS.SEPOLIA, ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], blockExplorerUrl: `https://${NETWORK_TYPES.SEPOLIA}.etherscan.io`, }, [NETWORK_TYPES.LINEA_GOERLI]: { - networkId: NETWORK_IDS.LINEA_GOERLI, chainId: CHAIN_IDS.LINEA_GOERLI, ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_GOERLI], blockExplorerUrl: 'https://goerli.lineascan.build', }, [NETWORK_TYPES.MAINNET]: { - networkId: NETWORK_IDS.MAINNET, chainId: CHAIN_IDS.MAINNET, blockExplorerUrl: `https://etherscan.io`, }, [NETWORK_TYPES.LINEA_MAINNET]: { - networkId: NETWORK_IDS.LINEA_MAINNET, chainId: CHAIN_IDS.LINEA_MAINNET, blockExplorerUrl: 'https://lineascan.build', }, [NETWORK_TYPES.LOCALHOST]: { - networkId: NETWORK_IDS.LOCALHOST, chainId: CHAIN_IDS.LOCALHOST, }, } as const; @@ -352,13 +328,6 @@ export const NETWORK_TO_NAME_MAP = { [NETWORK_TYPES.LINEA_MAINNET]: LINEA_MAINNET_DISPLAY_NAME, [NETWORK_TYPES.LOCALHOST]: LOCALHOST_DISPLAY_NAME, - [NETWORK_IDS.GOERLI]: GOERLI_DISPLAY_NAME, - [NETWORK_IDS.SEPOLIA]: SEPOLIA_DISPLAY_NAME, - [NETWORK_IDS.LINEA_GOERLI]: LINEA_GOERLI_DISPLAY_NAME, - [NETWORK_IDS.MAINNET]: MAINNET_DISPLAY_NAME, - [NETWORK_IDS.LINEA_MAINNET]: LINEA_MAINNET_DISPLAY_NAME, - [NETWORK_IDS.LOCALHOST]: LOCALHOST_DISPLAY_NAME, - [CHAIN_IDS.GOERLI]: GOERLI_DISPLAY_NAME, [CHAIN_IDS.SEPOLIA]: SEPOLIA_DISPLAY_NAME, [CHAIN_IDS.LINEA_GOERLI]: LINEA_GOERLI_DISPLAY_NAME, @@ -402,21 +371,12 @@ export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { [CHAIN_IDS.GNOSIS]: GNOSIS_TOKEN_IMAGE_URL, } as const; -export const NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP = { - [NETWORK_IDS.GOERLI]: NETWORK_TYPES.GOERLI, - [NETWORK_IDS.SEPOLIA]: NETWORK_TYPES.SEPOLIA, - [NETWORK_IDS.LINEA_GOERLI]: NETWORK_TYPES.LINEA_GOERLI, - [NETWORK_IDS.MAINNET]: NETWORK_NAMES.HOMESTEAD, - [NETWORK_IDS.LINEA_MAINNET]: NETWORK_TYPES.LINEA_MAINNET, -} as const; - -export const CHAIN_ID_TO_NETWORK_ID_MAP = { - [CHAIN_IDS.MAINNET]: NETWORK_IDS.MAINNET, - [CHAIN_IDS.GOERLI]: NETWORK_IDS.GOERLI, - [CHAIN_IDS.SEPOLIA]: NETWORK_IDS.SEPOLIA, - [CHAIN_IDS.LINEA_GOERLI]: NETWORK_IDS.LINEA_GOERLI, - [CHAIN_IDS.LINEA_MAINNET]: NETWORK_IDS.LINEA_MAINNET, - [CHAIN_IDS.LOCALHOST]: NETWORK_IDS.LOCALHOST, +export const CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP = { + [CHAIN_IDS.GOERLI]: NETWORK_TYPES.GOERLI, + [CHAIN_IDS.SEPOLIA]: NETWORK_TYPES.SEPOLIA, + [CHAIN_IDS.LINEA_GOERLI]: NETWORK_TYPES.LINEA_GOERLI, + [CHAIN_IDS.MAINNET]: NETWORK_NAMES.HOMESTEAD, + [CHAIN_IDS.LINEA_MAINNET]: NETWORK_TYPES.LINEA_MAINNET, } as const; export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = { @@ -445,99 +405,80 @@ export const ETHERSCAN_SUPPORTED_NETWORKS = { subdomain: `${defaultEtherscanSubdomainPrefix}-${ CHAIN_ID_TO_TYPE_MAP[CHAIN_IDS.GOERLI] }`, - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.GOERLI], }, [CHAIN_IDS.MAINNET]: { domain: defaultEtherscanDomain, subdomain: defaultEtherscanSubdomainPrefix, - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.MAINNET], }, [CHAIN_IDS.SEPOLIA]: { domain: defaultEtherscanDomain, subdomain: `${defaultEtherscanSubdomainPrefix}-${ CHAIN_ID_TO_TYPE_MAP[CHAIN_IDS.SEPOLIA] }`, - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.SEPOLIA], }, [CHAIN_IDS.LINEA_GOERLI]: { domain: 'lineascan.build', subdomain: 'goerli', - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.LINEA_GOERLI], }, [CHAIN_IDS.LINEA_MAINNET]: { domain: 'lineascan.build', subdomain: defaultEtherscanSubdomainPrefix, - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.LINEA_MAINNET], }, [CHAIN_IDS.BSC]: { domain: 'bscscan.com', subdomain: defaultEtherscanSubdomainPrefix, - networkId: parseInt(CHAIN_IDS.BSC, 16).toString(), }, [CHAIN_IDS.BSC_TESTNET]: { domain: 'bscscan.com', subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`, - networkId: parseInt(CHAIN_IDS.BSC_TESTNET, 16).toString(), }, [CHAIN_IDS.OPTIMISM]: { domain: defaultEtherscanDomain, subdomain: `${defaultEtherscanSubdomainPrefix}-optimistic`, - networkId: parseInt(CHAIN_IDS.OPTIMISM, 16).toString(), }, [CHAIN_IDS.OPTIMISM_TESTNET]: { domain: defaultEtherscanDomain, subdomain: `${defaultEtherscanSubdomainPrefix}-goerli-optimistic`, - networkId: parseInt(CHAIN_IDS.OPTIMISM_TESTNET, 16).toString(), }, [CHAIN_IDS.POLYGON]: { domain: 'polygonscan.com', subdomain: defaultEtherscanSubdomainPrefix, - networkId: parseInt(CHAIN_IDS.POLYGON, 16).toString(), }, [CHAIN_IDS.POLYGON_TESTNET]: { domain: 'polygonscan.com', subdomain: `${defaultEtherscanSubdomainPrefix}-mumbai`, - networkId: parseInt(CHAIN_IDS.POLYGON_TESTNET, 16).toString(), }, [CHAIN_IDS.AVALANCHE]: { domain: 'snowtrace.io', subdomain: defaultEtherscanSubdomainPrefix, - networkId: parseInt(CHAIN_IDS.AVALANCHE, 16).toString(), }, [CHAIN_IDS.AVALANCHE_TESTNET]: { domain: 'snowtrace.io', subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`, - networkId: parseInt(CHAIN_IDS.AVALANCHE_TESTNET, 16).toString(), }, [CHAIN_IDS.FANTOM]: { domain: 'ftmscan.com', subdomain: defaultEtherscanSubdomainPrefix, - networkId: parseInt(CHAIN_IDS.FANTOM, 16).toString(), }, [CHAIN_IDS.FANTOM_TESTNET]: { domain: 'ftmscan.com', subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`, - networkId: parseInt(CHAIN_IDS.FANTOM_TESTNET, 16).toString(), }, [CHAIN_IDS.MOONBEAM]: { domain: 'moonscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-moonbeam`, - networkId: parseInt(CHAIN_IDS.MOONBEAM, 16).toString(), }, [CHAIN_IDS.MOONBEAM_TESTNET]: { domain: 'moonscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-moonbase`, - networkId: parseInt(CHAIN_IDS.MOONBEAM_TESTNET, 16).toString(), }, [CHAIN_IDS.MOONRIVER]: { domain: 'moonscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-moonriver`, - networkId: parseInt(CHAIN_IDS.MOONRIVER, 16).toString(), }, [CHAIN_IDS.GNOSIS]: { domain: 'gnosisscan.io', subdomain: `${defaultEtherscanSubdomainPrefix}-gnosis`, - networkId: parseInt(CHAIN_IDS.GNOSIS, 16).toString(), }, }; diff --git a/shared/constants/security-provider.ts b/shared/constants/security-provider.ts index cb2773d290a6..d2d7373d24b2 100644 --- a/shared/constants/security-provider.ts +++ b/shared/constants/security-provider.ts @@ -43,12 +43,10 @@ export enum BlockaidReason { transferFarming = 'transfer_farming', /** Direct theft of assets using transferFrom */ transferFromFarming = 'transfer_from_farming', - /** Malicious trade that results in the victim being rained */ - unfairTrade = 'unfair_trade', other = 'other', - // Locally defined + // MetaMask defined reasons failed = 'Failed', notApplicable = 'NotApplicable', } @@ -57,7 +55,8 @@ export enum BlockaidResultType { Malicious = 'Malicious', Warning = 'Warning', Benign = 'Benign', - // Locally defined + + // MetaMask defined result types Failed = 'Failed', NotApplicable = 'NotApplicable', } diff --git a/shared/constants/snaps.ts b/shared/constants/snaps.ts index 7c15f6d71d3b..b6843afb9cf7 100644 --- a/shared/constants/snaps.ts +++ b/shared/constants/snaps.ts @@ -156,7 +156,7 @@ export const SNAPS_DERIVATION_PATHS: SnapsDerivationPath[] = [ name: 'Secret Network', }, { - path: ['m', `44'`, `397'`], + path: ['m', `44'`, `397'`, `0'`], curve: 'ed25519', name: 'NEAR Protocol', }, diff --git a/shared/constants/snaps/permissions.ts b/shared/constants/snaps/permissions.ts index 26d1f8ee9417..cec2717315ac 100644 --- a/shared/constants/snaps/permissions.ts +++ b/shared/constants/snaps/permissions.ts @@ -9,6 +9,9 @@ export const EndowmentPermissions = Object.freeze({ 'endowment:lifecycle-hooks': 'endowment:lifecycle-hooks', 'endowment:name-lookup': 'endowment:name-lookup', ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) + 'endowment:keyring': 'endowment:keyring', + ///: END:ONLY_INCLUDE_IN } as const); // Methods / permissions in external packages that we are temporarily excluding. @@ -17,8 +20,6 @@ export const ExcludedSnapPermissions = Object.freeze({ ///: BEGIN:ONLY_INCLUDE_IN(build-main) snap_getLocale: 'This permission is still in development and therefore not available.', - 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/issues/990.', @@ -26,8 +27,6 @@ export const ExcludedSnapPermissions = Object.freeze({ export const ExcludedSnapEndowments = Object.freeze({ ///: BEGIN:ONLY_INCLUDE_IN(build-main) - 'endowment:keyring': - 'This endowment is still in development therefore not available.', 'endowment:lifecycle-hooks': 'This endowment is experimental and therefore not available.', 'endowment:name-lookup': diff --git a/shared/constants/swaps.ts b/shared/constants/swaps.ts index 1b60dc8f95fc..7d456df38a90 100644 --- a/shared/constants/swaps.ts +++ b/shared/constants/swaps.ts @@ -109,6 +109,10 @@ export const ZKSYNC_ERA_SWAPS_TOKEN_OBJECT: SwapsTokenObject = { ...ETH_SWAPS_TOKEN_OBJECT, } as const; +export const LINEA_SWAPS_TOKEN_OBJECT: SwapsTokenObject = { + ...ETH_SWAPS_TOKEN_OBJECT, +} as const; + // A gas value for ERC20 approve calls that should be sufficient for all ERC20 approve implementations export const DEFAULT_ERC20_APPROVE_GAS = '0x1d4c0'; @@ -120,6 +124,7 @@ const POLYGON_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31'; const AVALANCHE_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31'; const OPTIMISM_CONTRACT_ADDRESS = '0x9dda6ef3d919c9bc8885d5560999a3640431e8e6'; const ARBITRUM_CONTRACT_ADDRESS = '0x9dda6ef3d919c9bc8885d5560999a3640431e8e6'; +const LINEA_CONTRACT_ADDRESS = '0x9dda6ef3d919c9bc8885d5560999a3640431e8e6'; const ZKSYNC_ERA_CONTRACT_ADDRESS = '0xf504c1fe13d14df615e66dcd0abf39e60c697f34'; @@ -139,6 +144,8 @@ export const WETH_ARBITRUM_CONTRACT_ADDRESS = '0x82af49447d8a07e3bd95bd0d56f35241523fbab1'; export const WETH_ZKSYNC_ERA_CONTRACT_ADDRESS = '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91'; +export const WETH_LINEA_CONTRACT_ADDRESS = + '0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f'; const SWAPS_TESTNET_CHAIN_ID = '0x539'; @@ -155,6 +162,7 @@ const AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL = 'https://snowtrace.io/'; const OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL = 'https://optimistic.etherscan.io/'; const ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL = 'https://arbiscan.io/'; const ZKSYNC_DEFAULT_BLOCK_EXPLORER_URL = 'https://explorer.zksync.io/'; +const LINEA_DEFAULT_BLOCK_EXPLORER_URL = 'https://lineascan.build/'; export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [ CHAIN_IDS.MAINNET, @@ -165,6 +173,7 @@ export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [ CHAIN_IDS.OPTIMISM, CHAIN_IDS.ARBITRUM, CHAIN_IDS.ZKSYNC_ERA, + CHAIN_IDS.LINEA_MAINNET, ] as const; export const ALLOWED_DEV_SWAPS_CHAIN_IDS = [ @@ -187,6 +196,7 @@ export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = { [CHAIN_IDS.OPTIMISM]: OPTIMISM_CONTRACT_ADDRESS, [CHAIN_IDS.ARBITRUM]: ARBITRUM_CONTRACT_ADDRESS, [CHAIN_IDS.ZKSYNC_ERA]: ZKSYNC_ERA_CONTRACT_ADDRESS, + [CHAIN_IDS.LINEA_MAINNET]: LINEA_CONTRACT_ADDRESS, } as const; export const SWAPS_WRAPPED_TOKENS_ADDRESSES = { @@ -199,6 +209,7 @@ export const SWAPS_WRAPPED_TOKENS_ADDRESSES = { [CHAIN_IDS.OPTIMISM]: WETH_OPTIMISM_CONTRACT_ADDRESS, [CHAIN_IDS.ARBITRUM]: WETH_ARBITRUM_CONTRACT_ADDRESS, [CHAIN_IDS.ZKSYNC_ERA]: WETH_ZKSYNC_ERA_CONTRACT_ADDRESS, + [CHAIN_IDS.LINEA_MAINNET]: WETH_LINEA_CONTRACT_ADDRESS, } as const; export const ALLOWED_CONTRACT_ADDRESSES = { @@ -238,6 +249,10 @@ export const ALLOWED_CONTRACT_ADDRESSES = { SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.ZKSYNC_ERA], SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.ZKSYNC_ERA], ], + [CHAIN_IDS.LINEA_MAINNET]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.LINEA_MAINNET], + SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.LINEA_MAINNET], + ], } as const; export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = { @@ -250,6 +265,7 @@ export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = { [CHAIN_IDS.OPTIMISM]: OPTIMISM_SWAPS_TOKEN_OBJECT, [CHAIN_IDS.ARBITRUM]: ARBITRUM_SWAPS_TOKEN_OBJECT, [CHAIN_IDS.ZKSYNC_ERA]: ZKSYNC_ERA_SWAPS_TOKEN_OBJECT, + [CHAIN_IDS.LINEA_MAINNET]: LINEA_SWAPS_TOKEN_OBJECT, } as const; export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = { @@ -261,6 +277,7 @@ export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = { [CHAIN_IDS.OPTIMISM]: OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL, [CHAIN_IDS.ARBITRUM]: ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL, [CHAIN_IDS.ZKSYNC_ERA]: ZKSYNC_DEFAULT_BLOCK_EXPLORER_URL, + [CHAIN_IDS.LINEA_MAINNET]: LINEA_DEFAULT_BLOCK_EXPLORER_URL, } as const; export const ETHEREUM = 'ethereum'; @@ -271,6 +288,7 @@ export const AVALANCHE = 'avalanche'; export const OPTIMISM = 'optimism'; export const ARBITRUM = 'arbitrum'; export const ZKSYNC_ERA = 'zksync'; +export const LINEA = 'linea'; export const SWAPS_CLIENT_ID = 'extension'; diff --git a/shared/constants/test-flags.js b/shared/constants/test-flags.js deleted file mode 100644 index a5018d6be90d..000000000000 --- a/shared/constants/test-flags.js +++ /dev/null @@ -1 +0,0 @@ -export const ACTION_QUEUE_METRICS_E2E_TEST = 'action_queue_metrics_e2e_test'; diff --git a/shared/constants/transaction.ts b/shared/constants/transaction.ts index bd80d34bb3cc..24ee75d5d194 100644 --- a/shared/constants/transaction.ts +++ b/shared/constants/transaction.ts @@ -1,4 +1,5 @@ import { AccessList } from '@ethereumjs/tx'; +import { Hex } from '@metamask/utils'; export enum TransactionType { /** @@ -284,12 +285,16 @@ export interface TxParams { accessList?: AccessList; maxFeePerGas?: string; maxPriorityFeePerGas?: string; + estimateSuggested?: string; + estimateUsed?: string; } export interface TxReceipt { blockHash?: string; blockNumber?: string; transactionIndex?: string; + gasUsed?: string; + status?: string; } export interface TxError { @@ -320,6 +325,8 @@ interface DappSuggestedGasFees { * An object representing a transaction, in whatever state it is in. */ export interface TransactionMeta { + /** Unique ID to prevent duplicate requests.*/ + actionId?: string; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) custodyStatus: string; custodyId?: string; @@ -329,7 +336,7 @@ export interface TransactionMeta { * on incoming transactions! */ blockNumber?: string; - chainId: string; + chainId: Hex; /** An internally unique tx identifier. */ id: string; /** Time the transaction was first suggested, in unix epoch time (ms). */ @@ -342,6 +349,7 @@ export interface TransactionMeta { dappProposedTokenAmount: string; /** The original gas fees suggested by the dapp that proposed this transaction */ dappSuggestedGasFees?: DappSuggestedGasFees; + defaultGasEstimates?: any; /** The balance of the token that is being sent */ currentTokenBalance: string; /** The original dapp proposed token approval amount before edit by user */ @@ -362,8 +370,12 @@ export interface TransactionMeta { originalType: TransactionType; /** The current status of the transaction. */ status: TransactionStatus; - /** The transaction's network ID, used for EIP-155 compliance. */ - metamaskNetworkId: string; + /** + * The transaction's network ID, used for EIP-155 compliance. + * + * @deprecated Use `chainId` instead. + */ + readonly metamaskNetworkId?: string; /** TODO: Find out what this is and document it */ loadingDefaults: boolean; /** The transaction params as passed to the network provider. */ @@ -385,6 +397,7 @@ export interface TransactionMeta { * network. */ rawTx: string; + replacedById?: string; /** * A hex string of the transaction hash, used to identify the transaction * on the network. @@ -399,11 +412,13 @@ export interface TransactionMeta { */ submittedTime?: number; /** The error encountered during the transaction */ - txErr?: TxError; + error?: TxError; /** * Whether the transaction is verified on the blockchain. */ verifiedOnBlockchain?: boolean; + securityProviderResponse?: Record; + securityAlertResponse?: any; } /** diff --git a/shared/lib/ui-utils.js b/shared/lib/ui-utils.js index 4cbc5fddf722..cb6f119e6f79 100644 --- a/shared/lib/ui-utils.js +++ b/shared/lib/ui-utils.js @@ -11,3 +11,4 @@ export const ETHERSCAN_PRIVACY_LINK = 'https://etherscan.io/privacyPolicy'; export const CONSENSYS_PRIVACY_LINK = 'https://consensys.net/privacy-policy/'; export const AUTO_DETECT_TOKEN_LEARN_MORE_LINK = 'https://consensys.net/privacy-policy/'; +export const CONSENSYS_TERMS_OF_USE = 'https://consensys.io/terms-of-use'; diff --git a/shared/modules/Numeric.test.ts b/shared/modules/Numeric.test.ts index 33e8f4ee217c..4123524dc41b 100644 --- a/shared/modules/Numeric.test.ts +++ b/shared/modules/Numeric.test.ts @@ -225,37 +225,37 @@ describe('Numeric', () => { it('should compute correct results for division of big numbers', () => { expect( new Numeric('175671432', 10).divide('686216', 10).toString(), - ).toStrictEqual('256.00019818832554181191'); + ).toStrictEqual('256.000198188325541811907620924023922497'); expect( new Numeric('1756714320', 10) .divide(new Numeric('686216', 10)) .toString(), - ).toStrictEqual('2560.00198188325541811908'); + ).toStrictEqual('2560.001981883255418119076209240239224967'); expect( new Numeric('41756714320', 10) .divide(new Numeric('6862160', 10)) .toString(), - ).toStrictEqual('6085.06859647691106007438'); + ).toStrictEqual('6085.068596476911060074378912762162351213'); }); it('should compute correct results for division of negative big numbers', () => { expect( new Numeric('175671432', 10).divide('-686216', 10).toString(), - ).toStrictEqual('-256.00019818832554181191'); + ).toStrictEqual('-256.000198188325541811907620924023922497'); expect( new Numeric('1756714320', 10) .divide(new Numeric('-686216', 10)) .toString(), - ).toStrictEqual('-2560.00198188325541811908'); + ).toStrictEqual('-2560.001981883255418119076209240239224967'); expect( new Numeric('-41756714320', 10) .divide(new Numeric('-6862160', 10)) .toString(), - ).toStrictEqual('6085.06859647691106007438'); + ).toStrictEqual('6085.068596476911060074378912762162351213'); }); }); @@ -377,7 +377,7 @@ describe('Numeric', () => { it('should multiply the value by the inverse of conversionRate supplied when second parameter is true', () => { expect( new Numeric(10, 10).applyConversionRate(468.5, true).toString(), - ).toStrictEqual('0.0213447171824973319'); + ).toStrictEqual('0.02134471718249733191035218783351121'); }); it('should multiply the value by the inverse of the BigNumber conversionRate supplied when second parameter is true', () => { @@ -385,7 +385,22 @@ describe('Numeric', () => { new Numeric(10, 10) .applyConversionRate(new BigNumber(468.5, 10), true) .toString(), - ).toStrictEqual('0.0213447171824973319'); + ).toStrictEqual('0.02134471718249733191035218783351121'); + }); + it('should not return 0 if decimals is greater than 20', () => { + expect( + new Numeric(10, 10) + .applyConversionRate(new BigNumber(1e27, 10), true) + .toString(), + ).toStrictEqual('0.00000000000000000000000001'); + }); + + it('should return 0 if decimals is greater than 32', () => { + expect( + new Numeric(10, 10) + .applyConversionRate(new BigNumber(1e40, 10), true) + .toString(), + ).toStrictEqual('0'); }); }); }); diff --git a/shared/modules/Numeric.ts b/shared/modules/Numeric.ts index e116fdfc7346..875f7b95d903 100644 --- a/shared/modules/Numeric.ts +++ b/shared/modules/Numeric.ts @@ -5,6 +5,8 @@ import { addHexPrefix } from 'ethereumjs-util'; import { EtherDenomination } from '../constants/common'; import { stripHexPrefix } from './hexstring-utils'; +const MAX_DECIMALS_FOR_TOKENS = 36; +BigNumber.config({ DECIMAL_PLACES: MAX_DECIMALS_FOR_TOKENS }); export type NumericValue = string | number | BN | BigNumber; export type NumericBase = 10 | 16; diff --git a/shared/modules/network.utils.ts b/shared/modules/network.utils.ts index 9aeb439eb6e8..d7e14a7c0d3b 100644 --- a/shared/modules/network.utils.ts +++ b/shared/modules/network.utils.ts @@ -1,3 +1,5 @@ +import { isStrictHexString } from '@metamask/utils'; +import { convertHexToDecimal } from '@metamask/controller-utils'; import { CHAIN_IDS, MAX_SAFE_CHAIN_ID } from '../constants/network'; /** @@ -39,6 +41,8 @@ export function isTokenDetectionEnabledForNetwork(chainId: string | undefined) { case CHAIN_IDS.BSC: case CHAIN_IDS.POLYGON: case CHAIN_IDS.AVALANCHE: + case CHAIN_IDS.LINEA_GOERLI: + case CHAIN_IDS.LINEA_MAINNET: case CHAIN_IDS.AURORA: return true; default: @@ -60,3 +64,23 @@ function isSafeInteger(value: unknown): value is number { export function shouldShowLineaMainnet(): boolean { return new Date().getTime() > Date.UTC(2023, 6, 11, 18); } + +/** + * TODO: Delete when ready to remove `networkVersion` from provider object + * Convert the given value into a valid network ID. The ID is accepted + * as either a number, a decimal string, or a 0x-prefixed hex string. + * + * @param value - The network ID to convert, in an unknown format. + * @returns A valid network ID (as a decimal string) + * @throws If the given value cannot be safely parsed. + */ +export function convertNetworkId(value: unknown): string { + if (typeof value === 'number' && !Number.isNaN(value)) { + return `${value}`; + } else if (isStrictHexString(value)) { + return `${convertHexToDecimal(value)}`; + } else if (typeof value === 'string' && /^\d+$/u.test(value)) { + return value; + } + throw new Error(`Cannot parse as a valid network ID: '${value}'`); +} diff --git a/shared/modules/snap-accounts.test.ts b/shared/modules/snap-accounts.test.ts new file mode 100644 index 000000000000..a1c14e302300 --- /dev/null +++ b/shared/modules/snap-accounts.test.ts @@ -0,0 +1,23 @@ +import { showSnapAccountExperimentalToggle } from './snap-accounts'; + +describe('showSnapAccountExperimentalToggle', () => { + beforeEach(() => { + process.env = Object.assign(process.env, { + KEYRING_SNAPS_AVAILABILITY_DATE: '02 Nov 2023 15:00:00 GMT', + }); + }); + + afterEach(() => { + delete process.env.KEYRING_SNAPS_AVAILABILITY_DATE; + }); + + it('returns false if the current date is before November 2, 2023', () => { + jest.useFakeTimers().setSystemTime(new Date(Date.UTC(2023, 9, 31, 5))); + expect(showSnapAccountExperimentalToggle()).toBe(false); + }); + + it('returns true if the current date is after November 2, 2023', () => { + jest.useFakeTimers().setSystemTime(new Date(Date.UTC(2023, 10, 3, 5))); + expect(showSnapAccountExperimentalToggle()).toBe(true); + }); +}); diff --git a/shared/modules/snap-accounts.ts b/shared/modules/snap-accounts.ts new file mode 100644 index 000000000000..42e1d281c6b8 --- /dev/null +++ b/shared/modules/snap-accounts.ts @@ -0,0 +1,21 @@ +///: BEGIN:ONLY_INCLUDE_IN(build-main) +/** + * Compares the current date to a specific date in UTC. + * This method is meant to be used in a specific case related to the + * Snap Accounts API. It should not be used in other cases. + * Note: Months are 0-indexed in JS, so 10 represents November + * + * @returns true if the current date is after the specified date, false otherwise. + */ +export function showSnapAccountExperimentalToggle(): boolean { + const keyringSnapsAvailabilityDate = + process.env.KEYRING_SNAPS_AVAILABILITY_DATE; + if (!keyringSnapsAvailabilityDate) { + return false; + } + + return ( + new Date().getTime() > new Date(keyringSnapsAvailabilityDate).getTime() + ); +} +///: END:ONLY_INCLUDE_IN diff --git a/shared/modules/transaction.utils.js b/shared/modules/transaction.utils.js index 44a681519e1e..558eaa99ee7d 100644 --- a/shared/modules/transaction.utils.js +++ b/shared/modules/transaction.utils.js @@ -39,13 +39,6 @@ const erc20Interface = new Interface(abiERC20); const erc721Interface = new Interface(abiERC721); const erc1155Interface = new Interface(abiERC1155); -export function transactionMatchesNetwork(transaction, chainId, networkId) { - if (typeof transaction.chainId !== 'undefined') { - return transaction.chainId === chainId; - } - return transaction.metamaskNetworkId === networkId; -} - /** * Determines if the maxFeePerGas and maxPriorityFeePerGas fields are supplied * and valid inputs. This will return false for non hex string inputs. diff --git a/shared/notifications/institutional/index.js b/shared/notifications/institutional/index.js deleted file mode 100644 index 78b93399ee8b..000000000000 --- a/shared/notifications/institutional/index.js +++ /dev/null @@ -1,35 +0,0 @@ -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/data/mock-pending-transaction-data.json b/test/data/mock-pending-transaction-data.json index 26ce780eb027..36f1d74767fe 100644 --- a/test/data/mock-pending-transaction-data.json +++ b/test/data/mock-pending-transaction-data.json @@ -5,7 +5,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "approved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": false, "dappSuggestedGasFees": null, @@ -25,7 +24,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "unapproved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": true, "dappSuggestedGasFees": null, @@ -81,7 +79,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "approved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": false, "dappSuggestedGasFees": null, @@ -101,7 +98,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "unapproved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": true, "dappSuggestedGasFees": null, @@ -158,7 +154,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "approved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": false, "dappSuggestedGasFees": null, @@ -178,7 +173,6 @@ "id": 6854191329910881, "time": 1631558469046, "status": "unapproved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": true, "dappSuggestedGasFees": null, diff --git a/test/data/mock-send-state.json b/test/data/mock-send-state.json index 14d4967d70f9..94283e5c29e5 100644 --- a/test/data/mock-send-state.json +++ b/test/data/mock-send-state.json @@ -32,7 +32,6 @@ "id": 3111025347726181, "time": 1620723786838, "status": "unapproved", - "metamaskNetworkId": "5", "chainId": "0x5", "loadingDefaults": false, "txParams": { @@ -302,7 +301,7 @@ "id": 8393540981007587, "time": 1536268017676, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", @@ -316,7 +315,7 @@ "id": 8393540981007587, "time": 1536268017676, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", @@ -350,7 +349,7 @@ "id": 3387511061307736, "time": 1528133130531, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -365,7 +364,7 @@ "id": 3387511061307736, "time": 1528133130531, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -482,7 +481,7 @@ "id": 3387511061307737, "time": 1528133149983, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -497,7 +496,7 @@ "id": 3387511061307737, "time": 1528133149983, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -614,7 +613,7 @@ "id": 3387511061307738, "time": 1528133180635, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -629,7 +628,7 @@ "id": 3387511061307738, "time": 1528133180635, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -746,7 +745,7 @@ "id": 3387511061307739, "time": 1528133223918, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -762,7 +761,7 @@ "id": 3387511061307739, "time": 1528133223918, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -883,7 +882,7 @@ "id": 3387511061307740, "time": 1528133291381, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -899,7 +898,7 @@ "id": 3387511061307740, "time": 1528133291381, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1017,7 +1016,7 @@ "id": 3387511061307741, "time": 1528133318440, "status": "rejected", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1031,7 +1030,7 @@ "id": 3387511061307741, "time": 1528133318440, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", diff --git a/test/data/mock-state.json b/test/data/mock-state.json index be994991cb49..803a2447df90 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -20,6 +20,14 @@ "warning": null, "customTokenAmount": "10" }, + "confirmTransaction": { + "txData": { + "txParams": { + "gas": "0x153e2", + "value": "0x0" + } + } + }, "history": { "mostRecentOverviewPage": "/mostRecentOverviewPage" }, @@ -109,7 +117,6 @@ "unconnectedAccount": true }, "featureFlags": {}, - "networkId": "5", "providerConfig": { "type": "rpc", "nickname": "goerli", @@ -152,6 +159,10 @@ { "type": "Snap Keyring", "accounts": ["0xb552685e3d2790efd64a175b00d51f02cdafee5d"] + }, + { + "type": "Custody test", + "accounts": ["0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281"] } ], "identities": { @@ -170,6 +181,10 @@ "0xeb9e64b93097bc15f01f13eae97015c57ab64823": { "name": "Test Account 3", "address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823" + }, + "0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281": { + "name": "Custody test", + "address": "0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281" } }, "selectedNetworkClientId": "goerli", @@ -597,7 +612,6 @@ "id": 8393540981007587, "time": 1536268017676, "status": "unapproved", - "metamaskNetworkId": "4", "loadingDefaults": false, "txParams": { "data": "0xa9059cbb000000000000000000000000b19ac54efa18cc3a14a5b821bfec73d284bf0c5e0000000000000000000000000000000000000000000000003782dace9d900000", @@ -612,7 +626,7 @@ "id": 8393540981007587, "time": 1536268017676, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", @@ -646,7 +660,7 @@ "id": 3387511061307736, "time": 1528133130531, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -661,7 +675,7 @@ "id": 3387511061307736, "time": 1528133130531, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -778,7 +792,7 @@ "id": 3387511061307737, "time": 1528133149983, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -793,7 +807,7 @@ "id": 3387511061307737, "time": 1528133149983, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -910,7 +924,7 @@ "id": 3387511061307738, "time": 1528133180635, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -925,7 +939,7 @@ "id": 3387511061307738, "time": 1528133180635, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1042,7 +1056,7 @@ "id": 3387511061307739, "time": 1528133223918, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1058,7 +1072,7 @@ "id": 3387511061307739, "time": 1528133223918, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1179,7 +1193,7 @@ "id": 3387511061307740, "time": 1528133291381, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1195,7 +1209,7 @@ "id": 3387511061307740, "time": 1528133291381, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1313,7 +1327,7 @@ "id": 3387511061307741, "time": 1528133318440, "status": "rejected", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": false, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1327,7 +1341,7 @@ "id": 3387511061307741, "time": 1528133318440, "status": "unapproved", - "metamaskNetworkId": "4", + "chainId": "0x5", "loadingDefaults": true, "txParams": { "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", @@ -1391,7 +1405,40 @@ "requestState": { "test": "value" } } }, - "pendingApprovalCount": 1 + "pendingApprovalCount": 1, + "database": { + "verifiedSnaps": { + "npm:@metamask/test-snap-bip44": { + "id": "npm:@metamask/test-snap-bip44", + "metadata": { + "name": "BIP-44", + "author": { + "name": "Consensys", + "website": "https://consensys.io/" + }, + "website": "https://snaps.consensys.io/", + "summary": "An example Snap that signs messages using BLS.", + "description": "An example Snap that signs messages using BLS.", + "audits": [ + { + "auditor": "Consensys Diligence", + "report": "https://consensys.io/diligence/audits/" + } + ], + "category": "interoperability", + "support": { + "contact": "https://github.com/MetaMask" + }, + "sourceCode": "https://github.com/MetaMask/test-snaps" + }, + "versions": { + "5.1.2": { + "checksum": "L1k+dT9Q+y3KfIqzaH09MpDZVPS9ZowEh9w01ZMTWMU=" + } + } + } + } + } }, "send": { "amountMode": "INPUT", diff --git a/test/data/mock-tx-history.json b/test/data/mock-tx-history.json index 1291e9fb5b69..9a5b82e0ed66 100644 --- a/test/data/mock-tx-history.json +++ b/test/data/mock-tx-history.json @@ -5,21 +5,20 @@ "id": 6616756286038869, "time": 1502438908445, "status": "confirmed", - "metamaskNetworkId": "5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "value": "0x0", "gasPrice": "0x3b9aca00", "gas": "0x7b0d", - "chainId": 5 + "chainId": "0x5" }, "history": [ { "id": 6616756286038869, "time": 1502438908445, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -32,7 +31,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -45,7 +44,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -58,7 +57,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -71,7 +70,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -85,7 +84,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -99,7 +98,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -113,7 +112,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -127,7 +126,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -142,7 +141,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -157,7 +156,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -173,7 +172,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -189,7 +188,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -205,7 +204,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -221,7 +220,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -238,7 +237,7 @@ "id": 6616756286038869, "time": 1502438908445, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -260,7 +259,7 @@ "id": 6616756286038870, "time": 1502573153664, "status": "rejected", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -273,7 +272,7 @@ "id": 6616756286038870, "time": 1502573153664, "status": "rejected", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -286,7 +285,7 @@ "id": 6616756286038870, "time": 1502573153664, "status": "rejected", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -301,7 +300,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -316,7 +315,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -329,7 +328,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -342,7 +341,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -355,7 +354,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -368,7 +367,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -382,7 +381,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -396,7 +395,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -411,7 +410,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -426,7 +425,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -442,7 +441,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -458,7 +457,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -475,7 +474,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -492,7 +491,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -509,7 +508,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -526,7 +525,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -544,7 +543,7 @@ "id": 6616756286038871, "time": 1502573157128, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -567,7 +566,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -582,7 +581,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -595,7 +594,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -608,7 +607,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -621,7 +620,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -634,7 +633,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -648,7 +647,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -662,7 +661,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -677,7 +676,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -692,7 +691,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -708,7 +707,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -724,7 +723,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -741,7 +740,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -758,7 +757,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -775,7 +774,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -792,7 +791,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -810,7 +809,7 @@ "id": 6616756286038872, "time": 1502734903652, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -833,7 +832,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -848,7 +847,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -861,7 +860,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -874,7 +873,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -887,7 +886,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -900,7 +899,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -914,7 +913,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -928,7 +927,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -943,7 +942,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -958,7 +957,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -974,7 +973,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -990,7 +989,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1007,7 +1006,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1024,7 +1023,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1041,7 +1040,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1058,7 +1057,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1076,7 +1075,7 @@ "id": 6616756286038873, "time": 1502734910224, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1099,7 +1098,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1114,7 +1113,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1127,7 +1126,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1140,7 +1139,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1153,7 +1152,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1166,7 +1165,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1180,7 +1179,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1194,7 +1193,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1209,7 +1208,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1224,7 +1223,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1240,7 +1239,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1256,7 +1255,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1273,7 +1272,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1290,7 +1289,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1307,7 +1306,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1324,7 +1323,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1342,7 +1341,7 @@ "id": 6616756286038874, "time": 1502734917414, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1365,7 +1364,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1380,7 +1379,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1393,7 +1392,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "unapproved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1406,7 +1405,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1419,7 +1418,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1432,7 +1431,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1446,7 +1445,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "approved", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1460,7 +1459,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1475,7 +1474,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1490,7 +1489,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1506,7 +1505,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1522,7 +1521,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1539,7 +1538,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "signed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1556,7 +1555,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1573,7 +1572,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "submitted", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1590,7 +1589,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", @@ -1608,7 +1607,7 @@ "id": 6616756286038875, "time": 1502734922745, "status": "confirmed", - "metamaskNetworkId": "5", + "chainId": "0x5", "txParams": { "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", diff --git a/test/data/transaction-data.json b/test/data/transaction-data.json index e9189cf15f6b..a9155363ff19 100644 --- a/test/data/transaction-data.json +++ b/test/data/transaction-data.json @@ -5,7 +5,7 @@ "id": 4243712234858512, "time": 1589314601567, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -43,7 +43,7 @@ "id": 4243712234858512, "time": 1589314601567, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -81,7 +81,7 @@ "id": 4243712234858512, "time": 1589314601567, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -123,7 +123,7 @@ "id": 4243712234858507, "time": 1589314355872, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -161,7 +161,7 @@ "id": 4243712234858507, "time": 1589314355872, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -199,7 +199,7 @@ "id": 4243712234858507, "time": 1589314355872, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -241,7 +241,7 @@ "id": 4243712234858506, "time": 1589314345433, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -280,7 +280,7 @@ "id": 4243712234858506, "time": 1589314345433, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -319,7 +319,7 @@ "id": 4243712234858506, "time": 1589314345433, "status": "confirmed", - "metamaskNetworkId": "4", + "chainId": "0x4", "loadingDefaults": false, "txParams": { "from": "0x9eca64466f257793eaa52fcfff5066894b76a149", @@ -360,7 +360,7 @@ "initialTransaction": { "blockNumber": "6477257", "id": 4243712234858505, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1589314295000, "txParams": { @@ -378,7 +378,7 @@ { "blockNumber": "6477257", "id": 4243712234858505, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1589314295000, "txParams": { @@ -396,7 +396,7 @@ "primaryTransaction": { "blockNumber": "6477257", "id": 4243712234858505, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1589314295000, "txParams": { @@ -417,7 +417,7 @@ "initialTransaction": { "blockNumber": "6454493", "id": 4243712234858475, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1588972833000, "txParams": { @@ -435,7 +435,7 @@ { "blockNumber": "6454493", "id": 4243712234858475, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1588972833000, "txParams": { @@ -453,7 +453,7 @@ "primaryTransaction": { "blockNumber": "6454493", "id": 4243712234858475, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1588972833000, "txParams": { @@ -474,7 +474,7 @@ "initialTransaction": { "blockNumber": "6195526", "id": 4243712234858466, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585087013000, "txParams": { @@ -492,7 +492,7 @@ { "blockNumber": "6195526", "id": 4243712234858466, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585087013000, "txParams": { @@ -510,7 +510,7 @@ "primaryTransaction": { "blockNumber": "6195526", "id": 4243712234858466, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585087013000, "txParams": { @@ -531,7 +531,7 @@ "initialTransaction": { "blockNumber": "6195527", "id": 4243712234858467, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -551,7 +551,7 @@ { "blockNumber": "6195527", "id": 4243712234858467, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -572,7 +572,7 @@ "primaryTransaction": { "blockNumber": "6195527", "id": 4243712234858467, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -596,7 +596,7 @@ "initialTransaction": { "blockNumber": "6195527", "id": 4243712234858468, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -615,7 +615,7 @@ { "blockNumber": "6195527", "id": 4243712234858468, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -633,7 +633,7 @@ "primaryTransaction": { "blockNumber": "6195527", "id": 4243712234858468, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -657,7 +657,6 @@ "id": 1441203963845330, "time": 1652206763566, "status": "confirmed", - "metamaskNetworkId": "5", "originalGasEstimate": "0x118e0", "userEditedGasLimit": false, "chainId": "0x5", @@ -750,7 +749,6 @@ "id": 1441203963845330, "time": 1652206763566, "status": "confirmed", - "metamaskNetworkId": "5", "originalGasEstimate": "0x118e0", "userEditedGasLimit": false, "chainId": "0x5", @@ -842,7 +840,6 @@ "id": 1441203963845330, "time": 1652206763566, "status": "confirmed", - "metamaskNetworkId": "5", "originalGasEstimate": "0x118e0", "userEditedGasLimit": false, "chainId": "0x5", @@ -937,7 +934,7 @@ "initialTransaction": { "blockNumber": "6195527", "id": 8401691827212777, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -958,7 +955,7 @@ { "blockNumber": "6195527", "id": 8401691827212777, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { @@ -978,7 +975,7 @@ "primaryTransaction": { "blockNumber": "6195527", "id": 8401691827212777, - "metamaskNetworkId": "4", + "chainId": "0x4", "status": "confirmed", "time": 1585088013000, "txParams": { diff --git a/test/e2e/accounts/test-create-snap-account.spec.js b/test/e2e/accounts/test-create-snap-account.spec.js index 1cd8e2d00656..eb4ffdf93a3e 100644 --- a/test/e2e/accounts/test-create-snap-account.spec.js +++ b/test/e2e/accounts/test-create-snap-account.spec.js @@ -177,6 +177,15 @@ describe('Create Snap Account', function () { tag: 'p', text: 'Successful request', }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + await driver.findElement({ + css: '[data-testid="account-menu-icon"]', + text: 'Account 2', + }); }, ); }); diff --git a/test/e2e/accounts/test-remove-accounts-snap.spec.js b/test/e2e/accounts/test-remove-accounts-snap.spec.js index 850eca6a1f72..a014baafe460 100644 --- a/test/e2e/accounts/test-remove-accounts-snap.spec.js +++ b/test/e2e/accounts/test-remove-accounts-snap.spec.js @@ -96,7 +96,6 @@ describe('Remove Account Snap', function () { await driver.clickElement( '[data-testid="account-options-menu-button"]', ); - await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Snaps', tag: 'div' }); await driver.clickElement({ text: 'MetaMask Simple Snap Keyring', @@ -107,15 +106,30 @@ describe('Remove Account Snap', function () { await driver.clickElement('.toggle-button > div'); // Remove the snap. + const removeButton = await driver.findElement( + '[data-testid="remove-snap-button"]', + ); + await driver.scrollToElement(removeButton); + await driver.clickElement('[data-testid="remove-snap-button"]'); + await driver.clickElement({ - text: 'Remove MetaMask Simple Snap Keyring', - tag: 'p', + text: 'Continue', + tag: 'button', + }); + + await driver.fill( + '[data-testid="remove-snap-confirmation-input"]', + 'MetaMask Simple Snap Keyring', + ); + + await driver.clickElement({ + text: 'Remove Snap', + tag: 'button', }); - await driver.clickElement('#popoverRemoveSnapButton'); // Assert that the snap was removed. const removeResult = await driver.findElement( - '.snap-list-tab__container--no-snaps_inner', + '.snaps__content__list__container--no-snaps_inner', ); assert.equal( await removeResult.getText(), diff --git a/test/e2e/accounts/test-snap-accounts.spec.js b/test/e2e/accounts/test-snap-accounts.spec.js index 91adb3e6ba99..7fb9cc347399 100644 --- a/test/e2e/accounts/test-snap-accounts.spec.js +++ b/test/e2e/accounts/test-snap-accounts.spec.js @@ -1,11 +1,12 @@ +const { strict: assert } = require('assert'); const util = require('ethereumjs-util'); const FixtureBuilder = require('../fixture-builder'); const { - clickSignOnSignatureConfirmation, convertETHToHexGwei, openDapp, PRIVATE_KEY, PRIVATE_KEY_TWO, + defaultGanacheOptions, sendTransaction, switchToNotificationWindow, switchToOrOpenDapp, @@ -59,6 +60,79 @@ describe('Test Snap Account', function () { ); }); + it('will display the keyring snap account removal warning', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + failOnConsoleError: false, + title: this.test.title, + }, + async ({ driver }) => { + await installSnapSimpleKeyring(driver, false); + + await makeNewAccountAndSwitch(driver); + + const windowHandles = await driver.waitUntilXWindowHandles( + 2, + 1000, + 10000, + ); + + // switch to metamask extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + windowHandles, + ); + + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + + await driver.clickElement({ text: 'Snaps', tag: 'div' }); + + await driver.clickElement( + '[data-testid="npm:@metamask/snap-simple-keyring-snap"]', + ); + + const removeButton = await driver.findElement( + '[data-testid="remove-snap-button"]', + ); + await driver.scrollToElement(removeButton); + await driver.clickElement('[data-testid="remove-snap-button"]'); + + assert.equal( + await driver.isElementPresentAndVisible({ text: 'Account 2' }), + true, + ); + + await driver.clickElement({ + text: 'Continue', + tag: 'button', + }); + + await driver.fill( + '[data-testid="remove-snap-confirmation-input"]', + 'MetaMask Simple Snap Keyring', + ); + + await driver.clickElement({ + text: 'Remove Snap', + tag: 'button', + }); + + // Checking result modal + assert.equal( + await driver.isElementPresentAndVisible({ + text: 'MetaMask Simple Snap Keyring removed', + tag: 'p', + }), + true, + ); + }, + ); + }); + it('can import a private key and transfer 1 ETH (sync flow)', async function () { await withFixtures( accountSnapFixtures(this.test.title), @@ -209,14 +283,25 @@ describe('Test Snap Account', function () { await switchToOrOpenDapp(driver); await driver.clickElement(locatorID); - await switchToNotificationWindow(driver, 4); + + // behaviour of chrome and firefox is different, + // chrome needs extra time to load the popup + if (driver.browser === 'chrome') { + await driver.delay(500); + } + const handles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Notification, + handles, + 2000, + ); // these two don't have a contract details page if (locatorID !== '#personalSign' && locatorID !== '#signTypedData') { await validateContractDetails(driver); } - await clickSignOnSignatureConfirmation(driver, 3); + await driver.clickElement({ text: 'Sign', tag: 'button' }); if (isAsyncFlow) { await approveOrRejectRequest(driver, flowType); @@ -345,7 +430,12 @@ describe('Test Snap Account', function () { }); // Click "Create" on the Snap's confirmation popup - await switchToNotificationWindow(driver, 3); + const handles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Notification, + handles, + 2000, + ); await driver.clickElement('[data-testid="confirmation-submit-button"]'); await driver.clickElement('[data-testid="confirmation-submit-button"]'); await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); @@ -373,7 +463,10 @@ describe('Test Snap Account', function () { // click on Accounts await driver.clickElement('[data-testid="account-menu-icon"]'); - const label = await driver.findElement({ css: '.mm-tag', text: 'Snaps' }); + const label = await driver.findElement({ + css: '.mm-tag', + text: 'Snaps (Beta)', + }); label.click(); @@ -386,7 +479,12 @@ describe('Test Snap Account', function () { async function connectAccountToTestDapp(driver) { await switchToOrOpenDapp(driver); await driver.clickElement('#connectButton'); - await switchToNotificationWindow(driver, 4); + const handles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Notification, + handles, + 2000, + ); await driver.clickElement('[data-testid="page-container-footer-next"]'); await driver.clickElement('[data-testid="page-container-footer-next"]'); } @@ -409,6 +507,39 @@ describe('Test Snap Account', function () { * @param {string} flowType */ async function approveOrRejectRequest(driver, flowType) { + // Click redirect button for async flows + if (flowType === 'approve' || flowType === 'reject') { + // There is a try catch here because when using the send eth flow, + // MetaMask is still active, and therefore the popup notification will + // no appear. The workaround is to reload the extension and + // force the notification to appear in the full window. + try { + // Adding a delay here because there is a race condition where + // the driver tries to switch to the first notification window + // and not the second notification window with the redirect button + await driver.delay(500); + const handles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Notification, + handles, + 2000, + ); + } catch (error) { + console.log('SNAPS/ error switching to notification window', error); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.navigate(); + } + await driver.clickElement({ + text: 'Go to site', + tag: 'button', + }); + + await driver.delay(500); + } + await driver.switchToWindowWithTitle('SSK - Simple Snap Keyring'); await driver.clickElementUsingMouseMove({ diff --git a/test/e2e/accounts/utils.ts b/test/e2e/accounts/utils.ts index 3aab19654db4..7087531c91b2 100644 --- a/test/e2e/accounts/utils.ts +++ b/test/e2e/accounts/utils.ts @@ -1,2 +1,2 @@ export const TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL = - 'https://metamask.github.io/snap-simple-keyring/0.2.3/'; + 'https://metamask.github.io/snap-simple-keyring/1.0.1/'; diff --git a/test/e2e/e2e-process-report.js b/test/e2e/e2e-process-report.js deleted file mode 100644 index a9146ec12958..000000000000 --- a/test/e2e/e2e-process-report.js +++ /dev/null @@ -1,10 +0,0 @@ -const fs = require('fs'); - -const dir = 'test/test-results/e2e'; - -fs.readdirSync(dir).forEach((file) => { - const currentFile = `${dir}/${file}`; - let data = fs.readFileSync(currentFile, { encoding: 'utf8', flag: 'r' }); - data = data.substring(data.indexOf(' 0) { + if (process.env.UPDATE_PRIVACY_SNAPSHOT === 'true') { + writeFileSync( + './privacy-snapshot.json', + JSON.stringify(mergedReport, null, 2), + ); + } else { + throw new Error( + `A new host not contained in the privacy-snapshot received a network + request during test execution. Please update the privacy-snapshot + file by passing the --update-privacy-snapshot option to the test + command or add the new hosts to the snapshot manually. + + New hosts found: ${newHosts}.`, + ); + } + } } catch (error) { failed = true; if (webDriver) { @@ -212,7 +256,6 @@ const WINDOW_TITLES = Object.freeze({ ExtensionInFullScreenView: 'MetaMask', TestDApp: 'E2E Test Dapp', Notification: 'MetaMask Notification', - ServiceWorkerSettings: 'Inspect with Chrome Developer Tools', InstalledExtensions: 'Extensions', SnapSimpleKeyringDapp: 'SSK - Simple Snap Keyring', }); @@ -546,8 +589,6 @@ const defaultGanacheOptions = { accounts: [{ secretKey: PRIVATE_KEY, balance: convertETHToHexGwei(25) }], }; -const SERVICE_WORKER_URL = 'chrome://inspect/#service-workers'; - const sendTransaction = async ( driver, recipientAddress, @@ -557,8 +598,16 @@ const sendTransaction = async ( await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill('[data-testid="ens-input"]', recipientAddress); await driver.fill('.unit-input__input', quantity); - await driver.clickElement('[data-testid="page-container-footer-next"]'); - await driver.clickElement('[data-testid="page-container-footer-next"]'); + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); // the default is to do this block, but if we're testing an async flow, it would get stuck here if (!isAsyncFlow) { @@ -592,19 +641,10 @@ const TEST_SEED_PHRASE = const TEST_SEED_PHRASE_TWO = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'; -// Usually happens when onboarded to make sure the state is retrieved from metamaskState properly -const assertAccountBalanceForDOM = async (driver, ganacheServer) => { - const balance = await ganacheServer.getBalance(); - const balanceElement = await driver.findElement( - '[data-testid="eth-overview__primary-currency"]', - ); - assert.equal(`${balance}\nETH`, await balanceElement.getText()); -}; - -// Usually happens after txn is made +// Usually happens when onboarded to make sure the state is retrieved from metamaskState properly, or after txn is made const locateAccountBalanceDOM = async (driver, ganacheServer) => { const balance = await ganacheServer.getBalance(); - await driver.waitForSelector({ + await driver.findElement({ css: '[data-testid="eth-overview__primary-currency"]', text: `${balance} ETH`, }); @@ -639,13 +679,9 @@ const unlockWallet = async (driver) => { const logInWithBalanceValidation = async (driver, ganacheServer) => { await unlockWallet(driver); - await assertAccountBalanceForDOM(driver, ganacheServer); + await locateAccountBalanceDOM(driver, ganacheServer); }; -async function sleepSeconds(sec) { - return new Promise((resolve) => setTimeout(resolve, sec * 1000)); -} - function roundToXDecimalPlaces(number, decimalPlaces) { return Math.round(number * 10 ** decimalPlaces) / 10 ** decimalPlaces; } @@ -669,34 +705,6 @@ function genRandInitBal(minETHBal = 10, maxETHBal = 100, decimalPlaces = 4) { return { initialBalance, initialBalanceInHex }; } -async function terminateServiceWorker(driver) { - await driver.openNewPage(SERVICE_WORKER_URL); - - await driver.waitForSelector({ - text: 'Service workers', - tag: 'button', - }); - await driver.clickElement({ - text: 'Service workers', - tag: 'button', - }); - - await driver.delay(tinyDelayMs); - const serviceWorkerElements = await driver.findClickableElements({ - text: 'terminate', - tag: 'span', - }); - - // 1st one is app-init.js; while 2nd one is service-worker.js - await serviceWorkerElements[serviceWorkerElements.length - 1].click(); - - const serviceWorkerTab = await driver.switchToWindowWithTitle( - WINDOW_TITLES.ServiceWorkerSettings, - ); - - await driver.closeWindowHandle(serviceWorkerTab); -} - /** * This method handles clicking the sign button on signature confrimation * screen. @@ -726,7 +734,13 @@ async function validateContractDetails(driver) { await driver.clickElement({ text: 'Got it', tag: 'button' }); // Approve signing typed data - await driver.clickElement('[data-testid="signature-request-scroll-button"]'); + try { + await driver.clickElement( + '[data-testid="signature-request-scroll-button"]', + ); + } catch (error) { + // Ignore error if scroll button is not present + } await driver.delay(regularDelayMs); } @@ -809,7 +823,6 @@ function assertInAnyOrder(requests, assertions) { module.exports = { DAPP_URL, DAPP_ONE_URL, - SERVICE_WORKER_URL, TEST_SEED_PHRASE, TEST_SEED_PHRASE_TWO, PRIVATE_KEY, @@ -839,7 +852,6 @@ module.exports = { findAnotherAccountFromAccountList, unlockWallet, logInWithBalanceValidation, - assertAccountBalanceForDOM, locateAccountBalanceDOM, waitForAccountRendered, generateGanacheOptions, @@ -849,8 +861,6 @@ module.exports = { convertETHToHexGwei, roundToXDecimalPlaces, generateRandNumBetween, - sleepSeconds, - terminateServiceWorker, clickSignOnSignatureConfirmation, validateContractDetails, switchToNotificationWindow, diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js new file mode 100644 index 000000000000..e88424c01876 --- /dev/null +++ b/test/e2e/json-rpc/switchEthereumChain.spec.js @@ -0,0 +1,321 @@ +const { strict: assert } = require('assert'); +const { + withFixtures, + defaultGanacheOptions, + openDapp, + DAPP_URL, + DAPP_ONE_URL, + switchToNotificationWindow, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); + +describe('Switch Ethereum Chain for two dapps', function () { + it('switches the chainId of two dapps when switchEthereumChain of one dapp is confirmed', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTwoTestDapps() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: { port: 8546, chainId: 1338 }, + }, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Window Handling + const windowHandles = await driver.getAllWindowHandles(); + const dappOne = windowHandles[1]; + const dappTwo = windowHandles[2]; + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Confirm switchEtheruemChain + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + + // Switch to Dapp One + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Wait for chain id element to change, there's a page reload. + await driver.waitForSelector({ + css: '#chainId', + text: '0x53a', + }); + + // Dapp One ChainId assertion + const dappOneChainId = await driver.findElement('#chainId'); + assert.equal(await dappOneChainId.getText(), '0x53a'); + + // Switch to Dapp Two + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // Dapp Two ChainId Assertion + const dappTwoChainId = await driver.findElement('#chainId'); + assert.equal(await dappTwoChainId.getText(), '0x53a'); + }, + ); + }); + + it('should queue switchEthereumChain request from second dapp after send tx request', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTwoTestDapps() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: { port: 8546, chainId: 1338 }, + }, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Window Handling + const windowHandles = await driver.getAllWindowHandles(); + const dappOne = windowHandles[1]; + + // Initiate send transaction on Dapp two + await driver.clickElement('#sendButton'); + + // Switch Ethereum chain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Switch to Dapp One + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to tx and confirm send tx. + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + // Delay here after notification for second notification popup for switchEthereumChain + await driver.delay(1000); + + // Switch and confirm to queued notification for switchEthereumChain + await switchToNotificationWindow(driver, 4); + + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + }, + ); + }); + + it('should queue send tx after switchEthereum request with a warning, confirming removes pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTwoTestDapps() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: { port: 8546, chainId: 1338 }, + }, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Window Handling + let windowHandles = await driver.getAllWindowHandles(); + const dappOne = windowHandles[1]; + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to notification of switchEthereumChain + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + + // Switch to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + + // Switch to nofication that should still be switchEthereumChain request but with an warning. + await switchToNotificationWindow(driver, 4); + + await driver.findElement({ + span: 'span', + text: 'Switching networks will cancel all pending confirmations', + }); + + // Confirm switchEtheruemChain with queued pending tx + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + + // Window handles should only be expanded mm, dapp one, dapp 2 (3 total) + await driver.wait(async () => { + windowHandles = await driver.getAllWindowHandles(); + return windowHandles.length === 3; + }); + }, + ); + }); + + it('should queue send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTwoTestDapps() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: { port: 8546, chainId: 1338 }, + }, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Window Handling + const windowHandles = await driver.getAllWindowHandles(); + const dappOne = windowHandles[1]; + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to notification of switchEthereumChain + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + + // Switch to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + + // Switch to notification that should still be switchEthereumChain request but with an warning. + await switchToNotificationWindow(driver, 4); + + await driver.findElement({ + span: 'span', + text: 'Switching networks will cancel all pending confirmations', + }); + + // Cancel switchEtheruemChain with queued pending tx + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + + // Delay for second notification of the pending tx + await driver.delay(1000); + + // Switch to new pending tx notification + await switchToNotificationWindow(driver, 4); + await driver.findElement({ + text: 'Sending ETH', + tag: 'span', + }); + + // Confirm pending tx + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + }, + ); + }); +}); diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 86964698f907..4abc5f24972c 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -17,7 +17,7 @@ const Ganache = require('./ganache'); const ganacheServer = new Ganache(); const dappPort = 8080; -describe('MetaMask', function () { +describe('MetaMask @no-mmi', function () { let driver; let dappServer; let tokenAddress; @@ -232,7 +232,7 @@ describe('MetaMask', function () { await driver.delay(tinyDelayMs); const tokenContractAddress = await driver.waitForSelector({ - css: '#tokenAddress', + css: '#tokenAddresses', text: '0x', }); tokenAddress = await tokenContractAddress.getText(); diff --git a/test/e2e/metrics/signature-approved.spec.js b/test/e2e/metrics/signature-approved.spec.js index e45900e4264d..52b3eac7c774 100644 --- a/test/e2e/metrics/signature-approved.spec.js +++ b/test/e2e/metrics/signature-approved.spec.js @@ -75,18 +75,24 @@ describe('Signature Approved Event @no-mmi', function () { await clickSignOnSignatureConfirmation(driver); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { + account_type: 'MetaMask', signature_type: 'eth_signTypedData_v4', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); assert.deepStrictEqual(events[1].properties, { + account_type: 'MetaMask', signature_type: 'eth_signTypedData_v4', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); }, ); @@ -118,18 +124,24 @@ describe('Signature Approved Event @no-mmi', function () { await clickSignOnSignatureConfirmation(driver); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { + account_type: 'MetaMask', signature_type: 'eth_signTypedData_v3', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); assert.deepStrictEqual(events[1].properties, { + account_type: 'MetaMask', signature_type: 'eth_signTypedData_v3', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); }, ); @@ -160,18 +172,24 @@ describe('Signature Approved Event @no-mmi', function () { await clickSignOnSignatureConfirmation(driver); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { + account_type: 'MetaMask', signature_type: 'eth_signTypedData', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); assert.deepStrictEqual(events[1].properties, { + account_type: 'MetaMask', signature_type: 'eth_signTypedData', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); }, ); @@ -202,18 +220,24 @@ describe('Signature Approved Event @no-mmi', function () { await clickSignOnSignatureConfirmation(driver); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { + account_type: 'MetaMask', signature_type: 'personal_sign', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); assert.deepStrictEqual(events[1].properties, { + account_type: 'MetaMask', signature_type: 'personal_sign', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); }, ); @@ -253,18 +277,24 @@ describe('Signature Approved Event @no-mmi', function () { ); const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { + account_type: 'MetaMask', signature_type: 'eth_sign', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); assert.deepStrictEqual(events[1].properties, { + account_type: 'MetaMask', signature_type: 'eth_sign', category: 'inpage_provider', locale: 'en', chain_id: '0x539', environment_type: 'background', + security_alert_reason: 'NotApplicable', + security_alert_response: 'NotApplicable', }); }, ); diff --git a/test/e2e/metrics/transaction-finalized.spec.js b/test/e2e/metrics/transaction-finalized.spec.js index b72bb0598de7..b8a77077454a 100644 --- a/test/e2e/metrics/transaction-finalized.spec.js +++ b/test/e2e/metrics/transaction-finalized.spec.js @@ -3,10 +3,10 @@ const { isEqual, omit } = require('lodash'); const { defaultGanacheOptions, withFixtures, - unlockWallet, sendTransaction, getEventPayloads, assertInAnyOrder, + logInWithBalanceValidation, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -147,9 +147,9 @@ describe('Transaction Finalized Event', function () { title: this.test.title, testSpecificMock: mockSegment, }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { + async ({ driver, mockedEndpoint: mockedEndpoints, ganacheServer }) => { await driver.navigate(); - await unlockWallet(driver); + await logInWithBalanceValidation(driver, ganacheServer); await sendTransaction(driver, RECIPIENT, '2.0'); diff --git a/test/e2e/metrics/unlock-wallet.spec.js b/test/e2e/metrics/unlock-wallet.spec.js index b980b0bb7ae4..5c6afc76d8d2 100644 --- a/test/e2e/metrics/unlock-wallet.spec.js +++ b/test/e2e/metrics/unlock-wallet.spec.js @@ -2,6 +2,7 @@ const { strict: assert } = require('assert'); const { withFixtures, unlockWallet, + waitForAccountRendered, defaultGanacheOptions, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -35,6 +36,7 @@ describe('Unlock wallet', function () { async ({ driver, mockedEndpoint }) => { await driver.navigate(); await unlockWallet(driver); + await waitForAccountRendered(driver); await driver.wait(async () => { const isPending = await mockedEndpoint.isPending(); return isPending === false; diff --git a/test/e2e/metrics/wallet-created.spec.js b/test/e2e/metrics/wallet-created.spec.js index 17152df356fa..7f9979fd4086 100644 --- a/test/e2e/metrics/wallet-created.spec.js +++ b/test/e2e/metrics/wallet-created.spec.js @@ -48,8 +48,8 @@ async function mockSegment(mockServer) { ]; } -describe('Wallet Created Events', function () { - it('are sent when onboarding user who chooses to opt in metrics @no-mmi', async function () { +describe('Wallet Created Events @no-mmi', function () { + it('are sent when onboarding user who chooses to opt in metrics', async function () { await withFixtures( { fixtures: new FixtureBuilder({ onboarding: true }) diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index 91a32c5c5fdb..f54507bef438 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -8,16 +8,49 @@ const { mockEmptyStalelistAndHotlist, } = require('./tests/phishing-controller/mocks'); +const emptyHtmlPage = () => ` + + + + E2E Test Page + + + Empty page by MetaMask + +`; + +/** + * The browser makes requests to domains within its own namespace for + * functionality specific to the browser. For example when running E2E tests in + * firefox the act of adding the extension from the firefox settins triggers + * a series of requests to various mozilla.net or mozilla.com domains. These + * are not requests that the extension itself makes. + */ +const browserAPIRequestDomains = + /^.*\.(googleapis\.com|google\.com|mozilla\.net|mozilla\.com|mozilla\.org|gvt1\.com)$/iu; + +/** + * @typedef {import('mockttp').Mockttp} Mockttp + * @typedef {import('mockttp').MockedEndpoint} MockedEndpoint + */ + +/** + * @typedef {object} SetupMockReturn + * @property {MockedEndpoint} mockedEndpoint - If a testSpecificMock was provided, returns the mockedEndpoint + * @property {() => string[]} getPrivacyReport - A function to get the current privacy report. + */ + /** * Setup E2E network mocks. * - * @param {object} server - The mock server used for network mocks. - * @param {Function} testSpecificMock - A function for setting up test-specific network mocks + * @param {Mockttp} server - The mock server used for network mocks. + * @param {(server: Mockttp) => MockedEndpoint} testSpecificMock - A function for setting up test-specific network mocks * @param {object} options - Network mock options. * @param {string} options.chainId - The chain ID used by the default configured network. - * @returns + * @returns {SetupMockReturn} */ async function setupMocking(server, testSpecificMock, { chainId }) { + const privacyReport = new Set(); await server.forAnyRequest().thenPassThrough({ beforeRequest: (req) => { const { host } = req.headers; @@ -34,6 +67,35 @@ async function setupMocking(server, testSpecificMock, { chainId }) { // Mocks below this line can be overridden by test-specific mocks + // Account link + const accountLinkRegex = + /^https:\/\/etherscan.io\/address\/0x[a-fA-F0-9]{40}$/u; + await server.forGet(accountLinkRegex).thenCallback(() => { + return { + statusCode: 200, + body: emptyHtmlPage(), + }; + }); + + // Token tracker link + const tokenTrackerRegex = + /^https:\/\/etherscan.io\/token\/0x[a-fA-F0-9]{40}$/u; + await server.forGet(tokenTrackerRegex).thenCallback(() => { + return { + statusCode: 200, + body: emptyHtmlPage(), + }; + }); + + // Explorer link + const explorerLinkRegex = /^https:\/\/etherscan.io\/tx\/0x[a-fA-F0-9]{64}$/u; + await server.forGet(explorerLinkRegex).thenCallback(() => { + return { + statusCode: 200, + body: emptyHtmlPage(), + }; + }); + await server .forPost( 'https://arbitrum-mainnet.infura.io/v3/00000000000000000000000000000000', @@ -391,7 +453,31 @@ async function setupMocking(server, testSpecificMock, { chainId }) { await mockLensNameProvider(server); await mockTokenNameProvider(server, chainId); - return mockedEndpoint; + /** + * Returns an array of alphanumerically sorted hostnames that were requested + * during the current test suite. + * + * @returns {string[]} privacy report for the current test suite. + */ + function getPrivacyReport() { + return [...privacyReport].sort(); + } + + /** + * Listen for requests and add the hostname to the privacy report if it did + * not previously exist. This is used to track which hosts are requested + * during the current test suite and used to ask for extra scrutiny when new + * hosts are added to the privacy-snapshot.json file. We intentionally do not + * add hosts to the report that are requested as part of the browsers normal + * operation. See the browserAPIRequestDomains regex above. + */ + server.on('request-initiated', (request) => { + if (request.headers.host.match(browserAPIRequestDomains) === null) { + privacyReport.add(request.headers.host); + } + }); + + return { mockedEndpoint, getPrivacyReport }; } async function mockLensNameProvider(server) { @@ -445,4 +531,4 @@ async function mockTokenNameProvider(server) { } } -module.exports = { setupMocking }; +module.exports = { setupMocking, emptyHtmlPage }; diff --git a/test/e2e/mock-server-json-rpc.ts b/test/e2e/mock-server-json-rpc.ts new file mode 100644 index 000000000000..810135247d76 --- /dev/null +++ b/test/e2e/mock-server-json-rpc.ts @@ -0,0 +1,69 @@ +import { MockttpServer } from 'mockttp'; +import { mockJsonRpcResult } from '../mocks/json-rpc-result'; + +type RequestConfig = [ + method: string, + options?: { + /** optional arbitrary method variant name to return various result values */ + methodResultVariant?: string; + /** optional params value for JSON request used in withJsonBodyIncluding() */ + params?: any; + /** optional result value returned in JSON response */ + result?: any; + }, +]; + +const DEFAULT_VARIANT = 'default'; + +/** + * Helper function to assist with mocking JSON-RPC POST requests + * + * @param mockServer + * @param listOfRequestConfigs + * @example + * ``` + * await mockServerJsonRpc(mockServer, [ + * ['eth_call', { + * params: ['0x52cbe0f49ccdd4dc6e9c13bab024eabd2842045b'], + * result: '0x0000000000000000000000000000000000000000000000000ddfe4d79cbd3de5', + * }], + * ['eth_call', { + * methodResultVariant: 'balanceChecker', + * params: [{to :'0x52cbe0f49ccdd4dc6e9c13bab024eabd2842045b'}], + * }], + * ['eth_gasPrice], + * ['eth_getBalance', { + * result: '0x27d17a5b79f77509541', + * }], + * ['eth_getBlockByNumber'] + * ]); + * ``` + */ +async function mockServerJsonRpc( + mockServer: MockttpServer, + listOfRequestConfigs: RequestConfig[], +) { + for (const [method, options] of listOfRequestConfigs) { + const { methodResultVariant, params, result: _result } = options || {}; + + const result = + _result || + mockJsonRpcResult[method][methodResultVariant || DEFAULT_VARIANT]; + + await mockServer + .forPost() + .withJsonBodyIncluding(params ? { method, params } : { method }) + .thenCallback((req: any) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result, + }, + }; + }); + } +} + +module.exports = { mockServerJsonRpc }; diff --git a/test/e2e/mv3/multiple-restarts.spec.js b/test/e2e/mv3/multiple-restarts.spec.js deleted file mode 100644 index 9dad3a34ab48..000000000000 --- a/test/e2e/mv3/multiple-restarts.spec.js +++ /dev/null @@ -1,426 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - openDapp, - generateGanacheOptions, - WALLET_PASSWORD, - WINDOW_TITLES, - DEFAULT_GANACHE_OPTIONS, - generateRandNumBetween, - sleepSeconds, - terminateServiceWorker, - unlockWallet, - largeDelayMs, - genRandInitBal, - roundToXDecimalPlaces, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); - -describe('MV3 - Restart service worker multiple times', function () { - it('Simple simple send flow within full screen view should still be usable', async function () { - const { initialBalance, initialBalanceInHex } = genRandInitBal(); - - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: generateGanacheOptions({ - accounts: [ - { - secretKey: DEFAULT_GANACHE_OPTIONS.accounts[0].secretKey, - balance: initialBalanceInHex, - }, - ], - }), - title: this.test.title, - driverOptions: { openDevToolsForTabs: true }, - }, - async ({ driver }) => { - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - await assertETHBalance(driver, initialBalance); - - // first send ETH and then terminate SW - const RECIPIENT_ADDRESS = '0x985c30949c92df7a0bd42e0f3e3d539ece98db24'; - const amountFirstTx = roundToXDecimalPlaces( - generateRandNumBetween(0.5, 2), - 4, - ); - - const gasFeesFirstTx = await simpleSendETH( - driver, - amountFirstTx, - RECIPIENT_ADDRESS, - ); - const totalAfterFirstTx = roundToXDecimalPlaces( - initialBalance - amountFirstTx - gasFeesFirstTx, - 4, - ); - - await terminateServiceWorker(driver); - - await assertETHBalance(driver, totalAfterFirstTx); - - // first send ETH #2 and then terminate SW - const amountSecondTx = roundToXDecimalPlaces( - generateRandNumBetween(0.5, 2), - 4, - ); - const gasFeesSecondTx = await simpleSendETH( - driver, - amountSecondTx, - RECIPIENT_ADDRESS, - ); - const totalAfterSecondTx = roundToXDecimalPlaces( - initialBalance - - amountFirstTx - - gasFeesFirstTx - - amountSecondTx - - gasFeesSecondTx, - 4, - ); - - await terminateServiceWorker(driver); - - await assertETHBalance(driver, totalAfterSecondTx); - - // first terminate SW and then send ETH - const amountThirdTx = roundToXDecimalPlaces( - generateRandNumBetween(0.5, 2), - 4, - ); - const gasFeesThirdTx = await simpleSendETH( - driver, - amountThirdTx, - RECIPIENT_ADDRESS, - ); - const totalAfterThirdTx = roundToXDecimalPlaces( - initialBalance - - amountFirstTx - - gasFeesFirstTx - - amountSecondTx - - gasFeesSecondTx - - amountThirdTx - - gasFeesThirdTx, - 4, - ); - - await assertETHBalance(driver, totalAfterThirdTx); - }, - ); - - async function simpleSendETH(driver, value, recipient) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.clickElement('[data-testid="eth-overview-send"]'); - await driver.fill('[data-testid="ens-input"]', recipient); - const formattedValue = `${value}`.replace('.', ','); - await driver.fill('.unit-input__input', formattedValue); - - await driver.clickElement('[data-testid="page-container-footer-next"]'); - - const gasFeesEl = await driver.findElement( - '.transaction-detail-item__detail-values .currency-display-component', - ); - const gasFees = await gasFeesEl.getText(); - - await driver.clickElement('[data-testid="page-container-footer-next"]'); - await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.findElement('.activity-list-item'); - // reset view to assets tab - await driver.clickElement('[data-testid="home__asset-tab"]'); - - return gasFees; - } - - async function assertETHBalance(driver, expectedBalance) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - const isETHBalanceOverviewPresentAndVisible = - await driver.isElementPresentAndVisible({ - css: '[data-testid="eth-overview__primary-currency"]', - text: `${expectedBalance} ETH`, - }); - - assert.equal( - isETHBalanceOverviewPresentAndVisible, - true, - `Balance DOM element should be visible and match ${expectedBalance} ETH.`, - ); - } - }); - - it('Should continue to support add network dApp interactions after service worker re-starts multiple times', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: generateGanacheOptions({ - concurrent: { port: 8546, chainId: 1338 }, - }), - title: this.test.title, - driverOptions: { openDevToolsForTabs: true }, - }, - async ({ driver }) => { - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - await openDapp(driver); - - // Click add Ethereum chain - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#addEthereumChain'); - - // Notification pop up opens - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - let notification = await driver.isElementPresent({ - text: 'Allow this site to add a network?', - tag: 'h3', - }); - assert.ok(notification, 'Dapp action does not appear in Metamask'); - - // Cancel Notification - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - - // Terminate Service Worker - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - await terminateServiceWorker(driver); - - // Click add Ethereum chain #2 - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#addEthereumChain'); - - // Notification pop up opens - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - notification = await driver.isElementPresent({ - text: 'Allow this site to add a network?', - tag: 'h3', - }); - assert.ok(notification, 'Dapp action does not appear in Metamask'); - - // Cancel Notification - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - - // Terminate Service Worker - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await terminateServiceWorker(driver); - - // Click add Ethereum chain #3 - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.clickElement('#addEthereumChain'); - - // Notification pop up opens - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - notification = await driver.isElementPresent({ - text: 'Allow this site to add a network?', - tag: 'h3', - }); - assert.ok(notification, 'Dapp action does not appear in Metamask'); - - // Accept Notification - await driver.clickElement({ text: 'Approve', tag: 'button' }); - await driver.clickElement({ text: 'Switch network', tag: 'button' }); - }, - ); - }); - - it('Should continue to support send ETH dApp interactions after service worker re-starts multiple times', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: generateGanacheOptions({ - concurrent: { port: 8546, chainId: 1338 }, - }), - title: this.test.title, - driverOptions: { openDevToolsForTabs: true }, - }, - async ({ driver }) => { - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - await openDapp(driver); - - await driver.delay(largeDelayMs); - - await clickSendButton(driver); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - await terminateServiceWorker(driver); - - await clickSendButton(driver); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await terminateServiceWorker(driver); - - await clickSendButton(driver); - - await assertNumberOfTransactionsInPopUp(driver, 3); - - await confirmETHSendNotification(driver, 1); - - await assertNumberOfTransactionsInPopUp(driver, 2); - - await confirmETHSendNotification(driver, 1); - - await confirmETHSendNotification(driver, 1); - }, - ); - - async function clickSendButton(driver) { - // Click send button - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - await driver.waitForSelector({ - css: '#sendButton', - text: 'Send', - }); - await driver.clickElement('#sendButton'); - } - - async function confirmETHSendNotification(driver, amount) { - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - - await driver.clickElement({ - text: 'Edit', - tag: 'span', - }); - - await driver.fill('[data-testid="currency-input"]', amount); - - await driver.clickElement({ - text: 'Next', - tag: 'button', - }); - - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - } - - async function assertNumberOfTransactionsInPopUp(driver, number) { - await driver.delay(largeDelayMs); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); - - const foundElement = await driver.findElements({ - css: '.confirm-page-container-navigation__navtext', - text: `1 of ${number}`, - }); - - assert.ok(foundElement, true); - } - }); - - it('Should lock wallet when a browser session ends (after turning off the extension)', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: generateGanacheOptions({ - concurrent: { port: 8546, chainId: 1338 }, - }), - title: this.test.title, - }, - async ({ driver }) => { - const { extensionUrl } = driver; - const extensionId = extensionUrl.split('//')[1]; - - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - await reloadExtension(driver, extensionId); - - // ensure extension finishes reloading before reopening full screen extension - await sleepSeconds(0.1); - - await driver.openNewPage(`${extensionUrl}/home.html`); - - const passwordField = await driver.isElementPresent('#password'); - assert.ok( - passwordField, - 'Password screen is not visible. Wallet should have been locked.', - ); - }, - ); - - async function reloadExtension(driver, extensionId) { - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - await driver.openNewPage('chrome://extensions/'); - - // extensions-manager - const extensionsManager = await driver.findElement('extensions-manager'); - - // shadowRoot - const extensionsManagerShadowRoot = await driver.executeScript( - 'return arguments[0][0].shadowRoot', - extensionsManager, - ); - - // cr-view-manager - const viewManager = await extensionsManagerShadowRoot.findElement({ - css: '#viewManager', - }); - - // extensions-item-list - const itemList = await viewManager.findElement({ - css: '#items-list', - }); - - // shadowRoot - const itemListShadowRoot = await driver.executeScript( - 'return arguments[0][0].shadowRoot', - itemList, - ); - - // extension-item - const extensionItem = await await itemListShadowRoot.findElement({ - css: `#${extensionId}`, - }); - - // shadowRoot - const extensionItemShadowRoot = await driver.executeScript( - 'return arguments[0][0].shadowRoot', - extensionItem, - ); - - // cr-icon-button - const devReloadButton = await extensionItemShadowRoot.findElement({ - css: '#dev-reload-button', - }); - - // shadowRoot - const devReloadButtonShadowRoot = await driver.executeScript( - 'return arguments[0][0].shadowRoot', - devReloadButton, - ); - - // cr-icon-button - const reloadBtn = await devReloadButtonShadowRoot.findElement({ - css: '#maskedImage', - }); - - await reloadBtn.click(); - } - }); -}); diff --git a/test/e2e/mv3/phishing-warning-sw-restart.spec.js b/test/e2e/mv3/phishing-warning-sw-restart.spec.js deleted file mode 100644 index 8de3ad7e0c60..000000000000 --- a/test/e2e/mv3/phishing-warning-sw-restart.spec.js +++ /dev/null @@ -1,78 +0,0 @@ -const { strict: assert } = require('assert'); -const FixtureBuilder = require('../fixture-builder'); -const { - withFixtures, - openDapp, - defaultGanacheOptions, - assertAccountBalanceForDOM, - SERVICE_WORKER_URL, - regularDelayMs, - WALLET_PASSWORD, - unlockWallet, - terminateServiceWorker, -} = require('../helpers'); - -const { - setupPhishingDetectionMocks, - BlockProvider, -} = require('../tests/phishing-controller/helpers'); - -describe('Phishing warning page', function () { - const driverOptions = { openDevToolsForTabs: true }; - - it('should restore the transaction when service worker restarts', async function () { - let windowHandles; - - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.title, - driverOptions, - testSpecificMock: async (mockServer) => { - return setupPhishingDetectionMocks(mockServer, { - blockProvider: BlockProvider.MetaMask, - blocklist: ['127.0.0.1'], - }); - }, - dapp: true, - }, - async ({ driver, ganacheServer }) => { - await driver.navigate(); - - await unlockWallet(driver, WALLET_PASSWORD); - - // DAPP is detected as phishing page - await openDapp(driver); - - const phishingPageHeader = await driver.findElements({ - text: 'Deceptive site ahead', - tag: 'h1', - }); - assert.ok(phishingPageHeader.length, 1); - - // Restart service worker - await driver.openNewPage(SERVICE_WORKER_URL); - await terminateServiceWorker(driver); - - await driver.delay(regularDelayMs); - // wait until extension is reloaded - windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - await driver.switchToWindow(extension); - await assertAccountBalanceForDOM(driver, ganacheServer); - - // Open the dapp site and extension detect it as phishing warning page - await openDapp(driver); - // - extension, dapp, service worker and new dapp - await driver.waitUntilXWindowHandles(4); - - const newPhishingPageHeader = await driver.findElements({ - text: 'Deceptive site ahead', - tag: 'h1', - }); - assert.ok(newPhishingPageHeader.length, 1); - }, - ); - }); -}); diff --git a/test/e2e/mv3/service-worker-restart.spec.js b/test/e2e/mv3/service-worker-restart.spec.js deleted file mode 100644 index 20061834ad80..000000000000 --- a/test/e2e/mv3/service-worker-restart.spec.js +++ /dev/null @@ -1,255 +0,0 @@ -const { strict: assert } = require('assert'); - -const { - convertToHexValue, - withFixtures, - openDapp, - SERVICE_WORKER_URL, - defaultGanacheOptions, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { - ACTION_QUEUE_METRICS_E2E_TEST, -} = require('../../../shared/constants/test-flags'); -const { - MetaMetricsEventName, - MetaMetricsEventCategory, -} = require('../../../shared/constants/metametrics'); - -const numberOfSegmentRequests = 3; - -async function mockSegment(mockServer) { - return await mockServer - .forPost('https://api.segment.io/v1/batch') - .withJsonBodyIncluding({ - batch: [ - { - event: MetaMetricsEventName.ServiceWorkerRestarted, - }, - ], - }) - .times(numberOfSegmentRequests) - .thenCallback(() => { - return { - statusCode: 200, - }; - }); -} - -describe('MV3 - Service worker restart', function () { - let windowHandles; - const driverOptions = { openDevToolsForTabs: true }; - - it('should continue to add new a account when service worker can not restart immediately', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withMetaMetricsController({ - metaMetricsId: 'fake-metrics-id', - participateInMetaMetrics: true, - }) - .withAppStateController({ - [ACTION_QUEUE_METRICS_E2E_TEST]: true, - }) - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.title, - // because of segment - failOnConsoleError: false, - testSpecificMock: mockSegment, - driverOptions, - }, - async ({ driver, mockedEndpoint }) => { - await driver.navigate(); - - // unlock wallet - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // open the account menu - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement({ text: 'Create account', tag: 'div' }); - - await driver.fill('.new-account-create-form__input', 'Test Account'); - - await driver.clickElement({ text: 'Create', tag: 'button' }); - - await driver.openNewPage(SERVICE_WORKER_URL); - await driver.clickElement({ - text: 'Service workers', - tag: 'button', - }); - - await driver.clickElement({ - text: 'terminate', - tag: 'span', - }); - - windowHandles = await driver.getAllWindowHandles(); - const extension = windowHandles[0]; - await driver.switchToWindow(extension); - - // balance renders - await driver.waitForSelector( - { - css: '[class="eth-overview__primary-container"]', - // balance is 0 because we switched to an empty account - text: '0 ETH', - }, - { timeout: 50_000 }, - ); - - await driver.findElement({ text: '0x097...7950', tag: 'div' }); - - // assert that the segment request has been sent through inspecting the mock - await driver.wait(async () => { - const isPending = await mockedEndpoint.isPending(); - return isPending === false; - }, 10_000); - const mockedRequests = await mockedEndpoint.getSeenRequests(); - - assert.equal(mockedRequests.length, numberOfSegmentRequests); - - await assertSWRestartTimeEvent(mockedRequests[0]); - await assertSWRestartTimeEvent(mockedRequests[1]); - await assertSWProcessActionQueueEvent( - mockedRequests[2], - 'addNewAccount', - ); - }, - ); - }); - - it('should restore the transaction when service worker restarts', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.title, - // because of segment - failOnConsoleError: false, - driverOptions, - }, - async ({ driver }) => { - await driver.navigate(); - // log in wallet - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // initialize a transaction of send from dapp - await openDapp(driver); - await driver.clickElement('#sendButton'); - - // A popup window is initialized - windowHandles = await driver.getAllWindowHandles(); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - - // Assert recipient and eth quantity are correct - await assertTransactionDetails(driver); - - // Restart service worker in a new window - // Because if we stay in the same window we will lose the popup when opening a new tab - await driver.switchToNewWindow(); - await driver.openNewURL(SERVICE_WORKER_URL); - windowHandles = await driver.getAllWindowHandles(); - // MM expanded view, Dapp, Notification popup, console and service worker - await driver.waitUntilXWindowHandles(5); - await driver.clickElement({ - text: 'terminate', - tag: 'span', - }); - - // Should still have only 1 popup - windowHandles = await driver.getAllWindowHandles(); - await driver.waitUntilXWindowHandles(5); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - - // And popup has the same value - await assertTransactionDetails(driver); - - // Confirm the transaction - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.switchToWindowWithTitle('MetaMask', windowHandles); - await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .transaction-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); - }); -}); - -async function assertSWRestartTimeEvent(request) { - assert.equal(request.url, 'https://api.segment.io/v1/batch'); - - assert.equal(request.body.json.batch.length, 1); - - const [firstResult] = request.body.json.batch; - - assert.equal(firstResult.event, MetaMetricsEventName.ServiceWorkerRestarted); - - assert.equal( - typeof firstResult.properties.service_worker_restarted_time, - 'number', - ); - - assert.equal(firstResult.properties.service_worker_restarted_time > 0, true); - assert.equal( - firstResult.properties.category, - MetaMetricsEventCategory.ServiceWorkers, - ); - assert.equal(firstResult.properties.chain_id, convertToHexValue(1337)); - assert.equal(firstResult.properties.environment_type, 'background'); - assert.equal(firstResult.properties.locale, 'en'); -} - -async function assertSWProcessActionQueueEvent(request, method) { - assert.equal(request.url, 'https://api.segment.io/v1/batch'); - - assert.equal(request.body.json.batch.length, 1); - - const [firstResult] = request.body.json.batch; - - assert.equal(firstResult.event, MetaMetricsEventName.ServiceWorkerRestarted); - - assert.equal( - firstResult.properties.service_worker_action_queue_methods.indexOf( - method, - ) !== '-1', - true, - ); - assert.equal( - firstResult.properties.category, - MetaMetricsEventCategory.ServiceWorkers, - ); - assert.equal(firstResult.properties.chain_id, convertToHexValue(1337)); - assert.equal(firstResult.properties.environment_type, 'background'); - assert.equal(firstResult.properties.locale, 'en'); -} - -async function assertTransactionDetails(driver) { - const TRUNCATED_RECIPIENT_ADDRESS = '0x0c5...AaFb'; - const recipientAddress = await driver.findElement( - '[data-testid="sender-to-recipient__name"]', - ); - assert.equal(await recipientAddress.getText(), TRUNCATED_RECIPIENT_ADDRESS); - const transactionAmounts = await driver.findElements( - '.currency-display-component__text', - ); - const transactionAmount = transactionAmounts[0]; - assert.equal(await transactionAmount.getText(), '0'); -} diff --git a/test/e2e/nft/remove-erc1155.spec.js b/test/e2e/nft/remove-erc1155.spec.js index 377d7a592f33..7923277cc4d7 100644 --- a/test/e2e/nft/remove-erc1155.spec.js +++ b/test/e2e/nft/remove-erc1155.spec.js @@ -15,7 +15,7 @@ describe('Remove ERC1155 NFT', function () { ], }; - it('user should be able to remove ERC1155 NFT on details page', async function () { + it('user should be able to remove ERC1155 NFT on details page @no-mmi', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/nft/send-nft.spec.js b/test/e2e/nft/send-nft.spec.js index 63ff670d3d77..4ec37faf2728 100644 --- a/test/e2e/nft/send-nft.spec.js +++ b/test/e2e/nft/send-nft.spec.js @@ -15,7 +15,7 @@ describe('Send NFT', function () { ], }; - it('should be able to send ERC721 NFT', async function () { + it('should be able to send ERC721 NFT @no-mmi', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/nft/view-erc1155-details.spec.js b/test/e2e/nft/view-erc1155-details.spec.js index b782a82b3088..29794412cf10 100644 --- a/test/e2e/nft/view-erc1155-details.spec.js +++ b/test/e2e/nft/view-erc1155-details.spec.js @@ -15,7 +15,7 @@ describe('View ERC1155 NFT details', function () { ], }; - it('user should be able to view ERC1155 NFT details', async function () { + it('user should be able to view ERC1155 NFT details @no-mmi', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/provider/eip-6963.spec.js b/test/e2e/provider/eip-6963.spec.js new file mode 100644 index 000000000000..751a4e674633 --- /dev/null +++ b/test/e2e/provider/eip-6963.spec.js @@ -0,0 +1,94 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); + +// https://github.com/thenativeweb/uuidv4/blob/bdcf3a3138bef4fb7c51f389a170666f9012c478/lib/uuidv4.ts#L5 +const UUID_V4_REGEX = + /(?:^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}$)|(?:^0{8}-0{4}-0{4}-0{4}-0{12}$)/u; + +const SVG_DATA_URI_REGEX = /^data:image\/svg\+xml,/u; + +describe('EIP-6963 Provider', function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + + it('should respond to the request provider event', async function () { + 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); + await driver.executeScript(` + window.announceProviderEvents = [] + window.addEventListener( + "eip6963:announceProvider", + (event) => { + window.announceProviderEvents.push(event) + } + ); + window.dispatchEvent(new Event("eip6963:requestProvider")); + `); + const announceProviderEvents = await driver.executeScript(` + return window.announceProviderEvents.map(event => { + return { + type: event.type, + detail: { + ...event.detail, + provider: Boolean(event.detail.provider) + } + } + }) + `); + + assert.match(announceProviderEvents[0].detail.info.uuid, UUID_V4_REGEX); + delete announceProviderEvents[0].detail.info.uuid; + assert.match( + announceProviderEvents[0].detail.info.icon, + SVG_DATA_URI_REGEX, + ); + delete announceProviderEvents[0].detail.info.icon; + assert.deepStrictEqual(announceProviderEvents, [ + { + type: 'eip6963:announceProvider', + detail: { + info: { + name: 'MetaMask Main', + rdns: 'io.metamask', + }, + provider: true, + }, + }, + ]); + + const request = JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_chainId', + params: [], + id: 0, + }); + const result = await driver.executeScript(` + return window.announceProviderEvents[0].detail.provider.request(${request}) + `); + + assert.equal(result, '0x539'); + }, + ); + }); +}); diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index c5383103d6b2..996b7ac34afd 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -1,5 +1,6 @@ const path = require('path'); -const { promises: fs } = require('fs'); +const fs = require('fs'); +const { execSync } = require('child_process'); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const { runInShell } = require('../../development/lib/run-command'); @@ -7,7 +8,9 @@ const { exitWithError } = require('../../development/lib/exit-with-error'); const { loadBuildTypesConfig } = require('../../development/lib/build-type'); const getTestPathsForTestDir = async (testDir) => { - const testFilenames = await fs.readdir(testDir, { withFileTypes: true }); + const testFilenames = await fs.promises.readdir(testDir, { + withFileTypes: true, + }); const testPaths = []; for (const itemInDirectory of testFilenames) { @@ -24,15 +27,37 @@ const getTestPathsForTestDir = async (testDir) => { return testPaths; }; -// Heavily inspired by: https://stackoverflow.com/a/51514813 -// Splits the array into totalChunks chunks with a decent spread of items in each chunk -function chunk(array, totalChunks) { - const copyArray = [...array]; - const result = []; - for (let chunkIndex = totalChunks; chunkIndex > 0; chunkIndex--) { - result.push(copyArray.splice(0, Math.ceil(copyArray.length / chunkIndex))); +// For running E2Es in parallel in CI +function runningOnCircleCI(testPaths) { + const fullTestList = testPaths.join('\n'); + console.log('Full test list:', fullTestList); + fs.writeFileSync('test/test-results/fullTestList.txt', fullTestList); + + // Use `circleci tests run` on `testList.txt` to do two things: + // 1. split the test files into chunks based on how long they take to run + // 2. support "Rerun failed tests" on CircleCI + const result = execSync( + 'circleci tests run --command=">test/test-results/myTestList.txt xargs echo" --split-by=timings --timings-type=filename --time-default=30s < test/test-results/fullTestList.txt', + ).toString('utf8'); + + // Report if no tests found, exit gracefully + if (result.indexOf('There were no tests found') !== -1) { + console.log(`run-all.js info: Skipping this node because "${result}"`); + return []; } - return result; + + // If there's no text file, it means this node has no tests, so exit gracefully + if (!fs.existsSync('test/test-results/myTestList.txt')) { + console.log( + 'run-all.js info: Skipping this node because there is no myTestList.txt', + ); + return []; + } + + // take the space-delimited result and split into an array + return fs + .readFileSync('test/test-results/myTestList.txt', { encoding: 'utf8' }) + .split(' '); } async function main() { @@ -61,10 +86,6 @@ async function main() { description: `run snaps e2e tests`, type: 'boolean', }) - .option('mv3', { - description: `run mv3 specific e2e tests`, - type: 'boolean', - }) .option('rpc', { description: `run json-rpc specific e2e tests`, type: 'boolean', @@ -84,6 +105,12 @@ async function main() { default: false, description: 'Update E2E snapshots', type: 'boolean', + }) + .option('update-privacy-snapshot', { + default: false, + description: + 'Update the privacy snapshot to include new hosts and paths', + type: 'boolean', }), ) .strict() @@ -95,10 +122,10 @@ async function main() { retries, mmi, snaps, - mv3, rpc, buildType, updateSnapshot, + updatePrivacySnapshot, } = argv; let testPaths; @@ -121,25 +148,13 @@ async function main() { ...(await getTestPathsForTestDir(path.join(__dirname, 'metrics'))), path.join(__dirname, 'metamask-ui.spec.js'), ]; - - if (mv3) { - testPaths.push( - ...(await getTestPathsForTestDir(path.join(__dirname, 'mv3'))), - ); - } } // These tests should only be run on Flask for now. if (buildType !== 'flask') { const filteredTests = [ - 'settings-add-snap-account-toggle.spec.js', - 'test-snap-accounts.spec.js', - 'test-create-snap-account.spec.js', - 'test-remove-accounts-snap.spec.js', 'test-snap-lifecycle.spec.js', 'test-snap-get-locale.spec.js', - 'ppom-blockaid-alert.spec.js', - 'ppom-toggle-settings.spec.js', 'petnames.spec.js', ]; testPaths = testPaths.filter((p) => @@ -162,20 +177,30 @@ async function main() { if (updateSnapshot) { args.push('--update-snapshot'); } + if (updatePrivacySnapshot) { + args.push('--update-privacy-snapshot'); + } if (mmi) { args.push('--mmi'); } - // For running E2Es in parallel in CI - const currentChunkIndex = process.env.CIRCLE_NODE_INDEX ?? 0; - const totalChunks = process.env.CIRCLE_NODE_TOTAL ?? 1; - const chunks = chunk(testPaths, totalChunks); - const currentChunk = chunks[currentChunkIndex]; + await fs.promises.mkdir('test/test-results/e2e', { recursive: true }); - for (const testPath of currentChunk) { - const dir = 'test/test-results/e2e'; - fs.mkdir(dir, { recursive: true }); - await runInShell('node', [...args, testPath]); + let myTestList; + if (process.env.CIRCLECI) { + myTestList = runningOnCircleCI(testPaths); + } else { + myTestList = testPaths; + } + + console.log('My test list:', myTestList); + + // spawn `run-e2e-test.js` for each test in myTestList + for (let testPath of myTestList) { + if (testPath !== '') { + testPath = testPath.replace('\n', ''); // sometimes there's a newline at the end of the testPath + await runInShell('node', [...args, testPath]); + } } } diff --git a/test/e2e/run-e2e-test.js b/test/e2e/run-e2e-test.js index ca8ee6966ccd..ed25d13443a0 100644 --- a/test/e2e/run-e2e-test.js +++ b/test/e2e/run-e2e-test.js @@ -52,6 +52,12 @@ async function main() { description: 'Update E2E snapshots', type: 'boolean', }) + .option('update-privacy-snapshot', { + default: false, + description: + 'Update the privacy snapshot to include new hosts and paths', + type: 'boolean', + }) .positional('e2e-test-path', { describe: 'The path for the E2E test to run.', type: 'string', @@ -70,6 +76,7 @@ async function main() { retryUntilFailure, leaveRunning, updateSnapshot, + updatePrivacySnapshot, } = argv; if (!browser) { @@ -100,8 +107,6 @@ async function main() { throw error; } - const testFileName = path.basename(e2eTestPath); - if (debug) { process.env.E2E_DEBUG = 'true'; } @@ -119,6 +124,10 @@ async function main() { process.env.UPDATE_SNAPSHOTS = 'true'; } + if (updatePrivacySnapshot) { + process.env.UPDATE_PRIVACY_SNAPSHOT = 'true'; + } + const configFile = path.join(__dirname, '.mocharc.js'); const extraArgs = process.env.E2E_ARGS?.split(' ') || []; @@ -133,19 +142,17 @@ async function main() { fs.mkdir(dir, { recursive: true }); await retry({ retries, retryUntilFailure }, async () => { - await runInShell( - 'yarn', - [ - 'mocha', - `--config=${configFile}`, - `--timeout=${testTimeoutInMilliseconds}`, - '--reporter=xunit', - ...extraArgs, - e2eTestPath, - exit, - ], - `${dir}/${testFileName}.xml`, - ); + await runInShell('yarn', [ + 'mocha', + `--config=${configFile}`, + `--timeout=${testTimeoutInMilliseconds}`, + '--reporter=mocha-junit-reporter', + '--reporter-options', + `mochaFile=test/test-results/e2e/[hash].xml`, + ...extraArgs, + e2eTestPath, + exit, + ]); }); } diff --git a/test/e2e/snaps/ppom-blockaid-alert.spec.js b/test/e2e/snaps/ppom-blockaid-alert.spec.js deleted file mode 100644 index a9fb855acb24..000000000000 --- a/test/e2e/snaps/ppom-blockaid-alert.spec.js +++ /dev/null @@ -1,265 +0,0 @@ -const { strict: assert } = require('assert'); -const FixtureBuilder = require('../fixture-builder'); -const { - defaultGanacheOptions, - getWindowHandles, - openDapp, - unlockWallet, - withFixtures, -} = require('../helpers'); - -const { - CHAIN_IDS, - NETWORK_TYPES, -} = require('../../../shared/constants/network'); - -const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; -const selectedAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; - -const mainnetProviderConfig = { - providerConfig: { - chainId: CHAIN_IDS.MAINNET, - nickname: '', - rpcUrl: '', - type: NETWORK_TYPES.MAINNET, - }, -}; - -async function mockInfura(mockServer) { - await mockServer - .forPost() - .withJsonBodyIncluding({ method: 'eth_estimateGas' }) - .thenCallback((req) => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: req.body.json.id, - result: '0x5cec', - }, - }; - }); - await mockServer - .forPost() - .withJsonBodyIncluding({ method: 'eth_getBalance' }) - .thenCallback((req) => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: req.body.json.id, - result: '0x55DE6A779BBAC0000', - }, - }; - }); - await mockServer - .forPost() - .withJsonBodyIncluding({ method: 'eth_getTransactionCount' }) - .thenCallback((req) => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: req.body.json.id, - result: '0x115e89f', - }, - }; - }); - await mockServer - .forPost() - .withJsonBodyIncluding({ method: 'eth_blockNumber' }) - .thenCallback((req) => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: req.body.json.id, - result: '0x1', - // result: '0x115e89f', - }, - }; - }); - await mockServer - .forPost() - .withJsonBodyIncluding({ method: 'eth_gasPrice' }) - .thenCallback((req) => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: req.body.json.id, - result: '0x09184e72a000', - }, - }; - }); -} - -/** - * Tests various Blockaid PPOM security alerts. Data for the E2E test requests and responses are provided here: - * - * @see {@link https://wobbly-nutmeg-8a5.notion.site/MM-E2E-Testing-1e51b617f79240a49cd3271565c6e12d} - */ -describe('Confirmation Security Alert - Blockaid', function () { - it('should not show security alerts for benign requests', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkController(mainnetProviderConfig) - .withPermissionControllerConnectedToTestDapp() - .withPreferencesController({ - securityAlertsEnabled: true, - }) - .build(), - defaultGanacheOptions, - testSpecificMock: mockInfura, - title: this.test.title, - }, - - async ({ driver }) => { - await driver.navigate(); - await unlockWallet(driver); - await openDapp(driver); - - const testBenignConfigs = [ - { - logExpectedDetail: 'Benign 1', - method: 'eth_sendTransaction', - params: [ - { - from: selectedAddress, - data: '0x095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - to: '0x6b175474e89094c44da98b954eedeac495271d0f', - value: '0x0', - }, - ], - }, - { - logExpectedDetail: 'blur', - method: 'eth_signTypedData_v4', - params: [ - selectedAddress, - '{"types":{"Order":[{"name":"trader","type":"address"},{"name":"side","type":"uint8"},{"name":"matchingPolicy","type":"address"},{"name":"collection","type":"address"},{"name":"tokenId","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"paymentToken","type":"address"},{"name":"price","type":"uint256"},{"name":"listingTime","type":"uint256"},{"name":"expirationTime","type":"uint256"},{"name":"fees","type":"Fee[]"},{"name":"salt","type":"uint256"},{"name":"extraParams","type":"bytes"},{"name":"nonce","type":"uint256"}],"Fee":[{"name":"rate","type":"uint16"},{"name":"recipient","type":"address"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Blur Exchange","version":"1.0","chainId":"1","verifyingContract":"0x000000000000ad05ccc4f10045630fb830b95127"},"primaryType":"Order","message":{"trader":"0xd854343f41b2138b686f2d3ba38402a9f7fb4337","side":"1","matchingPolicy":"0x0000000000dab4a563819e8fd93dba3b25bc3495","collection":"0xc4a5025c4563ad0acc09d92c2506e6744dad58eb","tokenId":"30420","amount":"1","paymentToken":"0x0000000000000000000000000000000000000000","price":"1000000000000000000","listingTime":"1679418212","expirationTime":"1680023012","salt":"154790208154270131670189427454206980105","extraParams":"0x01","nonce":"0"}}', - ], - }, - { - logExpectedDetail: 'seaport', - method: 'eth_signTypedData_v4', - params: [ - selectedAddress, - '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.4","chainId":"1","verifyingContract":"0x00000000000001ad428e4906aE43D8F9852d0dD6"},"message":{"offerer":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC","offer":[{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"2500000000000000","endAmount":"2500000000000000"}],"consideration":[{"itemType":"2","token":"0xaA7200ee500dE2dcde75E996De83CBD73BCa9705","identifierOrCriteria":"11909","startAmount":"1","endAmount":"1","recipient":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"62500000000000","endAmount":"62500000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"12500000000000","endAmount":"12500000000000","recipient":"0x8324BdEF2F30E08E368f2Fa2F14143cDCA77423D"}],"startTime":"1681835413","endTime":"1682094598","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929812618382526293324216","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}}', - ], - }, - ]; - - for (const config of testBenignConfigs) { - const { logExpectedDetail, method, params } = config; - - // Send JSON-RPC request - const request = JSON.stringify({ - jsonrpc: '2.0', - method, - params, - }); - await driver.executeScript( - `window.transactionHash = window.ethereum.request(${request})`, - ); - - // Wait for confirmation pop-up - await driver.waitUntilXWindowHandles(3); - const windowHandles = await getWindowHandles(driver, 3); - await driver.switchToWindowWithTitle('MetaMask Notification'); - - const isPresent = await driver.isElementPresent(bannerAlertSelector); - assert.equal( - isPresent, - false, - `Banner alert unexpectedly found. \nExpected detail: ${logExpectedDetail}`, - ); - - // Wait for confirmation pop-up to close - await driver.clickElement({ text: 'Reject', tag: 'button' }); - await driver.switchToWindow(windowHandles.dapp); - } - }, - ); - }); - - /** - * Disclaimer: this test may be missing checks for some reason types. e.g. blur, domain, and failed - */ - it('should show security alerts for malicious requests', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkController(mainnetProviderConfig) - .withPermissionControllerConnectedToTestDapp() - .withPreferencesController({ - securityAlertsEnabled: true, - }) - .build(), - defaultGanacheOptions, - testSpecificMock: mockInfura, - title: this.test.title, - }, - - async ({ driver }) => { - await driver.navigate(); - await unlockWallet(driver); - await openDapp(driver); - - const expectedTitle = 'This is a deceptive request'; - - const testMaliciousConfigs = [ - { - btnSelector: '#maliciousPermit', - expectedDescription: - 'If you approve this request, a third party known for scams might take all your assets.', - expectedReason: 'permit_farming', - }, - { - btnSelector: '#maliciousSeaport', - expectedDescription: - 'If you approve this request, someone can steal your assets listed on OpenSea.', - expectedReason: 'seaport_farming', - }, - { - btnSelector: '#maliciousTradeOrder', - expectedDescription: - 'If you approve this request, you might lose your assets.', - expectedReason: 'trade_order_farming', - }, - ]; - - for (const config of testMaliciousConfigs) { - const { expectedDescription, expectedReason, btnSelector } = config; - - // Click TestDapp button to send JSON-RPC request - await driver.clickElement(btnSelector); - - // Wait for confirmation pop-up - await driver.waitUntilXWindowHandles(3); - const windowHandles = await getWindowHandles(driver, 3); - await driver.switchToWindowWithTitle('MetaMask Notification'); - - const bannerAlert = await driver.findElement(bannerAlertSelector); - const bannerAlertText = await bannerAlert.getText(); - - assert( - bannerAlertText.includes(expectedTitle), - `Expected banner alert title: ${expectedTitle} \nExpected reason: ${expectedReason}\n`, - ); - assert( - bannerAlertText.includes(expectedDescription), - `Expected banner alert description: ${expectedDescription} \nExpected reason: ${expectedReason}\n`, - ); - - // Wait for confirmation pop-up to close - await driver.clickElement({ text: 'Reject', tag: 'button' }); - await driver.switchToWindow(windowHandles.dapp); - } - }, - ); - }); -}); diff --git a/test/e2e/snaps/test-snap-error.spec.js b/test/e2e/snaps/test-snap-error.spec.js deleted file mode 100644 index 9c19cd7234ec..000000000000 --- a/test/e2e/snaps/test-snap-error.spec.js +++ /dev/null @@ -1,106 +0,0 @@ -const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); -const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); - -describe('Test Snap Error', function () { - it('can pop up a snap error and see the error', async function () { - const ganacheOptions = { - accounts: [ - { - secretKey: - '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', - balance: 25000000000000000000, - }, - ], - }; - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions, - failOnConsoleError: false, - title: this.test.title, - }, - async ({ driver }) => { - await driver.navigate(); - - // enter pw into extension - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // navigate to test snaps page and connect - await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); - await driver.delay(1000); - const snapButton = await driver.findElement('#connecterrors'); - await driver.scrollToElement(snapButton); - await driver.delay(1000); - await driver.clickElement('#connecterrors'); - await driver.delay(1000); - - // switch to metamask extension and click connect - const windowHandles = await driver.waitUntilXWindowHandles( - 3, - 1000, - 10000, - ); - const extensionPage = windowHandles[0]; - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.waitForSelector({ text: 'Install' }); - - await driver.clickElement({ - text: 'Install', - tag: 'button', - }); - - await driver.waitForSelector({ text: 'OK' }); - - await driver.clickElement({ - text: 'OK', - tag: 'button', - }); - - // click send inputs on test snap page - await driver.switchToWindowWithTitle('Test Snaps', windowHandles); - - // wait for npm installation success - await driver.waitForSelector({ - css: '#connecterrors', - text: 'Reconnect to Errors Snap', - }); - - // find and click on send error - await driver.clickElement('#sendError'); - - // switch back to the extension page - await driver.switchToWindow(extensionPage); - await driver.delay(500); - - // look for the actual error and check if it is correct - const error = await driver.findElement( - '.home-notification__content-container', - ); - const text = await error.getText(); - assert.equal( - text.includes( - "Snap Error: 'Random error inside a promise.'. Error Code: '-32603'", - ), - true, - ); - - // try to click on the dismiss button and pass test if it works - await driver.clickElement({ - text: 'Dismiss', - tag: 'button', - }); - }, - ); - }); -}); diff --git a/test/e2e/snaps/test-snap-management.spec.js b/test/e2e/snaps/test-snap-management.spec.js index 6af006bd9ab1..f286d1c5af02 100644 --- a/test/e2e/snaps/test-snap-management.spec.js +++ b/test/e2e/snaps/test-snap-management.spec.js @@ -78,10 +78,6 @@ describe('Test Snap Management', function () { '[data-testid="account-options-menu-button"]', ); - // try to click on the notification item - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.delay(1000); - // try to click on the snaps item await driver.clickElement({ text: 'Snaps', @@ -150,7 +146,7 @@ describe('Test Snap Management', function () { // check the results of the removal await driver.delay(2000); const removeResult = await driver.findElement( - '.snap-list-tab__container--no-snaps_inner', + '.snaps__content__list__container--no-snaps_inner', ); assert.equal( await removeResult.getText(), diff --git a/test/e2e/snaps/test-snap-revoke-perm.spec.js b/test/e2e/snaps/test-snap-revoke-perm.spec.js new file mode 100644 index 000000000000..b500f583de21 --- /dev/null +++ b/test/e2e/snaps/test-snap-revoke-perm.spec.js @@ -0,0 +1,189 @@ +const { withFixtures } = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); +const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); + +describe('Test Snap revoke permission', function () { + it('can revoke a permission', async function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: 25000000000000000000, + }, + ], + }; + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions, + failOnConsoleError: false, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + + // enter pw into extension + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // navigate to test snaps page and connect + await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); + await driver.delay(1000); + const snapButton = await driver.findElement( + '#connectethereum-provider', + ); + await driver.scrollToElement(snapButton); + await driver.delay(1000); + await driver.clickElement('#connectethereum-provider'); + await driver.delay(1000); + + // switch to metamask extension and click connect + const windowHandles = await driver.waitUntilXWindowHandles( + 3, + 1000, + 10000, + ); + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.waitForSelector({ text: 'Install' }); + + await driver.clickElement({ + text: 'Install', + tag: 'button', + }); + + await driver.waitForSelector({ text: 'OK' }); + + await driver.clickElement({ + text: 'OK', + tag: 'button', + }); + + // switch to test snap page + await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + + // wait for npm installation success + await driver.waitForSelector({ + css: '#connectethereum-provider', + text: 'Reconnect to Ethereum Provider Snap', + }); + + // find and click on send get version + const snapButton3 = await driver.findElement( + '#sendEthproviderAccounts', + ); + await driver.scrollToElement(snapButton3); + await driver.delay(500); + await driver.clickElement('#sendEthproviderAccounts'); + + // switch to metamask window and click through confirmations + const windowHandles2 = await driver.waitUntilXWindowHandles( + 3, + 1000, + 10000, + ); + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles2, + ); + await driver.clickElement({ + text: 'Next', + tag: 'button', + }); + await driver.delay(500); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // switch to test snap page + await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + + // check the results of the message signature using waitForSelector + await driver.waitForSelector({ + css: '#ethproviderResult', + text: '"0x5cfe73b6021e818b776b421b1c4db2474086a7e1"', + }); + + // switch to the original MM tab + const extensionPage = windowHandles[0]; + await driver.switchToWindow(extensionPage); + await driver.delay(1000); + + // click on the global action menu + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + + // try to click on the snaps item + await driver.clickElement({ + text: 'Snaps', + tag: 'div', + }); + + // try to click on the Ethereum Provider Example Snap + await driver.clickElement({ + text: 'Ethereum Provider Example Snap', + tag: 'p', + }); + + // try to click on options menu + await driver.clickElement('[data-testid="eth_accounts"]'); + + // try to click on revoke permission + await driver.clickElement({ + text: 'Revoke permission', + tag: 'p', + }); + + // switch to test snap page + await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + + // find and click on send get version + const snapButton4 = await driver.findElement( + '#sendEthproviderAccounts', + ); + await driver.scrollToElement(snapButton4); + await driver.delay(500); + await driver.clickElement('#sendEthproviderAccounts'); + + // switch to metamask window and click through confirmations + const windowHandles3 = await driver.waitUntilXWindowHandles( + 3, + 1000, + 10000, + ); + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles3, + ); + await driver.clickElement({ + text: 'Next', + tag: 'button', + }); + await driver.delay(500); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // switch to test snap page + await driver.switchToWindowWithTitle('Test Snaps', windowHandles); + + // check the results of the message signature using waitForSelector + await driver.waitForSelector({ + css: '#ethproviderResult', + text: '"0x5cfe73b6021e818b776b421b1c4db2474086a7e1"', + }); + }, + ); + }); +}); diff --git a/test/e2e/swaps/shared.js b/test/e2e/swaps/shared.js index 0d961a65443a..eacb0f537c16 100644 --- a/test/e2e/swaps/shared.js +++ b/test/e2e/swaps/shared.js @@ -156,17 +156,22 @@ const checkActivityTransaction = async (driver, options) => { }; const checkNotification = async (driver, options) => { - const boxTitle = await driver.findElement( - '[data-testid="swaps-banner-title"]', - ); - assert.equal(await boxTitle.getText(), options.title, 'Invalid box title'); - const boxContent = await driver.findElement( - '[data-testid="mm-banner-alert-notification-text"]', - ); - const bodyText = await boxContent.getText(); - console.log(`test: ${bodyText}`); + const isExpectedBoxTitlePresentAndVisible = + await driver.isElementPresentAndVisible({ + css: '[data-testid="swaps-banner-title"]', + text: options.title, + }); + + assert.equal(isExpectedBoxTitlePresentAndVisible, true, 'Invalid box title'); + + const isExpectedBoxContentPresentAndVisible = + await driver.isElementPresentAndVisible({ + css: '[data-testid="mm-banner-alert-notification-text"]', + text: options.text, + }); + assert.equal( - bodyText.includes(options.text), + isExpectedBoxContentPresentAndVisible, true, 'Invalid box text content', ); diff --git a/test/e2e/swaps/swaps-notifications.spec.js b/test/e2e/swaps/swaps-notifications.spec.js index 4aa0508696af..1c8ab9488c1c 100644 --- a/test/e2e/swaps/swaps-notifications.spec.js +++ b/test/e2e/swaps/swaps-notifications.spec.js @@ -1,4 +1,3 @@ -const { strict: assert } = require('assert'); const { withFixtures } = require('../helpers'); const { withFixturesOptions, @@ -89,11 +88,10 @@ describe('Swaps - notifications @no-mmi', function () { swapTo: 'INUINU', skipCounter: true, }); - const swapButton = await driver.findElement({ + await driver.findClickableElement({ text: 'Swap', tag: 'button', }); - assert.equal(await swapButton.isEnabled(), true); }, ); }); @@ -119,12 +117,11 @@ describe('Swaps - notifications @no-mmi', function () { amount: 50, skipCounter: true, }); - const swapButton = await driver.waitForSelector({ + await driver.waitForSelector({ text: 'Swap', tag: 'button', + css: '[disabled]', }); - assert.equal(await swapButton.getText(), 'Swap'); - assert.equal(await swapButton.isEnabled(), false); }, ); }); diff --git a/test/e2e/tests/account-details.spec.js b/test/e2e/tests/account-details.spec.js index eeee82694f54..b6601fb51b5c 100644 --- a/test/e2e/tests/account-details.spec.js +++ b/test/e2e/tests/account-details.spec.js @@ -105,6 +105,9 @@ describe('Show account details', function () { // Create and focus on different account await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement( '[data-testid="multichain-account-menu-popover-add-account"]', ); @@ -153,6 +156,9 @@ describe('Show account details', function () { // Create and focus on different account await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement( '[data-testid="multichain-account-menu-popover-add-account"]', ); diff --git a/test/e2e/tests/add-account.spec.js b/test/e2e/tests/add-account.spec.js index b44fbad027d6..5b2551b86294 100644 --- a/test/e2e/tests/add-account.spec.js +++ b/test/e2e/tests/add-account.spec.js @@ -41,6 +41,9 @@ describe('Add account', function () { await unlockWallet(driver); await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement( '[data-testid="multichain-account-menu-popover-add-account"]', ); @@ -82,6 +85,9 @@ describe('Add account', function () { // Create 2nd account await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement( '[data-testid="multichain-account-menu-popover-add-account"]', ); @@ -176,7 +182,9 @@ describe('Add account', function () { await unlockWallet(driver); await driver.clickElement('[data-testid="account-menu-icon"]'); - + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement( '[data-testid="multichain-account-menu-popover-add-account"]', ); @@ -191,7 +199,6 @@ describe('Add account', function () { }); await driver.clickElement('[data-testid="account-menu-icon"]'); - const menuItems = await driver.findElements( '.multichain-account-list-item', ); @@ -207,6 +214,9 @@ describe('Add account', function () { // Create 3rd account with private key await driver.clickElement('.mm-text-field'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement({ text: 'Import account', tag: 'button' }); await driver.fill('#private-key-box', testPrivateKey); diff --git a/test/e2e/tests/add-custom-network.spec.js b/test/e2e/tests/add-custom-network.spec.js index 84024ed0582c..c4c3c3c1a31c 100644 --- a/test/e2e/tests/add-custom-network.spec.js +++ b/test/e2e/tests/add-custom-network.spec.js @@ -163,6 +163,7 @@ describe('Custom network', function () { dapp: true, fixtures: new FixtureBuilder() .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ useSafeChainsListValidation: true }) .build(), title: this.test.title, }, @@ -195,6 +196,27 @@ describe('Custom network', function () { windowHandles, ); + const warningMsg1 = + 'According to our record the network name may not correctly match this chain ID.'; + await driver.findElement({ + tag: 'span', + text: warningMsg1, + }); + + const errorMsg1 = + 'The submitted currency symbol does not match what we expect for this chain ID.'; + await driver.findElement({ + tag: 'span', + text: errorMsg1, + }); + + const errorMsg2 = + 'According to our records the submitted RPC URL value does not match a known provider for this chain ID.'; + await driver.findElement({ + tag: 'span', + text: errorMsg2, + }); + const errMsg1 = 'verify the network details'; await driver.findElement({ tag: 'a', @@ -227,6 +249,76 @@ describe('Custom network', function () { ); }); + it("don't validate bad rpc custom network when toggle is off", async function () { + async function mockRPCURLAndChainId(mockServer) { + return [ + await mockServer + .forPost('https://responsive-rpc.url/') + .thenCallback(() => ({ + statusCode: 200, + json: { + id: '1694444405781', + jsonrpc: '2.0', + result: TEST_CHAIN_ID, + }, + })), + ]; + } + + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ useSafeChainsListValidation: false }) + .build(), + title: this.test.title, + testSpecificMock: mockRPCURLAndChainId, + }, + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + + await openDapp(driver); + await driver.executeScript(` + var params = [{ + chainId: "${TEST_CHAIN_ID}", + chainName: "Antani", + nativeCurrency: { + name: "", + symbol: "ANTANI", + decimals: 18 + }, + rpcUrls: ["https://responsive-rpc.url/"], + blockExplorerUrls: [ "http://localhost:8080/api/customRPC" ] + }] + window.ethereum.request({ + method: 'wallet_addEthereumChain', + params + }) + `); + const windowHandles = await driver.waitUntilXWindowHandles(3); + + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + + await driver.clickElement({ + tag: 'button', + text: 'Approve', + }); + + const switchNetworkBtn = await driver.findElement({ + tag: 'button', + text: 'Switch network', + }); + + await switchNetworkBtn.click(); + }, + ); + }); + it("don't add unreachable custom network", async function () { await withFixtures( { @@ -366,7 +458,7 @@ describe('Custom network', function () { }); // verify network switched const networkDisplayed = await driver.findElement({ - tag: 'p', + tag: 'span', text: 'Arbitrum One', }); assert.equal( @@ -470,7 +562,7 @@ describe('Custom network', function () { ); }); - it('when the network details validation toggle is turned on, validate user inserted details against data from "chainid.network"', async function () { + it("when the network details validation toggle is turned on, validate user inserted details against data from 'chainid.network'", async function () { async function mockRPCURLAndChainId(mockServer) { return [ await mockServer diff --git a/test/e2e/tests/add-multiple-tokens.spec.js b/test/e2e/tests/add-multiple-tokens.spec.js new file mode 100644 index 000000000000..a249e3ce36b1 --- /dev/null +++ b/test/e2e/tests/add-multiple-tokens.spec.js @@ -0,0 +1,110 @@ +const { strict: assert } = require('assert'); +const { + withFixtures, + defaultGanacheOptions, + openDapp, + switchToNotificationWindow, + WINDOW_TITLES, + DAPP_URL, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); + +describe('Multiple ERC20 Watch Asset', function () { + // TODO: This assertion will change once the method is fixed. + it('should show multiple erc20 watchAsset token list, only confirms one bug', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await openDapp(driver, undefined, DAPP_URL); + + // Create Token 1 + await driver.clickElement({ text: 'Create Token', tag: 'button' }); + await switchToNotificationWindow(driver); + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Wait for token 1 address to populate in dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.wait(async () => { + const tokenAddressesElement = await driver.findElement( + '#tokenAddresses', + ); + const tokenAddresses = await tokenAddressesElement.getText(); + return tokenAddresses !== ''; + }, 10000); + + // Create Token 2 + await driver.clickElement({ text: 'Create Token', tag: 'button' }); + await switchToNotificationWindow(driver); + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Wait for token 2 address to populate in dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.wait(async () => { + const tokenAddressesElement = await driver.findElement( + '#tokenAddresses', + ); + const tokenAddresses = await tokenAddressesElement.getText(); + return tokenAddresses.split(',').length === 2; + }, 10000); + + // Create Token 3 + await driver.clickElement({ text: 'Create Token', tag: 'button' }); + await switchToNotificationWindow(driver); + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Wait for token 3 address to populate in dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.wait(async () => { + const tokenAddressesElement = await driver.findElement( + '#tokenAddresses', + ); + const tokenAddresses = await tokenAddressesElement.getText(); + return tokenAddresses.split(',').length === 3; + }, 10000); + + // Watch all 3 tokens + await driver.clickElement({ + text: 'Add Token(s) to Wallet', + tag: 'button', + }); + + // Switch to watchAsset notificaiton + await switchToNotificationWindow(driver); + const multipleSuggestedtokens = await driver.findElements( + '.confirm-add-suggested-token__token-list-item', + ); + + // Confirm all 3 tokens are present as suggested token list + assert.equal(multipleSuggestedtokens.length, 3); + await driver.findClickableElement({ text: 'Add token', tag: 'button' }); + await driver.clickElement({ text: 'Add token', tag: 'button' }); + + // Switch to fullscreen extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Check all three tokens have been added to the token list. + const addedTokens = await driver.findElements({ + tag: 'p', + text: 'TST', + }); + assert.equal(addedTokens.length, 3); + }, + ); + }); +}); diff --git a/test/e2e/tests/backup-restore.spec.js b/test/e2e/tests/backup-restore.spec.js index dfba955c62a2..43690cbc1e2b 100644 --- a/test/e2e/tests/backup-restore.spec.js +++ b/test/e2e/tests/backup-restore.spec.js @@ -127,7 +127,7 @@ describe('Backup and Restore', function () { // Dismiss success message await driver.waitForSelector({ - css: '.actionable-message__message', + css: '[data-testid="restore-user-data-banner-alert-description"]', text: 'Your data has been restored successfully', }); await driver.clickElement({ text: 'Dismiss', tag: 'button' }); diff --git a/test/e2e/tests/block-explorer.spec.js b/test/e2e/tests/block-explorer.spec.js new file mode 100644 index 000000000000..4627d4df5322 --- /dev/null +++ b/test/e2e/tests/block-explorer.spec.js @@ -0,0 +1,145 @@ +const { strict: assert } = require('assert'); +const { defaultGanacheOptions, withFixtures } = require('../helpers'); +const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); +const FixtureBuilder = require('../fixture-builder'); + +describe('Block Explorer', function () { + it('links to the users account on the explorer, ', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withNetworkController({ + providerConfig: { + rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, + }, + }) + .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); + + // View account on explorer + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="account-list-item-menu-button"]', + ); + await driver.clickElement({ text: 'View on explorer', tag: 'p' }); + + // Switch to block explorer + await driver.waitUntilXWindowHandles(2); + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('E2E Test Page', windowHandles); + const body = await driver.findElement( + '[data-testid="empty-page-body"]', + ); + + // Verify block explorer + assert.equal(await body.getText(), 'Empty page by MetaMask'); + assert.equal( + await driver.getCurrentUrl(), + 'https://etherscan.io/address/0x5CfE73b6021E818B776b421B1c4Db2474086a7e1', + ); + }, + ); + }); + + it('links to the token tracker in the explorer, ', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkController({ + providerConfig: { + rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, + }, + }) + .withTokensControllerERC20() + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract: SMART_CONTRACTS.HST, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // View TST token in block explorer + await driver.clickElement('[data-testid="home__asset-tab"]'); + const [, tst] = await driver.findElements( + '[data-testid="multichain-token-list-button"]', + ); + await tst.click(); + await driver.clickElement('[data-testid="asset-options__button"]'); + await driver.clickElement({ + text: 'View Asset in explorer', + tag: 'div', + }); + + // Switch to block explorer + await driver.waitUntilXWindowHandles(2); + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('E2E Test Page', windowHandles); + const body = await driver.findElement( + '[data-testid="empty-page-body"]', + ); + + // Verify block explorer + assert.equal(await body.getText(), 'Empty page by MetaMask'); + assert.equal( + await driver.getCurrentUrl(), + 'https://etherscan.io/token/0x581c3C1A2A4EBDE2A0Df29B5cf4c116E42945947', + ); + }, + ); + }); + + it('links to the transaction on the explorer, ', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withNetworkController({ + providerConfig: { + rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, + }, + }) + .withTransactionControllerCompletedTransaction() + .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); + + // View transaction on block explorer + await driver.clickElement('[data-testid="home__activity-tab"]'); + await driver.clickElement('[data-testid="activity-list-item-action"]'); + await driver.clickElement({ + text: 'View on block explorer', + tag: 'a', + }); + + // Switch to block explorer + await driver.waitUntilXWindowHandles(2); + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle('E2E Test Page', windowHandles); + const body = await driver.findElement( + '[data-testid="empty-page-body"]', + ); + + // Verify block explorer + assert.equal(await body.getText(), 'Empty page by MetaMask'); + assert.equal( + await driver.getCurrentUrl(), + 'https://etherscan.io/tx/0xe5e7b95690f584b8f66b33e31acc6184fea553fa6722d42486a59990d13d5fa2', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/custom-rpc-history.spec.js b/test/e2e/tests/custom-rpc-history.spec.js index 46bde39df39e..93709a98817b 100644 --- a/test/e2e/tests/custom-rpc-history.spec.js +++ b/test/e2e/tests/custom-rpc-history.spec.js @@ -70,7 +70,7 @@ describe('Stores custom RPC history', function () { '.networks-tab__add-network-form-footer .btn-primary', ); - await driver.findElement({ text: networkName, tag: 'p' }); + await driver.findElement({ text: networkName, tag: 'span' }); }, ); }); diff --git a/test/e2e/tests/ens.spec.js b/test/e2e/tests/ens.spec.js index 2f6f67cb453c..7ed1ab3bb63e 100644 --- a/test/e2e/tests/ens.spec.js +++ b/test/e2e/tests/ens.spec.js @@ -78,16 +78,7 @@ describe('ENS', function () { it('domain resolves to a correct address', async function () { await withFixtures( { - fixtures: new FixtureBuilder() - .withNetworkController({ - providerConfig: { - chainId: '0x1', - nickname: '', - rpcUrl: '', - type: 'mainnet', - }, - }) - .build(), + fixtures: new FixtureBuilder().withNetworkControllerOnMainnet().build(), ganacheOptions, title: this.test.title, testSpecificMock: mockInfura, diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 245a26cbd925..8043cd79ae4b 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -43,6 +43,7 @@ const removedBackgroundFields = [ // These properties are set to undefined, causing inconsistencies between Chrome and Firefox 'AppStateController.currentPopupId', 'AppStateController.timeoutMinutes', + 'PPOMController.chainStatus.0x539.lastVisited', ]; const removedUiFields = removedBackgroundFields.map(backgroundToUiField); diff --git a/test/e2e/tests/import-flow.spec.js b/test/e2e/tests/import-flow.spec.js index 23f51ec87f50..6d0ace645dd4 100644 --- a/test/e2e/tests/import-flow.spec.js +++ b/test/e2e/tests/import-flow.spec.js @@ -11,6 +11,7 @@ const { findAnotherAccountFromAccountList, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); +const { emptyHtmlPage } = require('../mock-e2e'); const ganacheOptions = { accounts: [ @@ -22,7 +23,18 @@ const ganacheOptions = { ], }; -describe('Import flow', function () { +async function mockTrezor(mockServer) { + return await mockServer + .forGet('https://connect.trezor.io/9/popup.html') + .thenCallback(() => { + return { + statusCode: 200, + body: emptyHtmlPage(), + }; + }); +} + +describe('Import flow @no-mmi', function () { it('Import wallet using Secret Recovery Phrase', async function () { const testPassword = 'correct horse battery staple'; @@ -83,7 +95,10 @@ describe('Import flow', function () { // choose Create account from the account menu await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement({ text: 'Add account', tag: 'button' }); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); + await driver.clickElement({ text: 'Add a new account', tag: 'button' }); // set account name await driver.fill('[placeholder="Account 2"]', '2nd account'); @@ -196,6 +211,9 @@ describe('Import flow', function () { await driver.press('#password', driver.Key.ENTER); await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement({ text: 'Import account', tag: 'button' }); // Imports Account 4 with private key @@ -206,7 +224,7 @@ describe('Import flow', function () { ); // New imported account has correct name and label - await driver.findElement({ + await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 4', }); @@ -222,6 +240,9 @@ describe('Import flow', function () { }); // Imports Account 5 with private key + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement({ text: 'Import account', tag: 'button' }); await driver.findClickableElement('#private-key-box'); await driver.fill('#private-key-box', testPrivateKey2); @@ -230,7 +251,7 @@ describe('Import flow', function () { ); // New imported account has correct name and label - await driver.findElement({ + await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 5', }); @@ -247,7 +268,7 @@ describe('Import flow', function () { // Account 5 can be removed await driver.clickElement('[data-testid="account-list-menu-remove"]'); await driver.clickElement({ text: 'Remove', tag: 'button' }); - await driver.findElement({ + await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 1', }); @@ -277,6 +298,9 @@ describe('Import flow', function () { // Imports an account with JSON file await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement({ text: 'Import account', tag: 'button' }); await driver.clickElement('.dropdown__select'); @@ -298,7 +322,7 @@ describe('Import flow', function () { ); // New imported account has correct name and label - await driver.findElement({ + await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 4', }); @@ -340,6 +364,9 @@ describe('Import flow', function () { // choose Import Account from the account menu await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); await driver.clickElement({ text: 'Import account', tag: 'button' }); // enter private key @@ -358,36 +385,42 @@ describe('Import flow', function () { ); }); - if (process.env.ENABLE_MV3) { - it('Connects to a Hardware wallet for trezor', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions, - title: this.test.title, - }, - async ({ driver }) => { - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // choose Connect hardware wallet from the account menu - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement({ - text: 'Add hardware wallet', - tag: 'button', - }); - await driver.delay(regularDelayMs); - - // should open the TREZOR Connect popup - await driver.clickElement('.hw-connect__btn:nth-of-type(2)'); - await driver.delay(largeDelayMs * 2); - await driver.clickElement({ text: 'Continue', tag: 'button' }); - await driver.waitUntilXWindowHandles(2); - const allWindows = await driver.getAllWindowHandles(); - assert.equal(allWindows.length, 2); - }, - ); - }); - } + it('Connects to a Hardware wallet for trezor', async function () { + if (process.env.ENABLE_MV3) { + // Hardware wallets not supported in MV3 build yet + this.skip(); + } + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions, + title: this.test.title, + testSpecificMock: mockTrezor, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // choose Connect hardware wallet from the account menu + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); + await driver.clickElement({ + text: 'Add hardware wallet', + tag: 'button', + }); + await driver.delay(regularDelayMs); + + // should open the TREZOR Connect popup + await driver.clickElement('.hw-connect__btn:nth-of-type(2)'); + await driver.delay(largeDelayMs * 2); + await driver.clickElement({ text: 'Continue', tag: 'button' }); + await driver.waitUntilXWindowHandles(2); + const allWindows = await driver.getAllWindowHandles(); + assert.equal(allWindows.length, 2); + }, + ); + }); }); diff --git a/test/e2e/tests/ipfs-toggle.spec.js b/test/e2e/tests/ipfs-toggle.spec.js index dc174f2c34a1..74cfa4fb6122 100644 --- a/test/e2e/tests/ipfs-toggle.spec.js +++ b/test/e2e/tests/ipfs-toggle.spec.js @@ -33,10 +33,6 @@ describe('Settings', function () { '.settings-page__header__title-container__close-button', ); await driver.clickElement('[data-testid="home__nfts-tab"]'); - const nftDefaultImage1 = await driver.findElement( - '[data-testid=nft-default-image]', - ); - assert.equal(await nftDefaultImage1.isDisplayed(), true); const importedNftImage = await driver.findVisibleElement( '.nft-item__container', ); @@ -65,13 +61,6 @@ describe('Settings', function () { '[data-testid="nft-image"]', ); assert.equal(await nftImage.isDisplayed(), true); - - await driver.clickElement('[data-testid="asset__back"]'); - await driver.clickElement('[data-testid="home__nfts-tab"]'); - await driver.clickElement('[data-testid="collection-expander-button"]'); - - const nftImage2 = await driver.findElement('[data-testid=nft-image]'); - assert.equal(await nftImage2.isDisplayed(), true); }, ); }); diff --git a/test/e2e/tests/metamask-responsive-ui.spec.js b/test/e2e/tests/metamask-responsive-ui.spec.js index 3e89d5e25ae6..fe01902dfc15 100644 --- a/test/e2e/tests/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/metamask-responsive-ui.spec.js @@ -3,12 +3,12 @@ const { TEST_SEED_PHRASE_TWO, convertToHexValue, withFixtures, - assertAccountBalanceForDOM, + locateAccountBalanceDOM, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('MetaMask Responsive UI', function () { - it('Creating a new wallet', async function () { + it('Creating a new wallet @no-mmi', async function () { const driverOptions = { openDevToolsForTabs: true }; await withFixtures( @@ -109,7 +109,7 @@ describe('MetaMask Responsive UI', function () { await driver.press('#confirm-password', driver.Key.ENTER); // balance renders - await assertAccountBalanceForDOM(driver, ganacheServer); + await locateAccountBalanceDOM(driver, ganacheServer); }, ); }); diff --git a/test/e2e/tests/multiple-transactions.spec.js b/test/e2e/tests/multiple-transactions.spec.js index a4c9a505b91e..4bac6e54fff6 100644 --- a/test/e2e/tests/multiple-transactions.spec.js +++ b/test/e2e/tests/multiple-transactions.spec.js @@ -4,6 +4,7 @@ const { withFixtures, openDapp, regularDelayMs, + unlockWallet, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -31,8 +32,7 @@ describe('Multiple transactions', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // initiates a transaction from the dapp await openDapp(driver); @@ -62,7 +62,13 @@ describe('Multiple transactions', function () { tag: 'a', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); - await driver.waitForElementNotPresent('.loading-overlay__spinner'); + await driver.switchToWindow(confirmation); + + // wait for the "Reject 2 transactions" to disappear + await driver.waitForElementNotPresent( + '.page-container__footer-secondary a', + ); + // confirms first transaction await driver.clickElement({ text: 'Confirm', tag: 'button' }); @@ -95,8 +101,7 @@ describe('Multiple transactions', function () { }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await unlockWallet(driver); // initiates a transaction from the dapp await openDapp(driver); diff --git a/test/e2e/tests/onboarding.spec.js b/test/e2e/tests/onboarding.spec.js index c81e93520e7a..6c506104959f 100644 --- a/test/e2e/tests/onboarding.spec.js +++ b/test/e2e/tests/onboarding.spec.js @@ -9,12 +9,12 @@ const { importSRPOnboardingFlow, importWrongSRPOnboardingFlow, testSRPDropdownIterations, - assertAccountBalanceForDOM, + locateAccountBalanceDOM, defaultGanacheOptions, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -describe('MetaMask onboarding', function () { +describe('MetaMask onboarding @no-mmi', function () { const testPassword = 'correct horse battery staple'; const wrongSeedPhrase = 'test test test test test test test test test test test test'; @@ -239,7 +239,7 @@ describe('MetaMask onboarding', function () { ); }); - it(`User can add custom network during onboarding`, async function () { + it('User can add custom network during onboarding', async function () { const networkName = 'Localhost 8546'; const networkUrl = 'http://127.0.0.1:8546'; const currencySymbol = 'ETH'; @@ -296,7 +296,7 @@ describe('MetaMask onboarding', function () { text: networkName, }); - await assertAccountBalanceForDOM(driver, secondaryGanacheServer); + await locateAccountBalanceDOM(driver, secondaryGanacheServer); }, ); }); diff --git a/test/e2e/tests/phishing-controller/phishing-detection.spec.js b/test/e2e/tests/phishing-controller/phishing-detection.spec.js index 2e29a2411ac3..823432c35015 100644 --- a/test/e2e/tests/phishing-controller/phishing-detection.spec.js +++ b/test/e2e/tests/phishing-controller/phishing-detection.spec.js @@ -58,6 +58,7 @@ describe('Phishing Detection', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); await openDapp(driver); + await driver.switchToWindowWithTitle('MetaMask Phishing Detection'); await driver.clickElement({ text: 'continue to the site.', }); @@ -177,6 +178,7 @@ describe('Phishing Detection', function () { await driver.press('#password', driver.Key.ENTER); await openDapp(driver); + await driver.switchToWindowWithTitle('MetaMask Phishing Detection'); await driver.clickElement({ text: 'report a detection problem.' }); // wait for page to load before checking URL. @@ -215,6 +217,7 @@ describe('Phishing Detection', function () { await driver.press('#password', driver.Key.ENTER); await driver.openNewPage(phishingSite.href); + await driver.switchToWindowWithTitle('MetaMask Phishing Detection'); await driver.clickElement({ text: 'report a detection problem.' }); // wait for page to load before checking URL. @@ -252,6 +255,7 @@ describe('Phishing Detection', function () { await driver.press('#password', driver.Key.ENTER); await driver.openNewPage('http://127.0.0.1:8080'); + await driver.switchToWindowWithTitle('MetaMask Phishing Detection'); await driver.clickElement({ text: 'report a detection problem.' }); // wait for page to load before checking URL. diff --git a/test/e2e/tests/portfolio-site.spec.js b/test/e2e/tests/portfolio-site.spec.js index b708460e0a13..af47e22da231 100644 --- a/test/e2e/tests/portfolio-site.spec.js +++ b/test/e2e/tests/portfolio-site.spec.js @@ -1,6 +1,7 @@ const { strict: assert } = require('assert'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); +const { emptyHtmlPage } = require('../mock-e2e'); describe('Portfolio site', function () { const ganacheOptions = { @@ -12,6 +13,22 @@ describe('Portfolio site', function () { }, ], }; + + async function mockPortfolioSite(mockServer) { + return await mockServer + .forGet('https://portfolio.metamask.io/') + .withQuery({ + metamaskEntry: 'ext_portfolio_button', + metametricsId: 'null', + }) + .thenCallback(() => { + return { + statusCode: 200, + body: emptyHtmlPage(), + }; + }); + } + it('should link to the portfolio site @no-mmi', async function () { await withFixtures( { @@ -19,6 +36,7 @@ describe('Portfolio site', function () { fixtures: new FixtureBuilder().build(), ganacheOptions, title: this.test.title, + testSpecificMock: mockPortfolioSite, }, async ({ driver }) => { await driver.navigate(); @@ -29,12 +47,12 @@ describe('Portfolio site', function () { await driver.clickElement('[data-testid="eth-overview-portfolio"]'); await driver.waitUntilXWindowHandles(2); const windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); + await driver.switchToWindowWithTitle('E2E Test Page', windowHandles); // Verify site assert.equal( await driver.getCurrentUrl(), - 'http://127.0.0.1:8080/?metamaskEntry=ext_portfolio_button&metametricsId=null', + 'https://portfolio.metamask.io/?metamaskEntry=ext_portfolio_button&metametricsId=null', ); }, ); diff --git a/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js b/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js new file mode 100644 index 000000000000..8de02f62edaf --- /dev/null +++ b/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js @@ -0,0 +1,216 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { mockServerJsonRpc } = require('../mock-server-json-rpc'); + +const { + WINDOW_TITLES, + defaultGanacheOptions, + openDapp, + unlockWallet, + withFixtures, +} = require('../helpers'); + +const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; + +const selectedAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; +const selectedAddressWithoutPrefix = '5cfe73b6021e818b776b421b1c4db2474086a7e1'; + +const CONTRACT_ADDRESS = { + BalanceChecker: '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39', + BUSD: '0x4fabb145d64652a948d72533023f6e7a623c7c53', + BUSDImplementation: '0x2a3f1a37c04f82aa274f5353834b2d002db91015', + OffChainOracle: '0x52cbe0f49ccdd4dc6e9c13bab024eabd2842045b', +}; + +async function mockInfura(mockServer) { + await mockServerJsonRpc(mockServer, [ + ['eth_blockNumber'], + [ + 'eth_call', + { + methodResultVariant: 'balanceChecker', + params: [{ to: CONTRACT_ADDRESS.BalanceChecker }], + }, + ], + [ + 'eth_call', + { + methodResultVariant: 'offchainOracle', + params: [{ to: CONTRACT_ADDRESS.OffChainOracle }], + }, + ], + [ + 'eth_call', + { + methodResultVariant: 'balance', + params: [ + { + accessList: [], + data: `0x70a08231000000000000000000000000${selectedAddressWithoutPrefix}`, + to: CONTRACT_ADDRESS.BUSD, + }, + ], + }, + ], + ['eth_estimateGas'], + ['eth_feeHistory'], + ['eth_gasPrice'], + ['eth_getBalance'], + ['eth_getBlockByNumber'], + [ + 'eth_getCode', + { + methodResultVariant: 'BUSD', + params: [CONTRACT_ADDRESS.BUSD], + }, + ], + ['eth_getTransactionCount'], + ]); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [{ accessList: [], data: '0x00000000' }], + }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: { + calls: [ + { + error: 'execution reverted', + from: CONTRACT_ADDRESS.BUSD, + gas: '0x1d55c2cb', + gasUsed: '0x39c', + input: '0x00000000', + to: CONTRACT_ADDRESS.BUSDImplementation, + type: 'DELEGATECALL', + value: '0x0', + }, + ], + error: 'execution reverted', + from: '0x0000000000000000000000000000000000000000', + gas: '0x1dcd6500', + gasUsed: '0x721e', + input: '0x00000000', + to: CONTRACT_ADDRESS.BUSD, + type: 'CALL', + value: '0x0', + }, + }, + }; + }); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [ + { + data: '0x095ea7b3000000000000000000000000e50a2dbc466d01a34c3e8b7e8e45fce4f7da39e6000000000000000000000000000000000000000000000000ffffffffffffffff', + }, + ], + }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: { + calls: [ + { + from: CONTRACT_ADDRESS.BUSD, + gas: '0x291ee', + gasUsed: '0x79bb', + input: + '0x095ea7b3000000000000000000000000e50a2dbc466d01a34c3e8b7e8e45fce4f7da39e6000000000000000000000000000000000000000000000000ffffffffffffffff', + logs: [ + { + address: CONTRACT_ADDRESS.BUSD, + data: '0x000000000000000000000000000000000000000000000000ffffffffffffffff', + topics: [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + `0x000000000000000000000000${selectedAddressWithoutPrefix}`, + '0x000000000000000000000000e50a2dbc466d01a34c3e8b7e8e45fce4f7da39e6', + ], + }, + ], + output: + '0x0000000000000000000000000000000000000000000000000000000000000001', + to: CONTRACT_ADDRESS.BUSDImplementation, + type: 'DELEGATECALL', + value: '0x0', + }, + ], + from: selectedAddress, + gas: '0x30d40', + gasUsed: '0xeac5', + input: + '0x095ea7b3000000000000000000000000e50a2dbc466d01a34c3e8b7e8e45fce4f7da39e6000000000000000000000000000000000000000000000000ffffffffffffffff', + output: + '0x0000000000000000000000000000000000000000000000000000000000000001', + to: CONTRACT_ADDRESS.BUSD, + type: 'CALL', + value: '0x0', + }, + }, + }; + }); +} + +describe('PPOM Blockaid Alert - Malicious ERC20 Approval @no-mmi', function () { + it('should show banner alert', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfura, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + const expectedTitle = 'This is a deceptive request'; + const expectedDescription = + 'If you approve this request, a third party known for scams might take all your assets.'; + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement('#maliciousApprovalButton'); + + // Wait for confirmation pop-up + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + + const bannerAlertFoundByTitle = await driver.findElement({ + css: bannerAlertSelector, + text: expectedTitle, + }); + const bannerAlertText = await bannerAlertFoundByTitle.getText(); + + assert( + bannerAlertFoundByTitle, + `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: approval_farming\n`, + ); + assert( + bannerAlertText.includes(expectedDescription), + `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: approval_farming\n`, + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js b/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js new file mode 100644 index 000000000000..795f0cc056b0 --- /dev/null +++ b/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js @@ -0,0 +1,213 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { mockServerJsonRpc } = require('../mock-server-json-rpc'); + +const { + WINDOW_TITLES, + defaultGanacheOptions, + openDapp, + unlockWallet, + withFixtures, +} = require('../helpers'); + +const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; + +const selectedAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; +const selectedAddressWithoutPrefix = '5cfe73b6021e818b776b421b1c4db2474086a7e1'; + +const CONTRACT_ADDRESS = { + BalanceChecker: '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39', + FiatTokenV2_1: '0xa2327a938febf5fec13bacfb16ae10ecbc4cbdcf', + OffChainOracle: '0x52cbe0f49ccdd4dc6e9c13bab024eabd2842045b', + USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', +}; + +async function mockInfura(mockServer) { + await mockServerJsonRpc(mockServer, [ + ['eth_blockNumber'], + [ + 'eth_call', + { + methodResultVariant: 'balanceChecker', + params: [{ to: CONTRACT_ADDRESS.BalanceChecker }], + }, + ], + [ + 'eth_call', + { + methodResultVariant: 'offchainOracle', + params: [{ to: CONTRACT_ADDRESS.OffChainOracle }], + }, + ], + [ + 'eth_call', + { + methodResultVariant: 'balance', + params: [ + { + accessList: [], + data: `0x70a08231000000000000000000000000${selectedAddressWithoutPrefix}`, + to: CONTRACT_ADDRESS.USDC, + }, + ], + }, + ], + ['eth_estimateGas'], + ['eth_feeHistory'], + ['eth_gasPrice'], + ['eth_getBalance'], + ['eth_getBlockByNumber'], + [ + 'eth_getCode', + { + methodResultVariant: 'USDC', + params: [CONTRACT_ADDRESS.USDC], + }, + ], + ['eth_getTransactionCount'], + ]); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [{ accessList: [], data: '0x00000000' }], + }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: { + calls: [ + { + error: 'execution reverted', + from: CONTRACT_ADDRESS.USDC, + gas: '0x1d55c2c7', + gasUsed: '0xf0', + input: '0x00000000', + to: CONTRACT_ADDRESS.FiatTokenV2_1, + type: 'DELEGATECALL', + value: '0x0', + }, + ], + error: 'execution reverted', + from: '0x0000000000000000000000000000000000000000', + gas: '0x1dcd6500', + gasUsed: '0x6f79', + input: '0x00000000', + to: CONTRACT_ADDRESS.USDC, + type: 'CALL', + value: '0x0', + }, + }, + }; + }); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [{ from: selectedAddress }], + }) + .thenCallback((req) => { + const mockFakePhishingAddress = + '5fbdb2315678afecb367f032d93f642f64180aa3'; + + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: { + calls: [ + { + from: CONTRACT_ADDRESS.USDC, + gas: '0x2923d', + gasUsed: '0x4cac', + input: `0xa9059cbb000000000000000000000000${mockFakePhishingAddress}0000000000000000000000000000000000000000000000000000000000000064`, + logs: [ + { + address: CONTRACT_ADDRESS.USDC, + data: '0x0000000000000000000000000000000000000000000000000000000000000064', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + `0x000000000000000000000000${selectedAddressWithoutPrefix}`, + `0x000000000000000000000000${mockFakePhishingAddress}`, + ], + }, + ], + output: + '0x0000000000000000000000000000000000000000000000000000000000000001', + to: CONTRACT_ADDRESS.FiatTokenV2_1, + type: 'DELEGATECALL', + value: '0x0', + }, + ], + from: selectedAddress, + gas: '0x30d40', + gasUsed: '0xbd69', + input: `0xa9059cbb000000000000000000000000${mockFakePhishingAddress}0000000000000000000000000000000000000000000000000000000000000064`, + output: + '0x0000000000000000000000000000000000000000000000000000000000000001', + to: CONTRACT_ADDRESS.USDC, + type: 'CALL', + value: '0x0', + }, + }, + }; + }); +} + +describe('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { + it('should show banner alert', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfura, + title: this.test.title, + }, + + async ({ driver }) => { + const expectedTitle = 'This is a deceptive request'; + const expectedDescription = + 'If you approve this request, a third party known for scams will take all your assets.'; + + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement('#maliciousERC20TransferButton'); + + // Wait for confirmation pop-up + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + + const bannerAlertFoundByTitle = await driver.findElement({ + css: bannerAlertSelector, + text: expectedTitle, + }); + const bannerAlertText = await bannerAlertFoundByTitle.getText(); + + assert( + bannerAlertFoundByTitle, + `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: transfer_farming\n`, + ); + assert( + bannerAlertText.includes(expectedDescription), + `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: transfer_farming\n`, + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/ppom-blockaid-alert.spec.js b/test/e2e/tests/ppom-blockaid-alert.spec.js new file mode 100644 index 000000000000..7afb4131ffee --- /dev/null +++ b/test/e2e/tests/ppom-blockaid-alert.spec.js @@ -0,0 +1,321 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { mockServerJsonRpc } = require('../mock-server-json-rpc'); + +const { + WINDOW_TITLES, + defaultGanacheOptions, + openDapp, + unlockWallet, + withFixtures, +} = require('../helpers'); + +const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; +const selectedAddress = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; +const mockMaliciousAddress = '0x5fbdb2315678afecb367f032d93f642f64180aa3'; + +const expectedMaliciousTitle = 'This is a deceptive request'; + +const testBenignConfigs = [ + { + logExpectedDetail: 'benign eth_sendTransaction with no value', + btnSelector: '#sendButton', + }, + { + logExpectedDetail: 'benign eth_sendTransaction with value', + method: 'eth_sendTransaction', + params: [ + { + from: selectedAddress, + to: '0xf977814e90da44bfa03b6295a0616a897441acec', + value: '0x9184e72a000', + }, + ], + }, + { + logExpectedDetail: 'benign Blur eth_sendTransaction', + method: 'eth_signTypedData_v4', + params: [ + selectedAddress, + '{"types":{"Order":[{"name":"trader","type":"address"},{"name":"side","type":"uint8"},{"name":"matchingPolicy","type":"address"},{"name":"collection","type":"address"},{"name":"tokenId","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"paymentToken","type":"address"},{"name":"price","type":"uint256"},{"name":"listingTime","type":"uint256"},{"name":"expirationTime","type":"uint256"},{"name":"fees","type":"Fee[]"},{"name":"salt","type":"uint256"},{"name":"extraParams","type":"bytes"},{"name":"nonce","type":"uint256"}],"Fee":[{"name":"rate","type":"uint16"},{"name":"recipient","type":"address"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}]},"domain":{"name":"Blur Exchange","version":"1.0","chainId":"1","verifyingContract":"0x000000000000ad05ccc4f10045630fb830b95127"},"primaryType":"Order","message":{"trader":"0xd854343f41b2138b686f2d3ba38402a9f7fb4337","side":"1","matchingPolicy":"0x0000000000dab4a563819e8fd93dba3b25bc3495","collection":"0xc4a5025c4563ad0acc09d92c2506e6744dad58eb","tokenId":"30420","amount":"1","paymentToken":"0x0000000000000000000000000000000000000000","price":"1000000000000000000","listingTime":"1679418212","expirationTime":"1680023012","salt":"154790208154270131670189427454206980105","extraParams":"0x01","nonce":"0"}}', + ], + }, + { + logExpectedDetail: 'benign Seaport eth_sendTransaction', + method: 'eth_signTypedData_v4', + params: [ + selectedAddress, + '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.4","chainId":"1","verifyingContract":"0x00000000000001ad428e4906aE43D8F9852d0dD6"},"message":{"offerer":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC","offer":[{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"2500000000000000","endAmount":"2500000000000000"}],"consideration":[{"itemType":"2","token":"0xaA7200ee500dE2dcde75E996De83CBD73BCa9705","identifierOrCriteria":"11909","startAmount":"1","endAmount":"1","recipient":"0xCaFca5eDFb361E8A39a735233f23DAf86CBeD5FC"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"62500000000000","endAmount":"62500000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"1","token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","identifierOrCriteria":"0","startAmount":"12500000000000","endAmount":"12500000000000","recipient":"0x8324BdEF2F30E08E368f2Fa2F14143cDCA77423D"}],"startTime":"1681835413","endTime":"1682094598","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929812618382526293324216","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}}', + ], + }, + { + logExpectedDetail: 'benign eth_signTypedData', + btnSelector: '#signTypedData', + }, +]; + +const testMaliciousConfigs = [ + { + btnSelector: '#maliciousPermit', + expectedDescription: + 'If you approve this request, a third party known for scams might take all your assets.', + expectedReason: 'permit_farming', + }, + { + btnSelector: '#maliciousRawEthButton', + expectedDescription: + 'If you approve this request, a third party known for scams will take all your assets.', + expectedReason: 'raw_native_token_transfer', + }, + { + btnSelector: '#maliciousSeaport', + expectedDescription: + 'If you approve this request, someone can steal your assets listed on OpenSea.', + expectedReason: 'seaport_farming', + }, + { + btnSelector: '#maliciousTradeOrder', + expectedDescription: + 'If you approve this request, you might lose your assets.', + expectedReason: 'trade_order_farming', + }, +]; + +async function mockInfura(mockServer) { + await mockServerJsonRpc(mockServer, [ + ['eth_blockNumber'], + ['eth_call'], + ['eth_estimateGas'], + ['eth_feeHistory'], + ['eth_gasPrice'], + ['eth_getBalance'], + ['eth_getBlockByNumber'], + ['eth_getCode'], + ['eth_getTransactionCount'], + ]); +} + +async function mockInfuraWithMaliciousResponses(mockServer) { + await mockInfura(mockServer); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [{ accessList: [], data: '0x00000000' }], + }) + .thenCallback((req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: req.body.json.id, + result: { + calls: [ + { + error: 'execution reverted', + from: '0x0000000000000000000000000000000000000000', + gas: '0x1d55c2cb', + gasUsed: '0x39c', + input: '0x00000000', + to: mockMaliciousAddress, + type: 'DELEGATECALL', + value: '0x0', + }, + ], + error: 'execution reverted', + from: '0x0000000000000000000000000000000000000000', + gas: '0x1dcd6500', + gasUsed: '0x721e', + input: '0x00000000', + to: mockMaliciousAddress, + type: 'CALL', + value: '0x0', + }, + }, + }; + }); +} + +/** + * Tests various Blockaid PPOM security alerts. Some other tests live in separate files due to + * the need for more sophisticated JSON-RPC mock requests. Some example PPOM Blockaid + * requests and responses are provided here: + * + * @see {@link https://wobbly-nutmeg-8a5.notion.site/MM-E2E-Testing-1e51b617f79240a49cd3271565c6e12d} + */ +describe('Confirmation Security Alert - Blockaid @no-mmi', function () { + it('should not show security alerts for benign requests', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfura, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + for (const config of testBenignConfigs) { + const { btnSelector, logExpectedDetail, method, params } = config; + + // Either click TestDapp button to send JSON-RPC request or manually send request + if (btnSelector) { + await driver.clickElement(btnSelector); + } else { + const request = JSON.stringify({ + jsonrpc: '2.0', + method, + params, + }); + await driver.executeScript( + `window.transactionHash = window.ethereum.request(${request})`, + ); + } + + const windowHandles = await driver.waitUntilXWindowHandles(3); + // Wait for confirmation pop-up + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Notification, + windowHandles, + ); + + const isPresent = await driver.isElementPresent(bannerAlertSelector); + assert.equal( + isPresent, + false, + `Banner alert unexpectedly found. \nExpected detail: ${logExpectedDetail}`, + ); + + // Wait for confirmation pop-up to close + await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.TestDApp, + windowHandles, + ); + } + }, + ); + }); + + /** + * Disclaimer: This test does not test all reason types. e.g. 'blur_farming', + * 'malicious_domain'. Some other tests are found in other files: + * e.g. test/e2e/flask/ppom-blockaid-alert-.spec.js + */ + it('should show security alerts for malicious requests', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfuraWithMaliciousResponses, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + for (const config of testMaliciousConfigs) { + const { expectedDescription, expectedReason, btnSelector } = config; + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement(btnSelector); + + // Wait for confirmation pop-up + const windowHandles = await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Notification, + windowHandles, + ); + + // Find element by title + const bannerAlertFoundByTitle = await driver.findElement({ + css: bannerAlertSelector, + text: expectedMaliciousTitle, + }); + const bannerAlertText = await bannerAlertFoundByTitle.getText(); + + assert( + bannerAlertFoundByTitle, + `Banner alert not found. Expected Title: ${expectedMaliciousTitle} \nExpected reason: ${expectedReason}\n`, + ); + assert( + bannerAlertText.includes(expectedDescription), + `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: ${expectedReason}\n`, + ); + + // Wait for confirmation pop-up to close + await driver.clickElement({ text: 'Reject', tag: 'button' }); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.TestDApp, + windowHandles, + ); + } + }, + ); + }); + + it('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + title: this.test.title, + }, + + async ({ driver }) => { + await driver.navigate(); + await unlockWallet(driver); + await openDapp(driver); + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement('#maliciousApprovalButton'); + + // Wait for confirmation pop-up + const windowHandles = await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Notification, + windowHandles, + ); + + const expectedTitle = 'Request may not be safe'; + + const bannerAlert = await driver.findElement({ + css: bannerAlertSelector, + text: expectedTitle, + }); + + assert( + bannerAlert, + `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: transfer_farming\n`, + ); + }, + ); + }); +}); diff --git a/test/e2e/flask/ppom-toggle-settings.spec.js b/test/e2e/tests/ppom-toggle-settings.spec.js similarity index 89% rename from test/e2e/flask/ppom-toggle-settings.spec.js rename to test/e2e/tests/ppom-toggle-settings.spec.js index f3a19631aca6..fc2035746cbf 100644 --- a/test/e2e/flask/ppom-toggle-settings.spec.js +++ b/test/e2e/tests/ppom-toggle-settings.spec.js @@ -8,22 +8,13 @@ const { } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -const mainnetProviderConfig = { - providerConfig: { - chainId: '0x1', - nickname: '', - rpcUrl: '', - type: 'mainnet', - }, -}; - -describe('PPOM Settings', function () { +describe('PPOM Settings @no-mmi', function () { it('should not show the PPOM warning when toggle is off', async function () { await withFixtures( { dapp: true, fixtures: new FixtureBuilder() - .withNetworkController(mainnetProviderConfig) + .withNetworkControllerOnMainnet() .withPermissionControllerConnectedToTestDapp() .build(), ganacheOptions: defaultGanacheOptions, @@ -51,7 +42,7 @@ describe('PPOM Settings', function () { { dapp: true, fixtures: new FixtureBuilder() - .withNetworkController(mainnetProviderConfig) + .withNetworkControllerOnMainnet() .withPermissionControllerConnectedToTestDapp() .build(), ganacheOptions: defaultGanacheOptions, diff --git a/test/e2e/tests/provider-api.spec.js b/test/e2e/tests/provider-api.spec.js index 79ac8f9fe7f1..eee5daa32d3b 100644 --- a/test/e2e/tests/provider-api.spec.js +++ b/test/e2e/tests/provider-api.spec.js @@ -32,15 +32,10 @@ describe('MetaMask', function () { await driver.press('#password', driver.Key.ENTER); await openDapp(driver); - const networkDiv = await driver.waitForSelector({ - css: '#network', - text: '1337', - }); const chainIdDiv = await driver.waitForSelector({ css: '#chainId', text: '0x539', }); - assert.equal(await networkDiv.getText(), '1337'); assert.equal(await chainIdDiv.getText(), '0x539'); const windowHandles = await driver.getAllWindowHandles(); @@ -50,17 +45,12 @@ describe('MetaMask', function () { await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'button' }); await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - const switchedNetworkDiv = await driver.waitForSelector({ - css: '#network', - text: '0x1', - }); const switchedChainIdDiv = await driver.waitForSelector({ css: '#chainId', text: '0x1', }); const accountsDiv = await driver.findElement('#accounts'); - assert.equal(await switchedNetworkDiv.getText(), '0x1'); assert.equal(await switchedChainIdDiv.getText(), '0x1'); assert.equal(await accountsDiv.getText(), publicAddress); }, diff --git a/test/e2e/tests/send-eth.spec.js b/test/e2e/tests/send-eth.spec.js index 320102bc57e1..cf9f0bbdb912 100644 --- a/test/e2e/tests/send-eth.spec.js +++ b/test/e2e/tests/send-eth.spec.js @@ -4,7 +4,7 @@ const { convertToHexValue, withFixtures, openDapp, - assertAccountBalanceForDOM, + locateAccountBalanceDOM, logInWithBalanceValidation, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -429,7 +429,7 @@ describe('Send ETH from inside MetaMask to a Multisig Address', function () { await driver.clickElement({ text: 'Confirm', tag: 'button' }); // Go back to home screen to check txn - await assertAccountBalanceForDOM(driver, ganacheServer); + await locateAccountBalanceDOM(driver, ganacheServer); await driver.clickElement('[data-testid="home__activity-tab"]'); const txn = await driver.isElementPresent( '.transaction-list__completed-transactions .activity-list-item', diff --git a/test/e2e/tests/send-hex-address.spec.js b/test/e2e/tests/send-hex-address.spec.js index cd656e9963e5..8b4851ce07b4 100644 --- a/test/e2e/tests/send-hex-address.spec.js +++ b/test/e2e/tests/send-hex-address.spec.js @@ -1,4 +1,3 @@ -const { strict: assert } = require('assert'); const { convertToHexValue, withFixtures, @@ -40,7 +39,7 @@ describe('Send ETH to a 40 character hexadecimal address', function () { 'input[placeholder="Enter public address (0x) or ENS name"]', nonHexPrefixedAddress, ); - await driver.waitForSelector({ + await driver.findElement({ css: '.ens-input__selected-input__title', text: hexPrefixedAddress, }); @@ -49,7 +48,7 @@ describe('Send ETH to a 40 character hexadecimal address', function () { // Confirm transaction await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement('[data-testid="home__activity-tab"]'); - const sendTransactionListItem = await driver.waitForSelector( + const sendTransactionListItem = await driver.findElement( '.transaction-list__completed-transactions .activity-list-item', ); await sendTransactionListItem.click(); @@ -57,10 +56,10 @@ describe('Send ETH to a 40 character hexadecimal address', function () { await driver.clickElement('[data-testid="sender-to-recipient__name"]'); // Verify address in activity log - const publicAddress = await driver.findElement( - '.nickname-popover__public-address', - ); - assert.equal(await publicAddress.getText(), hexPrefixedAddress); + await driver.findElement({ + css: '.nickname-popover__public-address', + text: hexPrefixedAddress, + }); }, ); }); @@ -72,10 +71,9 @@ describe('Send ETH to a 40 character hexadecimal address', function () { title: this.test.title, failOnConsoleError: false, }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await logInWithBalanceValidation(driver, ganacheServer); // Send ETH await driver.clickElement('[data-testid="eth-overview-send"]'); @@ -85,7 +83,7 @@ describe('Send ETH to a 40 character hexadecimal address', function () { 'input[placeholder="Enter public address (0x) or ENS name"]', nonHexPrefixedAddress, ); - await driver.waitForSelector({ + await driver.findElement({ css: '.ens-input__selected-input__title', text: hexPrefixedAddress, }); @@ -94,18 +92,17 @@ describe('Send ETH to a 40 character hexadecimal address', function () { // Confirm transaction await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement('[data-testid="home__activity-tab"]'); - const sendTransactionListItem = await driver.waitForSelector( + await driver.clickElement( '.transaction-list__completed-transactions .activity-list-item', ); - await sendTransactionListItem.click(); await driver.clickElement({ text: 'Activity log', tag: 'summary' }); await driver.clickElement('[data-testid="sender-to-recipient__name"]'); // Verify address in activity log - const publicAddress = await driver.findElement( - '.nickname-popover__public-address', - ); - assert.equal(await publicAddress.getText(), hexPrefixedAddress); + await driver.findElement({ + css: '.nickname-popover__public-address', + text: hexPrefixedAddress, + }); }, ); }); @@ -148,27 +145,27 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { 'input[placeholder="Enter public address (0x) or ENS name"]', nonHexPrefixedAddress, ); - await driver.waitForSelector({ + await driver.findElement({ css: '.ens-input__selected-input__title', text: hexPrefixedAddress, }); - await driver.waitForSelector({ + await driver.findElement({ css: '.transaction-detail-item', text: '0.000042 ETH', }); await driver.clickElement({ text: 'Next', tag: 'button' }); // Confirm transaction - await driver.waitForSelector({ + await driver.findElement({ css: '.confirm-page-container-summary__title', text: '0', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.waitForSelector( + await driver.findElement( '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', ); - const sendTransactionListItem = await driver.waitForSelector( + const sendTransactionListItem = await driver.findElement( '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', ); await sendTransactionListItem.click(); @@ -176,10 +173,10 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { await driver.clickElement('[data-testid="sender-to-recipient__name"]'); // Verify address in activity log - const publicAddress = await driver.findElement( - '.nickname-popover__public-address', - ); - assert.equal(await publicAddress.getText(), hexPrefixedAddress); + await driver.findElement({ + css: '.nickname-popover__public-address', + text: hexPrefixedAddress, + }); }, ); }); @@ -209,27 +206,27 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { 'input[placeholder="Enter public address (0x) or ENS name"]', nonHexPrefixedAddress, ); - await driver.waitForSelector({ + await driver.findElement({ css: '.ens-input__selected-input__title', text: hexPrefixedAddress, }); - await driver.waitForSelector({ + await driver.findElement({ css: '.transaction-detail-item', text: '0.000042 ETH', }); await driver.clickElement({ text: 'Next', tag: 'button' }); // Confirm transaction - await driver.waitForSelector({ + await driver.findElement({ css: '.confirm-page-container-summary__title', text: '0', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.waitForSelector( + await driver.findElement( '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', ); - const sendTransactionListItem = await driver.waitForSelector( + const sendTransactionListItem = await driver.findElement( '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', ); await sendTransactionListItem.click(); @@ -237,10 +234,10 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { await driver.clickElement('[data-testid="sender-to-recipient__name"]'); // Verify address in activity log - const publicAddress = await driver.findElement( - '.nickname-popover__public-address', - ); - assert.equal(await publicAddress.getText(), hexPrefixedAddress); + await driver.findElement({ + css: '.nickname-popover__public-address', + text: hexPrefixedAddress, + }); }, ); }); diff --git a/test/e2e/tests/settings-add-snap-account-toggle.spec.js b/test/e2e/tests/settings-add-snap-account-toggle.spec.js deleted file mode 100644 index 54a32f5d1662..000000000000 --- a/test/e2e/tests/settings-add-snap-account-toggle.spec.js +++ /dev/null @@ -1,52 +0,0 @@ -const { strict: assert } = require('assert'); -const { withFixtures } = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); - -describe('Add snap account experimental settings', function () { - it('switch "Enable Add snap account" to on', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - title: this.test.title, - failOnConsoleError: false, - }, - async ({ driver }) => { - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - // Make sure the "Add snap account" button is not visible. - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.assertElementNotPresent({ - text: 'Add snap account', - tag: 'button', - }); - await driver.clickElement('.mm-box button[aria-label="Close"]'); - - // Navigate to experimental settings. - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Experimental', tag: 'div' }); - - // Switch "Enable Add snap account" to on. - const toggle = await driver.findElement( - '[data-testid="add-snap-account-toggle"]', - ); - await driver.scrollToElement(toggle); - await driver.clickElement('[data-testid="add-snap-account-toggle"]'); - - // Make sure the "Add snap account" button is visible. - await driver.clickElement('[data-testid="account-menu-icon"]'); - assert.equal( - await driver.isElementPresentAndVisible({ - text: 'Add snap account', - tag: 'button', - }), - true, - ); - }, - ); - }); -}); diff --git a/test/e2e/tests/settings-search.spec.js b/test/e2e/tests/settings-search.spec.js index 343b16ccfe1b..39513874fff4 100644 --- a/test/e2e/tests/settings-search.spec.js +++ b/test/e2e/tests/settings-search.spec.js @@ -19,7 +19,7 @@ describe('Settings Search', function () { security: 'Reveal Secret', alerts: 'Browsing a website', networks: 'Ethereum Mainnet', - experimental: 'Enable security alerts', + experimental: 'Security alerts', about: 'Terms of Use', }; diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 5aa7a4426840..1fbed2703528 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -33,8 +33,7 @@ "termsOfUseLastAgreed": "number", "qrHardware": {}, "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, - "snapsInstallPrivacyWarningShown": true, - "serviceWorkerLastActiveTime": 0 + "snapsInstallPrivacyWarningShown": true }, "ApprovalController": { "pendingApprovals": "object", @@ -83,7 +82,6 @@ }, "NetworkController": { "selectedNetworkClientId": "string", - "networkId": "1337", "providerConfig": { "chainId": "0x539", "nickname": "Localhost 8545", @@ -113,6 +111,13 @@ "completedOnboarding": true, "onboardingTabs": "object" }, + "PPOMController": { + "versionInfo": {}, + "storageMetadata": {}, + "chainStatus": { + "0x539": { "chainId": "0x539", "dataFetched": false, "versionInfo": [] } + } + }, "PermissionController": { "subjects": "object" }, "PermissionLogController": { "permissionHistory": "object", @@ -131,6 +136,8 @@ "use4ByteResolution": true, "useCurrencyRateCheck": true, "openSeaEnabled": false, + "securityAlertsEnabled": "boolean", + "addSnapAccountEnabled": "boolean", "advancedGasFee": {}, "featureFlags": {}, "incomingTransactionsPreferences": {}, @@ -151,6 +158,7 @@ "snapRegistryList": "object", "transactionSecurityCheckEnabled": false, "theme": "light", + "snapsAddSnapAccountModalDismissed": "boolean", "isLineaMainnetReleased": true, "selectedAddress": "string" }, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index b3fa6f2ba7e7..daddadc886e6 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -57,13 +57,11 @@ "qrHardware": {}, "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, "snapsInstallPrivacyWarningShown": true, - "serviceWorkerLastActiveTime": 0, "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": "number", "selectedNetworkClientId": "string", - "networkId": "1337", "providerConfig": { "chainId": "0x539", "nickname": "Localhost 8545", @@ -91,6 +89,8 @@ "useNftDetection": false, "useCurrencyRateCheck": true, "openSeaEnabled": false, + "securityAlertsEnabled": "boolean", + "addSnapAccountEnabled": "boolean", "advancedGasFee": {}, "incomingTransactionsPreferences": {}, "lostIdentities": "object", @@ -101,6 +101,7 @@ "snapRegistryList": "object", "transactionSecurityCheckEnabled": false, "theme": "light", + "snapsAddSnapAccountModalDismissed": "boolean", "isLineaMainnetReleased": true, "selectedAddress": "string", "metaMetricsId": "fake-metrics-id", @@ -194,7 +195,12 @@ "ensResolutionsByAddress": "object", "pendingApprovals": "object", "pendingApprovalCount": "number", - "approvalFlows": "object" + "approvalFlows": "object", + "versionInfo": {}, + "storageMetadata": {}, + "chainStatus": { + "0x539": { "chainId": "0x539", "dataFetched": false, "versionInfo": [] } + } }, "send": "object", "swaps": "object", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index 75bd62d27908..766636cbf671 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -50,7 +50,6 @@ "traits": "object" }, "NetworkController": { - "networkId": "1337", "selectedNetworkClientId": "string", "networksMetadata": { "networkConfigurationId": { "EIPS": {}, "status": "available" } diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 5fa13c429985..722b0dc854d8 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -50,7 +50,6 @@ "traits": "object" }, "NetworkController": { - "networkId": "1337", "selectedNetworkClientId": "string", "networksMetadata": { "networkConfigurationId": { "EIPS": {}, "status": "available" } diff --git a/test/e2e/tests/switch-custom-network.spec.js b/test/e2e/tests/switch-custom-network.spec.js index 67c3ad1fc5d8..ca40232d13a4 100644 --- a/test/e2e/tests/switch-custom-network.spec.js +++ b/test/e2e/tests/switch-custom-network.spec.js @@ -88,7 +88,7 @@ describe('Switch ethereum chain', function () { await driver.switchToWindow(extension); const currentNetworkName = await driver.findElement({ - tag: 'p', + tag: 'span', text: 'Localhost 8546', }); diff --git a/test/e2e/user-actions-benchmark.js b/test/e2e/user-actions-benchmark.js index 07843ecdf32f..3c2d71045a68 100644 --- a/test/e2e/user-actions-benchmark.js +++ b/test/e2e/user-actions-benchmark.js @@ -34,6 +34,9 @@ async function loadNewAccount() { await driver.press('#password', driver.Key.ENTER); await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-popover-action-button"]', + ); const timestampBeforeAction = new Date(); await driver.clickElement( '[data-testid="multichain-account-menu-popover-add-account"]', diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 5dfaf22258b8..1061fe1b59bd 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -8,6 +8,7 @@ const { until, } = require('selenium-webdriver'); const cssToXPath = require('css-to-xpath'); +const { retry } = require('../../../development/lib/retry'); /** * Temporary workaround to patch selenium's element handle API with methods @@ -435,15 +436,25 @@ class Driver { initialWindowHandles, delayStep = 1000, timeout = this.timeout, + { retries = 8, retryDelay = 2500 } = {}, ) { let windowHandles = initialWindowHandles || (await this.driver.getAllWindowHandles()); let timeElapsed = 0; + while (timeElapsed <= timeout) { for (const handle of windowHandles) { - await this.driver.switchTo().window(handle); + const handleTitle = await retry( + { + retries, + delay: retryDelay, + }, + async () => { + await this.driver.switchTo().window(handle); + return await this.driver.getTitle(); + }, + ); - const handleTitle = await this.driver.getTitle(); if (handleTitle === title) { return handle; } diff --git a/test/jest/background.js b/test/jest/background.js deleted file mode 100644 index 566a0d397b19..000000000000 --- a/test/jest/background.js +++ /dev/null @@ -1,5 +0,0 @@ -import { _setBackgroundConnection } from '../../ui/store/action-queue'; - -export const setBackgroundConnection = (backgroundConnection = {}) => { - _setBackgroundConnection(backgroundConnection); -}; diff --git a/test/jest/index.js b/test/jest/index.js index 1b4174afa8b0..06ee400b24fe 100644 --- a/test/jest/index.js +++ b/test/jest/index.js @@ -1,6 +1,5 @@ export { screen, fireEvent, waitFor } from '@testing-library/react'; export { createSwapsMockStore } from './mock-store'; export { renderWithProvider } from './rendering'; -export { setBackgroundConnection } from './background'; export * as MOCKS from './mocks'; export * as CONSTANTS from './constants'; diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index 96697f354f23..8544b6ce233f 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -159,7 +159,6 @@ export const createSwapsMockStore = () => { id: 6571648590592143, time: 1667403993369, status: 'confirmed', - metamaskNetworkId: '5', originalGasEstimate: '0x7548', userEditedGasLimit: false, chainId: CHAIN_IDS.MAINNET, diff --git a/test/jest/mocks.js b/test/jest/mocks.js index bec4b41556ea..b68e27685054 100644 --- a/test/jest/mocks.js +++ b/test/jest/mocks.js @@ -142,6 +142,7 @@ export const getInitialSendStateWithExistingTxState = (draftTxState) => ({ ...draftTxState.recipient, }, history: draftTxState.history ?? [], + userInputHexData: draftTxState.userInputHexData ?? null, // Use this key if you want to console.log inside the send.js file. test: draftTxState.test ?? 'yo', }, diff --git a/test/jest/rendering.js b/test/jest/rendering.js index 2c46c306ad2a..091c2a629e2b 100644 --- a/test/jest/rendering.js +++ b/test/jest/rendering.js @@ -7,6 +7,7 @@ import PropTypes from 'prop-types'; import { I18nContext, LegacyI18nProvider } from '../../ui/contexts/i18n'; import { getMessage } from '../../ui/helpers/utils/i18n-helper'; import * as en from '../../app/_locales/en/messages.json'; +import { LegacyMetaMetricsProvider } from '../../ui/contexts/metametrics'; export const I18nProvider = (props) => { const { currentLocale, current, en: eng } = props; @@ -38,7 +39,9 @@ export function renderWithProvider(component, store, initialEntries) { const WithoutStore = () => ( - {children} + + {children} + ); diff --git a/test/mocks/json-rpc-result.ts b/test/mocks/json-rpc-result.ts new file mode 100644 index 000000000000..a5166eba8b7c --- /dev/null +++ b/test/mocks/json-rpc-result.ts @@ -0,0 +1,311 @@ +const MOCK_BLOCK_NUMBER = '0x1'; + +export interface mockJsonRpcResultType { + [methodName: string]: { [arbitraryVariantName: string]: any }; +} + +export const mockJsonRpcResult: mockJsonRpcResultType = { + eth_blockNumber: { + default: MOCK_BLOCK_NUMBER, + }, + + eth_estimateGas: { + default: '0x5cec', + }, + + eth_call: { + balance: + '0x000000000000000000000000000000000000000000000000000000000001ea4c', + balanceChecker: + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000091d6cb8fbf55100000000000000000000000000000000000000000000000000014e4d6652a8200000000000000000000000000000000000000000000000000001beca58919dc000000000000000000000000000000000000000000000000000177480e2c2667f0000000000000000000000000000000000000000000000000001d9ae54845818000000000000000000000000000000000000000000000000000009184e72a000', + default: '0x4563918244F40000', + offchainOracle: + '0x0000000000000000000000000000000000000000000000000ddfe4d79cbd3de5', + }, + + eth_feeHistory: { + default: { + baseFeePerGas: [ + '0x69b11e562', + '0x666a7c239', + '0x6d9e609f6', + '0x6e9ab5408', + '0x6bca983cb', + '0x6a6f790c3', + ], + gasUsedRatio: [ + 0.37602026666666666, 0.7813118333333333, 0.5359671, 0.39827006666666664, + 0.44968263333333336, + ], + oldestBlock: '0x115e9c0', + reward: [ + ['0xfbc521', '0x21239e6', '0x5f5e100'], + ['0x5f5e100', '0x68e7780', '0x314050eb'], + ['0xfbc521', '0xfbc521', '0xfbc521'], + ['0x21239e6', '0x5f5e100', '0x5f5e100'], + ['0x21239e6', '0x5f5e100', '0x5f5e100'], + ], + }, + }, + + eth_gasPrice: { + default: '0x09184e72a000', + }, + + eth_getBalance: { + default: '0x55DE6A779BBAC0000', + }, + + eth_getBlockByNumber: { + default: { + baseFeePerGas: '0x16c696eb7', + difficulty: '0x0', + extraData: '0x6631622e696f', + gasLimit: '0x1c9c380', + gasUsed: '0xa0056a', + hash: '0x46be2982228b663026adae2bcedf1fcff63e244deeb1092a9bf498be54215d0c', + logsBloom: + '0x2937858c6d9cfb38c4d80264a351d63c9128489348959c00413114802722281e0048c4e3038ac093709089b0809a2192e6c185500811b86e2074c92d092a386c0342f1084e3828786823ee6b77080b7cbc9732020058d48c4d5348e78a6440c83bc06011ba0abbc94145107218404cf306b00a63216604053e4aa018000e902795c30170819001002bce21443b1aa083e84029559b61182c694110c0d8340efa8f0d117328a464460c1ec6f4305bac8275b4ce2ec90740a694e2884f810b0320c0c68492e781434908a160a30ced3a1c5041628bb4bc201d9e1502121421e6472e59ac58b6240680128499a9a4fb892083e692155b58a3559eaa123be01f0619', + miner: '0x5124fcc2b3f99f571ad67d075643c743f38f1c34', + mixHash: + '0xdcf58909059bf2c21d9f84862e5f038b622443395b8e91a147353d0403019c6f', + nonce: '0x0000000000000000', + number: MOCK_BLOCK_NUMBER, + parentHash: + '0x40bbc17240a659f2df8f3392a9d6d97f1ec60dd6407ee4553ddf40f32187a5cc', + receiptsRoot: + '0x8daf2c9ec0110723e3332be6de9d2ec7cc616bd6f2cab65c8db3f1116cc92076', + sha3Uncles: + '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + size: '0xf903', + stateRoot: + '0x8329c5c0838396dd640d00ab34f7aad71bf5d3d98a121e182a171f96e811e1dd', + timestamp: '0x651e594f', + totalDifficulty: '0xc70d815d562d3cfa955', + transactions: [ + '0x025d6db129bae563f653c7d0095a308f0fb605a6e519269753df46e9aaa7aeaf', + '0xe01dbeef9af2c47742a1c26069a71a9f70c544c6d80c4f63e5cfb56a6ba5e3db', + '0xc68b76b00344181378fc50e4914e99b6720470bb01d064f42fe721c5473918e2', + '0xe2d4323739c218a04013803f9961309f60b99db49b43e0b2215ac3624ff2da2f', + '0xd132b4a8f258e379bd0d4995347b333abca91b3d83ddba245595dafbb6cdac1a', + '0x0d9e5189abcf712e938bacd595240cd5467fb33ecac8f49ecd828db9eae5d842', + '0x4b9da2de3480e6431e0edc74c6000229417a761372701777dabe0fcc8582d228', + '0x4ab6221a5a2a1bb00e1b6583e9dc6dec05b27873b59ddf8955bfb0173d8f0f69', + '0x3e98a84a91cd8d6a54824804776c22a2f4dbe6d4b62b665d6fa41793c2ed64cc', + '0xb80f1371a9b541dd83c45fe450b54040a0bb949b697896874f73169156829225', + '0x60801d230e5a87cc0f5acae9e9be25a8ff89728a8e8a7f7694452a469cc890a7', + '0x089cb61bababa3a5d9f9aa2c65bba1fe4f659d2931069242d614fa53c991c858', + '0xb301035281c8001d8b1f8bd9423f00c56d144f92da40411a49ef73884dad0565', + '0x994553a9b9aa2348c9eb62165915af53d6aeba33558005fac5fa53953ba87eab', + '0x9f367bfc18e62885a8f9d71edfdeb86bf8256d218dd637015bbb185f1c228e1e', + '0x0c21e73c66d009dab799574510ed5b533256da2332e46f265d2a2ecf4df2f5d4', + '0x54e1fad4103782f1fe2304d1da249e6948dcb7d27646307f7c0f111460ab27e3', + '0xe793c71314eb429a249f26d62c82d588f76022eec775013f25841a441307ac20', + '0x9c33cb549884f94f4b05f095b75735b59ca01341cd8f3399bf9f7b4685f8017b', + '0x2f91e57ef968004af9105642f5b12d9955ff57799b1e983fe74ad3430ffe531c', + '0x617af6cf35eecbed22ac1b1d94e54ec105edf05a6c0d9cc4a5b7bcfdae942942', + '0xd31edbb8f112de1bd194853a0c4d50e9ee3e9a7a079253d50d5b10cb77afa3cd', + '0x0c37629d50b0c9e755c727522112cc381cd7f2ca3b201f1f0332e5b76b0d2eba', + '0xe6ae038c038df8d0091bb043b95c3c335e4ce538324fb1048ca98662a5d3fd8a', + '0xdb369684b55c236cfbcd6a9871621d796a626c43ecf8048d3365bd6854106e77', + '0xe084e6a6c86b9896d8ad654e348f29bba534431d187d32cc348b4c1fbdfb6ac2', + '0x137cbc7fb3cfbc868787c2a35cebc881ce3c130b4eade5592c004d348113e6cc', + '0xe037a224f579ebb494fb7789c99f059039204efca53c69a80259944d866397d6', + '0xc1f78a716f9b14e2f06947212c90b4c06c067574932e9a3d16aa111ea7a08325', + '0x74f30f6f2810711bd580b07cf0df42d71fc1404e555b6bcafbe8f83fbbf0613a', + '0x258ac5c91a8c34c4ece04b66882522d55639051468244a2adfbd421e96411c03', + '0x3eb7f91ae3d16d453d0ca327d02efe4cc46368c8d0148450afd4fbe23b6cd189', + '0x69b9d46d3a380cb836475fd8d7d89230f3cb1d12955ece13adba5ef289b7171f', + '0x9f3196f0211844deb3a84a00efd0224c4c2a1b4bd4ce4ab8289b36cb4083c202', + '0x4c203a40e9affc4f4be28ba9ba22f1c4a5c9e6c4226ebada8e63170e6e127183', + '0xc40a188843a35d90ac44e86b4ef738bf183bf3f44a037a8c5bf172fa1e6b3d82', + '0x87663321079a2233fd0c62f0714508aa03681ba17bbaf1b136668200ec91f94f', + '0xdd6ff72cab1d17bdcb274b67e9daedf142c909a05e0e9f1143c819f42ddc5d85', + '0xb4a922f6582276fdc2dd4d2f8cc87fc318a213e7ba0d87adb388cc2afa948aad', + '0xf48c8fd81c25dc5134d4327696f95cb90345b765658890ccba4842b09a3ee79e', + '0x1cdc4c9ec42401ee52a730c40062054826c518020704ff5afd706037299a3f4c', + '0x4384719429ce682f300c72a4e6d1de302571c53dcb9b925ac19919243027898f', + '0x2d1c5e589bbf1c3d84ac6a48cf6fffe73848add4d2606f2bc164d45d0a539bc5', + '0xcf095997048953d46637f4b23de28a7a0937387ee673aeac78956a4a2d18938e', + '0x4af3b1a11feabd6cdff5957737064abebbb3707aecf8f6f1d19d427f3ea6b88d', + '0xc8d426d5c0b342b1e34e205fec815a4e81dc440d14b2464eda894817746d583c', + '0xc1c83af1aff7327d5ad44eea78290eb25f308ef1d037fd152dc00db23163fac7', + '0x7b612d292c8bc245aac485a48c93e6ab510f79d260f0039c2dbc88cc53153ccf', + '0xcea5beff384c7defa0c290f803303531d68cdd3ea824c1169283206128edb824', + '0xc7ec7fca044fbe37bfeec938a9792f42ab1a3d8255feb00be93e68152ef130f2', + '0x370411cf89cd5e75b12136a0ba61070c0f072ebe877a313d2c55f2b0db96c233', + '0x62ded18451fb7ef92c5f8b3c888f8a304437095725c33691a9cacdc0665174eb', + '0x3be8d2cd758594cedd8a3e249f47ebed0d95ca1c8cbf109941811f0974daae6d', + '0x7eebdbdedf238af9ec6e0fb8860660db88d613b9a1c747de5b38b02763f2901b', + '0x337f14eab5bfdd58af32f28e115a1e2735c6bd52818e407a090d991bcacf900d', + '0xf9b377921e51e97b9a302d69dc55b99eeb96f8232b5fa8518f1cfddc4acd78aa', + '0x6a957b8481ad0935c6c864308ff09e78123daeb64a349b007ed714d810d14f54', + '0x3fafa02783759c232e9d06b65b3104f554e24410eb0ba8a6f02ba33f3e5f0bdb', + '0xaa3dff89f379cbb1f4c916885bb83763455c6cbd2ab4fb466032ada29c16bce0', + '0xc559e193f4abb05bd90f82525a3e14e345106da19cf405c73c462b7e17d4896a', + '0xfd487c02e24f2fbc8306027c459bb6559f8641596051e5dc56477c807324841a', + '0xfd62e1e1c9050034f11cb9e0109bd64cb727b261ca1c94fb4dbe6cdd2e4f8199', + '0xeaf0a50c8b204989689d827cd9ef7704d4fd0ee33fbfd1b6dc49c5ccd025068b', + '0x3fec28b8a00046992cb68b594cd98bf5aa9c11813c88e6d2a3a83a8ff2b1efd8', + '0x43cbfd77dbab4fca9cc310e90ce3e7b5b05e390968bfd43b71bd4e17ce73ae77', + '0x808e4b342458f54c258ce1dafc865d6cac2bdc263a488a988f1c2862fe02beda', + '0xafe2a9c974044d255669e8033d6c2a367c82ba2708f2a7d98c4cab2e1f8e1426', + '0x1aeb33eb00070c71fbb4649f718dff2e559bdfab80027932bbd8bff3e74ea6a6', + '0x9a64a51fdc0ad112b0ac516114e16efe281ed68f0a3eb39837aebbaab1bcbf9c', + '0xa0fe376754f08bbd2d9154283802e4259e46b178580169be0c21abdb8ef1ded8', + '0x8c68f0fbbfbe4ecfb2bc4c859538ee62531bd32dd488dd1b029d9bc2a639db64', + '0xa49bf5d42110fd52c30573bf2aba77b17d4ab081c0b8f68436eb40cf9b5ba3d6', + '0x115bc870a989ae86ad745015bf11cf3c66773ed2d26e80e11b8bc5a065194c13', + '0x316ac31966733f8c6b47aa75d5326a696bf5c4a651cb964df18f90f267dec789', + '0x8008670e8eba15b6d06bf6a7a3cbe9aca543d4e83a3a7369b0433e0b3e3ceae1', + '0x554ff6c269a97976ed8284cbfd024b04024bcf3b25d515d72dbcf038e47a1a08', + '0xa8b227e912c564b0a80aac19e3a618299985264d02a1b6baacf86df40e1ad758', + '0xf1cf308519a9025566d5210f7daf8195fe9c44e38e9cdc6f471c6b2699a7e5f0', + '0x7223a0b7cd4e47fc7b40ded2784cb2af6cb3ffd14674171f1c4c60b1ddb97b9c', + '0xc507db1b36a662139c09c232c00a2ae6c7dd45c1bc05e8d942037fae9d2c8833', + '0xbc15f576fb8e63dc0f7b70255ea0de87efb1082d86ca7f70f1bdef563b584426', + '0xa8d7889cf487e501ce6e3f019eed74f570def855fd47415fed45f3e0cd6e9e7f', + '0x52f950bd0ecb8ba81e6cd20ecf7a6e66371807c624123065adbe3c3d6c288500', + '0xfee006db800181afc124844c31b06924d25f60e8d2296288338af0e7e9f7044f', + '0x82d8d09880a7cab32dd3be06dac2dea0afea41e9f3fd9707a5e25f032dcb966f', + '0x7da65bbc41064b24bb5f272b187fa05df30ea22c768d2e6d336df313a266cb82', + '0x3c13b7c7e236d68d8c4799b71b9b6bd4ce36c520f3b2f29635421950f8a56e03', + '0x72aaefd1673c10dd0d8e5d4726a5eafd3a8726022ab382cf38cbce96c16dc728', + '0x90b5a4a70051fcc4e8312b563e43a1dd02c366f14659111225052d4bcac3a258', + '0xf4aa46063de8baa2ad61b3eabd1f12113e20a16eafd4c2a2e3b93cf94d8c9fcd', + '0xc18d5fa6f953d257cdb4699b58eb3132ad763c20fface4ec13b722e954aefbcf', + '0xd12e95ec8bfea56f712171dbc36c90a5c4bfa945e1dd6b87de9a01568856c23f', + '0x0142e97265b78e5e49b2bd552829753974872b0ebbeebb60257008fed298a203', + '0x532a433729da0def288b673ccbc5a192dffee962c93f1977719b6f8f76269a3d', + '0x1154125dcacd088953bb3eb1b28c547efe50bd40f74ac7e582154d22bcc15c36', + '0xd4bd5c72901df585e344a20125a4be4bc582a7d5ebf07d7999fae27daa31cce5', + '0x4fb02170dfd47274e00db7a23a9ec3446b8d89032438f6b9b5822178e8ad7e77', + '0xc8e62f9804f5e9934601245222a381c2571069fd4a939563517cc59f7b024cde', + '0x86ad26c2223eb7830306e57964e2050acc699ff5a50068ea8e940221fb08679e', + '0x7b96fd3e0f467ee48b6e249175feae53b6cc2a12482d2b2e4ba71774cfbd62cc', + '0xa0336d44a6f2f804fb0b1d83eadc27423242f79676545002da772899dfeea0b3', + '0x28e47bdefa59405e0e3267f047bf46d20304c98661f920fd87fd9f8bbc29e9d3', + '0xb498d5300881fd5f8482b991504876cbe51edcd14b6abda326f48201fc9f2234', + '0xd94762cee129d65e8178eba2c7b2aed14d340cea0bd8e777869003919b186712', + '0x47b50e56928b2b5bfa63adfb411ebf5b20b0fd9e1d3b445867cdfae52ae0aeef', + '0x56cf1b42f63e7718400fd465fa1af37397e5fa2cbe85fd6879f83473f5e3995a', + '0x8a1490011374ffe25b45862d41be4735e08d9e18b1b8b726d4a9d4d8e0194a27', + '0x3489042ecae7846b58488aa770bedd81ad7f1783efd1bee7756b00de4024b65d', + '0x44f85f310884644c48efe8f07cf549408ce942652a8f1f7ad7b01b4bc78399de', + ], + transactionsRoot: + '0xfc1f88c241dff40c33cc8cf48b18b1a59c86346f0f4ea15317142265f20f8b03', + uncles: [], + withdrawals: [ + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfbd866', + index: '0x1309706', + validatorIndex: '0x90fe9', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfbb65d', + index: '0x1309707', + validatorIndex: '0x90fea', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfc42ba', + index: '0x1309708', + validatorIndex: '0x90feb', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0x3682aae', + index: '0x1309709', + validatorIndex: '0x90fec', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfc16c5', + index: '0x130970a', + validatorIndex: '0x90fed', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfc672a', + index: '0x130970b', + validatorIndex: '0x90fee', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfbf672', + index: '0x130970c', + validatorIndex: '0x90fef', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0x3664440', + index: '0x130970d', + validatorIndex: '0x90ff0', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfbf9c4', + index: '0x130970e', + validatorIndex: '0x90ff1', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfc54c4', + index: '0x130970f', + validatorIndex: '0x90ff2', + }, + { + address: '0xd33cd8e9accd0b58a501e6076ad5c41a8702ca62', + amount: '0xdde150', + index: '0x1309710', + validatorIndex: '0x90ff3', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfc4959', + index: '0x1309711', + validatorIndex: '0x90ff4', + }, + { + address: '0xd33cd8e9accd0b58a501e6076ad5c41a8702ca62', + amount: '0xde3e8c', + index: '0x1309712', + validatorIndex: '0x90ff5', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfc7dbb', + index: '0x1309713', + validatorIndex: '0x90ff6', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfc8f5b', + index: '0x1309714', + validatorIndex: '0x90ff7', + }, + { + address: '0x036c9c0aae7a8268f332ba968dac5963c6adaca5', + amount: '0xfc9028', + index: '0x1309715', + validatorIndex: '0x90ff8', + }, + ], + withdrawalsRoot: + '0x43efdf43ed0f1ede09f4ab425311ea16ea229cda5878a44eecc2d0f6d5fc618f', + }, + }, + + eth_getCode: { + default: '0x', + BUSD: '0x60806040526004361061006c5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633659cfe681146100765780634f1ef286146100975780635c60da1b146100b75780638f283970146100e8578063f851a44014610109575b61007461011e565b005b34801561008257600080fd5b50610074600160a060020a0360043516610138565b61007460048035600160a060020a03169060248035908101910135610172565b3480156100c357600080fd5b506100cc6101ea565b60408051600160a060020a039092168252519081900360200190f35b3480156100f457600080fd5b50610074600160a060020a0360043516610227565b34801561011557600080fd5b506100cc610339565b610126610364565b610136610131610411565b610436565b565b61014061045a565b600160a060020a031633600160a060020a03161415610167576101628161047f565b61016f565b61016f61011e565b50565b61017a61045a565b600160a060020a031633600160a060020a031614156101dd5761019c8361047f565b30600160a060020a03163483836040518083838082843782019150509250505060006040518083038185875af19250505015156101d857600080fd5b6101e5565b6101e561011e565b505050565b60006101f461045a565b600160a060020a031633600160a060020a0316141561021c57610215610411565b9050610224565b61022461011e565b90565b61022f61045a565b600160a060020a031633600160a060020a0316141561016757600160a060020a03811615156102e557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f60448201527f787920746f20746865207a65726f206164647265737300000000000000000000606482015290519081900360840190fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61030e61045a565b60408051600160a060020a03928316815291841660208301528051918290030190a1610162816104c7565b600061034361045a565b600160a060020a031633600160a060020a0316141561021c5761021561045a565b61036c61045a565b600160a060020a031633141561040957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e20667260448201527f6f6d207468652070726f78792061646d696e0000000000000000000000000000606482015290519081900360840190fd5b610136610136565b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35490565b3660008037600080366000845af43d6000803e808015610455573d6000f35b3d6000fd5b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b5490565b610488816104eb565b60408051600160a060020a038316815290517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b9181900360200190a150565b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b55565b60006104f6826105ae565b151561058957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f43616e6e6f742073657420612070726f787920696d706c656d656e746174696f60448201527f6e20746f2061206e6f6e2d636f6e747261637420616464726573730000000000606482015290519081900360840190fd5b507f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c355565b6000903b11905600a165627a7a72305820b274fe16b200679a229fcce27c65314a32b3cff995c434133f535dd565bba4740029', + USDC: '0x60806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633659cfe6146100775780634f1ef286146100ba5780635c60da1b146101085780638f2839701461015f578063f851a440146101a2575b6100756101f9565b005b34801561008357600080fd5b506100b8600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610213565b005b610106600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001908201803590602001919091929391929390505050610268565b005b34801561011457600080fd5b5061011d610308565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561016b57600080fd5b506101a0600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610360565b005b3480156101ae57600080fd5b506101b761051e565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610201610576565b61021161020c610651565b610682565b565b61021b6106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561025c57610257816106d9565b610265565b6102646101f9565b5b50565b6102706106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102fa576102ac836106d9565b3073ffffffffffffffffffffffffffffffffffffffff163483836040518083838082843782019150509250505060006040518083038185875af19250505015156102f557600080fd5b610303565b6103026101f9565b5b505050565b60006103126106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156103545761034d610651565b905061035d565b61035c6101f9565b5b90565b6103686106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561051257600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151515610466576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260368152602001807f43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f81526020017f787920746f20746865207a65726f20616464726573730000000000000000000081525060400191505060405180910390fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61048f6106a8565b82604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a161050d81610748565b61051b565b61051a6101f9565b5b50565b60006105286106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561056a576105636106a8565b9050610573565b6105726101f9565b5b90565b61057e6106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151515610647576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001807f43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e20667281526020017f6f6d207468652070726f78792061646d696e000000000000000000000000000081525060400191505060405180910390fd5b61064f610777565b565b6000807f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c36001029050805491505090565b3660008037600080366000845af43d6000803e80600081146106a3573d6000f35b3d6000fd5b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b6001029050805491505090565b6106e281610779565b7fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b60010290508181555050565b565b60006107848261084b565b151561081e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b8152602001807f43616e6e6f742073657420612070726f787920696d706c656d656e746174696f81526020017f6e20746f2061206e6f6e2d636f6e74726163742061646472657373000000000081525060400191505060405180910390fd5b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c360010290508181555050565b600080823b9050600081119150509190505600a165627a7a72305820a4a547cfc7202c5acaaae74d428e988bc62ad5024eb0165532d3a8f91db4ed240029', + }, + + eth_getTransactionCount: { + default: MOCK_BLOCK_NUMBER, + }, +}; diff --git a/test/scenarios/10. address-book/add a contact to the address book.csv b/test/scenarios/10. address-book/add a contact to the address book.csv new file mode 100644 index 000000000000..67a63e88c1e2 --- /dev/null +++ b/test/scenarios/10. address-book/add a contact to the address book.csv @@ -0,0 +1,15 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Select Expand view in the wallet menu.,,"A new tab opens. +The expanded extension view is shown.", +4,Proceed to open the Accounts options menu.,,The accounts menu popover is shown., +5,Proceed to Contacts in the Settings menu.,,The contact list is displayed., +6,Proceed to Add contact.,,The New contact screen is displayed., +7,Enter a username.,e.g. Test User.,, +8,Enter a public address.,e.g. 0x56A355d3427bC2B1E22c78197AF091230919Cc2A.,, +9,Proceed to Save the contact.,,The newly added contact appears in the contact list., +10,Select the newly created contact.,,"The contact's username is displayed. +The contact's public address is displayed.", \ No newline at end of file diff --git a/test/scenarios/10. address-book/remove a contact from the address book.csv b/test/scenarios/10. address-book/remove a contact from the address book.csv new file mode 100644 index 000000000000..d5c2681eb9e9 --- /dev/null +++ b/test/scenarios/10. address-book/remove a contact from the address book.csv @@ -0,0 +1,12 @@ +Step,Test Steps,Preconditions,Test Data,Expected Result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Select Expand view in the wallet menu.,,,"A new tab opens. +The expanded extension view is shown.", +4,Proceed to open the Accounts options menu.,,,The accounts menu popover is shown., +5,Proceed to Contacts in the Settings menu.,,,The contact list is displayed., +6,Select a contact in the contact list.,A contact exists in the contact list.,,The view contact screen is displayed., +7,Proceed to Edit the contact.,,,The edit contact screen is displayed., +8,Proceed to Delete the contact.,,,The deleted contact does not appear in the contact list., \ No newline at end of file diff --git a/test/scenarios/10. address-book/update a contact in the address book.csv b/test/scenarios/10. address-book/update a contact in the address book.csv new file mode 100644 index 000000000000..41b3b6de7251 --- /dev/null +++ b/test/scenarios/10. address-book/update a contact in the address book.csv @@ -0,0 +1,15 @@ +Step,Test Steps,Preconditions,Test Data,Expected Result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Select Expand view in the wallet menu.,,,"A new tab opens. +The expanded extension view is shown.", +4,Proceed to open the Accounts options menu.,,,The accounts menu popover is shown., +5,Proceed to Contacts in the Settings menu.,,,The contact list is displayed., +6,Select a contact in the contact list.,A contact exists in the contact list.,,The view contact screen is displayed., +7,Proceed to Edit the contact.,,,The edit contact screen is displayed., +8,Update the username.,,e.g. Test User 1.,, +9,Proceed to Save the contact.,,,The newly updated contact appears in the contact list., +10,Select the newly updated contact.,,,"The contact's username is displayed. +The contact's public address is displayed.", \ No newline at end of file diff --git a/test/scenarios/3. transactions/cancel transaction.csv b/test/scenarios/3. transactions/cancel transaction.csv index 031edc14b130..cf7346919a54 100644 --- a/test/scenarios/3. transactions/cancel transaction.csv +++ b/test/scenarios/3. transactions/cancel transaction.csv @@ -3,9 +3,9 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Proceed to the Send flow.,,, 5,Enter a public address.,e.g. 0x56F2e03c8D30649818c022a9759CF43B240D08B3.,, 6,Enter an amount.,e.g. 0.1.,, diff --git a/test/scenarios/3. transactions/send erc20 token origin MM.csv b/test/scenarios/3. transactions/send erc20 token origin MM.csv index 05b0f9fb637a..5aa6d6b02a02 100644 --- a/test/scenarios/3. transactions/send erc20 token origin MM.csv +++ b/test/scenarios/3. transactions/send erc20 token origin MM.csv @@ -3,16 +3,16 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, 5,Proceed to Connect with MetaMask.,,, 6,Connect with the current account.,,, 7,Deploy the Token contract.,,, -8,Confirm the transaction and wait for the it to complete.,,, +8,Confirm the transaction and wait for it to complete.,,, 9,Proceed to add the token to the wallet.,,, -10,Confirm the suggested token notification.,,"The TST token is shown in the wallets token list. +10,Confirm the suggested token notification.,,"The TST token is shown in the wallet's token list. The TST token symbol is shown. The TST token icon is shown. The TST token balance is shown.", diff --git a/test/scenarios/3. transactions/send erc20 token origin dapp.csv b/test/scenarios/3. transactions/send erc20 token origin dapp.csv new file mode 100644 index 000000000000..617e669c25c1 --- /dev/null +++ b/test/scenarios/3. transactions/send erc20 token origin dapp.csv @@ -0,0 +1,20 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,"In test dapp, click ""create token"" to deploy the Token contract.",,, +8,Confirm the transaction and wait for it to complete.,,, +9,Switch to the wallet and open the activity list item.,,"""Contract deployment"" item is shown in list item. The transaction status, nonce, amount, gas and total are shown in the item details.", +10,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., +11,"In test dapp, proceed to transfer token.",,"In confirmation notification screen, the title is shown as ""Send TST"". The recipient's hexadecimal address is shown as . The amount is shown. The estimated gas details are shown. The total amount is shown.", +12,Confirm the transaction.,,"The transaction ""Send TST"" appears in the activity list. The recipient's hexadecimal address is shown in the activity list item. The amount is shown in the activity list item.", +13,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +14,Expand the Activity log.,,"The created, submitted and confirmed activity is shown in the activity log.", +15,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., \ No newline at end of file diff --git a/test/scenarios/3. transactions/send erc721 token origin MM.csv b/test/scenarios/3. transactions/send erc721 token origin MM.csv index 8a988375cbaa..faeca234a1a2 100644 --- a/test/scenarios/3. transactions/send erc721 token origin MM.csv +++ b/test/scenarios/3. transactions/send erc721 token origin MM.csv @@ -3,16 +3,16 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, 5,Proceed to Connect with MetaMask.,,, 6,Connect with the current account.,,, 7,Proceed to Deploy the NFT contract.,,, -8,Confirm the transaction and wait for the it to complete.,,, +8,Confirm the transaction and wait for it to complete.,,, 9,Proceed to Mint an NFT.,,, -10,Confirm the transaction and wait for the it to complete.,,, +10,Confirm the transaction and wait for it to complete.,,, 11,Proceed to Watch NFT.,,, 12,Confirm the suggested NFT notification.,,"The NFT is shown in the wallets NFTs list. The NFT image is shown.", diff --git a/test/scenarios/3. transactions/send erc721 token origin dapp.csv b/test/scenarios/3. transactions/send erc721 token origin dapp.csv new file mode 100644 index 000000000000..55e446a02325 --- /dev/null +++ b/test/scenarios/3. transactions/send erc721 token origin dapp.csv @@ -0,0 +1,26 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to Connect with MetaMask.,,, +6,Connect with the current account.,,, +7,Deploy the NFT contract.,,, +8,Confirm the transaction and wait for the it to complete.,,, +9,Proceed to Mint an NFT.,,, +10,Confirm the transaction and wait for it to complete.,,, +11,Close the test dapp and switch back to the wallet.,,, +12,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +13,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., +14,Close the block explorer and switch back to the wallet.,,, +15,"In test dapp, proceed and confirm Watch NFT.",,"The NFT is shown in the wallet's NFTs ", +16,"In test dapp, proceed to transfer token in NFT section.",,"In the confirmation notification screen, The TestDappNFTs NFT is shown in the asset field on the send flow. The specified NFT symbol is shown. The specified NFT image is shown. The specified NFT token id is shown. The recipient's hexadecimal address is shown. The estimated gas details are shown. The total amount is shown. ", +17,Confirm the transaction.,,"The transaction ""Send TDN"" appears in the activity list. The amount is shown in the activity list item.", +18,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +19,Expand the Activity log.,,"The created, submitted and confirmed activity is shown in the activity log.", +20,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., +21,Go back to wallet's NFT tab.,,"The test dapp NFT becomes ""previously owned"".", \ No newline at end of file diff --git a/test/scenarios/3. transactions/send native token origin MM.csv b/test/scenarios/3. transactions/send native token origin MM.csv index 8bf34a45033a..9e9172ff866c 100644 --- a/test/scenarios/3. transactions/send native token origin MM.csv +++ b/test/scenarios/3. transactions/send native token origin MM.csv @@ -3,9 +3,9 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Proceed to the Send flow.,,, 5,Enter a public address.,e.g. 0x56F2e03c8D30649818c022a9759CF43B240D08B3.,, 6,Enter an amount.,e.g. 0.1.,, diff --git a/test/scenarios/3. transactions/send native token origin dapp.csv b/test/scenarios/3. transactions/send native token origin dapp.csv new file mode 100644 index 000000000000..3430d0c38aa2 --- /dev/null +++ b/test/scenarios/3. transactions/send native token origin dapp.csv @@ -0,0 +1,22 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Sepolia.", +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Enter a public address.,,, +6,Proceed to connect with MetaMask.,,, +7,Connect with the current account.,,, +8,"On test dapp, proceed send legacy transaction.",,"In confirmation notification screen, The recipient's hexadecimal address is shown. The amount is shown. The estimated gas details are shown. The total amount is shown. ", +9,Confirm the transaction.,,The transaction appears in the activity list. The recipient's hexadecimal address is shown in the activity list item. The amount is shown in the activity list item., +10,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +11,Expand the Activity log.,,"The created, submitted and confirmed activity are shown in the activity log.", +12,Proceed to view the transaction on the block explorer. ,,The block explorer opens in a new tab., +13,"Go back to test dapp, proceed to send EIP1559 transaction.",,"In confirmation notification screen, The recipient's hexadecimal address is shown. The amount is shown. The estimated gas details are shown. The total amount is shown.", +14,Confirm the transaction.,,"Second transaction appears in the activity list. For the second transaction, the recipient's hexadecimal address is shown in the activity list item. The amount is shown in the activity list item.", +15,Open the activity list item.,,"Two transactions are shown in total. For the second transaction, the status, recipient's address, nonce, amount, gas and total are shown in the item details.", +16,Expand the activity log for the second transaction.,,"The created, submitted and confirmed activity are shown in the activity log.", +17,Proceed to view the second transaction on the block explorer. ,,The block explorer opens in a new tab., \ No newline at end of file diff --git a/test/scenarios/3. transactions/speed up transaction.csv b/test/scenarios/3. transactions/speed up transaction.csv index 14c87989aed3..259e3a156dc9 100644 --- a/test/scenarios/3. transactions/speed up transaction.csv +++ b/test/scenarios/3. transactions/speed up transaction.csv @@ -3,9 +3,9 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Proceed to the Send flow.,,, 5,Enter a public address.,e.g. 0x56F2e03c8D30649818c022a9759CF43B240D08B3.,, 6,Enter an amount.,e.g. 0.1.,, diff --git a/test/scenarios/4. tokens/autodetect NFTs.csv b/test/scenarios/4. tokens/autodetect NFTs.csv new file mode 100644 index 000000000000..42e2664816b2 --- /dev/null +++ b/test/scenarios/4. tokens/autodetect NFTs.csv @@ -0,0 +1,9 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Keep Ethereum Mainnet as the selected network.,,,, +4,Select or import an account with NFT balance.,,e.g.: orangefox.eth,,Please request access to this test account from George Balolong (Business Operations). +5,Switch to the NFTs list.,Make sure the autodetect NFT toggle is OFF in Settings.,,A prompt is shown to Turn on NFT detection in Settings.,Note that NFT autodetection is only available on Ethereum Mainnet. The option will not appear in settings if you have a different network selected. +6,Proceed to the Turn on NFT detection in Settings hyperlink.,,,The Security & Privacy Menu opens to Token autodetection., +7,Enable Autodetect NFTs.,,,,Enable OpenSea API toggle will turn ON automatically at this point. +8,Go back to the NFTs list.,,,The tokens list is updated with all imported tokens., \ No newline at end of file diff --git a/test/scenarios/4. tokens/autodetect tokens.csv b/test/scenarios/4. tokens/autodetect tokens.csv new file mode 100644 index 000000000000..e3645bb2cb15 --- /dev/null +++ b/test/scenarios/4. tokens/autodetect tokens.csv @@ -0,0 +1,16 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Keep Ethereum Mainnet as the selected network.,,,, +4,Select or import an account with a token balance.,,e.g.: orangefox.eth,,Please request access to this test account from George Balolong (Business Operations). +5,Switch back to the tokens list.,,,A prompt is shown to Import tokens found in the account.,Note that token detection takes place on Ethereum Mainnet regardless of whether this setting is on or off. +6,Proceed to Import tokens.,,,, +7,Select the tokens to import or import all.,,,The tokens list is updated with all imported tokens., +8,Switch to a network other than Ethereum Mainnet.,,e.g.: BNB Smart Chain,,"Note that token autodetection is currently available on: Ethereum Mainnet, Avalanche, BNB Smart Chain, Polygon, Aurora and Linea Mainnet." +9,Go to the Tokens list.,Make sure the autodetect tokens toggle is OFF in Settings.,,No prompt to Import tokens is shown., +10,Proceed to Privacy & Security Settings.,,,, +11,Search for Token autodetection.,,,, +12,Enable Autodetect tokens.,,,, +13,Go back to the Tokens list and Refresh list.,,,A prompt is shown to Import tokens found in the account., +14,Proceed to Import tokens.,,,, +15,Select the tokens to import or import all.,,,The tokens list is updated with all imported tokens., \ No newline at end of file diff --git a/test/scenarios/4. tokens/import erc1155 token origin MM.csv b/test/scenarios/4. tokens/import erc1155 token origin MM.csv index 1c4218d7cf42..1c2a7e7c72db 100644 --- a/test/scenarios/4. tokens/import erc1155 token origin MM.csv +++ b/test/scenarios/4. tokens/import erc1155 token origin MM.csv @@ -3,16 +3,16 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, 5,Proceed to Connect with MetaMask.,,, 6,Connect with the current account.,,, 7,Deploy the ERC1155 contract.,,, -8,Confirm the transaction and wait for the it to complete.,,, +8,Confirm the transaction and wait for it to complete.,,, 9,Proceed to Batch Mint NFTs.,,, -10,Confirm the transaction and wait for the it to complete.,,, +10,Confirm the transaction and wait for it to complete.,,, 11,Close the test dapp and switch back to the wallet.,,, 12,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", 13,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., Take a note of the Contract address. @@ -21,7 +21,7 @@ The selected network is Goerli.", 16,Proceed to the Import tokens flow.,,, 17,Enter the contract address.,Contract address.,, 18,Enter the token id.,e.g. 1.,, -19,Confirm the addition of the custom token.,,"The NFT is shown in the wallets NFTs list. +19,Confirm the addition of the custom token.,,"The NFT is shown in the wallet's NFTs list. The NFT image is shown.", 20,Proceed to open the NFT.,,"The NFT details page is shown. The NFT collection name is shown. diff --git a/test/scenarios/4. tokens/import erc20 token origin MM.csv b/test/scenarios/4. tokens/import erc20 token origin MM.csv index 4a7c2c2aca62..2c699689836c 100644 --- a/test/scenarios/4. tokens/import erc20 token origin MM.csv +++ b/test/scenarios/4. tokens/import erc20 token origin MM.csv @@ -3,14 +3,14 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, 5,Proceed to Connect with MetaMask.,,, 6,Connect with the current account.,,, 7,Deploy the Token contract.,,, -8,Confirm the transaction and wait for the it to complete.,,, +8,Confirm the transaction and wait for it to complete.,,, 9,Close the test dapp and switch back to the wallet.,,, 10,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", 11,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., Take a note of the Contract address. diff --git a/test/scenarios/4. tokens/import erc20 token origin dapp.csv b/test/scenarios/4. tokens/import erc20 token origin dapp.csv new file mode 100644 index 000000000000..eb07d85b05c9 --- /dev/null +++ b/test/scenarios/4. tokens/import erc20 token origin dapp.csv @@ -0,0 +1,14 @@ +Step,Test steps,Test data,Expected result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Switch networks to a test network.,e.g. Sepolia,The Sepolia balance is shown on the overview. The wallet address is shown on the overview. The selected network is Sepolia., +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to connect with MetaMask. ,,, +6,Connect with the current account. ,,, +7,Deploy the token contract.,,, +8,Confirm the transaction and wait for it to complete.,,, +9,Switch to the wallet and open the activity list item.,,"The transaction status, nonce, amount, gas and total are shown in the item details.", +10,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., +11,"In the test dapp, proceed to add token to wallet. ",,"A prompt is shown with the token icon, symbol and amount.", +12,Confirm the addition of the custom token.,,, +13,Switch back to the wallet.,,The token is shown in the wallet's tokens list. The token icon is shown. The token symbol is shown. The token balance is shown. , \ No newline at end of file diff --git a/test/scenarios/4. tokens/import erc721 token origin MM.csv b/test/scenarios/4. tokens/import erc721 token origin MM.csv index f4bab1bae136..503e46dd6bb1 100644 --- a/test/scenarios/4. tokens/import erc721 token origin MM.csv +++ b/test/scenarios/4. tokens/import erc721 token origin MM.csv @@ -3,16 +3,16 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, 5,Proceed to Connect with MetaMask.,,, 6,Connect with the current account.,,, 7,Deploy the NFT contract.,,, 8,Confirm the transaction and wait for the it to complete.,,, 9,Proceed to Mint an NFT.,,, -10,Confirm the transaction and wait for the it to complete.,,, +10,Confirm the transaction and wait for it to complete.,,, 11,Close the test dapp and switch back to the wallet.,,, 12,Open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", 13,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., Take a note of the Contract address. @@ -21,7 +21,7 @@ The selected network is Goerli.", 16,Proceed to the Import tokens flow.,,, 17,Enter the contract address.,Contract address.,, 18,Enter the token id.,e.g. 1.,, -19,Confirm the addition of the custom token.,,"The NFT is shown in the wallets NFTs list. +19,Confirm the addition of the custom token.,,"The NFT is shown in the wallet's NFTs list. The NFT image is shown.", 20,Proceed to open the NFT.,,"The NFT details page is shown. The NFT collection name is shown. diff --git a/test/scenarios/4. tokens/importing erc721 token origin dapp.csv b/test/scenarios/4. tokens/importing erc721 token origin dapp.csv new file mode 100644 index 000000000000..d00cdcabf4c9 --- /dev/null +++ b/test/scenarios/4. tokens/importing erc721 token origin dapp.csv @@ -0,0 +1,17 @@ +Step,Test steps,Test data,Expected result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Switch networks to a test network.,e.g. Sepolia,The Sepolia balance is shown on the overview. The wallet address is shown on the overview. The selected network is Sepolia., +4,Open the test dapp in another tab.,https://metamask.github.io/test-dapp/,, +5,Proceed to connect with MetaMask. ,,, +6,Connect with the current account. ,,, +7,Deploy the NFT contract. ,,, +8,Confirm the transaction and wait for it to complete.,,, +9,Proceed to mint NFT. ,,,"If NFT with token ID #3 must be watched, amount of NFTs minted should be equal to or greater than 3." +10,Confirm the transaction and wait for it to complete.,,, +11,Switch to the wallet and open the activity list item.,,"The transaction status, recipient's address, nonce, amount, gas and total are shown in the item details.", +12,Proceed to view the transaction on the block explorer.,,The block explorer opens in a new tab., +13,"In the test dapp, enter the NFT ID you wish to watch. ",e.g. 3,, +14,Proceed to Watch NFT. ,,, +15,Confirm the addition of the custom token.,,, +16,Switch back to the wallet.,,The NFT is shown in the wallets NFTs list. The NFT image is shown.,OpenSea API in Security & Privacy Settings has to be enabled for the NFT image to be shown. \ No newline at end of file diff --git a/test/scenarios/5. ens/name resolution.csv b/test/scenarios/5. ens/name resolution.csv index 2f9aa182568a..6490aad66451 100644 --- a/test/scenarios/5. ens/name resolution.csv +++ b/test/scenarios/5. ens/name resolution.csv @@ -3,9 +3,9 @@ Step,Test Steps,Test Data,Expected Result,Notes 2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet.", -3,Switch networks to a test network.,e.g. Goerli.,"The Goerli balance is shown on the overview. +3,Switch networks to a test network.,e.g. Sepolia.,"The Sepolia balance is shown on the overview. The wallet address is shown on the overview. -The selected network is Goerli.", +The selected network is Sepolia.", 4,Proceed to the Send flow.,,, 5,Enter an ENS address.,e.g. peteryinusa.eth.,"A matching result appears in the recipient list. The ENS address is shown in the list item. @@ -15,6 +15,5 @@ The ENS address is shown in the recipient field. The hexadecimal address is shown in the recipient field.", 7,Enter an amount.,e.g. 0.1.,, 8,Proceed to the confirmation screen.,,The recipient's ENS address is shown., -9,Confirm the transaction.,,"The transaction appears in the activity list. -The recipient's hexadecimal address is shown in the activity list item.", +9,Confirm the transaction.,,"The transaction appears in the activity list.", 10,Open the activity list item.,,The recipient's ENS address is shown in the item details., \ No newline at end of file diff --git a/test/scenarios/7. network/add custom network from dApp.csv b/test/scenarios/7. network/add custom network from dApp.csv new file mode 100644 index 000000000000..95700143afac --- /dev/null +++ b/test/scenarios/7. network/add custom network from dApp.csv @@ -0,0 +1,11 @@ +Step,Test steps,Test data,Expected result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Navigate to Chainlist. ,https://chainlist.wtf/,, +4,Select a network from the list.,e.g. BNB Smart Chain Mainnet. ,, +5,Proceed to Connect Wallet.,,, +6,Switch back to the wallet.,,A prompt is shown to confirm the dApp connection., +7,Confirm the connection and switch back to Chainlist.,,, +8,Proceed to Add Chain.,,"A wallet pop-up is shown to approve the network addition. The network name, URL, chain ID, currency symbol and block explorer URL are shown.", +9,Approve the network addition.,,A prompt to allow the site to switch the network is shown. , +10,Confirm switching the network. ,,The BNB balance is shown on the overview. The wallet address is shown on the overview. The selected network is BNB Smart Chain Mainnet. , \ No newline at end of file diff --git a/test/scenarios/7. network/add custom network manually.csv b/test/scenarios/7. network/add custom network manually.csv new file mode 100644 index 000000000000..0c3a21248931 --- /dev/null +++ b/test/scenarios/7. network/add custom network manually.csv @@ -0,0 +1,9 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Open Networks in the Settings menu. ,,,A list of main networks and test networks appears. The Add Network button is shown., +4,Select the Add Network option. ,,,The extension opens in a new tab. The Add a network manually button is shown. , +5,Proceed to Add a network manually.,,,"The network name, RPC URL, Chain ID, currency symbol and block explorer URL fields are shown. ", +6,Fill in the network details. ,Run a local blockchain.,e.g. Network name: Test; RPC URL: http://localhost:8545; Chain ID: 1337; Currency symbol: ETH,, +7,Save the changes.,,,The network is successfully added. The option to switch to the added network is shown. The option to dismiss is shown. , +8,Proceed to switch the network. ,,,The network is switched. The Ether balance is shown. , \ No newline at end of file diff --git a/test/scenarios/7. network/add network from the list of popular networks.csv b/test/scenarios/7. network/add network from the list of popular networks.csv new file mode 100644 index 000000000000..3ff58b36d9e5 --- /dev/null +++ b/test/scenarios/7. network/add network from the list of popular networks.csv @@ -0,0 +1,8 @@ +Step,Test steps,Test data,Expected result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Open the Network list drop-down.,,A list of main networks is shown. The Show Test Networks toggle is shown. The Add Network button is shown., +4,Proceed to Add Network. ,,The extended view opens in a new tab. The current network is shown. The Account is shown. The Settings Menu is shown. The list of popular networks is shown., +5,Add a Network from the list of Popular custom networks. ,e.g. Polygon Mainnet.,"The name, URL, chain ID, currency symbol and block explorer URL are shown in the network details. ", +6,Approve the Network.,,The network is successfully added. The option to switch to the added network is shown. The option to dismiss is shown. , +7,Proceed to switch the network. ,,The network is switched. The Things to keep in mind screen is shown. The MATIC balance is shown on the overview., \ No newline at end of file diff --git a/test/scenarios/7. network/delete networks from the dropdown list.csv b/test/scenarios/7. network/delete networks from the dropdown list.csv new file mode 100644 index 000000000000..ae76f4381b81 --- /dev/null +++ b/test/scenarios/7. network/delete networks from the dropdown list.csv @@ -0,0 +1,7 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Open the Network drop-down list. ,,,A list of main networks is shown. The Show Test Networks toggle is shown. The Add Network button is shown., +4,Hover over a custom network.,A custom network should be added first.,e.g. Arbitrum One.,The Delete icon is shown.,The Delete button is shown IF the network to be deleted is not the currently used network. +5,Proceed to Delete the network. ,,,, +6,Confirm deleting the network.,,,The network deleted is not shown in the networks list., \ No newline at end of file diff --git a/test/scenarios/7. network/delete networks in Settings.csv b/test/scenarios/7. network/delete networks in Settings.csv new file mode 100644 index 000000000000..0e3e5f6d51b3 --- /dev/null +++ b/test/scenarios/7. network/delete networks in Settings.csv @@ -0,0 +1,8 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Select Expand view in the wallet menu.,,,A new tab opens. The expanded extension view is shown., +4,Proceed to Networks in the Settings menu.,,,The list of main networks and test networks is shown., +5,Select the network to delete.,A custom network should be added first.,e.g. Avalanche Mainnet.,"The network name, RPC URL, chain ID, currency symbol and block explorer URL are shown. The Delete button is shown. ",The Delete button is shown IF the network to be deleted is not the currently used network. +6,Proceed to delete the network.,,,, +7,Confirm deleting the network. ,,,The network deleted is not shown in the networks list., \ No newline at end of file diff --git a/test/scenarios/7. network/switching networks.csv b/test/scenarios/7. network/switching networks.csv new file mode 100644 index 000000000000..34fa7efc2b34 --- /dev/null +++ b/test/scenarios/7. network/switching networks.csv @@ -0,0 +1,5 @@ +Step,Test steps,Test data,Expected result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Select the Network list drop-down.,,A list of added networks appears. The Show Test Networks toggle appears. The Add Network button appears., +4,Proceed to select a network from the drop-down list.,e.g. Linea Mainnet. ,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Linea Mainnet. , \ No newline at end of file diff --git a/test/scenarios/7. network/update networks.csv b/test/scenarios/7. network/update networks.csv new file mode 100644 index 000000000000..a2fec1411eed --- /dev/null +++ b/test/scenarios/7. network/update networks.csv @@ -0,0 +1,8 @@ +Step,Test steps,Preconditions,Test data,Expected result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,The Ether balance is shown on the overview. The wallet address is shown on the overview. The selected network is Ethereum Mainnet. , +3,Select Expand view in the wallet menu.,,,A new tab opens. The expanded extension view is shown., +4,Proceed to Networks in the Settings menu.,,,The list of main networks and test networks is shown., +5,Select the network to update.,A custom network should be added first.,e.g. Arbitrum One.,"The network name, RPC URL, chain ID, currency symbol and block explorer URL are shown.", +6,Update network RPC URL.,,e.g. https://rpc.ankr.com/arbitrum,, +7,Save changes. ,,,The request and status are shown in the Networks tab in the browser Developer Tools. , \ No newline at end of file diff --git a/test/scenarios/8. backup/backup user data.csv b/test/scenarios/8. backup/backup user data.csv new file mode 100644 index 000000000000..a3f46ecda05c --- /dev/null +++ b/test/scenarios/8. backup/backup user data.csv @@ -0,0 +1,13 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Select Expand view in the wallet menu.,,"A new tab opens. +The expanded extension view is shown.", +4,Proceed to open the Accounts options menu.,,The accounts menu popover is shown., +5,Proceed to General in the Settings menu.,,The General settings are displayed., +6,Change the Theme option.,e.g. Dark.,The extension background is Dark. , +7,Proceed to Advanced in the Settings menu.,,The Advanced settings are displayed., +8,Proceed to Backup your data.,,"Your user data is saved to disk. +The data payload is valid JSON and the Preferences object contains the Theme property with the value Dark.", \ No newline at end of file diff --git a/test/scenarios/8. backup/restore user data.csv b/test/scenarios/8. backup/restore user data.csv new file mode 100644 index 000000000000..c1ab84a6c3ff --- /dev/null +++ b/test/scenarios/8. backup/restore user data.csv @@ -0,0 +1,13 @@ +Step,Test Steps,Preconditions,Test Data,Expected Result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Select Expand view in the wallet menu.,,,"A new tab opens. +The expanded extension view is shown.", +4,Proceed to open the Accounts options menu.,,,The accounts menu popover is shown., +5,Proceed to General in the Settings menu.,,,The General settings are displayed., +6,Change the Theme option.,,e.g. Light.,The extension background is Light. , +7,Proceed to Advanced in the Settings menu.,,,The Advanced settings are displayed., +8,Proceed to Restore your data.,A user data backup exists with the theme set to Dark.,,"A success message appears to confirm the restore operation. +The extension background is Dark.", \ No newline at end of file diff --git a/test/scenarios/9. metrics/send event metrics.csv b/test/scenarios/9. metrics/send event metrics.csv new file mode 100644 index 000000000000..f01dca35c5cb --- /dev/null +++ b/test/scenarios/9. metrics/send event metrics.csv @@ -0,0 +1,18 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview. +The selected network is Ethereum Mainnet.", +3,Select Expand view in the wallet menu.,,"A new tab opens. +The expanded extension view is shown.", +4,Proceed to open the Accounts options menu.,,The accounts menu popover is shown., +5,Proceed to Security & Privacy in the Settings menu.,,The Security & privacy settings are displayed., +6,Ensure the Participate in MetaMetrics setting is enabled.,,, +7,Navigate to the browsers extension page in another tab.,"Firefox: about:addons +Chrome: chrome://extensions/",The extensions page is displayed., +8,Enable developer mode/debug addons.,,, +9,Inspect background.html using the browsers developer tools.,,The browsers developer tools window opens., +10,Select the Network tab.,,, +11,Keep the background console open and head over to the wallet.,,, +12,Proceed to General in the Settings menu.,,"A request to Sentry is made in the networks tab under payload - https://api.segment.io/v1/batch. +The request payload is valid JSON and contains the name General Settings Page.", \ No newline at end of file diff --git a/test/stub/tx-meta-stub.js b/test/stub/tx-meta-stub.js index 88b8bf99e72f..3523f131f0c1 100644 --- a/test/stub/tx-meta-stub.js +++ b/test/stub/tx-meta-stub.js @@ -11,7 +11,7 @@ export const txMetaStub = { { id: 405984854664302, loadingDefaults: true, - metamaskNetworkId: '5', + chainId: '0x5', status: TransactionStatus.unapproved, time: 1572395156620, type: TransactionType.simpleSend, @@ -136,7 +136,6 @@ export const txMetaStub = { ], id: 405984854664302, loadingDefaults: false, - metamaskNetworkId: '5', origin: 'MetaMask', r: '0x5f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57', rawTx: diff --git a/ui/components/app/account-menu/keyring-label.js b/ui/components/app/account-menu/keyring-label.js deleted file mode 100644 index 7abc718d1516..000000000000 --- a/ui/components/app/account-menu/keyring-label.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { HardwareKeyringNames } from '../../../../shared/constants/hardware-wallets'; -import { KeyringType } from '../../../../shared/constants/keyring'; - -export default function KeyringLabel({ keyring }) { - const t = useI18nContext(); - let label = null; - - // Keyring value might take a while to get a value - if (!keyring) { - return null; - } - const { type } = keyring; - - switch (type) { - case KeyringType.qr: - label = HardwareKeyringNames.qr; - break; - case KeyringType.imported: - label = t('imported'); - break; - case KeyringType.trezor: - label = HardwareKeyringNames.trezor; - break; - case KeyringType.ledger: - label = HardwareKeyringNames.ledger; - break; - case KeyringType.lattice: - label = HardwareKeyringNames.lattice; - break; - ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) - case KeyringType.snap: - label = t('snaps'); - break; - ///: END:ONLY_INCLUDE_IN - default: - label = null; - } - - ///: BEGIN:ONLY_INCLUDE_IN(mmi) - if (type.startsWith('Custody') && /JSONRPC/u.test(type)) { - label = type.split(' - ')[1]; - return null; - } - ///: END:ONLY_INCLUDE_IN - - if (label === null) { - return label; - } - - return ( - <>{label ?
{label}
: null} - ); -} - -KeyringLabel.propTypes = { - keyring: PropTypes.object, -}; diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index c5c610d5dc51..90c7e6da117d 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -28,13 +28,14 @@ @import 'edit-gas-fee-popover/network-statistics/index'; @import 'edit-gas-fee-popover/network-statistics/status-slider/index'; @import 'edit-gas-fee-popover/edit-gas-tooltip/index'; +@import 'edit-gas-popover/index'; @import 'flask/experimental-area/index'; @import 'snaps/snap-content-footer/index'; @import 'snaps/snap-install-warning/index'; @import 'snaps/snap-ui-renderer/index'; @import 'snaps/snap-ui-markdown/index'; @import 'snaps/snap-delineator/index'; -@import 'snaps/snap-settings-card/index'; +@import 'snaps/snap-list-item/index'; @import 'snaps/copyable/index'; @import 'snaps/snap-version/index'; @import 'gas-details-item/index'; @@ -48,6 +49,7 @@ @import 'multilayer-fee-message/index'; @import 'multiple-notifications/index'; @import 'network-display/index'; +@import 'ui/components/app/incoming-trasaction-toggle/network-toggle'; @import 'permission-page-container/index'; @import 'permissions-connect-footer/index'; @import 'permissions-connect-header/index'; diff --git a/ui/components/app/approve-content-card/approve-content-card.stories.js b/ui/components/app/approve-content-card/approve-content-card.stories.js index 17216ba18c10..cc7e1cdda4dd 100644 --- a/ui/components/app/approve-content-card/approve-content-card.stories.js +++ b/ui/components/app/approve-content-card/approve-content-card.stories.js @@ -93,7 +93,6 @@ export default { id: 3049568294499567, time: 1664449552289, status: 'unapproved', - metamaskNetworkId: '3', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x3', @@ -119,7 +118,6 @@ export default { id: 3049568294499567, time: 1664449552289, status: 'unapproved', - metamaskNetworkId: '3', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x3', diff --git a/ui/components/app/asset-list/__snapshots__/asset-list.test.js.snap b/ui/components/app/asset-list/__snapshots__/asset-list.test.js.snap deleted file mode 100644 index 99aa5dd42feb..000000000000 --- a/ui/components/app/asset-list/__snapshots__/asset-list.test.js.snap +++ /dev/null @@ -1,339 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AssetList calculates the correct fiat account total 1`] = ` - -`; diff --git a/ui/components/app/asset-list/asset-list.buy-receive.test.js b/ui/components/app/asset-list/asset-list.buy-receive.test.js new file mode 100644 index 000000000000..949f747cf660 --- /dev/null +++ b/ui/components/app/asset-list/asset-list.buy-receive.test.js @@ -0,0 +1,64 @@ +import React from 'react'; +import { renderWithProvider } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import mockState from '../../../../test/data/mock-state.json'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import AssetList from './asset-list'; + +// Specific to just the ETH FIAT conversion +const ETH_BALANCE = '0x041173b2c0e57d'; // 0.0011 ETH ($1.83) + +const render = ( + selectedAddress = mockState.metamask.selectedAddress, + balance = ETH_BALANCE, + chainId = CHAIN_IDS.MAINNET, +) => { + const state = { + ...mockState, + metamask: { + ...mockState.metamask, + providerConfig: { chainId }, + cachedBalances: { + [CHAIN_IDS.MAINNET]: { + [selectedAddress]: balance, + }, + }, + selectedAddress, + }, + }; + const store = configureStore(state); + return renderWithProvider( + undefined} />, + store, + ); +}; + +describe('AssetList Buy/Receive', () => { + it('shows Buy and Receive when the account is empty', () => { + process.env.MULTICHAIN = 1; + const { queryByText } = render( + '0xc42edfcc21ed14dda456aa0756c153f7985d8813', + '0x0', + ); + expect(queryByText('Buy')).toBeInTheDocument(); + expect(queryByText('Receive')).toBeInTheDocument(); + }); + + it('shows only Receive when chainId is not buyable', () => { + process.env.MULTICHAIN = 1; + const { queryByText } = render( + '0xc42edfcc21ed14dda456aa0756c153f7985d8813', + '0x0', + '0x8675309', // Custom chain ID that isn't buyable + ); + expect(queryByText('Buy')).not.toBeInTheDocument(); + expect(queryByText('Receive')).toBeInTheDocument(); + }); + + it('shows neither when the account has a balance', () => { + process.env.MULTICHAIN = 1; + const { queryByText } = render(); + expect(queryByText('Buy')).not.toBeInTheDocument(); + expect(queryByText('Receive')).not.toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index 48d82ee037b5..11fc1e961df7 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -1,7 +1,6 @@ import React, { useContext, useState } from 'react'; import PropTypes from 'prop-types'; -import { shallowEqual, useSelector } from 'react-redux'; -import { isEqual } from 'lodash'; +import { useSelector } from 'react-redux'; import TokenList from '../token-list'; import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency'; @@ -12,14 +11,12 @@ import { getDetectedTokensInCurrentNetwork, getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, getShouldHideZeroBalanceTokens, - getTokenExchangeRates, - getCurrentCurrency, + getIsBuyableChain, + getCurrentChainId, + getSwapsDefaultToken, + getSelectedAddress, } from '../../../selectors'; -import { - getConversionRate, - getNativeCurrency, - getTokens, -} from '../../../ducks/metamask/metamask'; +import { getNativeCurrency } from '../../../ducks/metamask/metamask'; import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'; import Box from '../../ui/box/box'; import { MetaMetricsContext } from '../../../contexts/metametrics'; @@ -33,25 +30,30 @@ import { TokenListItem, ImportTokenLink, BalanceOverview, + AssetListConversionButton, } from '../../multichain'; -import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils'; -import { getTokenFiatAmount } from '../../../helpers/utils/token-util'; -import { formatCurrency } from '../../../helpers/utils/confirm-tx.util'; -import { - getValueFromWeiHex, - sumDecimals, -} from '../../../../shared/modules/conversion.utils'; -import { useTokenTracker } from '../../../hooks/useTokenTracker'; + +import useRamps from '../../../hooks/experiences/useRamps'; +import { Display } from '../../../helpers/constants/design-system'; + +import { ReceiveModal } from '../../multichain/receive-modal'; +import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance'; +import { ASSET_LIST_CONVERSION_BUTTON_VARIANT_TYPES } from '../../multichain/asset-list-conversion-button/asset-list-conversion-button'; const AssetList = ({ onClickAsset }) => { const [showDetectedTokens, setShowDetectedTokens] = useState(false); - const selectedAccountBalance = useSelector(getSelectedAccountCachedBalance); const nativeCurrency = useSelector(getNativeCurrency); const showFiat = useSelector(getShouldShowFiat); const trackEvent = useContext(MetaMetricsContext); const balance = useSelector(getSelectedAccountCachedBalance); const balanceIsLoading = !balance; + const selectedAddress = useSelector(getSelectedAddress); + const shouldHideZeroBalanceTokens = useSelector( + getShouldHideZeroBalanceTokens, + ); + + const [showReceiveModal, setShowReceiveModal] = useState(false); const { currency: primaryCurrency, @@ -82,64 +84,21 @@ const AssetList = ({ onClickAsset }) => { getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, ); - const contractExchangeRates = useSelector( - getTokenExchangeRates, - shallowEqual, - ); - const conversionRate = useSelector(getConversionRate); - const currentCurrency = useSelector(getCurrentCurrency); + const { tokensWithBalances, totalFiatBalance, totalWeiBalance, loading } = + useAccountTotalFiatBalance(selectedAddress, shouldHideZeroBalanceTokens); - const nativeFiat = getValueFromWeiHex({ - value: balance, - toCurrency: currentCurrency, - conversionRate, - numberOfDecimals: 2, - }); - - const shouldHideZeroBalanceTokens = useSelector( - getShouldHideZeroBalanceTokens, - ); - // use `isEqual` comparison function because the token array is serialized - // from the background so it has a new reference with each background update, - // even if the tokens haven't changed - const tokens = useSelector(getTokens, isEqual); - const { loading, tokensWithBalances } = useTokenTracker( - tokens, - true, - shouldHideZeroBalanceTokens, - ); - - const dollarBalances = tokensWithBalances.map((token) => { - const contractExchangeTokenKey = Object.keys(contractExchangeRates).find( - (key) => isEqualCaseInsensitive(key, token.address), - ); - const tokenExchangeRate = - (contractExchangeTokenKey && - contractExchangeRates[contractExchangeTokenKey]) ?? - 0; - - const fiat = getTokenFiatAmount( - tokenExchangeRate, - conversionRate, - currentCurrency, - token.string, - token.symbol, - false, - false, - ); - - return fiat; - }); - - const totalFiat = formatCurrency( - sumDecimals(nativeFiat, ...dollarBalances).toString(10), - currentCurrency, - ); + const balanceIsZero = Number(totalFiatBalance) === 0; + const isBuyableChain = useSelector(getIsBuyableChain); + const shouldShowBuy = isBuyableChain && balanceIsZero; + const shouldShowReceive = balanceIsZero; + const { openBuyCryptoInPdapp } = useRamps(); + const chainId = useSelector(getCurrentChainId); + const defaultSwapsToken = useSelector(getSwapsDefaultToken); return ( <> {process.env.MULTICHAIN ? ( - + ) : null} {detectedTokens.length > 0 && !isTokenDetectionInactiveOnNonMainnetSupportedNetwork && ( @@ -148,31 +107,74 @@ const AssetList = ({ onClickAsset }) => { margin={4} /> )} - onClickAsset(nativeCurrency)} - title={nativeCurrency} - primary={ - primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value - } - tokenSymbol={primaryCurrencyProperties.suffix} - secondary={showFiat ? secondaryCurrencyDisplay : undefined} - tokenImage={balanceIsLoading ? null : primaryTokenImage} - /> - { - onClickAsset(tokenAddress); - trackEvent({ - event: MetaMetricsEventName.TokenScreenOpened, - category: MetaMetricsEventCategory.Navigation, - properties: { - token_symbol: primaryCurrencyProperties.suffix, - location: 'Home', - }, - }); - }} - /> + {process.env.MULTICHAIN && (shouldShowBuy || shouldShowReceive) ? ( + + {shouldShowBuy ? ( + { + openBuyCryptoInPdapp(); + trackEvent({ + event: MetaMetricsEventName.NavBuyButtonClicked, + category: MetaMetricsEventCategory.Navigation, + properties: { + location: 'Home', + text: 'Buy', + chain_id: chainId, + token_symbol: defaultSwapsToken, + }, + }); + }} + /> + ) : null} + {shouldShowReceive ? ( + setShowReceiveModal(true)} + /> + ) : null} + {showReceiveModal ? ( + setShowReceiveModal(false)} + /> + ) : null} + + ) : ( + <> + onClickAsset(nativeCurrency)} + title={nativeCurrency} + primary={ + primaryCurrencyProperties.value ?? + secondaryCurrencyProperties.value + } + tokenSymbol={primaryCurrencyProperties.suffix} + secondary={showFiat ? secondaryCurrencyDisplay : undefined} + tokenImage={balanceIsLoading ? null : primaryTokenImage} + /> + { + onClickAsset(tokenAddress); + trackEvent({ + event: MetaMetricsEventName.TokenScreenOpened, + category: MetaMetricsEventCategory.Navigation, + properties: { + token_symbol: primaryCurrencyProperties.suffix, + location: 'Home', + }, + }); + }} + /> + + )} 0 ? 0 : 4}> diff --git a/ui/components/app/asset-list/asset-list.test.js b/ui/components/app/asset-list/asset-list.test.js index cb12394ac4f3..9bea60390425 100644 --- a/ui/components/app/asset-list/asset-list.test.js +++ b/ui/components/app/asset-list/asset-list.test.js @@ -49,16 +49,20 @@ jest.mock('../../../hooks/useTokenTracker', () => { }; }); -const render = () => { +const render = ( + selectedAddress = mockState.metamask.selectedAddress, + balance = ETH_BALANCE, + chainId = CHAIN_IDS.MAINNET, +) => { const state = { ...mockState, metamask: { ...mockState.metamask, - providerConfig: { chainId: CHAIN_IDS.MAINNET }, + providerConfig: { chainId }, conversionRate: CONVERSION_RATE, cachedBalances: { [CHAIN_IDS.MAINNET]: { - [mockState.metamask.selectedAddress]: ETH_BALANCE, + [selectedAddress]: balance, }, }, contractExchangeRates: { @@ -66,6 +70,7 @@ const render = () => { [LINK_CONTRACT]: 0.00423239, [WBTC_CONTRACT]: 16.66575, }, + selectedAddress, }, }; const store = configureStore(state); @@ -81,10 +86,12 @@ describe('AssetList', () => { expect(screen.getByText('Refresh list')).toBeInTheDocument(); }); - it('calculates the correct fiat account total', () => { - process.env.MULTICHAIN = 1; - const { container } = render(); - expect(container).toMatchSnapshot(); - expect(screen.getByText('$63,356.88 USD')).toBeInTheDocument(); + describe('token fiat value calculations', () => { + it('calculates the correct fiat account total', () => { + process.env.MULTICHAIN = 1; + render(); + expect(screen.getByText('$63,356.88 USD')).toBeInTheDocument(); + jest.resetModules(); + }); }); }); diff --git a/ui/components/app/confirm-data/confirm-data.js b/ui/components/app/confirm-data/confirm-data.js index b5030e0e10b7..12a3f9f79e81 100644 --- a/ui/components/app/confirm-data/confirm-data.js +++ b/ui/components/app/confirm-data/confirm-data.js @@ -11,10 +11,10 @@ import { getKnownMethodData } from '../../../selectors'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { useTransactionFunctionType } from '../../../hooks/useTransactionFunctionType'; -import Box from '../../ui/box/box'; import Disclosure from '../../ui/disclosure'; import TransactionDecoding from '../transaction-decoding'; -import { Text } from '../../component-library'; +import { Text, Box } from '../../component-library'; +import TransactionInsightsDeprecationAlert from './transaction-insights-deprecation-alert'; const ConfirmData = ({ txData, dataComponent }) => { const t = useI18nContext(); @@ -25,7 +25,14 @@ const ConfirmData = ({ txData, dataComponent }) => { const { functionType } = useTransactionFunctionType(txData); if (dataComponent) { - return dataComponent; + return ( + + + + + {dataComponent} + + ); } if (!txParams.data) { @@ -38,28 +45,33 @@ const ConfirmData = ({ txData, dataComponent }) => { : ''; return ( - - - - {`${t('functionType')}:`} - - - {`${functionType} ${functionParams}`} - + + + + + + + + {`${t('functionType')}:`} + + + {`${functionType} ${functionParams}`} + + + + + - - - ); }; diff --git a/ui/components/app/confirm-data/index.scss b/ui/components/app/confirm-data/index.scss index db19ef9e5f95..a17cd6d1835d 100644 --- a/ui/components/app/confirm-data/index.scss +++ b/ui/components/app/confirm-data/index.scss @@ -2,4 +2,9 @@ & > .disclosure { margin-top: 0; } + + &_alert-snaps { + font-size: var(--typography-l-body-sm-font-size); + vertical-align: baseline; + } } diff --git a/ui/components/app/confirm-data/transaction-insights-deprecation-alert.js b/ui/components/app/confirm-data/transaction-insights-deprecation-alert.js new file mode 100644 index 000000000000..a77acc85ed47 --- /dev/null +++ b/ui/components/app/confirm-data/transaction-insights-deprecation-alert.js @@ -0,0 +1,34 @@ +import React from 'react'; + +import { + TextVariant, + FontWeight, +} from '../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { BannerAlert, Text, ButtonLink } from '../../component-library'; + +const TransactionInsightsDeprecationAlert = () => { + const t = useI18nContext(); + + return ( + + + {t('transactionInsightsDeprecationTitle')} + + + {t('transactionInsightsDeprecationInfo')}{' '} + + {t('snaps')} + + . + + + ); +}; + +export default TransactionInsightsDeprecationAlert; diff --git a/ui/components/app/confirm-page-container/confirm-detail-row/__snapshots__/confirm-detail-row.component.test.js.snap b/ui/components/app/confirm-page-container/confirm-detail-row/__snapshots__/confirm-detail-row.component.test.js.snap index 68086ec616ff..ceda298bfe67 100644 --- a/ui/components/app/confirm-page-container/confirm-detail-row/__snapshots__/confirm-detail-row.component.test.js.snap +++ b/ui/components/app/confirm-page-container/confirm-detail-row/__snapshots__/confirm-detail-row.component.test.js.snap @@ -13,17 +13,9 @@ exports[`Confirm Detail Row Component should match snapshot 1`] = ` >
-
- -
@@ -34,15 +26,6 @@ exports[`Confirm Detail Row Component should match snapshot 1`] = ` class="mm-box currency-display-component confirm-detail-row__secondary mm-box--display-flex mm-box--flex-wrap-wrap mm-box--align-items-center" title="0" > -
- -
diff --git a/ui/components/app/confirm-page-container/confirm-detail-row/confirm-detail-row.component.test.js b/ui/components/app/confirm-page-container/confirm-detail-row/confirm-detail-row.component.test.js index 5f808c299a2b..221c598eb1e8 100644 --- a/ui/components/app/confirm-page-container/confirm-detail-row/confirm-detail-row.component.test.js +++ b/ui/components/app/confirm-page-container/confirm-detail-row/confirm-detail-row.component.test.js @@ -1,17 +1,22 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; - import { NetworkStatus } from '@metamask/network-controller'; import { NetworkType } from '@metamask/controller-utils'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; +import { + CHAIN_IDS, + GOERLI_DISPLAY_NAME, + NETWORK_TYPES, +} from '../../../../../shared/constants/network'; import ConfirmDetailRow from '.'; describe('Confirm Detail Row Component', () => { const mockState = { metamask: { providerConfig: { - type: 'rpc', - chainId: '0x5', + chainId: CHAIN_IDS.GOERLI, + nickname: GOERLI_DISPLAY_NAME, + type: NETWORK_TYPES.GOERLI, }, preferences: { useNativeCurrencyAsPrimaryCurrency: true, diff --git a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js index 6432fad41027..72f5e3d3a6ca 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js @@ -33,7 +33,7 @@ const props = { id: 8783053010106567, time: 1656448479005, status: 'unapproved', - metamaskNetworkId: '5', + chainId: '0x5', originalGasEstimate: '0x5208', userEditedGasLimit: false, loadingDefaults: false, @@ -66,7 +66,6 @@ const props = { id: 1230035278491151, time: 1671022500513, status: 'unapproved', - metamaskNetworkId: '80001', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x13881', @@ -92,7 +91,6 @@ const props = { id: 1230035278491151, time: 1671022500513, status: 'unapproved', - metamaskNetworkId: '80001', originalGasEstimate: '0xea60', userEditedGasLimit: false, chainId: '0x13881', @@ -319,6 +317,8 @@ describe('Confirm Page Container Container Test', () => { }; mockState.metamask.addressBook = addressBook; + mockState.confirmTransaction.txData.txParams.to = + '0x7a1A4Ad9cc746a70ee58568466f7996dD0aCE4E8'; const store = configureMockStore()(mockState); diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js index f81301c195ec..5261d935c185 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js @@ -7,6 +7,7 @@ import { INSUFFICIENT_FUNDS_ERROR_KEY, TRANSACTION_ERROR_KEY, } from '../../../../helpers/constants/error-keys'; +import { shortenAddress } from '../../../../helpers/utils/util'; import ConfirmPageContainerContent from './confirm-page-container-content.component'; describe('Confirm Page Container Content', () => { @@ -25,6 +26,17 @@ describe('Confirm Page Container Content', () => { }, }, }, + identities: {}, + tokenList: {}, + }, + confirmTransaction: { + txData: { + txParams: { + gas: '0x153e2', + value: '0x0', + to: '0x0BC30598F0F386371eB3d2195AcAA14C7566534b', + }, + }, }, }; @@ -106,7 +118,7 @@ describe('Confirm Page Container Content', () => { expect(props.onCancel).toHaveBeenCalledTimes(1); }); - it('render contract address name from addressBook in title for contract', async () => { + it('render contract address in the content component', async () => { props.disabled = false; props.toAddress = '0x06195827297c7A80a443b6894d3BDB8824b43896'; props.transactionType = TransactionType.contractInteraction; @@ -114,8 +126,11 @@ describe('Confirm Page Container Content', () => { , store, ); + const expectedAddress = shortenAddress( + mockStore.confirmTransaction.txData.txParams.to, + ); - expect(queryByText('Address Book Account 1')).toBeInTheDocument(); + expect(queryByText(`${expectedAddress}`)).toBeInTheDocument(); }); it('render simple title without address name for simple send', async () => { diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js index 46a3f068f42e..2421f11ee39b 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js @@ -9,7 +9,7 @@ import { TransactionType } from '../../../../../../shared/constants/transaction' import { toChecksumHexAddress } from '../../../../../../shared/modules/hexstring-utils'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import useAddressDetails from '../../../../../hooks/useAddressDetails'; -import { getIpfsGateway } from '../../../../../selectors'; +import { getIpfsGateway, txDataSelector } from '../../../../../selectors'; import Identicon from '../../../../ui/identicon'; import InfoTooltip from '../../../../ui/info-tooltip'; @@ -25,7 +25,6 @@ const ConfirmPageContainerSummary = (props) => { subtitleComponent, className, tokenAddress, - toAddress, nonce, origin, image, @@ -36,6 +35,10 @@ const ConfirmPageContainerSummary = (props) => { const t = useI18nContext(); const ipfsGateway = useSelector(getIpfsGateway); + const txData = useSelector(txDataSelector); + const { txParams = {} } = txData; + const { to: txParamsToAddress } = txParams; + const contractInitiatedTransactionType = [ TransactionType.contractInteraction, TransactionType.tokenMethodTransfer, @@ -48,14 +51,15 @@ const ConfirmPageContainerSummary = (props) => { if (isContractTypeTransaction) { // If the transaction is TOKEN_METHOD_TRANSFER or TOKEN_METHOD_TRANSFER_FROM // the contract address is passed down as tokenAddress, if it is anyother - // type of contract interaction it is passed as toAddress + // type of contract interaction it is "to" from txParams + contractAddress = transactionType === TransactionType.tokenMethodTransfer || transactionType === TransactionType.tokenMethodTransferFrom || transactionType === TransactionType.tokenMethodSafeTransferFrom || transactionType === TransactionType.tokenMethodSetApprovalForAll ? tokenAddress - : toAddress; + : txParamsToAddress; } const { toName, isTrusted } = useAddressDetails(contractAddress); @@ -146,7 +150,6 @@ ConfirmPageContainerSummary.propTypes = { subtitleComponent: PropTypes.node, className: PropTypes.string, tokenAddress: PropTypes.string, - toAddress: PropTypes.string, nonce: PropTypes.string, origin: PropTypes.string.isRequired, transactionType: PropTypes.string, diff --git a/ui/components/app/connected-accounts-list/index.scss b/ui/components/app/connected-accounts-list/index.scss index 89d91ea30d8d..449a39978062 100644 --- a/ui/components/app/connected-accounts-list/index.scss +++ b/ui/components/app/connected-accounts-list/index.scss @@ -5,6 +5,7 @@ &__row { width: 100%; + word-break: break-word; } &__row-content { diff --git a/ui/components/app/connected-sites-list/connected-snaps.js b/ui/components/app/connected-sites-list/connected-snaps.js index 30e6fcfb48ad..7a1a350e514d 100644 --- a/ui/components/app/connected-sites-list/connected-snaps.js +++ b/ui/components/app/connected-sites-list/connected-snaps.js @@ -7,7 +7,6 @@ import { SnapCaveatType } from '@metamask/rpc-methods'; import { Box, IconName, IconSize, Text } from '../../component-library'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { MenuItem } from '../../ui/menu'; -import { SNAPS_VIEW_ROUTE } from '../../../helpers/constants/routes'; import SnapAvatar from '../snaps/snap-avatar'; import { AlignItems, @@ -23,6 +22,7 @@ import { getPermissionSubjects, } from '../../../selectors'; import { removePermissionsFor, updateCaveat } from '../../../store/actions'; +import { getSnapRoute } from '../../../helpers/utils/util'; export default function ConnectedSnaps({ connectedSubjects }) { const [showOptions, setShowOptions] = useState(); @@ -74,9 +74,7 @@ export default function ConnectedSnaps({ connectedSubjects }) { - history.push(`${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snapId)}`) - } + onClick={() => history.push(getSnapRoute(snapId))} > {t('snapsSettings')} diff --git a/ui/components/app/desktop-enable-button/__snapshots__/desktop-enable-button.component.test.js.snap b/ui/components/app/desktop-enable-button/__snapshots__/desktop-enable-button.component.test.js.snap index de0fd70b8ea2..c26996423a3c 100644 --- a/ui/components/app/desktop-enable-button/__snapshots__/desktop-enable-button.component.test.js.snap +++ b/ui/components/app/desktop-enable-button/__snapshots__/desktop-enable-button.component.test.js.snap @@ -4,8 +4,6 @@ exports[`Desktop Enable Button should match snapshot 1`] = `
diff --git a/ui/components/app/detected-token/detected-token-values/detected-token-values.js b/ui/components/app/detected-token/detected-token-values/detected-token-values.js index ef35f6315cc3..667c356b0e1f 100644 --- a/ui/components/app/detected-token/detected-token-values/detected-token-values.js +++ b/ui/components/app/detected-token/detected-token-values/detected-token-values.js @@ -21,7 +21,7 @@ const DetectedTokenValues = ({ return tokensListDetected[token.address]?.selected; }); - const { tokensWithBalances } = useTokenTracker([token]); + const { tokensWithBalances } = useTokenTracker({ tokens: [token] }); const balanceString = tokensWithBalances[0]?.string; const formattedFiatBalance = useTokenFiatAmount( token.address, diff --git a/ui/components/app/detected-token/detected-token.js b/ui/components/app/detected-token/detected-token.js index 582befda8e9e..3d9038cc52b9 100644 --- a/ui/components/app/detected-token/detected-token.js +++ b/ui/components/app/detected-token/detected-token.js @@ -11,6 +11,7 @@ import { import { getCurrentChainId, getDetectedTokensInCurrentNetwork, + getSelectedNetworkClientId, } from '../../../selectors'; import { MetaMetricsContext } from '../../../contexts/metametrics'; @@ -52,6 +53,7 @@ const DetectedToken = ({ setShowDetectedTokens }) => { const chainId = useSelector(getCurrentChainId); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork); + const networkClientId = useSelector(getSelectedNetworkClientId); const [tokensListDetected, setTokensListDetected] = useState(() => detectedTokens.reduce((tokenObj, token) => { @@ -81,7 +83,7 @@ const DetectedToken = ({ setShowDetectedTokens }) => { }, }); }); - await dispatch(addImportedTokens(selectedTokens)); + await dispatch(addImportedTokens(selectedTokens, networkClientId)); const tokenSymbols = selectedTokens.map(({ symbol }) => symbol); dispatch(setNewTokensImported(tokenSymbols.join(', '))); }; diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js index b7382bc5ddb0..94f0df605d36 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js @@ -7,6 +7,15 @@ import { ETH } from '../../../helpers/constants/common'; import configureStore from '../../../store/store'; import { GasFeeContextProvider } from '../../../contexts/gasFee'; +import { + TransactionStatus, + TransactionType, +} from '../../../../shared/constants/transaction'; +import { + NETWORK_TYPES, + CHAIN_IDS, + GOERLI_DISPLAY_NAME, +} from '../../../../shared/constants/network'; import EditGasFeePopover from './edit-gas-fee-popover'; jest.mock('../../../store/actions', () => ({ @@ -53,7 +62,11 @@ const render = ({ txProps, contextProps } = {}) => { const store = configureStore({ metamask: { nativeCurrency: ETH, - providerConfig: {}, + providerConfig: { + chainId: CHAIN_IDS.GOERLI, + nickname: GOERLI_DISPLAY_NAME, + type: NETWORK_TYPES.GOERLI, + }, cachedBalances: {}, accounts: { '0xAddress': { @@ -112,13 +125,25 @@ describe('EditGasFeePopover', () => { }); it('should not show insufficient balance message if transaction value is less than balance', () => { - render({ txProps: { userFeeLevel: 'high', txParams: { value: '0x64' } } }); + render({ + txProps: { + status: TransactionStatus.unapproved, + type: TransactionType.simpleSend, + userFeeLevel: 'high', + txParams: { value: '0x64', from: '0xAddress' }, + }, + }); expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument(); }); it('should show insufficient balance message if transaction value is more than balance', () => { render({ - txProps: { userFeeLevel: 'high', txParams: { value: '0x5208' } }, + txProps: { + status: TransactionStatus.unapproved, + type: TransactionType.simpleSend, + userFeeLevel: 'high', + txParams: { value: '0x5208', from: '0xAddress' }, + }, }); expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument(); }); diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js index ea3191e2d7e5..3f2aa9c73ef5 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js @@ -10,7 +10,11 @@ import { ETH } from '../../../../helpers/constants/common'; import configureStore from '../../../../store/store'; import { GasFeeContextProvider } from '../../../../contexts/gasFee'; -import { CHAIN_IDS } from '../../../../../shared/constants/network'; +import { + CHAIN_IDS, + GOERLI_DISPLAY_NAME, + NETWORK_TYPES, +} from '../../../../../shared/constants/network'; import EditGasItem from './edit-gas-item'; jest.mock('../../../../store/actions', () => ({ @@ -62,6 +66,8 @@ const renderComponent = ({ nativeCurrency: ETH, providerConfig: { chainId: CHAIN_IDS.GOERLI, + nickname: GOERLI_DISPLAY_NAME, + type: NETWORK_TYPES.GOERLI, }, cachedBalances: {}, accounts: { diff --git a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js index a3b9920d3066..5a9b620aa546 100644 --- a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js +++ b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js @@ -10,8 +10,15 @@ import { GasRecommendations, } from '../../../../shared/constants/gas'; -import Popover from '../../ui/popover'; -import Button from '../../ui/button'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + Button, + ButtonVariant, + ButtonSize, +} from '../../component-library'; import EditGasDisplay from '../edit-gas-display'; import { I18nContext } from '../../../contexts/i18n'; @@ -170,44 +177,47 @@ export default function EditGasPopover({ const footerButtonText = confirmButtonText || t('save'); return ( - + + + + {title} + +
+ {process.env.IN_TEST ? null : } + +
- } - > -
- {process.env.IN_TEST ? null : } - -
-
+ + ); } diff --git a/ui/components/app/edit-gas-popover/index.scss b/ui/components/app/edit-gas-popover/index.scss index e81e9d382c7d..b6b4e8bdf362 100644 --- a/ui/components/app/edit-gas-popover/index.scss +++ b/ui/components/app/edit-gas-popover/index.scss @@ -1,7 +1,5 @@ .edit-gas-popover { - &__wrapper { - @include screen-sm-min { - max-height: 84vh; - } + &__edit-gas-display { + position: relative; } } diff --git a/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap b/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap index 0113330cccdd..465ba53bbb89 100644 --- a/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap +++ b/ui/components/app/incoming-trasaction-toggle/__snapshots__/incoming-transaction-toggle.test.js.snap @@ -16,11 +16,11 @@ exports[`IncomingTransactionToggle should render existing incoming transaction p This relies on different third-party APIs for each network, which expose your Ethereum address and your IP address.

Ethereum Mainnet

-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+ style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);" + > +
+
+
+
+
+
-
+ + Off + + + On +
- -
-
- - Off - - - On - -
- + +
-
+
diff --git a/ui/components/app/metamask-template-renderer/safe-component-list.js b/ui/components/app/metamask-template-renderer/safe-component-list.js index 513a0520e544..623c945e40c1 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -19,10 +19,12 @@ import { SnapDelineator } from '../snaps/snap-delineator'; import { Copyable } from '../snaps/copyable'; import Spinner from '../../ui/spinner'; import { SnapUIMarkdown } from '../snaps/snap-ui-markdown'; +import { SnapUIImage } from '../snaps/snap-ui-image'; ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) import { CreateSnapAccount } from '../../../pages/create-snap-account'; import { RemoveSnapAccount } from '../../../pages/remove-snap-account'; +import { SnapAccountRedirect } from '../../../pages/snap-account-redirect'; import SnapAuthorshipHeader from '../snaps/snap-authorship-header'; ///: END:ONLY_INCLUDE_IN @@ -53,11 +55,13 @@ export const safeComponentList = { Copyable, SnapDelineator, SnapUIMarkdown, + SnapUIImage, Spinner, ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(keyring-snaps) CreateSnapAccount, RemoveSnapAccount, SnapAuthorshipHeader, + SnapAccountRedirect, ///: END:ONLY_INCLUDE_IN }; diff --git a/ui/components/app/modal/__snapshots__/modal.component.test.js.snap b/ui/components/app/modal/__snapshots__/modal.component.test.js.snap index afd63d7dc269..40d89b7594a7 100644 --- a/ui/components/app/modal/__snapshots__/modal.component.test.js.snap +++ b/ui/components/app/modal/__snapshots__/modal.component.test.js.snap @@ -13,15 +13,11 @@ exports[`Modal Component should render a modal with a cancel and a submit button > @@ -56,15 +52,11 @@ exports[`Modal Component should render a modal with a header 1`] = ` > @@ -109,15 +101,11 @@ exports[`Modal Component should render a modal with children 1`] = ` > @@ -139,15 +127,11 @@ exports[`Modal Component should render a modal with different button types 1`] = > diff --git a/ui/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.test.js b/ui/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.test.js index 31b225e53ce6..591964bca0b0 100644 --- a/ui/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.test.js +++ b/ui/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.test.js @@ -1,13 +1,20 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import { renderWithProvider } from '../../../../../../test/lib/render-helpers'; +import { + CHAIN_IDS, + GOERLI_DISPLAY_NAME, + NETWORK_TYPES, +} from '../../../../../../shared/constants/network'; import CancelTransactionGasFee from './cancel-transaction-gas-fee.component'; describe('CancelTransactionGasFee Component', () => { const mockState = { metamask: { providerConfig: { - chainId: '0x4', + chainId: CHAIN_IDS.GOERLI, + nickname: GOERLI_DISPLAY_NAME, + type: NETWORK_TYPES.GOERLI, }, preferences: { useNativeCurrencyAsPrimaryCurrency: false, diff --git a/ui/components/app/modals/confirm-delete-network/__snapshots__/confirm-delete-network.test.js.snap b/ui/components/app/modals/confirm-delete-network/__snapshots__/confirm-delete-network.test.js.snap index 07bc3f75dc5c..4f7cf9c922a1 100644 --- a/ui/components/app/modals/confirm-delete-network/__snapshots__/confirm-delete-network.test.js.snap +++ b/ui/components/app/modals/confirm-delete-network/__snapshots__/confirm-delete-network.test.js.snap @@ -28,15 +28,11 @@ exports[`Confirm Delete Network should match snapshot 1`] = ` > diff --git a/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap b/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap index 0585d2b1d6b3..41b9359dd8c4 100644 --- a/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap +++ b/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap @@ -136,15 +136,11 @@ exports[`Confirm Remove Account should match snapshot 1`] = ` > diff --git a/ui/components/app/modals/confirm-reset-account/__snapshots__/confirm-reset-account.test.js.snap b/ui/components/app/modals/confirm-reset-account/__snapshots__/confirm-reset-account.test.js.snap index e1fd4e4cce98..3f44ce472913 100644 --- a/ui/components/app/modals/confirm-reset-account/__snapshots__/confirm-reset-account.test.js.snap +++ b/ui/components/app/modals/confirm-reset-account/__snapshots__/confirm-reset-account.test.js.snap @@ -28,15 +28,11 @@ exports[`Confirm Reset Account should match snapshot 1`] = ` > diff --git a/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap b/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap index 53c02de3c1fa..1bd81f144164 100644 --- a/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap +++ b/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap @@ -99,15 +99,11 @@ exports[`Customize Nonce should match snapshot 1`] = ` > diff --git a/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap b/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap index 15ec6ad35b0f..c82d870b2466 100644 --- a/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap +++ b/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap @@ -47,7 +47,9 @@ exports[`Eth Sign Modal should match snapshot 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-error-default" style="mask-image: url('./images/icons/danger.svg');" /> -
+
If you've been asked to turn this setting on, you might be getting scammed
diff --git a/ui/components/app/modals/hide-token-confirmation-modal/__snapshots__/hide-token-confirmation-modal.test.js.snap b/ui/components/app/modals/hide-token-confirmation-modal/__snapshots__/hide-token-confirmation-modal.test.js.snap index e12bbb688c53..16ef9005c5a5 100644 --- a/ui/components/app/modals/hide-token-confirmation-modal/__snapshots__/hide-token-confirmation-modal.test.js.snap +++ b/ui/components/app/modals/hide-token-confirmation-modal/__snapshots__/hide-token-confirmation-modal.test.js.snap @@ -73,16 +73,12 @@ exports[`Hide Token Confirmation Modal should match snapshot 1`] = ` diff --git a/ui/components/app/modals/keyring-snap-removal-modal/index.ts b/ui/components/app/modals/keyring-snap-removal-modal/index.ts new file mode 100644 index 000000000000..55f9f764ea0b --- /dev/null +++ b/ui/components/app/modals/keyring-snap-removal-modal/index.ts @@ -0,0 +1 @@ +export { default } from './keyring-snap-removal-result-modal'; diff --git a/ui/components/app/modals/keyring-snap-removal-modal/keyring-snap-removal-result-modal.test.tsx b/ui/components/app/modals/keyring-snap-removal-modal/keyring-snap-removal-result-modal.test.tsx new file mode 100644 index 000000000000..9f8536ce81c2 --- /dev/null +++ b/ui/components/app/modals/keyring-snap-removal-modal/keyring-snap-removal-result-modal.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import mockStore from '../../../../../test/data/mock-state.json'; +import { + fireEvent, + renderWithProvider, + waitFor, +} from '../../../../../test/jest'; +import KeyringSnapRemovalResult from './keyring-snap-removal-result-modal'; + +const mockOnClose = jest.fn(); + +const defaultArgs = { + isOpen: true, + onClose: mockOnClose, +}; + +const getStoreWithModalData = ({ + snapName, + result, +}: { + snapName: string; + result: 'success' | 'failed'; +}) => { + return configureMockStore()({ + ...mockStore, + + appState: { + ...mockStore.appState, + keyringRemovalSnapModal: { + snapName, + result, + }, + }, + }); +}; + +describe('Keyring Snap Remove Result', () => { + it('show render the success message', async () => { + const { getByText, getByLabelText } = renderWithProvider( + , + getStoreWithModalData({ snapName: 'mock-snap', result: 'success' }), + ); + + expect(getByText('mock-snap removed')).toBeInTheDocument(); + + const closeButton = getByLabelText('Close'); + + fireEvent.click(closeButton); + + await waitFor(() => { + expect(mockOnClose).toHaveBeenCalled(); + }); + }); + it('show render the failure message', () => { + const { getByText } = renderWithProvider( + , + getStoreWithModalData({ snapName: 'mock-snap', result: 'failed' }), + ); + expect(getByText('mock-snap not removed')).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/modals/keyring-snap-removal-modal/keyring-snap-removal-result-modal.tsx b/ui/components/app/modals/keyring-snap-removal-modal/keyring-snap-removal-result-modal.tsx new file mode 100644 index 000000000000..6214164adff4 --- /dev/null +++ b/ui/components/app/modals/keyring-snap-removal-modal/keyring-snap-removal-result-modal.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { + FlexDirection, + AlignItems, + Display, + JustifyContent, + IconColor, + TextVariant, + TextAlign, +} from '../../../../helpers/constants/design-system'; +import { + Box, + Icon, + IconName, + IconSize, + Modal, + ModalContent, + ModalHeader, + ModalOverlay, + Text, +} from '../../../component-library'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { getKeyringSnapRemovalResult } from '../../../../selectors'; + +const KeyringSnapRemovalResult = ({ + isOpen, + onClose, +}: { + isOpen: boolean; + onClose: () => void; +}) => { + const t = useI18nContext(); + const snapRemovalResult = useSelector(getKeyringSnapRemovalResult); + + return ( + <> + onClose()}> + + + {''} + + + + {t('keyringSnapRemovalResult1', [ + snapRemovalResult.snapName, + snapRemovalResult.result === 'failed' + ? t('keyringSnapRemovalResultNotSuccessful') + : '', + ])} + + + + + + ); +}; + +export default KeyringSnapRemovalResult; diff --git a/ui/components/app/modals/reject-transactions/__snapshots__/reject-transactions.test.js.snap b/ui/components/app/modals/reject-transactions/__snapshots__/reject-transactions.test.js.snap index 5d993d8a3857..cf681bac0fd8 100644 --- a/ui/components/app/modals/reject-transactions/__snapshots__/reject-transactions.test.js.snap +++ b/ui/components/app/modals/reject-transactions/__snapshots__/reject-transactions.test.js.snap @@ -34,15 +34,11 @@ exports[`Reject Transactions Model should match snapshot 1`] = ` > diff --git a/ui/components/app/modals/transaction-confirmed/__snapshots__/transaction-confirmed.test.js.snap b/ui/components/app/modals/transaction-confirmed/__snapshots__/transaction-confirmed.test.js.snap index b4b6ed44093b..e10cdb476f5c 100644 --- a/ui/components/app/modals/transaction-confirmed/__snapshots__/transaction-confirmed.test.js.snap +++ b/ui/components/app/modals/transaction-confirmed/__snapshots__/transaction-confirmed.test.js.snap @@ -32,8 +32,6 @@ exports[`Transaction Confirmed should match snapshot 1`] = ` > diff --git a/ui/components/app/multiple-notifications/index.scss b/ui/components/app/multiple-notifications/index.scss index af939b884c64..100857d0fe01 100644 --- a/ui/components/app/multiple-notifications/index.scss +++ b/ui/components/app/multiple-notifications/index.scss @@ -59,6 +59,11 @@ visibility: hidden; } + /* accommodates for the home "Wallet" / "Connections" footer */ + &.home-notification-wrapper--multichain > div { + bottom: 88px; + } + > div:first-of-type { visibility: visible; } diff --git a/ui/components/app/multiple-notifications/multiple-notifications.component.js b/ui/components/app/multiple-notifications/multiple-notifications.component.js index 96abbf4a2bf4..d2ca8e7be25d 100644 --- a/ui/components/app/multiple-notifications/multiple-notifications.component.js +++ b/ui/components/app/multiple-notifications/multiple-notifications.component.js @@ -31,6 +31,9 @@ export default class MultipleNotifications extends PureComponent { className={classnames(...classNames, { 'home-notification-wrapper--show-all': showAll, 'home-notification-wrapper--show-first': !showAll, + 'home-notification-wrapper--multichain': Boolean( + process.env.MULTICHAIN, + ), })} > {childrenToRender} diff --git a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap index f26559cf09eb..aa11c7659316 100644 --- a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap +++ b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap @@ -51,7 +51,7 @@ exports[`NameDetails renders proposed names 1`] = `
Send diff --git a/ui/components/app/nft-details/nft-details.test.js b/ui/components/app/nft-details/nft-details.test.js index 7733e75aed3b..3a78c969285a 100644 --- a/ui/components/app/nft-details/nft-details.test.js +++ b/ui/components/app/nft-details/nft-details.test.js @@ -8,6 +8,7 @@ import { startNewDraftTransaction } from '../../../ducks/send'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import mockState from '../../../../test/data/mock-state.json'; import { DEFAULT_ROUTE, SEND_ROUTE } from '../../../helpers/constants/routes'; +import { COPY_OPTIONS } from '../../../../shared/constants/copy'; import { AssetType } from '../../../../shared/constants/transaction'; import { removeAndIgnoreNft, @@ -111,7 +112,7 @@ describe('NFT Details', () => { const copyAddressButton = queryByTestId('nft-address-copy'); fireEvent.click(copyAddressButton); - expect(copyToClipboard).toHaveBeenCalledWith(nfts[5].address); + expect(copyToClipboard).toHaveBeenCalledWith(nfts[5].address, COPY_OPTIONS); }); it('should navigate to draft transaction send route with ERC721 data', async () => { diff --git a/ui/components/app/nfts-items/nfts-items.js b/ui/components/app/nfts-items/nfts-items.js index d2e5eeb6630d..ced4c8f5a897 100644 --- a/ui/components/app/nfts-items/nfts-items.js +++ b/ui/components/app/nfts-items/nfts-items.js @@ -192,7 +192,7 @@ export default function NftsItems({ ipfsGateway, ); const nftImageAlt = getNftImageAlt(nft); - const isImageHosted = image.startsWith('https:'); + const isImageHosted = image?.startsWith('https:'); const nftImageURL = imageOriginal?.startsWith('ipfs') ? nftImage : image; diff --git a/ui/components/app/nfts-tab/nfts-tab.js b/ui/components/app/nfts-tab/nfts-tab.js index 9b4af2f201bd..66a8c1056b54 100644 --- a/ui/components/app/nfts-tab/nfts-tab.js +++ b/ui/components/app/nfts-tab/nfts-tab.js @@ -24,6 +24,8 @@ import { import { Box, ButtonLink, IconName, Text } from '../../component-library'; import NFTsDetectionNoticeNFTsTab from '../nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab'; import NftsItems from '../nfts-items'; +import { AssetListConversionButton } from '../../multichain'; +import { ASSET_LIST_CONVERSION_BUTTON_VARIANT_TYPES } from '../../multichain/asset-list-conversion-button/asset-list-conversion-button'; export default function NftsTab() { const useNftDetection = useSelector(getUseNftDetection); @@ -46,14 +48,16 @@ export default function NftsTab() { checkAndUpdateAllNftsOwnershipStatus(); }; + const hasAnyNfts = Object.keys(collections).length > 0; + const showNftBanner = process.env.MULTICHAIN && hasAnyNfts === false; + if (nftsLoading) { return
{t('loadingNFTs')}
; } return ( - {Object.keys(collections).length > 0 || - previouslyOwnedCollection.nfts.length > 0 ? ( + {hasAnyNfts > 0 || previouslyOwnedCollection.nfts.length > 0 ? ( {isMainnet && !useNftDetection ? ( - + ) : null} + {showNftBanner ? ( + + + global.platform.openTab({ url: ZENDESK_URLS.NFT_TOKENS }) + } + /> + + ) : null} { expect(historyPushMock).toHaveBeenCalledWith(SECURITY_ROUTE); }); }); + + describe('nft conversion banner', () => { + it('shows the NFT conversion banner when there are no NFTs', () => { + process.env.MULTICHAIN = 1; + const { queryByText } = render({ + selectedAddress: ACCOUNT_1, + nfts: [], + }); + + expect(queryByText('Learn more about NFTs')).toBeInTheDocument(); + }); + + it('does not show the NFT conversion banner when there are NFTs', () => { + process.env.MULTICHAIN = 1; + const { queryByText } = render({ + selectedAddress: ACCOUNT_1, + nfts: NFTS, + }); + + expect(queryByText('Learn more about NFTs')).not.toBeInTheDocument(); + }); + }); }); diff --git a/ui/components/app/permission-cell/permission-cell-options.js b/ui/components/app/permission-cell/permission-cell-options.js index ec7dda4cd5c8..5cefdf7f7d8f 100644 --- a/ui/components/app/permission-cell/permission-cell-options.js +++ b/ui/components/app/permission-cell/permission-cell-options.js @@ -55,6 +55,7 @@ export const PermissionCellOptions = ({ iconName={IconName.MoreVertical} ariaLabel={t('options')} onClick={handleOpen} + data-testid={permissionName} /> {showOptions && (
diff --git a/ui/components/app/permissions-connect-header/permissions-connect-header.component.js b/ui/components/app/permissions-connect-header/permissions-connect-header.component.js index 725db0fc4e78..fcbccf1d1f87 100644 --- a/ui/components/app/permissions-connect-header/permissions-connect-header.component.js +++ b/ui/components/app/permissions-connect-header/permissions-connect-header.component.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import classnames from 'classnames'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) -import { SubjectType } from '@metamask/subject-metadata-controller'; +import { SubjectType } from '@metamask/permission-controller'; ///: END:ONLY_INCLUDE_IN import SiteOrigin from '../../ui/site-origin'; import Box from '../../ui/box'; diff --git a/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap b/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap index a5395a81917f..277990b592da 100644 --- a/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap +++ b/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap @@ -10,7 +10,9 @@ exports[`Security Provider Banner Alert should match snapshot 1`] = ` class="mm-box mm-icon mm-icon--size-lg mm-box--display-inline-block mm-box--color-error-default" style="mask-image: url('./images/icons/danger.svg');" /> -
+

@@ -52,6 +54,11 @@ exports[`Security Provider Banner Alert should match snapshot 1`] = ` List item +

+ [somethingDoesntLookRight] +