From 9785d8ae1c7787b975684e80f76973605536efc3 Mon Sep 17 00:00:00 2001 From: Chloe Gao Date: Thu, 25 Jan 2024 17:58:36 +0100 Subject: [PATCH 01/49] v11.10.0 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 092e55e4f89b..f655c96a0e51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.10.0] + ## [11.7.3] ### Fixed - Ensure fiat token balances are displayed on the homescreen [#22295](https://github.com/MetaMask/metamask-extension/pull/22295) @@ -4266,7 +4268,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c ### Uncategorized - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.7.3...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.10.0...HEAD +[11.10.0]: https://github.com/MetaMask/metamask-extension/compare/v11.7.3...v11.10.0 [11.7.3]: https://github.com/MetaMask/metamask-extension/compare/v11.7.2...v11.7.3 [11.7.2]: https://github.com/MetaMask/metamask-extension/compare/v11.7.1...v11.7.2 [11.7.1]: https://github.com/MetaMask/metamask-extension/compare/v11.7.0...v11.7.1 diff --git a/package.json b/package.json index 6ec20d359401..684e3527a2d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "11.7.3", + "version": "11.10.0", "private": true, "repository": { "type": "git", From 4c90ff7920e554f726b9975ff0dd3506cc387473 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 7 Feb 2024 13:22:29 +0100 Subject: [PATCH 02/49] Cherry pick hook fix to RC (#22849) ## **Description** Cherry-picks https://github.com/MetaMask/metamask-extension/commit/8ec8643f3f975ddccbe2b623d038d13f8e5bda78 to the RC. This fixes the `getIsLocked` hook, which would previously always return `false`. We were using the result of `appStateController.isUnlocked`, but that's a function rather than a boolean. Co-authored-by: Maarten Zuidhoorn --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2d030212fc61..952347a7b6df 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4861,7 +4861,7 @@ export default class MetamaskController extends EventEmitter { origin, ), getIsLocked: () => { - return !this.appStateController.isUnlocked; + return !this.appStateController.isUnlocked(); }, ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) From a1d3446085d94dd51fe179088f4edd8ce2159ee7 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 7 Feb 2024 20:55:14 -0330 Subject: [PATCH 03/49] Cherry-picks 7a3d9e2 (#22676) into Version-v11.10.0 (#22858) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry-picks 7a3d9e2 (Cancel transaction signing from activity list (#22676)) into Version-v11.10.0 ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. Co-authored-by: Matthew Walsh --- app/_locales/en/messages.json | 6 + app/scripts/metamask-controller.js | 2 + lavamoat/browserify/beta/policy.json | 47 ++---- lavamoat/browserify/desktop/policy.json | 47 ++---- lavamoat/browserify/flask/policy.json | 47 ++---- lavamoat/browserify/main/policy.json | 47 ++---- lavamoat/browserify/mmi/policy.json | 47 ++---- package.json | 2 +- test/data/mock-pending-transaction-data.json | 2 +- .../app/cancel-button/cancel-button.js | 27 ++-- ...onfirm-page-container-content.component.js | 18 ++- ...m-page-container-content.component.test.js | 14 ++ .../transaction-list-item.component.js | 34 +++- .../transaction-list-item.component.test.js | 116 ++++++++++---- .../transaction-status-label.test.js.snap | 21 +-- .../app/transaction-status-label/index.scss | 1 + .../transaction-status-label.js | 24 ++- .../transaction-status-label.test.js | 22 +-- ui/helpers/constants/error-keys.js | 1 + .../confirm-transaction-base.component.js | 14 +- .../confirm-transaction-base.container.js | 6 + ui/selectors/transactions.js | 13 ++ ui/selectors/transactions.test.js | 47 ++++++ ui/store/actions.test.js | 18 +++ ui/store/actions.ts | 14 ++ yarn.lock | 147 +++++++++++++----- 26 files changed, 481 insertions(+), 303 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 99f28118f8eb..4770ccf2e6fb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2209,6 +2209,9 @@ "ipfsToggleModalSettings": { "message": "Settings > Security and privacy" }, + "isSigningOrSubmitting": { + "message": "A previous transaction is still being signed or submitted" + }, "jazzAndBlockies": { "message": "Jazzicons and Blockies are two different styles of unique icons that help you identify an account at a glance." }, @@ -4273,6 +4276,9 @@ "signin": { "message": "Sign-In" }, + "signing": { + "message": "Signing" + }, "simulationErrorMessageV2": { "message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail." }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 47c298ebb679..6d4d16b841b9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -3024,6 +3024,8 @@ export default class MetamaskController extends EventEmitter { txController.updateTransactionSendFlowHistory.bind(txController), updatePreviousGasParams: txController.updatePreviousGasParams.bind(txController), + abortTransactionSigning: + txController.abortTransactionSigning.bind(txController), // decryptMessageController decryptMessage: this.decryptMessageController.decryptMessage.bind( diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 8ed58efdfc6c..0ffa820ae3ff 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -741,7 +741,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1462,6 +1462,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1478,8 +1484,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1488,37 +1494,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": 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/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1830,7 +1805,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -1875,7 +1850,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index e153c85706b9..6237039b011a 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -741,7 +741,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1539,6 +1539,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1555,8 +1561,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1565,37 +1571,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": 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/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1971,7 +1946,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -2016,7 +1991,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index d475accd352f..4ef6f7bd2812 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -741,7 +741,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1539,6 +1539,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1555,8 +1561,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1565,37 +1571,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": 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/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -2007,7 +1982,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -2052,7 +2027,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 0a169bea7d35..f0c1fccbf7b4 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -741,7 +741,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1462,6 +1462,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1478,8 +1484,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1488,37 +1494,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": 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/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1930,7 +1905,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -1975,7 +1950,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index f311b03a3bd9..339a51643b09 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -873,7 +873,7 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1594,6 +1594,12 @@ "immer": true } }, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { + "packages": { + "@metamask/ethjs>number-to-bn": true, + "bn.js": true + } + }, "@metamask/logo": { "globals": { "addEventListener": true, @@ -1610,8 +1616,8 @@ }, "@metamask/message-manager": { "packages": { - "@metamask/message-manager>@metamask/base-controller": true, - "@metamask/message-manager>@metamask/controller-utils": true, + "@metamask/base-controller": true, + "@metamask/controller-utils": true, "@metamask/message-manager>@metamask/eth-sig-util": true, "@metamask/message-manager>jsonschema": true, "browserify>buffer": true, @@ -1620,37 +1626,6 @@ "webpack>events": true } }, - "@metamask/message-manager>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": 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/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, "@metamask/message-manager>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -2026,7 +2001,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -2071,7 +2046,7 @@ }, "packages": { "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/message-manager>@metamask/controller-utils>ethjs-unit": true, + "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, diff --git a/package.json b/package.json index 5628576505ff..c6f18680c51d 100644 --- a/package.json +++ b/package.json @@ -296,7 +296,7 @@ "@metamask/snaps-rpc-methods": "^5.0.0", "@metamask/snaps-sdk": "^1.4.0", "@metamask/snaps-utils": "^5.2.0", - "@metamask/transaction-controller": "^20.0.0", + "@metamask/transaction-controller": "^21.1.0", "@metamask/user-operation-controller": "^1.0.0", "@metamask/utils": "^8.2.1", "@ngraveio/bc-ur": "^1.1.6", diff --git a/test/data/mock-pending-transaction-data.json b/test/data/mock-pending-transaction-data.json index 36f1d74767fe..12575a92df6c 100644 --- a/test/data/mock-pending-transaction-data.json +++ b/test/data/mock-pending-transaction-data.json @@ -78,7 +78,7 @@ "primaryTransaction": { "id": 6854191329910881, "time": 1631558469046, - "status": "approved", + "status": "submitted", "chainId": "0x5", "loadingDefaults": false, "dappSuggestedGasFees": null, diff --git a/ui/components/app/cancel-button/cancel-button.js b/ui/components/app/cancel-button/cancel-button.js index c751fa668847..37e454db62f2 100644 --- a/ui/components/app/cancel-button/cancel-button.js +++ b/ui/components/app/cancel-button/cancel-button.js @@ -3,6 +3,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import classnames from 'classnames'; +import { TransactionStatus } from '@metamask/transaction-controller'; import Button from '../../ui/button'; import { getMaximumGasTotalInHexWei } from '../../../../shared/modules/gas.utils'; import { getConversionRate } from '../../../ducks/metamask/metamask'; @@ -17,18 +18,20 @@ export default function CancelButton({ detailsModal, }) { const t = useI18nContext(); - + const { status } = transaction; const customCancelGasSettings = useIncrementedGasFees(transaction); - const selectedAccount = useSelector(getSelectedAccount); const conversionRate = useSelector(getConversionRate); - const hasEnoughCancelGas = isBalanceSufficient({ - amount: '0x0', - gasTotal: getMaximumGasTotalInHexWei(customCancelGasSettings), - balance: selectedAccount.balance, - conversionRate, - }); + const isDisabled = + status === TransactionStatus.approved + ? false + : !isBalanceSufficient({ + amount: '0x0', + gasTotal: getMaximumGasTotalInHexWei(customCancelGasSettings), + balance: selectedAccount.balance, + conversionRate, + }); const btn = ( ); - return hasEnoughCancelGas ? ( - btn - ) : ( + return isDisabled ? (
{btn}
+ ) : ( + btn ); } diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index acc4d81fd378..a8b932117e07 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -11,7 +11,10 @@ import { BannerAlert, } from '../../../component-library'; import { PageContainerFooter } from '../../../ui/page-container'; -import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../../helpers/constants/error-keys'; +import { + INSUFFICIENT_FUNDS_ERROR_KEY, + IS_SIGNING_OR_SUBMITTING, +} from '../../../../helpers/constants/error-keys'; import { Severity } from '../../../../helpers/constants/design-system'; import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'; @@ -185,6 +188,9 @@ export default class ConfirmPageContainerContent extends Component { const showInsuffienctFundsError = (errorKey || errorMessage) && errorKey === INSUFFICIENT_FUNDS_ERROR_KEY; + const showIsSigningOrSubmittingError = + errorKey === IS_SIGNING_OR_SUBMITTING; + return (
)} + {showIsSigningOrSubmittingError && ( + + )} { ); expect(getByRole('button', { name: 'Buy' })).toBeInTheDocument(); }); + + it('should show is signing or submitting error', () => { + const { queryByText } = renderWithProvider( + , + store, + ); + expect( + queryByText('A previous transaction is still being signed or submitted'), + ).toBeInTheDocument(); + }); }); diff --git a/ui/components/app/transaction-list-item/transaction-list-item.component.js b/ui/components/app/transaction-list-item/transaction-list-item.component.js index e48d5d7682c3..76688f59b0cb 100644 --- a/ui/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/components/app/transaction-list-item/transaction-list-item.component.js @@ -3,7 +3,7 @@ import React, { useMemo, useState, useCallback, useContext } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { TransactionStatus } from '@metamask/transaction-controller'; import { useTransactionDisplayData } from '../../../hooks/useTransactionDisplayData'; @@ -64,6 +64,7 @@ import EditGasFeePopover from '../edit-gas-fee-popover'; import EditGasPopover from '../edit-gas-popover'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { ActivityListItem } from '../../multichain'; +import { abortTransactionSigning } from '../../../store/actions'; function TransactionListItemInner({ transactionGroup, @@ -80,6 +81,7 @@ function TransactionListItemInner({ const { supportsEIP1559 } = useGasFeeContext(); const { openModal } = useTransactionModalContext(); const testNetworkBackgroundColor = useSelector(getTestNetworkBackgroundColor); + const dispatch = useDispatch(); const { initialTransaction: { id }, @@ -120,14 +122,24 @@ function TransactionListItemInner({ legacy_event: true, }, }); - if (supportsEIP1559) { + if (status === TransactionStatus.approved) { + dispatch(abortTransactionSigning(id)); + } else if (supportsEIP1559) { setEditGasMode(EditGasModes.cancel); openModal('cancelSpeedUpTransaction'); } else { setShowCancelEditGasPopover(true); } }, - [trackEvent, openModal, setEditGasMode, supportsEIP1559], + [ + trackEvent, + openModal, + setEditGasMode, + supportsEIP1559, + status, + dispatch, + id, + ], ); const shouldShowSpeedUp = useShouldShowSpeedUp( @@ -154,6 +166,8 @@ function TransactionListItemInner({ const isApproval = category === TransactionGroupCategory.approval; const isUnapproved = status === TransactionStatus.unapproved; const isSwap = category === TransactionGroupCategory.swap; + const isSigning = status === TransactionStatus.approved; + const isSubmitting = status === TransactionStatus.signed; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) const isCustodian = Boolean(transactionGroup.primaryTransaction.custodyId); ///: END:ONLY_INCLUDE_IF @@ -206,12 +220,19 @@ function TransactionListItemInner({ } ///: END:ONLY_INCLUDE_IF - if (!shouldShowSpeedUp || !isPending || isUnapproved) { + if ( + !shouldShowSpeedUp || + !isPending || + isUnapproved || + isSigning || + isSubmitting + ) { return null; } return (
`; -exports[`TransactionStatusLabel Component should render PENDING properly when status is APPROVED 1`] = ` +exports[`TransactionStatusLabel Component should render QUEUED properly 1`] = `
-
- Pending -
+ Queued
`; -exports[`TransactionStatusLabel Component should render QUEUED properly 1`] = ` +exports[`TransactionStatusLabel Component should render SIGNING if status is approved 1`] = `
- Queued + Signing
`; diff --git a/ui/components/app/transaction-status-label/index.scss b/ui/components/app/transaction-status-label/index.scss index 79e14bc20d77..cc0cf5d2c8cd 100644 --- a/ui/components/app/transaction-status-label/index.scss +++ b/ui/components/app/transaction-status-label/index.scss @@ -13,6 +13,7 @@ } &--unapproved, + &--signing, &--pending { color: var(--color-warning-default); } diff --git a/ui/components/app/transaction-status-label/transaction-status-label.js b/ui/components/app/transaction-status-label/transaction-status-label.js index 7c912007152e..bb5824acb05d 100644 --- a/ui/components/app/transaction-status-label/transaction-status-label.js +++ b/ui/components/app/transaction-status-label/transaction-status-label.js @@ -7,6 +7,7 @@ import { useI18nContext } from '../../../hooks/useI18nContext'; import { TransactionGroupStatus } from '../../../../shared/constants/transaction'; const QUEUED_PSEUDO_STATUS = 'queued'; +const SIGNING_PSUEDO_STATUS = 'signing'; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) const CUSTODIAN_PSEUDO_STATUS = 'inCustody'; ///: END:ONLY_INCLUDE_IF @@ -24,7 +25,6 @@ const CUSTODIAN_PSEUDO_STATUS = 'inCustody'; const pendingStatusHash = { [TransactionStatus.submitted]: TransactionGroupStatus.pending, [TransactionStatus.approved]: TransactionGroupStatus.pending, - [TransactionStatus.signed]: TransactionGroupStatus.pending, }; const statusToClassNameHash = { @@ -40,6 +40,20 @@ const statusToClassNameHash = { ///: END:ONLY_INCLUDE_IF }; +function getStatusKey(status, isEarliestNonce) { + if (status === TransactionStatus.approved) { + return SIGNING_PSUEDO_STATUS; + } + + if (pendingStatusHash[status]) { + return isEarliestNonce + ? TransactionGroupStatus.pending + : QUEUED_PSEUDO_STATUS; + } + + return status; +} + export default function TransactionStatusLabel({ status, date, @@ -53,14 +67,8 @@ export default function TransactionStatusLabel({ ///: END:ONLY_INCLUDE_IF }) { const t = useI18nContext(); + const statusKey = getStatusKey(status, isEarliestNonce); let tooltipText = error?.rpc?.message || error?.message; - let statusKey = status; - if (pendingStatusHash[status]) { - statusKey = isEarliestNonce - ? TransactionGroupStatus.pending - : QUEUED_PSEUDO_STATUS; - } - let statusText = statusKey && t(statusKey); ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) diff --git a/ui/components/app/transaction-status-label/transaction-status-label.test.js b/ui/components/app/transaction-status-label/transaction-status-label.test.js index 28f6cd17bdc9..e05dee4808aa 100644 --- a/ui/components/app/transaction-status-label/transaction-status-label.test.js +++ b/ui/components/app/transaction-status-label/transaction-status-label.test.js @@ -2,6 +2,7 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { EthAccountType, EthMethod } from '@metamask/keyring-api'; +import { TransactionStatus } from '@metamask/transaction-controller'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import TransactionStatusLabel from '.'; @@ -46,11 +47,11 @@ describe('TransactionStatusLabel Component', () => { expect(container).toMatchSnapshot(); }); - it('should render PENDING properly when status is APPROVED', () => { + it('should render PENDING properly', () => { const props = { - status: 'approved', + date: 'June 1', + status: TransactionStatus.submitted, isEarliestNonce: true, - error: { message: 'test-title' }, }; const { container } = renderWithProvider( @@ -61,11 +62,10 @@ describe('TransactionStatusLabel Component', () => { expect(container).toMatchSnapshot(); }); - it('should render PENDING properly', () => { + it('should render QUEUED properly', () => { const props = { - date: 'June 1', - status: 'submitted', - isEarliestNonce: true, + status: TransactionStatus.submitted, + isEarliestNonce: false, }; const { container } = renderWithProvider( @@ -76,9 +76,9 @@ describe('TransactionStatusLabel Component', () => { expect(container).toMatchSnapshot(); }); - it('should render QUEUED properly', () => { + it('should render UNAPPROVED properly', () => { const props = { - status: 'queued', + status: TransactionStatus.unapproved, }; const { container } = renderWithProvider( @@ -89,9 +89,9 @@ describe('TransactionStatusLabel Component', () => { expect(container).toMatchSnapshot(); }); - it('should render UNAPPROVED properly', () => { + it('should render SIGNING if status is approved', () => { const props = { - status: 'unapproved', + status: TransactionStatus.approved, }; const { container } = renderWithProvider( diff --git a/ui/helpers/constants/error-keys.js b/ui/helpers/constants/error-keys.js index 7d3cb020f724..dfa4cc142806 100644 --- a/ui/helpers/constants/error-keys.js +++ b/ui/helpers/constants/error-keys.js @@ -7,3 +7,4 @@ export const GAS_PRICE_EXCESSIVE_ERROR_KEY = 'gasPriceExcessive'; export const UNSENDABLE_ASSET_ERROR_KEY = 'unsendableAsset'; export const INSUFFICIENT_FUNDS_FOR_GAS_ERROR_KEY = 'insufficientFundsForGas'; export const INVALID_ASSET_TYPE = 'invalidAssetType'; +export const IS_SIGNING_OR_SUBMITTING = 'isSigningOrSubmitting'; diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js index 27ad6263e486..3a300b37f2e6 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -12,6 +12,7 @@ import { GAS_LIMIT_TOO_LOW_ERROR_KEY, ETH_GAS_PRICE_FETCH_WARNING_KEY, GAS_PRICE_FETCH_FAILURE_ERROR_KEY, + IS_SIGNING_OR_SUBMITTING, } from '../../helpers/constants/error-keys'; import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'; @@ -153,6 +154,7 @@ export default class ConfirmTransactionBase extends Component { tokenSymbol: PropTypes.string, updateTransaction: PropTypes.func, isUsingPaymaster: PropTypes.bool, + isSigningOrSubmitting: PropTypes.bool, }; state = { @@ -243,6 +245,7 @@ export default class ConfirmTransactionBase extends Component { customGas, noGasPrice, gasFeeIsCustom, + isSigningOrSubmitting, } = this.props; const insufficientBalance = @@ -275,6 +278,13 @@ export default class ConfirmTransactionBase extends Component { }; } + if (isSigningOrSubmitting) { + return { + valid: false, + errorKey: IS_SIGNING_OR_SUBMITTING, + }; + } + return { valid: true, }; @@ -958,6 +968,7 @@ export default class ConfirmTransactionBase extends Component { assetStandard, displayAccountBalanceHeader, title, + isSigningOrSubmitting, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) isNoteToTraderSupported, ///: END:ONLY_INCLUDE_IF @@ -1051,7 +1062,8 @@ export default class ConfirmTransactionBase extends Component { !valid || submitting || hardwareWalletRequiresConnection || - (gasIsLoading && !gasFeeIsCustom) + (gasIsLoading && !gasFeeIsCustom) || + isSigningOrSubmitting } onEdit={() => this.handleEdit()} onCancelAll={() => this.handleCancelAll()} diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js index a9b4042b7aff..8f2400ffd519 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -50,6 +50,7 @@ import { getUseCurrencyRateCheck, getUnapprovedTransactions, getInternalAccountByAddress, + getApprovedAndSignedTransactions, } from '../../selectors'; import { getMostRecentOverviewPage } from '../../ducks/history/history'; import { @@ -247,6 +248,10 @@ const mapStateToProps = (state, ownProps) => { const isMultiLayerFeeNetwork = getIsMultiLayerFeeNetwork(state); const isUsingPaymaster = getIsUsingPaymaster(state); + const isSigningOrSubmitting = Boolean( + getApprovedAndSignedTransactions(state).length, + ); + return { balance, fromAddress, @@ -299,6 +304,7 @@ const mapStateToProps = (state, ownProps) => { useCurrencyRateCheck: getUseCurrencyRateCheck(state), keyringForAccount: keyring, isUsingPaymaster, + isSigningOrSubmitting, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) accountType, isNoteToTraderSupported, diff --git a/ui/selectors/transactions.js b/ui/selectors/transactions.js index 13496dd32a79..5c116b2cd866 100644 --- a/ui/selectors/transactions.js +++ b/ui/selectors/transactions.js @@ -56,6 +56,19 @@ export const getUnapprovedTransactions = createDeepEqualSelector( (transactions) => transactions, ); +export const getApprovedAndSignedTransactions = createDeepEqualSelector( + (state) => { + const currentNetworkTransactions = getCurrentNetworkTransactions(state); + + return currentNetworkTransactions.filter((transaction) => + [TransactionStatus.approved, TransactionStatus.signed].includes( + transaction.status, + ), + ); + }, + (transactions) => transactions, +); + export const incomingTxListSelector = createDeepEqualSelector( (state) => { const { incomingTransactionsPreferences } = state.metamask; diff --git a/ui/selectors/transactions.test.js b/ui/selectors/transactions.test.js index 697ef64f841f..16a57a004fe8 100644 --- a/ui/selectors/transactions.test.js +++ b/ui/selectors/transactions.test.js @@ -10,6 +10,7 @@ import { nonceSortedCompletedTransactionsSelector, submittedPendingTransactionsSelector, hasTransactionPendingApprovals, + getApprovedAndSignedTransactions, } from './transactions'; describe('Transaction Selectors', () => { @@ -544,4 +545,50 @@ describe('Transaction Selectors', () => { }, ); }); + + describe('getApprovedAndSignedTransactions', () => { + it('returns transactions with status of approved or signed on current network', () => { + const state = { + metamask: { + providerConfig: { + chainId: CHAIN_IDS.MAINNET, + }, + transactions: [ + { + id: 0, + chainId: CHAIN_IDS.MAINNET, + status: TransactionStatus.approved, + }, + { + id: 1, + chainId: CHAIN_IDS.MAINNET, + status: TransactionStatus.submitted, + }, + { + id: 2, + chainId: CHAIN_IDS.MAINNET, + status: TransactionStatus.unapproved, + }, + { + id: 3, + chainId: CHAIN_IDS.MAINNET, + status: TransactionStatus.signed, + }, + { + id: 4, + chainId: CHAIN_IDS.GOERLI, + status: TransactionStatus.signed, + }, + ], + }, + }; + + const results = getApprovedAndSignedTransactions(state); + + expect(results).toStrictEqual([ + state.metamask.transactions[0], + state.metamask.transactions[3], + ]); + }); + }); }); diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index ec2fefaed364..a2f74da3b759 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -63,6 +63,7 @@ describe('Actions', () => { background.signMessage = sinon.stub(); background.signPersonalMessage = sinon.stub(); background.signTypedMessage = sinon.stub(); + background.abortTransactionSigning = sinon.stub(); }); describe('#tryUnlockMetamask', () => { @@ -2110,6 +2111,23 @@ describe('Actions', () => { }); }); + describe('abortTransactionSigning', () => { + it('submits request to background', async () => { + const transactionIdMock = '123-456'; + const store = mockStore(); + + setBackgroundConnection(background); + + store.dispatch(actions.abortTransactionSigning(transactionIdMock)); + + expect(background.abortTransactionSigning.callCount).toStrictEqual(1); + expect(background.abortTransactionSigning.getCall(0).args).toStrictEqual([ + transactionIdMock, + expect.any(Function), + ]); + }); + }); + describe('Desktop', () => { describe('#setDesktopEnabled', () => { it('calls background setDesktopEnabled method', async () => { diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 6d18be0f5d02..a553a1e8cdf9 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -2117,6 +2117,20 @@ export function clearPendingTokens(): Action { }; } +export function abortTransactionSigning( + transactionId: string, +): ThunkAction, MetaMaskReduxState, any, AnyAction> { + return async (dispatch: MetaMaskReduxDispatch) => { + try { + await submitRequestToBackground('abortTransactionSigning', [ + transactionId, + ]); + } catch (error) { + dispatch(displayWarning(error)); + } + }; +} + export function createCancelTransaction( txId: string, customGasSettings: CustomGasSettings, diff --git a/yarn.lock b/yarn.lock index f0c61e358193..3f825c1cab85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3946,18 +3946,18 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@npm:^8.0.1": - version: 8.0.1 - resolution: "@metamask/controller-utils@npm:8.0.1" +"@metamask/controller-utils@npm:^8.0.1, @metamask/controller-utils@npm:^8.0.2": + version: 8.0.2 + resolution: "@metamask/controller-utils@npm:8.0.2" dependencies: "@metamask/eth-query": "npm:^4.0.0" "@metamask/ethjs-unit": "npm:^0.2.1" - "@metamask/utils": "npm:^8.2.0" + "@metamask/utils": "npm:^8.3.0" "@spruceid/siwe-parser": "npm:1.1.3" eth-ens-namehash: "npm:^2.0.8" ethereumjs-util: "npm:^7.0.10" fast-deep-equal: "npm:^3.1.3" - checksum: c3f125d9a33a003e369b0312af4634f6205729ce718046a1f0add9e135a0602b405b948ddbd4caf700ba21c724c8ac9936f3e96630fb9286c9e0a312ef552e62 + checksum: 6a8099b883c51b47494678998fb14291cd0ea9904823b8e3a8cd1621dfc321b59b071e0f264225901177e4826499c32243d5b18388c521bbef351ab87a9d332b languageName: node linkType: hard @@ -4089,14 +4089,14 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-json-rpc-provider@npm:^2.1.0, @metamask/eth-json-rpc-provider@npm:^2.2.0, @metamask/eth-json-rpc-provider@npm:^2.3.1": - version: 2.3.1 - resolution: "@metamask/eth-json-rpc-provider@npm:2.3.1" +"@metamask/eth-json-rpc-provider@npm:^2.1.0, @metamask/eth-json-rpc-provider@npm:^2.2.0, @metamask/eth-json-rpc-provider@npm:^2.3.2": + version: 2.3.2 + resolution: "@metamask/eth-json-rpc-provider@npm:2.3.2" dependencies: - "@metamask/json-rpc-engine": "npm:^7.3.1" + "@metamask/json-rpc-engine": "npm:^7.3.2" "@metamask/safe-event-emitter": "npm:^3.0.0" - "@metamask/utils": "npm:^8.2.0" - checksum: fa0a987eb7e0dcff495489e95c358f6786a4a793a42ac900bb022027d27e6534ded743092e79a2191b9b4d760f418f39f6cfb99a5a5a0085f252016579be6865 + "@metamask/utils": "npm:^8.3.0" + checksum: e6731271aad3b972d85b9230c26d35a9b88722f3bd3024675ad2f568e634e9fdfef4717ef2892f3cc512d381cf17a4e20dbd5eb808ced765082bea3379ad6ddc languageName: node linkType: hard @@ -4148,7 +4148,7 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-sig-util@npm:^7.0.0": +"@metamask/eth-sig-util@npm:^7.0.0, @metamask/eth-sig-util@npm:^7.0.1": version: 7.0.1 resolution: "@metamask/eth-sig-util@npm:7.0.1" dependencies: @@ -4358,6 +4358,26 @@ __metadata: languageName: node linkType: hard +"@metamask/gas-fee-controller@npm:^13.0.0": + version: 13.0.0 + resolution: "@metamask/gas-fee-controller@npm:13.0.0" + dependencies: + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/controller-utils": "npm:^8.0.2" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/ethjs-unit": "npm:^0.2.1" + "@metamask/network-controller": "npm:^17.2.0" + "@metamask/polling-controller": "npm:^5.0.0" + "@metamask/utils": "npm:^8.3.0" + "@types/uuid": "npm:^8.3.0" + ethereumjs-util: "npm:^7.0.10" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/network-controller": ^17.2.0 + checksum: 8edbe4412a1bf80eb3a3c6305c35b45ee38f9262a4dd3b38c30d2e81e0f84d2f8ee261e3548fb25a049f53cf76ccf8238c4aca31ec6cd0c5b46ba9c58a705bc8 + languageName: node + linkType: hard + "@metamask/jazzicon@npm:^2.0.0": version: 2.0.0 resolution: "@metamask/jazzicon@npm:2.0.0" @@ -4368,14 +4388,14 @@ __metadata: languageName: node linkType: hard -"@metamask/json-rpc-engine@npm:^7.1.1, @metamask/json-rpc-engine@npm:^7.2.0, @metamask/json-rpc-engine@npm:^7.3.1": - version: 7.3.1 - resolution: "@metamask/json-rpc-engine@npm:7.3.1" +"@metamask/json-rpc-engine@npm:^7.1.1, @metamask/json-rpc-engine@npm:^7.2.0, @metamask/json-rpc-engine@npm:^7.3.1, @metamask/json-rpc-engine@npm:^7.3.2": + version: 7.3.2 + resolution: "@metamask/json-rpc-engine@npm:7.3.2" dependencies: "@metamask/rpc-errors": "npm:^6.1.0" "@metamask/safe-event-emitter": "npm:^3.0.0" - "@metamask/utils": "npm:^8.2.0" - checksum: 3988ec9229964c8c3c9172ad8ef36bea2f20e63d0dd720431db89a0031b7692c5ebaa2c53afe9896a823b334f4d5eb718e48a5db00477fe8b7643d332ae3010d + "@metamask/utils": "npm:^8.3.0" + checksum: d90e5fdf88461aa90af41ba0304729200afa8226ab8b73db348704a1f545e416c49281a1dfd58591dde769e1ab263080b26d5a0ab1be8362398639dc2d6354de languageName: node linkType: hard @@ -4485,18 +4505,18 @@ __metadata: linkType: hard "@metamask/message-manager@npm:^7.3.0, @metamask/message-manager@npm:^7.3.5": - version: 7.3.5 - resolution: "@metamask/message-manager@npm:7.3.5" + version: 7.3.8 + resolution: "@metamask/message-manager@npm:7.3.8" dependencies: - "@metamask/base-controller": "npm:^3.2.3" - "@metamask/controller-utils": "npm:^5.0.2" - "@metamask/eth-sig-util": "npm:^7.0.0" - "@metamask/utils": "npm:^8.1.0" + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/controller-utils": "npm:^8.0.2" + "@metamask/eth-sig-util": "npm:^7.0.1" + "@metamask/utils": "npm:^8.3.0" "@types/uuid": "npm:^8.3.0" ethereumjs-util: "npm:^7.0.10" jsonschema: "npm:^1.2.4" uuid: "npm:^8.3.2" - checksum: 6ee9214847d24228dcb2efe25ef1f40c82dd021780ff3e4bcc03d3703eb03ffdbc92f4a85e8276a499215dec994a4aa6bc6e0ab8a1cdfffcf36ba54f604dfd2a + checksum: 0aa94422751e405b542af1f2b6a6f89aa61c991a9a9312b2a4d5a2bbb007aae84bda2dae2e084206003bbfe75f06bd170820d88c9a39492a0f8f0e9edcb4afe3 languageName: node linkType: hard @@ -4540,25 +4560,25 @@ __metadata: languageName: node linkType: hard -"@metamask/network-controller@npm:^17.0.0, @metamask/network-controller@npm:^17.1.0": - version: 17.1.0 - resolution: "@metamask/network-controller@npm:17.1.0" +"@metamask/network-controller@npm:^17.0.0, @metamask/network-controller@npm:^17.1.0, @metamask/network-controller@npm:^17.2.0": + version: 17.2.0 + resolution: "@metamask/network-controller@npm:17.2.0" dependencies: - "@metamask/base-controller": "npm:^4.0.1" - "@metamask/controller-utils": "npm:^8.0.1" + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/controller-utils": "npm:^8.0.2" "@metamask/eth-json-rpc-infura": "npm:^9.0.0" "@metamask/eth-json-rpc-middleware": "npm:^12.0.1" - "@metamask/eth-json-rpc-provider": "npm:^2.3.1" + "@metamask/eth-json-rpc-provider": "npm:^2.3.2" "@metamask/eth-query": "npm:^4.0.0" - "@metamask/json-rpc-engine": "npm:^7.3.1" + "@metamask/json-rpc-engine": "npm:^7.3.2" "@metamask/rpc-errors": "npm:^6.1.0" - "@metamask/swappable-obj-proxy": "npm:^2.1.0" - "@metamask/utils": "npm:^8.2.0" + "@metamask/swappable-obj-proxy": "npm:^2.2.0" + "@metamask/utils": "npm:^8.3.0" async-mutex: "npm:^0.2.6" eth-block-tracker: "npm:^8.0.0" immer: "npm:^9.0.6" uuid: "npm:^8.3.2" - checksum: 57408c06a307cfeb604abb4e2e4e04d3985efab92562f3bec618aafcd088aea67a8d9b039834a27fd3d1b72d4a0f0908fee01698f918947c73f052af534bb435 + checksum: 0fa7cf92cdcde5dba68e4d35d0f23c060d43f8da8062922aa2b468a9ab7bbd51a600b93c9f80091bbe0fbc760f263b7d8ddd3e5130662b55fc6d9b4974654285 languageName: node linkType: hard @@ -4680,6 +4700,23 @@ __metadata: languageName: node linkType: hard +"@metamask/polling-controller@npm:^5.0.0": + version: 5.0.0 + resolution: "@metamask/polling-controller@npm:5.0.0" + dependencies: + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/controller-utils": "npm:^8.0.2" + "@metamask/network-controller": "npm:^17.2.0" + "@metamask/utils": "npm:^8.3.0" + "@types/uuid": "npm:^8.3.0" + fast-json-stable-stringify: "npm:^2.1.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/network-controller": ^17.2.0 + checksum: 78655074916c445594787e8573e10a992933477cf702aa458c28396b49dbcd13a60bfb1fac46f680819139f918eacff1f4c1e6386f5d74ceaa3715f8ee48d79f + languageName: node + linkType: hard + "@metamask/post-message-stream@npm:^7.0.0": version: 7.0.0 resolution: "@metamask/post-message-stream@npm:7.0.0" @@ -5050,10 +5087,10 @@ __metadata: languageName: node linkType: hard -"@metamask/swappable-obj-proxy@npm:^2.1.0": - version: 2.1.0 - resolution: "@metamask/swappable-obj-proxy@npm:2.1.0" - checksum: e6628ffbb9c34c07aee2e7f97f00f06eba2d00fcd3f6cac555256dd1b37a73d81eff53030cd129c9dd6db711c9e10c41e857082816f8a0e3de724448c1f2edaa +"@metamask/swappable-obj-proxy@npm:^2.1.0, @metamask/swappable-obj-proxy@npm:^2.2.0": + version: 2.2.0 + resolution: "@metamask/swappable-obj-proxy@npm:2.2.0" + checksum: bc7a1f496d06327f1db84fe2ed75637b6f2f5db0806d3927f250d5abab9cc70a26ff37283ea7f2db7987e48d2540f6821091d1f3000d6771f29c4d91c402f724 languageName: node linkType: hard @@ -5096,6 +5133,38 @@ __metadata: languageName: node linkType: hard +"@metamask/transaction-controller@npm:^21.1.0": + version: 21.1.0 + resolution: "@metamask/transaction-controller@npm:21.1.0" + dependencies: + "@ethereumjs/common": "npm:^3.2.0" + "@ethereumjs/tx": "npm:^4.2.0" + "@ethersproject/abi": "npm:^5.7.0" + "@metamask/approval-controller": "npm:^5.1.2" + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/controller-utils": "npm:^8.0.2" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/gas-fee-controller": "npm:^13.0.0" + "@metamask/metamask-eth-abis": "npm:^3.0.0" + "@metamask/network-controller": "npm:^17.2.0" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/utils": "npm:^8.3.0" + async-mutex: "npm:^0.2.6" + eth-method-registry: "npm:^3.0.0" + ethereumjs-util: "npm:^7.0.10" + fast-json-patch: "npm:^3.1.1" + lodash: "npm:^4.17.21" + nonce-tracker: "npm:^3.0.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/approval-controller": ^5.1.2 + "@metamask/gas-fee-controller": ^13.0.0 + "@metamask/network-controller": ^17.2.0 + babel-runtime: ^6.26.0 + checksum: 5dbe811870fb0775d017470ec4529fbf46969f072527e3ac7ce18370faafffda32056eeec02ff8b56ed35463aec8ceaeb667c64362db90529faf4b8e2c9c2631 + languageName: node + linkType: hard + "@metamask/user-operation-controller@npm:^1.0.0": version: 1.0.0 resolution: "@metamask/user-operation-controller@npm:1.0.0" @@ -24594,7 +24663,7 @@ __metadata: "@metamask/snaps-sdk": "npm:^1.4.0" "@metamask/snaps-utils": "npm:^5.2.0" "@metamask/test-dapp": "npm:^7.3.1" - "@metamask/transaction-controller": "npm:^20.0.0" + "@metamask/transaction-controller": "npm:^21.1.0" "@metamask/user-operation-controller": "npm:^1.0.0" "@metamask/utils": "npm:^8.2.1" "@ngraveio/bc-ur": "npm:^1.1.6" From 95292fa8547bc05b802c1b46db9e2218a5ac3c9a Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 6 Feb 2024 12:56:39 -0330 Subject: [PATCH 04/49] Stop blocking display of transaction approval window on resolution of ppom security alert response (#22778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Solves this problem: For people on slow networks, the amount of time it takes from "Dapp button click" -> "Popup notification window appears" might be a problem. When blockaid is toggled off, and the network is throttled to slow via the chrome dev tools, I see a ~3 second worse case for this time, but when blockaid is toggled on, I see a 12 second (or even a bit longer) worst case scenario for this time. The risk here is that users unexpectedly see a longer delay then usual, and so think they need to click the button again, and then get shown multiple confirmation requests and confirm all of them, leading to fund loss. This can happen on either slow internet connections, or if an infura request is taking longer than usual (which could effect people regardless of internet connection). Yesterday when I first saw this issue, I wasn't slowing down my connection, but it still took ~20 seconds to open. Probably a somewhat rare case (because as I mentioned I didn't notice any problems again until I intentionally throttled the network requests), but even if it only affected 10% of users only once a month, that would be enough to get plenty of "why did metamask just take 20 seconds to open a confirmation window?!?" complaints. Fixes: 1. Go to this page... 2. 3. - [ ] 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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/scripts/lib/ppom/ppom-middleware.ts | 9 +- app/scripts/lib/transaction/util.test.ts | 13 +- app/scripts/lib/transaction/util.ts | 93 +++++-- app/scripts/metamask-controller.js | 2 + .../ppom-blockaid-alert-simple-send.spec.js | 246 ++++++++++++++++++ 5 files changed, 335 insertions(+), 28 deletions(-) create mode 100644 test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index 3653884075e2..d5f3e869c218 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -10,14 +10,7 @@ import { import { CHAIN_IDS } from '../../../../shared/constants/network'; import { SIGNING_METHODS } from '../../../../shared/constants/transaction'; import PreferencesController from '../../controllers/preferences'; - -type SecurityAlertResponse = { - reason: string; - features?: string[]; - result_type: string; - providerRequestsCount?: Record; - securityAlertId?: string; -}; +import { SecurityAlertResponse } from '../transaction/util'; const { sentry } = global as any; diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index fbb8cb9b198a..16cfb21a844d 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -16,6 +16,15 @@ import { addTransaction, } from './util'; +jest.mock('uuid', () => { + const actual = jest.requireActual('uuid'); + + return { + ...actual, + v4: jest.fn(), + }; +}); + const TRANSACTION_PARAMS_MOCK: TransactionParams = { from: '0x1', }; @@ -380,8 +389,8 @@ describe('Transaction Utils', () => { ).toHaveBeenCalledWith(TRANSACTION_PARAMS_MOCK, { ...TRANSACTION_OPTIONS_MOCK, securityAlertResponse: { - reason: 'testReason', - result_type: 'testResultType', + reason: 'loading', + result_type: 'validation_in_progress', }, }); diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index 8e4420b1bc50..b5695dc0d296 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -3,6 +3,10 @@ import { TransactionController, TransactionMeta, TransactionParams, + WalletDevice, + TransactionType, + SendFlowHistoryEntry, + Result, } from '@metamask/transaction-controller'; import { AddUserOperationOptions, @@ -12,11 +16,45 @@ import { import { PPOMController } from '@metamask/ppom-validator'; import { captureException } from '@sentry/browser'; import { addHexPrefix } from 'ethereumjs-util'; +import { v4 as uuid } from 'uuid'; import { SUPPORTED_CHAIN_IDS } from '../ppom/ppom-middleware'; +import { + BlockaidReason, + BlockaidResultType, +} from '../../../../shared/constants/security-provider'; ///: END:ONLY_INCLUDE_IF +/** + * Type for security alert response from transaction validator. + */ +export type SecurityAlertResponse = { + reason: string; + features?: string[]; + result_type: string; + providerRequestsCount?: Record; + securityAlertId?: string; +}; + export type AddTransactionOptions = NonNullable< - Parameters[1] + Parameters< + ( + txParams: TransactionParams, + options?: { + actionId?: string; + deviceConfirmedOn?: WalletDevice; + method?: string; + origin?: string; + requireApproval?: boolean | undefined; + securityAlertResponse?: SecurityAlertResponse; + sendFlowHistory?: SendFlowHistoryEntry[]; + swaps?: { + hasApproveTx?: boolean; + meta?: Partial; + }; + type?: TransactionType; + }, + ) => Promise + >[1] >; type BaseAddTransactionRequest = { @@ -30,16 +68,6 @@ type BaseAddTransactionRequest = { userOperationController: UserOperationController; }; -/** - * Type for security alert response from transaction validator. - */ -export type SecurityAlertResponse = { - reason: string; - features?: string[]; - result_type: string; - providerRequestsCount?: Record; -}; - type FinalAddTransactionRequest = BaseAddTransactionRequest & { transactionOptions: AddTransactionOptions; }; @@ -83,6 +111,12 @@ export async function addDappTransaction( export async function addTransaction( request: AddTransactionRequest, + ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + updateSecurityAlertResponseByTxId: ( + req: AddTransactionOptions | undefined, + securityAlertResponse: SecurityAlertResponse, + ) => void, + ///: END:ONLY_INCLUDE_IF ): Promise { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const { @@ -109,13 +143,36 @@ export async function addTransaction( ], }; - const securityAlertResponse = await ppomController.usePPOM( - async (ppom) => { - return ppom.validateJsonRpc(ppomRequest); - }, - ); - - request.transactionOptions.securityAlertResponse = securityAlertResponse; + const securityAlertId = uuid(); + + ppomController.usePPOM(async (ppom) => { + try { + const securityAlertResponse = await ppom.validateJsonRpc(ppomRequest); + updateSecurityAlertResponseByTxId( + request.transactionOptions, + securityAlertResponse, + ); + } catch (e) { + captureException(e); + console.error('Error validating JSON RPC using PPOM: ', e); + const securityAlertResponse = { + result_type: BlockaidResultType.Failed, + reason: BlockaidReason.failed, + description: + 'Validating the confirmation failed by throwing error.', + }; + updateSecurityAlertResponseByTxId( + request.transactionOptions, + securityAlertResponse, + ); + } + }); + + request.transactionOptions.securityAlertResponse = { + reason: BlockaidResultType.Loading, + result_type: BlockaidReason.inProgress, + securityAlertId, + }; } catch (e) { captureException(e); } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6d4d16b841b9..ce5ed0961540 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2995,6 +2995,7 @@ export default class MetamaskController extends EventEmitter { transactionOptions, waitForSubmit: false, }), + this.updateSecurityAlertResponseByTxId.bind(this), ), addTransactionAndWaitForPublish: ( transactionParams, @@ -3006,6 +3007,7 @@ export default class MetamaskController extends EventEmitter { transactionOptions, waitForSubmit: true, }), + this.updateSecurityAlertResponseByTxId.bind(this), ), createTransactionEventFragment: createTransactionEventFragmentWithTxId.bind( diff --git a/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js b/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js new file mode 100644 index 000000000000..161e15e88aa6 --- /dev/null +++ b/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js @@ -0,0 +1,246 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { mockServerJsonRpc } = require('../mock-server-json-rpc'); + +const { + defaultGanacheOptions, + withFixtures, + sendScreenToConfirmScreen, + unlockWallet, +} = require('../helpers'); + +const bannerAlertSelector = '[data-testid="security-provider-banner-alert"]'; +const mockMaliciousAddress = '0x5fbdb2315678afecb367f032d93f642f64180aa3'; +const mockBenignAddress = '0x50587E46C5B96a3F6f9792922EC647F13E6EFAE4'; + +const expectedMaliciousTitle = 'This is a deceptive request'; +const expectedMaliciousDescription = + 'If you approve this request, a third party known for scams will take all your assets.'; + +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 mockInfuraWithBenignResponses(mockServer) { + await mockInfura(mockServer); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).id, + result: { + type: 'CALL', + from: '0x0000000000000000000000000000000000000000', + to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567', + value: '0xde0b6b3a7640000', + gas: '0x16c696eb7', + gasUsed: '0x0', + input: '0x', + output: '0x', + }, + }, + }; + }); +} + +async function mockInfuraWithMaliciousResponses(mockServer) { + await mockInfura(mockServer); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [{ accessList: [], data: '0x00000000' }], + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).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', + }, + }, + }; + }); +} + +async function mockInfuraWithFailedResponses(mockServer) { + await mockInfura(mockServer); + + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [{ accessList: [], data: '0x00000000' }], + }) + .thenCallback(() => { + return { + statusCode: 500, + }; + }); +} + +/** + * 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('Simple Send Security Alert - Blockaid @no-mmi', function () { + if (process.env.MULTICHAIN) { + return; + } + it('should not show security alerts for benign requests', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfuraWithBenignResponses, + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + // await driver.delay(10000) + await unlockWallet(driver); + + await sendScreenToConfirmScreen(driver, mockBenignAddress, '1'); + // await driver.delay(100000) + const isPresent = await driver.isElementPresent(bannerAlertSelector); + assert.equal(isPresent, false, `Banner alert unexpectedly found.`); + }, + ); + }); + + /** + * 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() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfuraWithMaliciousResponses, + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + await unlockWallet(driver); + + await sendScreenToConfirmScreen( + driver, + '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', + '1', + ); + + // 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}`, + ); + assert( + bannerAlertText.includes(expectedMaliciousDescription), + `Unexpected banner alert description. Expected: ${expectedMaliciousDescription}`, + ); + }, + ); + }); + + 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() + .withPreferencesController({ + securityAlertsEnabled: true, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfuraWithFailedResponses, + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + // await driver.delay(10000) + await unlockWallet(driver); + + await sendScreenToConfirmScreen( + driver, + '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', + '1', + ); + // await driver.delay(100000) + 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}`, + ); + }, + ); + }); +}); From 6d17bb313cdc6674aebb1dc3044da6aaae5930a0 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 9 Feb 2024 02:47:56 -0330 Subject: [PATCH 05/49] Stop doing ppom validation on metamask swaps (#22847) --- app/scripts/lib/transaction/util.test.ts | 44 ++++++++++++++++++++++++ app/scripts/lib/transaction/util.ts | 17 ++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index 16cfb21a844d..7d27624e8cde 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -425,6 +425,50 @@ describe('Transaction Utils', () => { expect(request.ppomController.usePPOM).toHaveBeenCalledTimes(0); }); + + it('does not validate if blockaid is enabled and chain id is supported, but transaction type is swap', async () => { + const swapRequest = { ...request }; + swapRequest.transactionOptions.type = TransactionType.swap; + await addTransaction({ + ...swapRequest, + securityAlertsEnabled: true, + chainId: '0x1', + }); + + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledTimes(1); + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledWith(TRANSACTION_PARAMS_MOCK, { + ...TRANSACTION_OPTIONS_MOCK, + type: TransactionType.swap, + }); + + expect(request.ppomController.usePPOM).toHaveBeenCalledTimes(0); + }); + + it('does not validate if blockaid is enabled and chain id is supported, but transaction type is swapApproval', async () => { + const swapRequest = { ...request }; + swapRequest.transactionOptions.type = TransactionType.swapApproval; + await addTransaction({ + ...swapRequest, + securityAlertsEnabled: true, + chainId: '0x1', + }); + + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledTimes(1); + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledWith(TRANSACTION_PARAMS_MOCK, { + ...TRANSACTION_OPTIONS_MOCK, + type: TransactionType.swapApproval, + }); + + expect(request.ppomController.usePPOM).toHaveBeenCalledTimes(0); + }); }); describe('when blockaid is disabled', () => { diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index b5695dc0d296..4aab8e8fa0f1 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -109,6 +109,13 @@ export async function addDappTransaction( return (await waitForHash()) as string; } +///: BEGIN:ONLY_INCLUDE_IF(blockaid) +const PPOM_EXCLUDED_TRANSACTION_TYPES = [ + TransactionType.swap, + TransactionType.swapApproval, +]; +///: END:ONLY_INCLUDE_IF + export async function addTransaction( request: AddTransactionRequest, ///: BEGIN:ONLY_INCLUDE_IF(blockaid) @@ -127,7 +134,15 @@ export async function addTransaction( chainId, } = request; - if (securityAlertsEnabled && SUPPORTED_CHAIN_IDS.includes(chainId)) { + const typeIsExcludedFromPPOM = + transactionOptions.type && + PPOM_EXCLUDED_TRANSACTION_TYPES.includes(transactionOptions.type); + + if ( + securityAlertsEnabled && + SUPPORTED_CHAIN_IDS.includes(chainId) && + !typeIsExcludedFromPPOM + ) { try { const ppomRequest = { method: 'eth_sendTransaction', From 9bbe682ee28237368f5d1d3816711138c53406ed Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Thu, 15 Feb 2024 01:18:42 +0100 Subject: [PATCH 06/49] fix: Remove unnecessary resolution for socks (#22960) Remove unnecessary resolution for the `socks` package. This removes the risk of the resolution causing problems in future updates, reducing maintenance burdens for the team. --- package.json | 3 +-- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 798832c254b5..41ca4661ef21 100644 --- a/package.json +++ b/package.json @@ -219,8 +219,7 @@ "nonce-tracker@npm:^3.0.0": "patch:nonce-tracker@npm%3A3.0.0#~/.yarn/patches/nonce-tracker-npm-3.0.0-c5e9a93f9d.patch", "@trezor/connect-web": "9.0.11", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", - "tar-stream@npm:^3.1.6": "patch:tar-stream@npm%3A3.1.6#~/.yarn/patches/tar-stream-npm-3.1.6-ce3ac17e49.patch", - "socks": "^2.7.3" + "tar-stream@npm:^3.1.6": "patch:tar-stream@npm%3A3.1.6#~/.yarn/patches/tar-stream-npm-3.1.6-ce3ac17e49.patch" }, "dependencies": { "@babel/runtime": "^7.23.2", diff --git a/yarn.lock b/yarn.lock index b08f6ae5b9a8..8398b061ea3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31584,7 +31584,7 @@ __metadata: languageName: node linkType: hard -"socks@npm:^2.7.3": +"socks@npm:^2.6.1, socks@npm:^2.6.2, socks@npm:^2.7.1": version: 2.8.0 resolution: "socks@npm:2.8.0" dependencies: From 11945f75569a1b70e64976daaf91dcf72fc88ae9 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Mon, 29 Jan 2024 22:10:07 +0800 Subject: [PATCH 07/49] fix: Create patch for custody keyring type in accounts controller (#22692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR creates a patch to check for all types of custodian keyrings that start with the prefix `Custody` - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] 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 - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [x] In case it's "ready for review", I've changed it from "draft" to "non-draft". - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ...nts-controller-npm-10.0.0-247993ca9a.patch | 88 +++++++++++++++++++ package.json | 2 +- yarn.lock | 25 +++++- 3 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 .yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch diff --git a/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch b/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch new file mode 100644 index 000000000000..4d04b1c74db2 --- /dev/null +++ b/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch @@ -0,0 +1,88 @@ +diff --git a/dist/utils.js b/dist/utils.js +index 810f229841ffff83f7a28191bc558862b1809e01..aa2ea845d4cccfac8e28f575d7972bd2dba8decf 100644 +--- a/dist/utils.js ++++ b/dist/utils.js +@@ -11,35 +11,40 @@ const uuid_1 = require("uuid"); + * @returns The name of the keyring type. + */ + function keyringTypeToName(keyringType) { +- switch (keyringType) { +- case keyring_controller_1.KeyringTypes.simple: { +- return 'Account'; +- } +- case keyring_controller_1.KeyringTypes.hd: { +- return 'Account'; +- } +- case keyring_controller_1.KeyringTypes.trezor: { +- return 'Trezor'; +- } +- case keyring_controller_1.KeyringTypes.ledger: { +- return 'Ledger'; +- } +- case keyring_controller_1.KeyringTypes.lattice: { +- return 'Lattice'; +- } +- case keyring_controller_1.KeyringTypes.qr: { +- return 'QR'; +- } +- case keyring_controller_1.KeyringTypes.snap: { +- return 'Snap Account'; +- } +- case keyring_controller_1.KeyringTypes.custody: { +- return 'Custody'; +- } +- default: { +- throw new Error(`Unknown keyring ${keyringType}`); +- } ++ // Custody Keyrings follow this pattern ++ if (/^Custody.*/.test(keyringType)) { ++ return "Custody"; ++ } ++ ++ switch (keyringType) { ++ case keyring_controller_1.KeyringTypes.simple: { ++ return "Account"; + } ++ case keyring_controller_1.KeyringTypes.hd: { ++ return "Account"; ++ } ++ case keyring_controller_1.KeyringTypes.trezor: { ++ return "Trezor"; ++ } ++ case keyring_controller_1.KeyringTypes.ledger: { ++ return "Ledger"; ++ } ++ case keyring_controller_1.KeyringTypes.lattice: { ++ return "Lattice"; ++ } ++ case keyring_controller_1.KeyringTypes.qr: { ++ return "QR"; ++ } ++ case keyring_controller_1.KeyringTypes.snap: { ++ return "Snap Account"; ++ } ++ case keyring_controller_1.KeyringTypes.custody: { ++ return "Custody"; ++ } ++ default: { ++ throw new Error(`Unknown keyring ${keyringType}`); ++ } ++ } + } + exports.keyringTypeToName = keyringTypeToName; + /** +@@ -48,10 +53,10 @@ exports.keyringTypeToName = keyringTypeToName; + * @returns The generated UUID. + */ + function getUUIDFromAddressOfNormalAccount(address) { +- const v4options = { +- random: (0, ethereumjs_util_1.sha256FromString)(address).slice(0, 16), +- }; +- return (0, uuid_1.v4)(v4options); ++ const v4options = { ++ random: (0, ethereumjs_util_1.sha256FromString)(address).slice(0, 16), ++ }; ++ return (0, uuid_1.v4)(v4options); + } + exports.getUUIDFromAddressOfNormalAccount = getUUIDFromAddressOfNormalAccount; + //# sourceMappingURL=utils.js.map diff --git a/package.json b/package.json index 41ca4661ef21..07a4faee64a7 100644 --- a/package.json +++ b/package.json @@ -245,7 +245,7 @@ "@metamask-institutional/rpc-allowlist": "^1.0.0", "@metamask-institutional/sdk": "^0.1.23", "@metamask-institutional/transaction-update": "^0.1.32", - "@metamask/accounts-controller": "^10.0.0", + "@metamask/accounts-controller": "patch:@metamask/accounts-controller@npm%3A10.0.0#~/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch", "@metamask/address-book-controller": "^3.0.0", "@metamask/announcement-controller": "^5.0.1", "@metamask/approval-controller": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 8398b061ea3a..965a00393045 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3766,7 +3766,7 @@ __metadata: languageName: node linkType: hard -"@metamask/accounts-controller@npm:^10.0.0": +"@metamask/accounts-controller@npm:10.0.0": version: 10.0.0 resolution: "@metamask/accounts-controller@npm:10.0.0" dependencies: @@ -3787,6 +3787,27 @@ __metadata: languageName: node linkType: hard +"@metamask/accounts-controller@patch:@metamask/accounts-controller@npm%3A10.0.0#~/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch": + version: 10.0.0 + resolution: "@metamask/accounts-controller@patch:@metamask/accounts-controller@npm%3A10.0.0#~/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch::version=10.0.0&hash=0a18f3" + dependencies: + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/eth-snap-keyring": "npm:^2.1.1" + "@metamask/keyring-api": "npm:^3.0.0" + "@metamask/snaps-sdk": "npm:^1.3.2" + "@metamask/snaps-utils": "npm:^5.1.2" + "@metamask/utils": "npm:^8.3.0" + deepmerge: "npm:^4.2.2" + ethereumjs-util: "npm:^7.0.10" + immer: "npm:^9.0.6" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/keyring-controller": ^12.2.0 + "@metamask/snaps-controllers": ^4.0.0 + checksum: 809c1a3bfe0982b6113c7c76c43576fc9a454c66ec5403b839e10468ec753d742027cb7674045c9005de32ca7822dcd2bc0a19f68be37e1184486652c7befba3 + languageName: node + linkType: hard + "@metamask/address-book-controller@npm:^3.0.0": version: 3.0.0 resolution: "@metamask/address-book-controller@npm:3.0.0" @@ -24664,7 +24685,7 @@ __metadata: "@metamask-institutional/rpc-allowlist": "npm:^1.0.0" "@metamask-institutional/sdk": "npm:^0.1.23" "@metamask-institutional/transaction-update": "npm:^0.1.32" - "@metamask/accounts-controller": "npm:^10.0.0" + "@metamask/accounts-controller": "patch:@metamask/accounts-controller@npm%3A10.0.0#~/.yarn/patches/@metamask-accounts-controller-npm-10.0.0-247993ca9a.patch" "@metamask/address-book-controller": "npm:^3.0.0" "@metamask/announcement-controller": "npm:^5.0.1" "@metamask/approval-controller": "npm:^5.1.1" From 4b24c1c5ef68482c89e6e8e6efe8d270f6eed155 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 16 Feb 2024 11:52:01 -0330 Subject: [PATCH 08/49] Cherry-pick 67eb0bca9e (#22997) to v11.10.0 (#23002) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Cherry pick of #22997 to v11.10.0. That PR's description is below Update the hardfork in the transaction common used to estimate L1 optimism fees to London, fixing display of Layer 1 optimsim fees The bug was that Layer 1 fees were not being displayed on the confirmation screen for transactions on Optimism. This was because `estimatedL1Fees` was null, because `fetchEstimatedL1Fee` was failing, because `TransactionFactory.fromTxData(txParams, { common })` in `buildUnserializedTransaction` was throwing an error, because `txParams` has a `type` === `2`, but EIP-1559 transactions are not supported on the spurious dragon hardfork, which is what was being used in the `common` configuration of that `TransactionFactory.fromTxData` call. This became a problem after https://github.com/MetaMask/core/pull/3817/files. That PR correctly fixed a bug which could result in the transaction type being removed from the `txMeta.txParams` when `addTransaction` in the tx controller was called. With that bug fix, there was now a `type` present on the `txParams` that got passed to the aforementioned `TransactionFactory.fromTxData` call. Prior to that bug fix, there was no `type` on `txParams` at that point, the ethereumjs-tx TransactionFactory was treating these transactions as legacy transactions, and so was constructing a type 0 transaction, and the fact that the hardfork was spurious dragon was not a problem This PR fixes the problem by updating the hardfork param used in the `common` configuration of that `TransactionFactory.fromTxData` call. This should not have a functional effect, as this will cause no change in data and value of the tx passed to the smart contract to generate an fee estimate, except that this may result in more accurate L1 fee estimates (that is not certain though). Fixes: #22945 1. Create a transaction on OP Mainnet 2. Click "Fee details" on the confirmation screen 3. The layer 1 fee details should be visible ![Screenshot from 2024-02-16 09-27-25](https://github.com/MetaMask/metamask-extension/assets/7499938/6c759096-f393-4101-b9af-a3542e313571) - [ ] 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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. ## **Description** ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ui/helpers/utils/optimism/buildUnserializedTransaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/helpers/utils/optimism/buildUnserializedTransaction.js b/ui/helpers/utils/optimism/buildUnserializedTransaction.js index 15f08b021cb9..39d7a2c6f592 100644 --- a/ui/helpers/utils/optimism/buildUnserializedTransaction.js +++ b/ui/helpers/utils/optimism/buildUnserializedTransaction.js @@ -22,7 +22,7 @@ function buildTransactionCommon(txMeta) { // Optimism only supports type-0 transactions; it does not support any of // the newer EIPs since EIP-155. Source: // - defaultHardfork: Hardfork.SpuriousDragon, + defaultHardfork: Hardfork.London, }); } From 0c433f6acdd84fb41c87ae77466abe6f90852988 Mon Sep 17 00:00:00 2001 From: Marina Boboc <120041701+benjisclowder@users.noreply.github.com> Date: Fri, 16 Feb 2024 19:45:16 +0200 Subject: [PATCH 09/49] V11.10.0 changelog (#22827) Adding 11.10.0 changelog --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f43aa221a83a..18db909ce3ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [11.10.0] +### Added +- Added preset network image avatars in the 'Select a network' pop-up ([#22643](https://github.com/MetaMask/metamask-extension/pull/22643)) +- Expanded Blockaid banner support to include send requests from wallets ([#22321](https://github.com/MetaMask/metamask-extension/pull/22321)) +- Expanded Blockaid banner support to include BNB chain, Polygon, Arbitrum, Optimism, Avalanche, and Linea networks ([#22633](https://github.com/MetaMask/metamask-extension/pull/22633)) +- Added support for imToken Wallet using EIP-4527 ([#21804](https://github.com/MetaMask/metamask-extension/pull/21804)) +- [FLASK] Introduced user operation support, enhancing transaction handling and alert display for user operations ([#22469](https://github.com/MetaMask/metamask-extension/pull/22469)) +- [FLASK] Added support for the Lattice hardware wallet in MV3 ([#22528](https://github.com/MetaMask/metamask-extension/pull/22528)) +- [FLASK] Added a 'You're sending to a contract' warning to the new send page ([#22551](https://github.com/MetaMask/metamask-extension/pull/22551)) + +### Changed +- Improved error messaging for Ledger connection issues ([#21038](https://github.com/MetaMask/metamask-extension/pull/21038)) +- Updated 'What's New' and 'Settings' to communicate the extension's additional network coverage ([#22618](https://github.com/MetaMask/metamask-extension/pull/22618)) +- Enhanced Token List layout for clearer token name and value display ([#22601](https://github.com/MetaMask/metamask-extension/pull/22601)) +- Updated the date for the upcoming user survey displayed on the home screen ([#22581](https://github.com/MetaMask/metamask-extension/pull/22581)) +- Improved QR code density for compatibility with the ZERO hardware wallet ([#22135](https://github.com/MetaMask/metamask-extension/pull/22135)) +- Updated snaps packages to the latest versions ([#22595](https://github.com/MetaMask/metamask-extension/pull/22595)) +- Updated the Gas API URL to ensure accurate gas fee calculations for Send and Swap transactions ([#22544](https://github.com/MetaMask/metamask-extension/pull/22544)) + +### Fixed +- Fixed an issue where clicking on the redirection link did not close the notification window ([#22583](https://github.com/MetaMask/metamask-extension/pull/22583)) +- Improved Blockaid 'What's New' image display for users in OS dark mode ([#22649](https://github.com/MetaMask/metamask-extension/pull/22649)) +- Added Blockaid dark mode support in the 'What's New' feature and introduced a new theme management utility ([#22613](https://github.com/MetaMask/metamask-extension/pull/22613)) +- Ensured all security alert banners correctly trigger events ([#22553](https://github.com/MetaMask/metamask-extension/pull/22553)) +- Updated 'What's New' Blockaid image to have a transparent background ([#22539](https://github.com/MetaMask/metamask-extension/pull/22539)) +- Fixed unresponsiveness on the phishing warning page ([#22645](https://github.com/MetaMask/metamask-extension/pull/22645)) +- Improved Trezor integration by updating to the correct version of the SDK ([#22591](https://github.com/MetaMask/metamask-extension/pull/22591)) +- Improved account list links to display the correct explorer domain based on the selected network ([#22483](https://github.com/MetaMask/metamask-extension/pull/22483)) +- Improved the reliability of snap installations by resolving an underlying technical issue ([#22602](https://github.com/MetaMask/metamask-extension/pull/22602)) + ## [11.9.5] ### Fixed - Fixed sometimes failing confirmation screen security validation checks ([$22978](https://github.com/MetaMask/metamask-extension/pull/22978)) @@ -4361,10 +4390,7 @@ Update styles and spacing on the critical error page ([#20350](https://github.c - Added the ability to restore accounts from seed words. [Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.10.0...HEAD -[11.10.0]: https://github.com/MetaMask/metamask-extension/compare/v11.7.3...v11.10.0 -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.9.0...HEAD -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.9.4...HEAD -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.9.5...HEAD +[11.10.0]: https://github.com/MetaMask/metamask-extension/compare/v11.9.5...v11.10.0 [11.9.5]: https://github.com/MetaMask/metamask-extension/compare/v11.9.4...v11.9.5 [11.9.4]: https://github.com/MetaMask/metamask-extension/compare/v11.9.3...v11.9.4 [11.9.3]: https://github.com/MetaMask/metamask-extension/compare/v11.9.2...v11.9.3 From c2d8a9b5fb19b7a7771eee1947ef0a3e9b723c75 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Fri, 16 Feb 2024 19:46:32 +0100 Subject: [PATCH 10/49] Cherry pick 2 metric PRs for Blockaid (#23022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry picks: - [[Bug|Feat] blockaid external link clicked metric update (](https://github.com/MetaMask/metamask-extension/pull/23022/commits/2f5e64c9e9443988dca7a6beea94f1605f44ef9a)https://github.com/MetaMask/metamask-extension/pull/22631[)](https://github.com/MetaMask/metamask-extension/pull/23022/commits/2f5e64c9e9443988dca7a6beea94f1605f44ef9a) [2f5e64c](https://github.com/MetaMask/metamask-extension/pull/23022/commits/2f5e64c9e9443988dca7a6beea94f1605f44ef9a) - [fix: for realease blocker bugs (](https://github.com/MetaMask/metamask-extension/pull/23022/commits/9c391caf5b82dc7af3e6fb2459b408f2e90c8ac4)https://github.com/MetaMask/metamask-extension/pull/22874[)](https://github.com/MetaMask/metamask-extension/pull/23022/commits/9c391caf5b82dc7af3e6fb2459b408f2e90c8ac4) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Signed-off-by: Akintayo A. Olusegun Co-authored-by: Ariella Vu <20778143+digiwand@users.noreply.github.com> Co-authored-by: Olusegun Akintayo Co-authored-by: Jyoti Puri --- app/scripts/controllers/app-state.js | 6 + .../lib/createRPCMethodTrackingMiddleware.js | 38 +++- .../createRPCMethodTrackingMiddleware.test.js | 21 +++ app/scripts/lib/ppom/ppom-middleware.ts | 43 +++-- app/scripts/lib/transaction/metrics.test.ts | 2 - app/scripts/lib/transaction/metrics.ts | 52 ++---- app/scripts/lib/transaction/util.ts | 48 ++--- app/scripts/metamask-controller.js | 3 + shared/constants/metametrics.ts | 7 + .../tests/metrics/signature-approved.spec.js | 7 +- .../blockaid-banner-alert.js | 14 ++ .../blockaid-banner-alert.test.js | 94 ++++++++-- .../signature-request-original.component.js | 24 +-- .../signature-request-siwe.js | 55 +----- .../signature-request/signature-request.js | 27 +-- .../terms-of-use-popup.test.js | 7 +- .../transaction-alerts/transaction-alerts.js | 37 +--- ui/helpers/utils/metric.test.js | 77 -------- ui/helpers/utils/metrics.js | 78 +++++---- ui/helpers/utils/metrics.test.js | 164 ++++++++++++++++++ ui/hooks/useTransactionEventFragment.js | 10 +- .../confirm-approve-content.component.js | 24 +-- ui/pages/token-allowance/token-allowance.js | 27 +-- 23 files changed, 470 insertions(+), 395 deletions(-) delete mode 100644 ui/helpers/utils/metric.test.js create mode 100644 ui/helpers/utils/metrics.test.js diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 8f4f8a834d86..345960ea812c 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -444,6 +444,12 @@ export default class AppStateController extends EventEmitter { } ///: END:ONLY_INCLUDE_IF + getSignatureSecurityAlertResponse(securityAlertId) { + return this.store.getState().signatureSecurityAlertResponses[ + securityAlertId + ]; + } + addSignatureSecurityAlertResponse(securityAlertResponse) { const currentState = this.store.getState(); const { signatureSecurityAlertResponses } = currentState; diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index e851ad9b9807..d16bb9f74b4f 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -10,13 +10,15 @@ import { } from '../../../shared/constants/metametrics'; import { SECOND } from '../../../shared/constants/time'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { - BlockaidReason, BlockaidResultType, + ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + BlockaidReason, + ///: END:ONLY_INCLUDE_IF } from '../../../shared/constants/security-provider'; -///: END:ONLY_INCLUDE_IF +import { SIGNING_METHODS } from '../../../shared/constants/transaction'; +import { getBlockaidMetricsProps } from '../../../ui/helpers/utils/metrics'; import { getSnapAndHardwareInfoForMetrics } from './snap-keyring/metrics'; /** @@ -121,6 +123,7 @@ const rateLimitTimeouts = {}; * @param {Function} opts.getAccountType * @param {Function} opts.getDeviceModel * @param {RestrictedControllerMessenger} opts.snapAndHardwareMessenger + * @param {AppStateController} opts.appStateController * @returns {Function} */ export default function createRPCMethodTrackingMiddleware({ @@ -131,6 +134,7 @@ export default function createRPCMethodTrackingMiddleware({ getAccountType, getDeviceModel, snapAndHardwareMessenger, + appStateController, }) { return async function rpcMethodTrackingMiddleware( /** @type {any} */ req, @@ -310,13 +314,39 @@ export default function createRPCMethodTrackingMiddleware({ event = eventType.APPROVED; } + let blockaidMetricProps = {}; + + if (!isDisabledRPCMethod) { + if (SIGNING_METHODS.includes(method)) { + const securityAlertResponse = + appStateController.getSignatureSecurityAlertResponse( + req.securityAlertResponse?.securityAlertId, + ); + + blockaidMetricProps = getBlockaidMetricsProps({ + securityAlertResponse, + }); + } + } + + const properties = { + ...eventProperties, + ...blockaidMetricProps, + // if security_alert_response from blockaidMetricProps is Benign, force set security_alert_reason to empty string + security_alert_reason: + blockaidMetricProps.security_alert_response === + BlockaidResultType.Benign + ? '' + : blockaidMetricProps.security_alert_reason, + }; + trackEvent({ event, category: MetaMetricsEventCategory.InpageProvider, referrer: { url: origin, }, - properties: eventProperties, + properties, }); return callback(); diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js index 65820078539d..2f3f563b3594 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js @@ -24,11 +24,30 @@ const securityProviderRequest = () => { }; }; +const appStateController = { + store: { + getState: () => ({ + signatureSecurityAlertResponses: { + 1: { + result_type: BlockaidResultType.Malicious, + reason: BlockaidReason.maliciousDomain, + }, + }, + }), + }, + getSignatureSecurityAlertResponse: (id) => { + return appStateController.store.getState().signatureSecurityAlertResponses[ + id + ]; + }, +}; + const handler = createRPCMethodTrackingMiddleware({ trackEvent, getMetricsState, rateLimitSeconds: 1, securityProviderRequest, + appStateController, }); function getNext(timeout = 500) { @@ -122,6 +141,7 @@ describe('createRPCMethodTrackingMiddleware', () => { securityAlertResponse: { result_type: BlockaidResultType.Malicious, reason: BlockaidReason.maliciousDomain, + securityAlertId: 1, }, }; @@ -154,6 +174,7 @@ describe('createRPCMethodTrackingMiddleware', () => { eth_call: 5, eth_getCode: 3, }, + securityAlertId: 1, }, }; diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index d5f3e869c218..31afeae8b92a 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -69,26 +69,35 @@ export function createPPOMMiddleware( // eslint-disable-next-line require-atomic-updates const securityAlertId = uuid(); - ppomController.usePPOM(async (ppom: PPOM) => { - try { - const securityAlertResponse = await ppom.validateJsonRpc(req); - securityAlertResponse.securityAlertId = securityAlertId; - updateSecurityAlertResponseByTxId(req, securityAlertResponse); - } catch (error: any) { - sentry?.captureException(error); - console.error('Error validating JSON RPC using PPOM: ', error); - const securityAlertResponse = { - result_type: BlockaidResultType.Failed, - reason: BlockaidReason.failed, - description: - 'Validating the confirmation failed by throwing error.', - }; - updateSecurityAlertResponseByTxId(req, securityAlertResponse); - } - }); + ppomController + .usePPOM(async (ppom: PPOM) => { + try { + const securityAlertResponse = await ppom.validateJsonRpc(req); + return securityAlertResponse; + } catch (error: any) { + sentry?.captureException(error); + console.error('Error validating JSON RPC using PPOM: ', error); + const securityAlertResponse = { + result_type: BlockaidResultType.Failed, + reason: BlockaidReason.failed, + description: + 'Validating the confirmation failed by throwing error.', + }; + + return securityAlertResponse; + } + }) + .then((securityAlertResponse) => { + updateSecurityAlertResponseByTxId(req, { + ...securityAlertResponse, + securityAlertId, + }); + }); if (SIGNING_METHODS.includes(req.method)) { req.securityAlertResponse = { + reason: BlockaidResultType.Loading, + result_type: BlockaidReason.inProgress, securityAlertId, }; appStateController.addSignatureSecurityAlertResponse({ diff --git a/app/scripts/lib/transaction/metrics.test.ts b/app/scripts/lib/transaction/metrics.test.ts index 55a3794c7734..6a0f4cc9bcf2 100644 --- a/app/scripts/lib/transaction/metrics.test.ts +++ b/app/scripts/lib/transaction/metrics.test.ts @@ -135,8 +135,6 @@ describe('Transaction metrics', () => { 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, diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index 1d76e70c46e0..a9c601ef4566 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -25,6 +25,7 @@ import { MetaMetricsEventCategory, MetaMetricsEventFragment, MetaMetricsEventName, + MetaMetricsEventUiCustomization, MetaMetricsPageObject, MetaMetricsReferrerObject, } from '../../../../shared/constants/metametrics'; @@ -35,11 +36,7 @@ import { TRANSACTION_ENVELOPE_TYPE_NAMES, } from '../../../../shared/lib/transactions-controller-utils'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) -import { - BlockaidReason, - BlockaidResultType, -} from '../../../../shared/constants/security-provider'; -import { getBlockaidMetricsParams } from '../../../../ui/helpers/utils/metrics'; +import { getBlockaidMetricsProps } from '../../../../ui/helpers/utils/metrics'; ///: END:ONLY_INCLUDE_IF import { getSnapAndHardwareInfoForMetrics, @@ -763,9 +760,6 @@ async function buildEventFragmentProperties({ finalApprovalAmount, contractMethodName, securityProviderResponse, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - securityAlertResponse, - ///: END:ONLY_INCLUDE_IF simulationFails, } = transactionMeta; const query = new EthQuery(transactionMetricsRequest.provider); @@ -928,37 +922,30 @@ async function buildEventFragmentProperties({ } } - let uiCustomizations; - let additionalBlockaidParams; + const uiCustomizations = []; + /** securityProviderResponse is used by the OpenSea <> Blockaid provider */ // eslint-disable-next-line no-lonely-if if (securityProviderResponse?.flagAsDangerous === 1) { - uiCustomizations = ['flagged_as_malicious']; + uiCustomizations.push(MetaMetricsEventUiCustomization.FlaggedAsMalicious); } else if (securityProviderResponse?.flagAsDangerous === 2) { - uiCustomizations = ['flagged_as_safety_unknown']; - } else { - uiCustomizations = null; + uiCustomizations.push( + MetaMetricsEventUiCustomization.FlaggedAsSafetyUnknown, + ); } ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - if (securityAlertResponse?.result_type === BlockaidResultType.Failed) { - uiCustomizations = ['security_alert_failed']; - } else { - additionalBlockaidParams = getBlockaidMetricsParams( - securityAlertResponse as any, - ); - uiCustomizations = additionalBlockaidParams?.ui_customizations ?? null; - } + const blockaidProperties: any = getBlockaidMetricsProps(transactionMeta); + if (blockaidProperties?.ui_customizations?.length > 0) { + uiCustomizations.push(...blockaidProperties.ui_customizations); + } ///: END:ONLY_INCLUDE_IF if (simulationFails) { - if (uiCustomizations === null) { - uiCustomizations = ['gas_estimation_failed']; - } else { - uiCustomizations.push('gas_estimation_failed'); - } + uiCustomizations.push(MetaMetricsEventUiCustomization.GasEstimationFailed); } + /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ let properties = { chain_id: chainId, @@ -969,6 +956,7 @@ async function buildEventFragmentProperties({ eip_1559_version: eip1559Version, gas_edit_type: 'none', gas_edit_attempted: 'none', + gas_estimation_failed: Boolean(simulationFails), account_type: await transactionMetricsRequest.getAccountType( transactionMetricsRequest.getSelectedAddress(), ), @@ -979,15 +967,11 @@ async function buildEventFragmentProperties({ token_standard: tokenStandard, transaction_type: transactionType, transaction_speed_up: type === TransactionType.retry, - ...additionalBlockaidParams, - ui_customizations: uiCustomizations?.length > 0 ? uiCustomizations : null, ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - security_alert_response: - securityAlertResponse?.result_type ?? BlockaidResultType.NotApplicable, - security_alert_reason: - securityAlertResponse?.reason ?? BlockaidReason.notApplicable, + ...blockaidProperties, ///: END:ONLY_INCLUDE_IF - gas_estimation_failed: Boolean(simulationFails), + // ui_customizations must come after ...blockaidProperties + ui_customizations: uiCustomizations.length > 0 ? uiCustomizations : null, } as Record; const snapAndHardwareInfo = await getSnapAndHardwareInfoForMetrics( diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index 4aab8e8fa0f1..cb621794a08f 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -160,28 +160,32 @@ export async function addTransaction( const securityAlertId = uuid(); - ppomController.usePPOM(async (ppom) => { - try { - const securityAlertResponse = await ppom.validateJsonRpc(ppomRequest); - updateSecurityAlertResponseByTxId( - request.transactionOptions, - securityAlertResponse, - ); - } catch (e) { - captureException(e); - console.error('Error validating JSON RPC using PPOM: ', e); - const securityAlertResponse = { - result_type: BlockaidResultType.Failed, - reason: BlockaidReason.failed, - description: - 'Validating the confirmation failed by throwing error.', - }; - updateSecurityAlertResponseByTxId( - request.transactionOptions, - securityAlertResponse, - ); - } - }); + ppomController + .usePPOM(async (ppom) => { + try { + const securityAlertResponse = await ppom.validateJsonRpc( + ppomRequest, + ); + return securityAlertResponse; + } catch (e) { + captureException(e); + console.error('Error validating JSON RPC using PPOM: ', e); + const securityAlertResponse = { + securityAlertId, + result_type: BlockaidResultType.Failed, + reason: BlockaidReason.failed, + description: + 'Validating the confirmation failed by throwing error.', + }; + return securityAlertResponse; + } + }) + .then((securityAlertResponse) => { + updateSecurityAlertResponseByTxId(request.transactionOptions, { + ...securityAlertResponse, + securityAlertId, + }); + }); request.transactionOptions.securityAlertResponse = { reason: BlockaidResultType.Loading, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ce5ed0961540..64989ab83d6c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4717,6 +4717,9 @@ export default class MetamaskController extends EventEmitter { 'AccountsController:getSelectedAccount', ], }), + ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + appStateController: this.appStateController, + ///: END:ONLY_INCLUDE_IF }), ); diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 9820fa70c36c..fa3c9c2f7ad1 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -492,6 +492,11 @@ export const REJECT_NOTIFICATION_CLOSE = 'Cancel Via Notification Close'; export const REJECT_NOTIFICATION_CLOSE_SIG = 'Cancel Sig Request Via Notification Close'; +/** + * The name of the event. Event definitions with corresponding properties can be found in the following document: + * + * @see {@link https://www.notion.so/f2997ab32326441793ff790ba5c60a6a?v=267d984721cd4a26be610b5caa3e25b7&pvs=4} + */ export enum MetaMetricsEventName { AccountAdded = 'Account Added', AccountAddSelected = 'Account Add Selected', @@ -754,7 +759,9 @@ export enum MetaMetricsEventLocation { export enum MetaMetricsEventUiCustomization { FlaggedAsMalicious = 'flagged_as_malicious', FlaggedAsSafetyUnknown = 'flagged_as_safety_unknown', + FlaggedAsWarning = 'flagged_as_warning', GasEstimationFailed = 'gas_estimation_failed', + SecurityAlertFailed = 'security_alert_failed', Siwe = 'sign_in_with_ethereum', } diff --git a/test/e2e/tests/metrics/signature-approved.spec.js b/test/e2e/tests/metrics/signature-approved.spec.js index bde3c6519d7f..274f1d2ba117 100644 --- a/test/e2e/tests/metrics/signature-approved.spec.js +++ b/test/e2e/tests/metrics/signature-approved.spec.js @@ -73,6 +73,7 @@ describe('Signature Approved Event @no-mmi', function () { await validateContractDetails(driver); await clickSignOnSignatureConfirmation(driver); const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { account_type: 'MetaMask', signature_type: 'eth_signTypedData_v4', @@ -83,6 +84,7 @@ describe('Signature Approved Event @no-mmi', function () { security_alert_reason: 'NotApplicable', security_alert_response: 'NotApplicable', }); + assert.deepStrictEqual(events[1].properties, { account_type: 'MetaMask', signature_type: 'eth_signTypedData_v4', @@ -90,7 +92,6 @@ describe('Signature Approved Event @no-mmi', function () { locale: 'en', chain_id: '0x539', environment_type: 'background', - security_alert_reason: 'NotApplicable', security_alert_response: 'NotApplicable', }); }, @@ -138,7 +139,6 @@ describe('Signature Approved Event @no-mmi', function () { locale: 'en', chain_id: '0x539', environment_type: 'background', - security_alert_reason: 'NotApplicable', security_alert_response: 'NotApplicable', }); }, @@ -185,7 +185,6 @@ describe('Signature Approved Event @no-mmi', function () { locale: 'en', chain_id: '0x539', environment_type: 'background', - security_alert_reason: 'NotApplicable', security_alert_response: 'NotApplicable', }); }, @@ -232,7 +231,6 @@ describe('Signature Approved Event @no-mmi', function () { locale: 'en', chain_id: '0x539', environment_type: 'background', - security_alert_reason: 'NotApplicable', security_alert_response: 'NotApplicable', }); }, @@ -288,7 +286,6 @@ describe('Signature Approved Event @no-mmi', function () { locale: 'en', chain_id: '0x539', environment_type: 'background', - security_alert_reason: 'NotApplicable', security_alert_response: 'NotApplicable', }); }, diff --git a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js index f736d52b510b..9c83a0457250 100644 --- a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js +++ b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js @@ -9,6 +9,7 @@ import { Severity, } from '../../../../helpers/constants/design-system'; import { I18nContext } from '../../../../contexts/i18n'; +import { useTransactionEventFragment } from '../../../../hooks/useTransactionEventFragment'; import { BlockaidReason, BlockaidResultType, @@ -57,6 +58,7 @@ function BlockaidBannerAlert({ txData, ...props }) { txData; const t = useContext(I18nContext); + const { updateTransactionEventFragment } = useTransactionEventFragment(); if ( !securityAlertResponse || @@ -121,6 +123,17 @@ function BlockaidBannerAlert({ txData, ...props }) { const reportUrl = getReportUrl(encodedData); + const onClickSupportLink = () => { + updateTransactionEventFragment( + { + properties: { + external_link_clicked: 'security_alert_support_link', + }, + }, + txData.id, + ); + }; + return ( ); diff --git a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js index 93b46dc35c16..6b64bdca2572 100644 --- a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js +++ b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js @@ -1,7 +1,9 @@ import React from 'react'; import * as Sentry from '@sentry/browser'; -import { renderWithLocalization } from '../../../../../test/lib/render-helpers'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import { Severity } from '../../../../helpers/constants/design-system'; +import configureStore from '../../../../store/store'; import { BlockaidReason, BlockaidResultType, @@ -13,6 +15,18 @@ jest.mock('zlib', () => ({ gzipSync: (val) => val, })); +const mockUpdateTransactionEventFragment = jest.fn(); + +jest.mock('../../../../hooks/useTransactionEventFragment', () => { + return { + useTransactionEventFragment: () => { + return { + updateTransactionEventFragment: mockUpdateTransactionEventFragment, + }; + }, + }; +}); + const mockSecurityAlertResponse = { result_type: BlockaidResultType.Warning, reason: BlockaidReason.setApprovalForAll, @@ -26,19 +40,20 @@ const mockSecurityAlertResponse = { describe('Blockaid Banner Alert', () => { it('should not render when securityAlertResponse is not present', () => { - const { container } = renderWithLocalization( + const { container } = renderWithProvider( , + configureStore(), ); expect(container.querySelector('.mm-banner-alert')).toBeNull(); }); it(`should not render when securityAlertResponse.result_type is '${BlockaidResultType.Benign}'`, () => { - const { container } = renderWithLocalization( + const { container } = renderWithProvider( { }, }} />, + configureStore(), ); expect(container.querySelector('.mm-banner-alert')).toBeNull(); }); it(`should render '${Severity.Warning}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Failed}`, () => { - const { container } = renderWithLocalization( + const { container } = renderWithProvider( { }, }} />, + configureStore(), ); const warningBannerAlert = container.querySelector( '.mm-banner-alert--severity-warning', @@ -72,12 +89,13 @@ describe('Blockaid Banner Alert', () => { }); it(`should render '${Severity.Warning}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Warning}`, () => { - const { container } = renderWithLocalization( + const { container } = renderWithProvider( , + configureStore(), ); const warningBannerAlert = container.querySelector( '.mm-banner-alert--severity-warning', @@ -88,7 +106,7 @@ describe('Blockaid Banner Alert', () => { }); it(`should render '${Severity.Danger}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Malicious}`, () => { - const { container } = renderWithLocalization( + const { container } = renderWithProvider( { }, }} />, + configureStore(), ); const dangerBannerAlert = container.querySelector( '.mm-banner-alert--severity-danger', @@ -107,19 +126,20 @@ describe('Blockaid Banner Alert', () => { }); it('should render title, "This is a deceptive request"', () => { - const { getByText } = renderWithLocalization( + const { getByText } = renderWithProvider( , + configureStore(), ); expect(getByText('This is a deceptive request')).toBeInTheDocument(); }); it(`should render title, "This is a suspicious request", when the reason is "${BlockaidReason.failed}"`, () => { - const { getByText } = renderWithLocalization( + const { getByText } = renderWithProvider( { }, }} />, + configureStore(), ); expect(getByText('Request may not be safe')).toBeInTheDocument(); }); it(`should render title, "This is a suspicious request", when the reason is "${BlockaidReason.rawSignatureFarming}"`, () => { - const { getByText } = renderWithLocalization( + const { getByText } = renderWithProvider( { }, }} />, + configureStore(), ); expect(getByText('This is a suspicious request')).toBeInTheDocument(); @@ -154,7 +176,7 @@ describe('Blockaid Banner Alert', () => { 'Operator is untrusted according to previous activity', ]; - const { container, getByText } = renderWithLocalization( + const { container, getByText } = renderWithProvider( { }, }} />, + configureStore(), ); expect(container).toMatchSnapshot(); @@ -173,13 +196,14 @@ describe('Blockaid Banner Alert', () => { }); it('should render details section even when features is not provided', () => { - const { container } = renderWithLocalization( + const { container } = renderWithProvider( , + configureStore(), ); expect(container).toMatchSnapshot(); @@ -187,13 +211,14 @@ describe('Blockaid Banner Alert', () => { }); it('should render link to report url', () => { - const { container, getByText, getByRole } = renderWithLocalization( + const { container, getByText, getByRole } = renderWithProvider( , + configureStore(), ); expect(container).toMatchSnapshot(); @@ -204,13 +229,14 @@ describe('Blockaid Banner Alert', () => { }); it('should pass required data in Report an issue URL', () => { - const { getByRole } = renderWithLocalization( + const { getByRole } = renderWithProvider( , + configureStore(), ); const elm = getByRole('link', { name: 'Report an issue' }); @@ -249,7 +275,7 @@ describe('Blockaid Banner Alert', () => { 'If you approve this request, a third party known for scams will take all your assets.', }).forEach(([reason, expectedDescription]) => { it(`should render for '${reason}' correctly`, () => { - const { getByText } = renderWithLocalization( + const { getByText } = renderWithProvider( { }, }} />, + configureStore(), ); expect(getByText(expectedDescription)).toBeInTheDocument(); @@ -269,7 +296,7 @@ describe('Blockaid Banner Alert', () => { it('renders the "other" description translation and logs a Sentry exception', () => { const stubOtherDescription = 'If you approve this request, you might lose your assets.'; - const { getByText } = renderWithLocalization( + const { getByText } = renderWithProvider( { }, }} />, + configureStore(), ); expect(getByText(stubOtherDescription)).toBeInTheDocument(); expect(Sentry.captureException).toHaveBeenCalledTimes(1); }); }); + + describe('when clicking "See details" > "Report an issue"', () => { + it('calls updateTransactionEventFragment to add "external_link_clicked" prop to metric', () => { + const stubScrollIntoView = jest.fn(); + const originalScrollIntoView = + window.HTMLElement.prototype.scrollIntoView; + window.HTMLElement.prototype.scrollIntoView = stubScrollIntoView; + + renderWithProvider( + , + configureStore(), + ); + + fireEvent.click(screen.queryByText('See details')); + fireEvent.click(screen.queryByText('Report an issue')); + + expect(mockUpdateTransactionEventFragment).toHaveBeenCalledTimes(1); + expect(mockUpdateTransactionEventFragment).toHaveBeenCalledWith( + { + properties: { + external_link_clicked: 'security_alert_support_link', + }, + }, + '1', + ); + window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView; + }); + }); }); diff --git a/ui/components/app/signature-request-original/signature-request-original.component.js b/ui/components/app/signature-request-original/signature-request-original.component.js index 8a03537c7ba3..6c95907dad53 100644 --- a/ui/components/app/signature-request-original/signature-request-original.component.js +++ b/ui/components/app/signature-request-original/signature-request-original.component.js @@ -45,10 +45,6 @@ import { } from '../../component-library'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) -import { - MetaMetricsEventCategory, - MetaMetricsEventName, -} from '../../../../shared/constants/metametrics'; import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; ///: END:ONLY_INCLUDE_IF @@ -152,20 +148,6 @@ export default class SignatureRequestOriginal extends Component { rows = [{ name: this.context.t('message'), value: data }]; } - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - const onClickSupportLink = () => { - this.context.trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: MetaMetricsEventName.ExternalLinkClicked, - properties: { - action: 'Sign Request', - origin: txData?.origin, - external_link_clicked: 'security_alert_support_link', - }, - }); - }; - ///: END:ONLY_INCLUDE_IF - const targetSubjectMetadata = txData.msgParams.origin ? subjectMetadata?.[txData.msgParams.origin] : null; @@ -174,11 +156,7 @@ export default class SignatureRequestOriginal extends Component {
{ ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {isSuspiciousResponse(txData?.securityProviderResponse) && ( diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe.js b/ui/components/app/signature-request-siwe/signature-request-siwe.js index 9d97dcf2df51..cca280868ad3 100644 --- a/ui/components/app/signature-request-siwe/signature-request-siwe.js +++ b/ui/components/app/signature-request-siwe/signature-request-siwe.js @@ -1,11 +1,4 @@ -import React, { - useCallback, - useContext, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - useEffect, - ///: END:ONLY_INCLUDE_IF - useState, -} from 'react'; +import React, { useCallback, useContext, useState } from 'react'; import PropTypes from 'prop-types'; import { useSelector, useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -47,12 +40,6 @@ import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-pa import { getMostRecentOverviewPage } from '../../../ducks/history/history'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -import { getBlockaidMetricsParams } from '../../../helpers/utils/metrics'; -import { MetaMetricsContext } from '../../../contexts/metametrics'; -import { - MetaMetricsEventCategory, - MetaMetricsEventName, -} from '../../../../shared/constants/metametrics'; ///: END:ONLY_INCLUDE_IF import LedgerInstructionField from '../ledger-instruction-field'; @@ -70,40 +57,6 @@ export default function SignatureRequestSIWE({ txData }) { const messagesList = useSelector(unconfirmedMessagesHashSelector); const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - const trackEvent = useContext(MetaMetricsContext); - - useEffect(() => { - if (txData.securityAlertResponse) { - const blockaidMetricsParams = getBlockaidMetricsParams( - txData.securityAlertResponse, - ); - - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: MetaMetricsEventName.SignatureRequested, - properties: { - action: 'Sign Request', - ...blockaidMetricsParams, - }, - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const onClickSupportLink = useCallback(() => { - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: MetaMetricsEventName.ExternalLinkClicked, - properties: { - action: 'Sign Request SIWE', - origin: txData?.origin, - external_link_clicked: 'security_alert_support_link', - }, - }); - }, [trackEvent, txData?.origin]); - ///: END:ONLY_INCLUDE_IF - const { msgParams: { from, @@ -180,11 +133,7 @@ export default function SignatureRequestSIWE({ txData }) { { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {showSecurityProviderBanner && ( diff --git a/ui/components/app/signature-request/signature-request.js b/ui/components/app/signature-request/signature-request.js index 5a666166058a..874f89485f2b 100644 --- a/ui/components/app/signature-request/signature-request.js +++ b/ui/components/app/signature-request/signature-request.js @@ -1,11 +1,4 @@ -import React, { - useContext, - useState, - useEffect, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - useCallback, - ///: END:ONLY_INCLUDE_IF -} from 'react'; +import React, { useContext, useState, useEffect } from 'react'; import { useDispatch, useSelector, @@ -56,7 +49,7 @@ import ContractDetailsModal from '../modals/contract-details-modal'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsEventCategory, - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi,blockaid) + ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) MetaMetricsEventName, ///: END:ONLY_INCLUDE_IF } from '../../../../shared/constants/metametrics'; @@ -162,21 +155,6 @@ const SignatureRequest = ({ txData }) => { return { sanitizedMessage, domain, primaryType }; }); - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - const onClickSupportLink = useCallback(() => { - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: MetaMetricsEventName.ExternalLinkClicked, - properties: { - action: 'Sign Request', - type, - version, - external_link_clicked: 'security_alert_support_link', - }, - }); - }, [trackEvent, type, version]); - ///: END:ONLY_INCLUDE_IF - const onSign = async () => { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) if (accountType === 'custody') { @@ -272,7 +250,6 @@ const SignatureRequest = ({ txData }) => { marginLeft={4} marginRight={4} marginBottom={4} - onClickSupportLink={onClickSupportLink} /> ///: END:ONLY_INCLUDE_IF } diff --git a/ui/components/app/terms-of-use-popup/terms-of-use-popup.test.js b/ui/components/app/terms-of-use-popup/terms-of-use-popup.test.js index 02125e981ccd..5373308337d1 100644 --- a/ui/components/app/terms-of-use-popup/terms-of-use-popup.test.js +++ b/ui/components/app/terms-of-use-popup/terms-of-use-popup.test.js @@ -34,15 +34,20 @@ describe('TermsOfUsePopup', () => { }); it('scrolls down when handleScrollDownClick is called', () => { - render(); const mockScrollIntoView = jest.fn(); + const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView; window.HTMLElement.prototype.scrollIntoView = mockScrollIntoView; + + render(); const button = document.querySelector( "[data-testid='popover-scroll-button']", ); + fireEvent.click(button); expect(mockScrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth', }); + + window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView; }); }); diff --git a/ui/components/app/transaction-alerts/transaction-alerts.js b/ui/components/app/transaction-alerts/transaction-alerts.js index a38d068ca2c8..92e8792ea147 100644 --- a/ui/components/app/transaction-alerts/transaction-alerts.js +++ b/ui/components/app/transaction-alerts/transaction-alerts.js @@ -1,9 +1,4 @@ -import React, { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - useCallback, - useContext, - ///: END:ONLY_INCLUDE_IF -} from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { TransactionType } from '@metamask/transaction-controller'; @@ -15,9 +10,6 @@ import { BannerAlert, ButtonLink, Text } from '../../component-library'; import SimulationErrorMessage from '../../ui/simulation-error-message'; import { SEVERITIES } from '../../../helpers/constants/design-system'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) -import { MetaMetricsContext } from '../../../contexts/metametrics'; -///: END:ONLY_INCLUDE_IF import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) @@ -27,12 +19,6 @@ import SecurityProviderBannerMessage from '../security-provider-banner-message/s import { getNativeCurrency } from '../../../ducks/metamask/metamask'; import { parseStandardTokenTransactionData } from '../../../../shared/modules/transaction.utils'; import { getTokenValueParam } from '../../../../shared/lib/metamask-controller-utils'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) -import { - MetaMetricsEventCategory, - MetaMetricsEventName, -} from '../../../../shared/constants/metametrics'; -///: END:ONLY_INCLUDE_IF const TransactionAlerts = ({ userAcknowledgedGasMissing, @@ -69,30 +55,11 @@ const TransactionAlerts = ({ hasProperTxType && (currentTokenAmount === '0x0' || currentTokenAmount === '0'); - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - const trackEvent = useContext(MetaMetricsContext); - - const onClickSupportLink = useCallback(() => { - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: MetaMetricsEventName.ExternalLinkClicked, - properties: { - action: 'Confirm Screen', - origin: txData?.origin, - external_link_clicked: 'security_alert_support_link', - }, - }); - }, [trackEvent, txData?.origin]); - ///: END:ONLY_INCLUDE_IF - return (
{ ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {isSuspiciousResponse(txData?.securityProviderResponse) && ( diff --git a/ui/helpers/utils/metric.test.js b/ui/helpers/utils/metric.test.js deleted file mode 100644 index 5dbfb8ced832..000000000000 --- a/ui/helpers/utils/metric.test.js +++ /dev/null @@ -1,77 +0,0 @@ -import { - BlockaidReason, - BlockaidResultType, -} from '../../../shared/constants/security-provider'; -import { getBlockaidMetricsParams, getMethodName } from './metrics'; - -describe('getMethodName', () => { - it('should get correct method names', () => { - expect(getMethodName(undefined)).toStrictEqual(''); - expect(getMethodName({})).toStrictEqual(''); - expect(getMethodName('confirm')).toStrictEqual('confirm'); - expect(getMethodName('balanceOf')).toStrictEqual('balance Of'); - expect(getMethodName('ethToTokenSwapInput')).toStrictEqual( - 'eth To Token Swap Input', - ); - }); -}); - -describe('getBlockaidMetricsParams', () => { - it('should return empty object when securityAlertResponse is not defined', () => { - const result = getBlockaidMetricsParams(undefined); - expect(result).toStrictEqual({}); - }); - - it('should return additionalParams object when securityAlertResponse defined', () => { - const securityAlertResponse = { - result_type: BlockaidResultType.Malicious, - reason: BlockaidReason.notApplicable, - providerRequestsCount: { - eth_call: 5, - eth_getCode: 3, - }, - features: [], - }; - - const result = getBlockaidMetricsParams(securityAlertResponse); - expect(result).toStrictEqual({ - ui_customizations: ['flagged_as_malicious'], - security_alert_response: BlockaidResultType.Malicious, - security_alert_reason: BlockaidReason.notApplicable, - ppom_eth_call_count: 5, - ppom_eth_getCode_count: 3, - }); - }); - - it('should not return eth call counts if providerRequestsCount is empty', () => { - const securityAlertResponse = { - result_type: BlockaidResultType.Malicious, - reason: BlockaidReason.notApplicable, - features: [], - providerRequestsCount: {}, - }; - - const result = getBlockaidMetricsParams(securityAlertResponse); - expect(result).toStrictEqual({ - ui_customizations: ['flagged_as_malicious'], - security_alert_response: BlockaidResultType.Malicious, - security_alert_reason: BlockaidReason.notApplicable, - }); - }); - - it('should not return eth call counts if providerRequestsCount is undefined', () => { - const securityAlertResponse = { - result_type: BlockaidResultType.Malicious, - reason: BlockaidReason.notApplicable, - features: [], - providerRequestsCount: undefined, - }; - - const result = getBlockaidMetricsParams(securityAlertResponse); - expect(result).toStrictEqual({ - ui_customizations: ['flagged_as_malicious'], - security_alert_response: BlockaidResultType.Malicious, - security_alert_reason: BlockaidReason.notApplicable, - }); - }); -}); diff --git a/ui/helpers/utils/metrics.js b/ui/helpers/utils/metrics.js index 4c8b2a2ae871..8902f1bed336 100644 --- a/ui/helpers/utils/metrics.js +++ b/ui/helpers/utils/metrics.js @@ -3,6 +3,7 @@ import { BlockaidReason, BlockaidResultType, } from '../../../shared/constants/security-provider'; +import { MetaMetricsEventUiCustomization } from '../../../shared/constants/metametrics'; ///: END:ONLY_INCLUDE_IF export function getMethodName(camelCase) { @@ -25,42 +26,61 @@ export function formatAccountType(accountType) { } ///: BEGIN:ONLY_INCLUDE_IF(blockaid) -export const getBlockaidMetricsParams = (securityAlertResponse = null) => { - const additionalParams = {}; +/** + * Returns the ui_customization string value based on the result type + * + * @param {BlockaidResultType} resultType + * @returns {MetaMetricsEventUiCustomization} + */ +const getBlockaidMetricUiCustomization = (resultType) => { + let uiCustomization; - if (securityAlertResponse) { - const { - result_type: resultType, - reason, - providerRequestsCount, - } = securityAlertResponse; + if (resultType === BlockaidResultType.Failed) { + uiCustomization = [MetaMetricsEventUiCustomization.SecurityAlertFailed]; + } else if (resultType === BlockaidResultType.Malicious) { + uiCustomization = [MetaMetricsEventUiCustomization.FlaggedAsMalicious]; + } else if (resultType === BlockaidResultType.Warning) { + uiCustomization = [MetaMetricsEventUiCustomization.FlaggedAsWarning]; + } + + return uiCustomization; +}; - if (resultType === BlockaidResultType.Malicious) { - additionalParams.ui_customizations = ['flagged_as_malicious']; - } +/** + * @param {import('@metamask/transaction-controller').TransactionMeta} transactionMeta + * @returns {object} + */ +export const getBlockaidMetricsProps = ({ securityAlertResponse }) => { + if (!securityAlertResponse) { + return {}; + } - if (resultType === BlockaidResultType.Failed) { - additionalParams.ui_customizations = ['security_alert_failed']; - } + const params = {}; + const { + providerRequestsCount, + reason, + result_type: resultType, + } = securityAlertResponse; - if (resultType !== BlockaidResultType.Benign) { - additionalParams.security_alert_reason = BlockaidReason.notApplicable; + const uiCustomization = getBlockaidMetricUiCustomization(resultType); + if (uiCustomization) { + params.ui_customizations = uiCustomization; + } - if (reason) { - additionalParams.security_alert_response = resultType; - additionalParams.security_alert_reason = reason; - } - } + if (resultType !== BlockaidResultType.Benign) { + params.security_alert_reason = reason ?? BlockaidReason.notApplicable; + } + params.security_alert_response = + resultType ?? BlockaidResultType.NotApplicable; - // add counts of each RPC call - if (providerRequestsCount) { - Object.keys(providerRequestsCount).forEach((key) => { - const metricKey = `ppom_${key}_count`; - additionalParams[metricKey] = providerRequestsCount[key]; - }); - } + // add counts of each RPC call + if (providerRequestsCount) { + Object.keys(providerRequestsCount).forEach((key) => { + const metricKey = `ppom_${key}_count`; + params[metricKey] = providerRequestsCount[key]; + }); } - return additionalParams; + return params; }; ///: END:ONLY_INCLUDE_IF diff --git a/ui/helpers/utils/metrics.test.js b/ui/helpers/utils/metrics.test.js new file mode 100644 index 000000000000..b75f5b7b2693 --- /dev/null +++ b/ui/helpers/utils/metrics.test.js @@ -0,0 +1,164 @@ +import { + BlockaidReason, + BlockaidResultType, +} from '../../../shared/constants/security-provider'; +import { getBlockaidMetricsProps, getMethodName } from './metrics'; + +describe('getMethodName', () => { + it('gets correct method names', () => { + expect(getMethodName(undefined)).toStrictEqual(''); + expect(getMethodName({})).toStrictEqual(''); + expect(getMethodName('confirm')).toStrictEqual('confirm'); + expect(getMethodName('balanceOf')).toStrictEqual('balance Of'); + expect(getMethodName('ethToTokenSwapInput')).toStrictEqual( + 'eth To Token Swap Input', + ); + }); +}); + +const securityAlertResponse = { + result_type: BlockaidResultType.Malicious, + reason: BlockaidReason.setApprovalForAll, + features: [], +}; + +describe('getBlockaidMetricsProps', () => { + it('returns an empty object when securityAlertResponse is not defined', () => { + const result = getBlockaidMetricsProps({}); + expect(result).toStrictEqual({}); + }); + + it('returns metric props when securityAlertResponse defined', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse, + }); + expect(result).toStrictEqual({ + security_alert_reason: BlockaidReason.setApprovalForAll, + security_alert_response: BlockaidResultType.Malicious, + ui_customizations: ['flagged_as_malicious'], + }); + }); + + it('includes not applicable reason or result type when they are not provided', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + reason: null, + result_type: null, + }, + }); + + expect(result.security_alert_reason).toBe(BlockaidReason.notApplicable); + expect(result.security_alert_response).toBe( + BlockaidResultType.NotApplicable, + ); + }); + + it('includes "security_alert_failed" ui_customization when type is failed', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + result_type: BlockaidResultType.Failed, + }, + }); + + expect(result).toStrictEqual({ + security_alert_reason: BlockaidReason.setApprovalForAll, + security_alert_response: BlockaidResultType.Failed, + ui_customizations: ['security_alert_failed'], + }); + }); + + it('includes "flagged_as_malicious" ui_customization when type is malicious', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + result_type: BlockaidResultType.Malicious, + }, + }); + + expect(result).toStrictEqual({ + security_alert_reason: BlockaidReason.setApprovalForAll, + security_alert_response: BlockaidResultType.Malicious, + ui_customizations: ['flagged_as_malicious'], + }); + }); + + it('includes "flagged_as_warning" ui_customization when type is a warning', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + result_type: BlockaidResultType.Malicious, + }, + }); + + expect(result).toStrictEqual({ + security_alert_reason: BlockaidReason.setApprovalForAll, + security_alert_response: BlockaidResultType.Malicious, + ui_customizations: ['flagged_as_malicious'], + }); + }); + + it('excludes reason when type is benign', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + result_type: BlockaidResultType.Benign, + }, + }); + + expect(result).toStrictEqual({ + security_alert_response: BlockaidResultType.Benign, + }); + }); + + it('includes eth call counts when providerRequestsCount is provided', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + providerRequestsCount: { + eth_call: 5, + eth_getCode: 3, + }, + }, + }); + + expect(result).toStrictEqual({ + ppom_eth_call_count: 5, + ppom_eth_getCode_count: 3, + ui_customizations: ['flagged_as_malicious'], + security_alert_response: BlockaidResultType.Malicious, + security_alert_reason: BlockaidReason.setApprovalForAll, + }); + }); + + it('excludes eth call counts if providerRequestsCount is empty', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + providerRequestsCount: {}, + }, + }); + + expect(result).toStrictEqual({ + ui_customizations: ['flagged_as_malicious'], + security_alert_response: BlockaidResultType.Malicious, + security_alert_reason: BlockaidReason.setApprovalForAll, + }); + }); + + it('excludes eth call counts if providerRequestsCount is undefined', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + providerRequestsCount: undefined, + }, + }); + + expect(result).toStrictEqual({ + ui_customizations: ['flagged_as_malicious'], + security_alert_response: BlockaidResultType.Malicious, + security_alert_reason: BlockaidReason.setApprovalForAll, + }); + }); +}); diff --git a/ui/hooks/useTransactionEventFragment.js b/ui/hooks/useTransactionEventFragment.js index f304e5ebdeec..ea3a70cf5750 100644 --- a/ui/hooks/useTransactionEventFragment.js +++ b/ui/hooks/useTransactionEventFragment.js @@ -18,14 +18,16 @@ export const useTransactionEventFragment = () => { ); const updateTransactionEventFragment = useCallback( - async (params) => { - if (!transaction || !transaction.id) { + async (params, _transactionId) => { + const transactionId = _transactionId || transaction?.id; + + if (!transactionId) { return; } if (!fragment) { - await createTransactionEventFragment(transaction.id); + await createTransactionEventFragment(transactionId); } - updateEventFragment(`transaction-added-${transaction.id}`, params); + updateEventFragment(`transaction-added-${transactionId}`, params); }, [fragment, transaction], ); diff --git a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js index 8db0c3665862..0e0e37cb8898 100644 --- a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js +++ b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -21,10 +21,6 @@ import { ConfirmPageContainerWarning } from '../../../components/app/confirm-pag import LedgerInstructionField from '../../../components/app/ledger-instruction-field'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) import BlockaidBannerAlert from '../../../components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -import { - MetaMetricsEventCategory, - MetaMetricsEventName, -} from '../../../../shared/constants/metametrics'; ///: END:ONLY_INCLUDE_IF import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; @@ -545,20 +541,6 @@ export default class ConfirmApproveContent extends Component { } = this.props; const { showFullTxDetails, setShowContractDetails } = this.state; - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - const onClickSupportLink = () => { - this.context.trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: MetaMetricsEventName.ExternalLinkClicked, - properties: { - action: 'Confirm Approve', - origin: txData?.origin, - external_link_clicked: 'security_alert_support_link', - }, - }); - }; - ///: END:ONLY_INCLUDE_IF - return (
{ ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {isSuspiciousResponse(txData?.securityProviderResponse) && ( diff --git a/ui/pages/token-allowance/token-allowance.js b/ui/pages/token-allowance/token-allowance.js index 4c4fc9f4c97d..2a0224186ce6 100644 --- a/ui/pages/token-allowance/token-allowance.js +++ b/ui/pages/token-allowance/token-allowance.js @@ -64,11 +64,6 @@ import { isSuspiciousResponse } from '../../../shared/modules/security-provider. ///: BEGIN:ONLY_INCLUDE_IF(blockaid) import BlockaidBannerAlert from '../../components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -import { MetaMetricsContext } from '../../contexts/metametrics'; -import { - MetaMetricsEventCategory, - MetaMetricsEventName, -} from '../../../shared/constants/metametrics'; ///: END:ONLY_INCLUDE_IF import { ConfirmPageContainerNavigation } from '../../components/app/confirm-page-container'; @@ -213,22 +208,6 @@ export default function TokenAllowance({ const networkName = NETWORK_TO_NAME_MAP[fullTxData.chainId] || networkIdentifier; - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - const trackEvent = useContext(MetaMetricsContext); - - const onClickSupportLink = useCallback(() => { - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: MetaMetricsEventName.ExternalLinkClicked, - properties: { - action: 'Confirm Screen', - origin: txData?.origin, - external_link_clicked: 'security_alert_support_link', - }, - }); - }, [trackEvent, txData?.origin]); - ///: END:ONLY_INCLUDE_IF - const customNonceMerge = (transactionData) => customNonceValue ? { @@ -400,11 +379,7 @@ export default function TokenAllowance({ /> { ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - + ///: END:ONLY_INCLUDE_IF } {isSuspiciousResponse(txData?.securityProviderResponse) && ( From dcaec4d09fb2797b312cb579155c788c4f7f8921 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 16 Feb 2024 19:06:30 -0330 Subject: [PATCH 11/49] Cherry-pick 167036f (#22856) into v11.10.0 (#23023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: fix selectedAddress and nftController instantiation (#22856) Noticed that when you freshly import the extension, usse testDapp to deploy and mint an NFT, then manually import the NFT. you will be able to see the NFT. Then when you switch to another account and go back to the account that has the NFT; 1- you wont be able to see the NFTs you imported earlier 2- When you try to import them again you will get an error (fired from core when trying to verify ownership) Noticed that this was due to core having the wrong userAddress to check ownership for, and then in metamask.js, the selectedAddress retrieved does not match the user's selected Address This behavior is also reported to be on v11.10.0, on this issue: https://github.com/MetaMask/metamask-extension/issues/22796 Fixes: https://github.com/MetaMask/metamask-extension/issues/22796 Fixes: https://github.com/MetaMask/metamask-extension/issues/22798 1. Remove and reimport the extension 2. Use testDapp to deploy and mint the NFT 3. Add the NFT manually by clicking "import NFT" 4. You should be able to see your newly import NFT 5. Create new account 6. Go back to the account that has the NFT 7. You should be able to see the NFT you imported https://github.com/MetaMask/metamask-extension/assets/10994169/a74cbf0b-014b-4e39-82ac-e8452941fc6e https://github.com/MetaMask/metamask-extension/assets/10994169/987dbb27-b938-4bce-9b43-4269d714ee33 - [ ] 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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- ## **Description** ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. Co-authored-by: sahar-fehri Co-authored-by: Monte Lai --- app/scripts/metamask-controller.js | 11 +++- test/e2e/tests/nft/import-nft.spec.js | 92 +++++++++++++++++++++++++++ ui/ducks/metamask/metamask.js | 9 ++- ui/store/actions.test.js | 7 ++ ui/store/actions.ts | 1 + 5 files changed, 114 insertions(+), 6 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 64989ab83d6c..dea79e72e80f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2827,10 +2827,10 @@ export default class MetamaskController extends EventEmitter { ), // PreferencesController setSelectedAddress: (address) => { - this.preferencesController.setSelectedAddress(address); const account = this.accountsController.getAccountByAddress(address); if (account) { this.accountsController.setSelectedAccount(account.id); + this.preferencesController.setSelectedAddress(address); } else { throw new Error(`No account found for address: ${address}`); } @@ -2875,8 +2875,13 @@ export default class MetamaskController extends EventEmitter { ///: END:ONLY_INCLUDE_IF // AccountsController - setSelectedInternalAccount: - accountsController.setSelectedAccount.bind(accountsController), + setSelectedInternalAccount: (id) => { + const account = this.accountsController.getAccount(id); + if (account) { + this.preferencesController.setSelectedAddress(account.address); + this.accountsController.setSelectedAccount(id); + } + }, setAccountName: accountsController.setAccountName.bind(accountsController), diff --git a/test/e2e/tests/nft/import-nft.spec.js b/test/e2e/tests/nft/import-nft.spec.js index bf6f3114e62f..445d135d9f2a 100644 --- a/test/e2e/tests/nft/import-nft.spec.js +++ b/test/e2e/tests/nft/import-nft.spec.js @@ -3,6 +3,8 @@ const { defaultGanacheOptions, withFixtures, unlockWallet, + findAnotherAccountFromAccountList, + waitForAccountRendered, } = require('../../helpers'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); const FixtureBuilder = require('../../fixture-builder'); @@ -57,6 +59,96 @@ describe('Import NFT', function () { ); }); + it('should continue to display an imported NFT after importing, adding a new account, and switching back', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract, + title: this.test.fullTitle(), + }, + async ({ driver, _, contractRegistry }) => { + const contractAddress = + contractRegistry.getContractAddress(smartContract); + await unlockWallet(driver); + + // After login, go to NFTs tab, open the import NFT form + await driver.clickElement('[data-testid="home__nfts-tab"]'); + await driver.clickElement({ text: 'Import NFT', tag: 'button' }); + + // Enter a valid NFT that belongs to user and check success message appears + await driver.fill('#address', contractAddress); + await driver.fill('#token-id', '1'); + await driver.clickElement( + '[data-testid="import-nfts-modal-import-button"]', + ); + + const newNftNotification = await driver.findElement({ + text: 'NFT was successfully added!', + tag: 'h6', + }); + assert.equal(await newNftNotification.isDisplayed(), true); + + // Check the imported NFT and its image are displayed in the NFT tab + const importedNft = await driver.waitForSelector({ + css: 'h5', + text: 'TestDappNFTs', + }); + const importedNftImage = await driver.findElement( + '.nft-item__container', + ); + assert.equal(await importedNft.isDisplayed(), true); + assert.equal(await importedNftImage.isDisplayed(), true); + + await driver.clickElement({ text: 'Tokens', tag: 'button' }); + await driver.clickElement('[data-testid="account-menu-icon"]'); + 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'); + await driver.delay(400); + await driver.clickElement({ text: 'Create', tag: 'button' }); + + await driver.isElementPresent({ + tag: 'span', + text: '2nd account', + }); + + const accountOneSelector = await findAnotherAccountFromAccountList( + driver, + 1, + 'Account 1', + ); + await waitForAccountRendered(driver); + await driver.clickElement(accountOneSelector); + + await driver.clickElement({ text: 'NFTs', tag: 'button' }); + const nftIsStillDisplayed = await driver.isElementPresentAndVisible({ + css: 'h5', + text: 'TestDappNFTs', + }); + const nftImageIsStillDisplayed = + await driver.isElementPresentAndVisible('.nft-item__container'); + assert.equal( + nftIsStillDisplayed, + true, + 'Nft is no longer displayed after adding an account and switching back to account 1', + ); + assert.equal( + nftImageIsStillDisplayed, + true, + 'Nft image is no longer displayed after adding an account and switching back to account 1', + ); + }, + ); + }); + it('should not be able to import an NFT that does not belong to user', async function () { await withFixtures( { diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index 885fe50b7eb8..a198c5ff06bc 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -14,6 +14,7 @@ import { checkNetworkAndAccountSupports1559, getAddressBook, getSelectedNetworkClientId, + getSelectedInternalAccount, } from '../../selectors'; import * as actionConstants from '../../store/actionConstants'; import { updateTransactionGasFees } from '../../store/actions'; @@ -271,8 +272,10 @@ export function getNftsDropdownState(state) { export const getNfts = (state) => { const { - metamask: { allNfts, selectedAddress }, + metamask: { allNfts }, } = state; + const { address: selectedAddress } = getSelectedInternalAccount(state); + const { chainId } = getProviderConfig(state); return allNfts?.[selectedAddress]?.[chainId] ?? []; @@ -280,10 +283,10 @@ export const getNfts = (state) => { export const getNftContracts = (state) => { const { - metamask: { allNftContracts, selectedAddress }, + metamask: { allNftContracts }, } = state; + const { address: selectedAddress } = getSelectedInternalAccount(state); const { chainId } = getProviderConfig(state); - return allNftContracts?.[selectedAddress]?.[chainId] ?? []; }; diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index a2f74da3b759..61f7da4aa835 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -889,15 +889,20 @@ describe('Actions', () => { const setSelectedInternalAccountSpy = sinon .stub() .callsFake((_, cb) => cb()); + const setSelectedAddressSpy = sinon.stub().callsFake((_, cb) => cb()); background.getApi.returns({ setSelectedInternalAccount: setSelectedInternalAccountSpy, + setSelectedAddress: setSelectedAddressSpy, }); setBackgroundConnection(background.getApi()); await store.dispatch(actions.setSelectedAccount('0x123')); expect(setSelectedInternalAccountSpy.callCount).toStrictEqual(1); + expect(setSelectedInternalAccountSpy.calledWith('mock-id')).toBe(true); + expect(setSelectedAddressSpy.callCount).toStrictEqual(1); + expect(setSelectedAddressSpy.calledWith('0x123')).toBe(true); }); it('displays warning if setSelectedAccount throws', async () => { @@ -936,9 +941,11 @@ describe('Actions', () => { const setSelectedInternalAccountSpy = sinon .stub() .callsFake((_, cb) => cb(new Error('error'))); + const setSelectedAddressSpy = sinon.stub().callsFake((_, cb) => cb()); background.getApi.returns({ setSelectedInternalAccount: setSelectedInternalAccountSpy, + setSelectedAddress: setSelectedAddressSpy, }); setBackgroundConnection(background.getApi()); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index a553a1e8cdf9..70e79d8e2315 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -1757,6 +1757,7 @@ export function setSelectedAccount( !currentTabIsConnectedToNextAddress; try { + await _setSelectedAddress(address); await _setSelectedInternalAccount(internalAccount.id); await forceUpdateMetamaskState(dispatch); } catch (error) { From 0cc816e6df9c63dd898f05728ba76c3aeb055c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Regadas?= Date: Mon, 19 Feb 2024 12:23:56 +0000 Subject: [PATCH 12/49] [MMI] 4564 - fix the way get custody type was being done (#22950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** We were hardcoding the custody type to always be `JSON-RPC`, but in reality it can be also `ECA3`, or a legacy custodian such as `Qredo`. Therefore we now get this value the correct way. [Ref ticket](https://consensyssoftware.atlassian.net/browse/MMI-4564) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/scripts/controllers/mmi-controller.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index 628c2e32d3aa..87577bf564aa 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -544,14 +544,18 @@ export default class MMIController extends EventEmitter { } async handleMmiCheckIfTokenIsPresent(req) { - const { token, envName } = req.params; - // TODO (Bernardo) - Check if this is the case - const custodyType = 'Custody - JSONRPC'; // Only JSONRPC is supported for now + const { token, envName, address } = req.params; + + const currentAddress = + address || this.preferencesController.getSelectedAddress(); + const currentCustodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(currentAddress), + ); // This can only work if the extension is unlocked await this.appStateController.getUnlockPromise(true); - const keyring = await this.addKeyringIfNotExists(custodyType); + const keyring = await this.addKeyringIfNotExists(currentCustodyType); return await this.custodyController.handleMmiCheckIfTokenIsPresent({ token, From 4df42ca35fa5af4a1f523ce7718e221e1da946f9 Mon Sep 17 00:00:00 2001 From: Victor Thomas <10986371+vthomas13@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:30:08 -0500 Subject: [PATCH 13/49] Multichain: Amon Hen Test Fixture Creation (#23008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Adding a method to the fixture-builder to help provide dapp connection data for Amon Hen features to test with. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/2081 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- test/e2e/fixture-builder.js | 131 ++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 189af02db271..d658741ef1e8 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -1560,6 +1560,137 @@ class FixtureBuilder { }); } + /* Steps to create fixture: + 1. Reinstall clean metamask & Onboard + 2. Create 4 more accounts in the wallet + 3. Connected to ENS dapp on Account 1 and 3 + 4. Connected to Uniswap dapp on Accounts 1 and 4 + 5. Connected to Dextools dapp on Accounts 1, 2, and 3 + 6. Connected to Coinmarketcap dapp on Account 1 (didnt log in) + 7. opened devtools and ran stateHooks.getCleanAppState() in console + */ + withConnectionsToManyDapps() { + return this.withPermissionController({ + subjects: { + 'https://app.ens.domains': { + origin: 'https://app.ens.domains', + permissions: { + eth_accounts: { + id: 'oKXoF_MNlffiR2u1Y3mDE', + parentCapability: 'eth_accounts', + invoker: 'https://app.ens.domains', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [ + '0xbee150bdc171c7d4190891e78234f791a3ac7b24', + '0xb9504634e5788208933b51ae7440b478bfadf865', + ], + }, + ], + date: 1708029792962, + }, + }, + }, + 'https://app.uniswap.org': { + origin: 'https://app.uniswap.org', + permissions: { + eth_accounts: { + id: 'vaa88u5Iv3VmsJwG3bDKW', + parentCapability: 'eth_accounts', + invoker: 'https://app.uniswap.org', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [ + '0xbee150bdc171c7d4190891e78234f791a3ac7b24', + '0xd1ca923697a701cba1364d803d72b4740fc39bc9', + ], + }, + ], + date: 1708029870079, + }, + }, + }, + 'https://www.dextools.io': { + origin: 'https://www.dextools.io', + permissions: { + eth_accounts: { + id: 'bvvPcFtIhkFyHyW0Tmwi4', + parentCapability: 'eth_accounts', + invoker: 'https://www.dextools.io', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [ + '0xbee150bdc171c7d4190891e78234f791a3ac7b24', + '0xa5c5293e124d04e2f85e8553851001fd2f192647', + '0xb9504634e5788208933b51ae7440b478bfadf865', + ], + }, + ], + date: 1708029948170, + }, + }, + }, + 'https://coinmarketcap.com': { + origin: 'https://coinmarketcap.com', + permissions: { + eth_accounts: { + id: 'AiblK84K1Cic-Y0FDSzMD', + parentCapability: 'eth_accounts', + invoker: 'https://coinmarketcap.com', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xbee150bdc171c7d4190891e78234f791a3ac7b24'], + }, + ], + date: 1708030049641, + }, + }, + }, + }, + subjectMetadata: { + 'https://ens.domains': { + iconUrl: null, + name: 'ens.domains', + subjectType: 'website', + origin: 'https://ens.domains', + extensionId: null, + }, + 'https://app.ens.domains': { + iconUrl: 'https://app.ens.domains/favicon-32x32.png', + name: 'ENS', + subjectType: 'website', + origin: 'https://app.ens.domains', + extensionId: null, + }, + 'https://app.uniswap.org': { + iconUrl: 'https://app.uniswap.org/favicon.png', + name: 'Uniswap Interface', + subjectType: 'website', + origin: 'https://app.uniswap.org', + extensionId: null, + }, + 'https://www.dextools.io': { + iconUrl: 'https://www.dextools.io/app/favicon.ico', + name: 'DEXTools.io', + subjectType: 'website', + origin: 'https://www.dextools.io', + extensionId: null, + }, + 'https://coinmarketcap.com': { + iconUrl: 'https://coinmarketcap.com/favicon.ico', + name: 'CoinMarketCap', + subjectType: 'website', + origin: 'https://coinmarketcap.com', + extensionId: null, + }, + }, + }); + } + withNameController(data) { merge( this.fixture.data.NameController From 04db93850d18ad2265d7f6c07db322233ad84f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Mon, 19 Feb 2024 15:59:00 +0100 Subject: [PATCH 14/49] [MMI] Removed unused methods (#23000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** In the `CustodyController` institutional package, we have two methods, `getCustodianConnectRequest` and `setCustodianConnectRequest`, that are not being used in the monorepo, so this ticket aims to remove them. ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMI-4578 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [x] I've included manual testing steps - [x] I've included screenshots/recordings if applicable - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/scripts/metamask-controller.js | 8 --- .../confirm-add-custodian-token.js | 8 --- .../confirm-add-custodian-token.test.js | 58 ------------------- ...interactive-replacement-token-page.test.js | 6 +- .../institution-background.test.js | 8 --- .../institutional/institution-background.ts | 14 ----- 6 files changed, 3 insertions(+), 99 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3073cc8b84a8..355655ed601c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -3114,14 +3114,6 @@ export default class MetamaskController extends EventEmitter { this.custodyController.setWaitForConfirmDeepLinkDialog.bind( this.custodyController, ), - setCustodianConnectRequest: - this.custodyController.setCustodianConnectRequest.bind( - this.custodyController, - ), - getCustodianConnectRequest: - this.custodyController.getCustodianConnectRequest.bind( - this.custodyController, - ), getMmiConfiguration: this.mmiConfigurationController.getConfiguration.bind( this.mmiConfigurationController, diff --git a/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.js b/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.js index bd6d8dd83319..078e3f8038db 100644 --- a/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.js +++ b/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.js @@ -74,14 +74,6 @@ const ConfirmAddCustodianToken = () => { await dispatch(setProviderType(networkType)); } - - await dispatch( - mmiActions.setCustodianConnectRequest({ - token: connectRequest.token, - envName: connectRequest.environment, - custodianType: connectRequest.service, - }), - ); } await dispatch( diff --git a/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.test.js b/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.test.js index 76fdfc41cdba..077c3b5649bb 100644 --- a/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.test.js +++ b/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.test.js @@ -8,14 +8,9 @@ const mockedRemoveAddTokenConnectRequest = jest .fn() .mockReturnValue({ type: 'TYPE' }); -const mockedSetCustodianConnectRequest = jest - .fn() - .mockReturnValue({ type: 'TYPE' }); - jest.mock('../../../store/institutional/institution-background', () => ({ mmiActionsFactory: () => ({ removeAddTokenConnectRequest: mockedRemoveAddTokenConnectRequest, - setCustodianConnectRequest: mockedSetCustodianConnectRequest, }), })); @@ -134,57 +129,4 @@ describe('Confirm Add Custodian Token', () => { }); }); }); - - it('clicks the confirm button without chainId and calls setCustodianConnectRequest with custodianName comming from the environment connectRequest', async () => { - const customMockedStore = { - metamask: { - providerConfig: { - type: 'test', - }, - preferences: { - useNativeCurrencyAsPrimaryCurrency: true, - }, - institutionalFeatures: { - connectRequests: [ - { - labels: [ - { - key: 'service', - value: 'test', - }, - ], - origin: 'origin', - token: '', - feature: 'custodian', - service: 'JSONRPC', - environment: 'jsonrpc', - }, - ], - }, - }, - history: { - push: '/', - mostRecentOverviewPage: '/', - }, - }; - - const customStore = configureMockStore()(customMockedStore); - - renderWithProvider(, customStore); - - await waitFor(() => { - const confirmButton = screen.getByTestId('confirm-btn'); - fireEvent.click(confirmButton); - }); - - expect(screen.getByTestId('pulse-loader')).toBeInTheDocument(); - - await waitFor(() => { - expect(mockedSetCustodianConnectRequest).toHaveBeenCalledWith({ - token: '', - custodianType: 'JSONRPC', - envName: 'jsonrpc', - }); - }); - }); }); diff --git a/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.js b/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.js index af4ccfdd4660..5afd89ee8fb3 100644 --- a/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.js +++ b/ui/pages/institutional/interactive-replacement-token-page/interactive-replacement-token-page.test.js @@ -44,7 +44,7 @@ const mockedRemoveAddTokenConnectRequest = jest const mockedSetCustodianNewRefreshToken = jest .fn() .mockReturnValue({ type: 'TYPE' }); -let mockedGetCustodianConnectRequest = jest +let mockedGetCustodianAccounts = jest .fn() .mockReturnValue(async () => await custodianAccounts); @@ -52,7 +52,7 @@ jest.mock('../../../store/institutional/institution-background', () => ({ mmiActionsFactory: () => ({ removeAddTokenConnectRequest: mockedRemoveAddTokenConnectRequest, setCustodianNewRefreshToken: mockedSetCustodianNewRefreshToken, - getCustodianAccounts: mockedGetCustodianConnectRequest, + getCustodianAccounts: mockedGetCustodianAccounts, }), showInteractiveReplacementTokenBanner: () => mockedShowInteractiveReplacementTokenBanner, @@ -203,7 +203,7 @@ describe('Interactive Replacement Token Page', function () { }); it('should reject if there are errors', async () => { - mockedGetCustodianConnectRequest = jest.fn().mockReturnValue(async () => { + mockedGetCustodianAccounts = jest.fn().mockReturnValue(async () => { throw new Error(); }); diff --git a/ui/store/institutional/institution-background.test.js b/ui/store/institutional/institution-background.test.js index 8fe56eefce70..75792512e9ad 100644 --- a/ui/store/institutional/institution-background.test.js +++ b/ui/store/institutional/institution-background.test.js @@ -40,8 +40,6 @@ describe('Institution Actions', () => { getCustodianToken: jest.fn(), getCustodianJWTList: jest.fn(), removeAddTokenConnectRequest: jest.fn(), - setCustodianConnectRequest: jest.fn(), - getCustodianConnectRequest: jest.fn(), getMmiConfiguration: jest.fn(), getAllCustodianAccountsWithToken: jest.fn(), setWaitForConfirmDeepLinkDialog: jest.fn(), @@ -85,7 +83,6 @@ describe('Institution Actions', () => { custodians: [], }); mmiActions.getCustodianToken({}); - mmiActions.getCustodianConnectRequest(); mmiActions.getCustodianTransactionDeepLink('0xAddress', 'txId'); mmiActions.getCustodianConfirmDeepLink('txId'); mmiActions.getCustodianSignMessageDeepLink('0xAddress', 'custodyTxId'); @@ -99,11 +96,6 @@ describe('Institution Actions', () => { token: 'token', environment: 'jupiter', }); - mmiActions.setCustodianConnectRequest({ - token: 'token', - custodianType: 'custodianType', - envName: 'jupiter', - }); const setWaitForConfirmDeepLinkDialog = mmiActions.setWaitForConfirmDeepLinkDialog(true); mmiActions.setCustodianNewRefreshToken('address', 'refreshToken'); diff --git a/ui/store/institutional/institution-background.ts b/ui/store/institutional/institution-background.ts index cee2924ce424..cd437d09f651 100644 --- a/ui/store/institutional/institution-background.ts +++ b/ui/store/institutional/institution-background.ts @@ -219,20 +219,6 @@ export function mmiActionsFactory() { environment, token, }), - setCustodianConnectRequest: ({ - token, - custodianType, - envName, - }: { - token: string; - custodianType: string; - envName: string; - }) => - createAsyncAction('setCustodianConnectRequest', [ - { token, custodianType, envName }, - ]), - getCustodianConnectRequest: () => - createAsyncAction('getCustodianConnectRequest', []), getMmiConfiguration: () => createAsyncAction('getMmiConfiguration', []), getAllCustodianAccountsWithToken: (custodyType: string, token: string) => createAsyncAction('getAllCustodianAccountsWithToken', [ From 17c31707a9db8b72a628d608b62e4b7091e4cdc8 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Mon, 19 Feb 2024 16:08:52 +0100 Subject: [PATCH 15/49] Fix Snap UI link misalignment (#23045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR fixes the link misalignment in Snap UI. ## **Related issues** Fixes: #23042 ## **Manual testing steps** 1. Go to test snaps 2. Use the dialogs snap 3. Look at the link alignment in the text ## **Screenshots/Recordings** ### **Before** ![image](https://github.com/MetaMask/metamask-extension/assets/13910212/917d699f-9edb-48f6-941e-51ddda02b567) ### **After** ![image](https://github.com/MetaMask/metamask-extension/assets/13910212/53d9d891-4f1d-45a4-beb0-2591c28e3486) ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ui/components/app/snaps/snap-ui-markdown/index.scss | 5 +++++ ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/components/app/snaps/snap-ui-markdown/index.scss b/ui/components/app/snaps/snap-ui-markdown/index.scss index e7cad21bcfad..17cc5a212854 100644 --- a/ui/components/app/snaps/snap-ui-markdown/index.scss +++ b/ui/components/app/snaps/snap-ui-markdown/index.scss @@ -9,5 +9,10 @@ & > span:last-child { margin-inline-start: 2px; } + + & .mm-icon--size-inherit { + // Fixes the icon misalignment in ButtonLink when using ButtonLinkSize.Inherit + top: 0.1em; + } } } diff --git a/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js b/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js index 96c9d7e2be5a..d91b2b377c4e 100644 --- a/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js +++ b/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js @@ -29,8 +29,6 @@ const Paragraph = (props) => ( const Link = ({ onClick, children, ...rest }) => ( Date: Mon, 19 Feb 2024 16:34:37 +0100 Subject: [PATCH 16/49] Add additional snap installation metrics (#23044) ## **Description** Adds additional snap installation metrics to be collected via the **opt-in** MetaMetrics program. Adds events for when snap installation and updating is started and ended prematurely (either rejected or otherwise failed). ## **Related issues** Closes: https://github.com/MetaMask/MetaMask-planning/issues/1839 --- app/scripts/metamask-controller.js | 43 ++++++++++++++++++++++++++++++ shared/constants/metametrics.ts | 6 +++++ 2 files changed, 49 insertions(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 355655ed601c..d9a461b93588 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2391,6 +2391,49 @@ export default class MetamaskController extends EventEmitter { ); ///: BEGIN:ONLY_INCLUDE_IF(snaps) + + this.controllerMessenger.subscribe( + `${this.snapController.name}:snapInstallStarted`, + (snapId, origin, isUpdate) => { + const snapCategory = this._getSnapMetadata(snapId)?.category; + this.metaMetricsController.trackEvent({ + event: isUpdate + ? MetaMetricsEventName.SnapUpdateStarted + : MetaMetricsEventName.SnapInstallStarted, + category: MetaMetricsEventCategory.Snaps, + properties: { + snap_id: snapId, + origin, + snap_category: snapCategory, + }, + }); + }, + ); + + this.controllerMessenger.subscribe( + `${this.snapController.name}:snapInstallFailed`, + (snapId, origin, isUpdate, error) => { + const isRejected = error.includes('User rejected the request.'); + const failedEvent = isUpdate + ? MetaMetricsEventName.SnapUpdateFailed + : MetaMetricsEventName.SnapInstallFailed; + const rejectedEvent = isUpdate + ? MetaMetricsEventName.SnapUpdateRejected + : MetaMetricsEventName.SnapInstallRejected; + + const snapCategory = this._getSnapMetadata(snapId)?.category; + this.metaMetricsController.trackEvent({ + event: isRejected ? rejectedEvent : failedEvent, + category: MetaMetricsEventCategory.Snaps, + properties: { + snap_id: snapId, + origin, + snap_category: snapCategory, + }, + }); + }, + ); + this.controllerMessenger.subscribe( `${this.snapController.name}:snapInstalled`, (truncatedSnap, origin) => { diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 29f3c213add3..b4825277670b 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -659,8 +659,14 @@ export enum MetaMetricsEventName { ExitedSwaps = 'Exited Swaps', SwapError = 'Swap Error', ///: BEGIN:ONLY_INCLUDE_IF(snaps) + SnapInstallStarted = 'Snap Install Started', + SnapInstallFailed = 'Snap Install Failed', + SnapInstallRejected = 'Snap Update Rejected', SnapInstalled = 'Snap Installed', SnapUninstalled = 'Snap Uninstalled', + SnapUpdateStarted = 'Snap Update Started', + SnapUpdateRejected = 'Snap Update Rejected', + SnapUpdateFailed = 'Snap Update Failed', SnapUpdated = 'Snap Updated', SnapExportUsed = 'Snap Export Used', ///: END:ONLY_INCLUDE_IF From 50625a893a8c454c5b4333936299b13350ed39cf Mon Sep 17 00:00:00 2001 From: Victor Thomas <10986371+vthomas13@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:49:41 -0500 Subject: [PATCH 17/49] Adding Permissions Tour & Empty State (#22905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR is for the conversion of the AllConnections component to the PermissionsPage component ## **Related issues** Fixes:https://github.com/MetaMask/MetaMask-planning/issues/1989 ## **Manual testing steps** 1. yarn && MULTICHAIN=1 yarn start 2. open fullscreen, add "#permissions" route to the end of the URL 3. disconnect all dapps and verify empty state after no dapps are connected. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [X] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [X] I've clearly explained what problem this PR is solving and how it is solved. - [X] I've linked related issues - [X] I've included manual testing steps - [X] I've included screenshots/recordings if applicable - [X] I’ve included tests if applicable - [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [X] I’ve properly set the pull request status: - [X] In case it's not yet "ready for review", I've set it to "draft". - [X] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/_locales/en/messages.json | 19 +- app/scripts/controllers/app-state.js | 10 + app/scripts/lib/setupSentry.js | 1 + app/scripts/metamask-controller.js | 2 + shared/constants/metametrics.ts | 1 + ...rs-after-init-opt-in-background-state.json | 1 + .../errors-after-init-opt-in-ui-state.json | 1 + .../multichain/global-menu/global-menu.js | 20 ++ .../all-connections.test.js.snap | 234 --------------- .../pages/all-connections/all-connections.js | 211 -------------- .../all-connections.stories.js | 9 - ui/components/multichain/pages/index.js | 2 +- ui/components/multichain/pages/index.scss | 2 +- .../permissions-page.test.js.snap | 271 ++++++++++++++++++ .../connection-list-item.js | 0 .../connection-list-item.stories.js | 0 .../permissions-page/permissions-page.js | 204 +++++++++++++ .../permissions-page.scss} | 2 +- .../permissions-page.stories.js | 9 + .../permissions-page.test.js} | 16 +- .../product-tour-popover/index.scss | 5 + .../product-tour-popover.js | 4 +- ui/helpers/constants/routes.ts | 6 +- ui/pages/routes/routes.component.js | 19 +- ui/selectors/selectors.js | 39 +++ ui/store/actions.ts | 4 + 26 files changed, 610 insertions(+), 482 deletions(-) delete mode 100644 ui/components/multichain/pages/all-connections/__snapshots__/all-connections.test.js.snap delete mode 100644 ui/components/multichain/pages/all-connections/all-connections.js delete mode 100644 ui/components/multichain/pages/all-connections/all-connections.stories.js create mode 100644 ui/components/multichain/pages/permissions-page/__snapshots__/permissions-page.test.js.snap rename ui/components/multichain/pages/{all-connections => permissions-page}/connection-list-item.js (100%) rename ui/components/multichain/pages/{all-connections => permissions-page}/connection-list-item.stories.js (100%) create mode 100644 ui/components/multichain/pages/permissions-page/permissions-page.js rename ui/components/multichain/pages/{all-connections/all-connections.scss => permissions-page/permissions-page.scss} (79%) create mode 100644 ui/components/multichain/pages/permissions-page/permissions-page.stories.js rename ui/components/multichain/pages/{all-connections/all-connections.test.js => permissions-page/permissions-page.test.js} (88%) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8a8b600b9a89..403509343c6a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -352,9 +352,6 @@ "alerts": { "message": "Alerts" }, - "allConnections": { - "message": "All Connections" - }, "allCustodianAccountsConnectedSubtitle": { "message": "You have either already connected all your custodian accounts or don’t have any account to connect to MetaMask Institutional." }, @@ -365,6 +362,9 @@ "message": "All of your $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allPermissions": { + "message": "All Permissions" + }, "allYourNFTsOf": { "message": "All of your NFTs from $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -3488,6 +3488,19 @@ "permissions": { "message": "Permissions" }, + "permissionsPageEmptyContent": { + "message": "Nothing to see here" + }, + "permissionsPageEmptySubContent": { + "message": "This is where you can see the permissions you've given to installed Snaps or connected sites." + }, + + "permissionsPageTourDescription": { + "message": "This is your control panel for managing permissions given to connected sites and installed Snaps." + }, + "permissionsPageTourTitle": { + "message": "Connected sites are now permissions" + }, "permissionsTitle": { "message": "Permissions" }, diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index ce890bb5672f..44d1a367362e 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -47,6 +47,7 @@ export default class AppStateController extends EventEmitter { nftsDetectionNoticeDismissed: false, showTestnetMessageInDropdown: true, showBetaHeader: isBeta(), + showPermissionsTour: true, showProductTour: true, showNetworkBanner: true, showAccountBanner: true, @@ -364,6 +365,15 @@ export default class AppStateController extends EventEmitter { this.store.updateState({ showBetaHeader }); } + /** + * Sets whether the permissions tour should be shown to the user + * + * @param showPermissionsTour + */ + setShowPermissionsTour(showPermissionsTour) { + this.store.updateState({ showPermissionsTour }); + } + /** * Sets whether the product tour should be shown * diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 8e650e6a674c..da4227be354d 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -85,6 +85,7 @@ export const SENTRY_BACKGROUND_STATE = { recoveryPhraseReminderHasBeenShown: true, recoveryPhraseReminderLastShown: true, showBetaHeader: true, + showPermissionsTour: true, showProductTour: true, showNetworkBanner: true, showAccountBanner: true, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d9a461b93588..89065229840f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -3005,6 +3005,8 @@ export default class MetamaskController extends EventEmitter { ), setShowBetaHeader: appStateController.setShowBetaHeader.bind(appStateController), + setShowPermissionsTour: + appStateController.setShowPermissionsTour.bind(appStateController), setShowProductTour: appStateController.setShowProductTour.bind(appStateController), setShowAccountBanner: diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index b4825277670b..563665135b41 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -542,6 +542,7 @@ export enum MetaMetricsEventName { NavAccountDetailsOpened = 'Account Details Opened', NavConnectedSitesOpened = 'Connected Sites Opened', NavMainMenuOpened = 'Main Menu Opened', + NavPermissionsOpened = 'Permissions Opened', NavNetworkMenuOpened = 'Network Menu Opened', NavSettingsOpened = 'Settings Opened', NavAccountSwitched = 'Account Switched', 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 2c0573f4bc8b..901d85891078 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 @@ -34,6 +34,7 @@ "showTestnetMessageInDropdown": true, "signatureSecurityAlertResponses": "object", "showBetaHeader": false, + "showPermissionsTour": true, "showProductTour": true, "showNetworkBanner": true, "showAccountBanner": true, 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 7b303208da63..57eae589e5c7 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 @@ -69,6 +69,7 @@ "showTestnetMessageInDropdown": true, "signatureSecurityAlertResponses": "object", "showBetaHeader": false, + "showPermissionsTour": true, "showProductTour": true, "showNetworkBanner": true, "showAccountBanner": true, diff --git a/ui/components/multichain/global-menu/global-menu.js b/ui/components/multichain/global-menu/global-menu.js index 5297312d0795..186d514fc5eb 100644 --- a/ui/components/multichain/global-menu/global-menu.js +++ b/ui/components/multichain/global-menu/global-menu.js @@ -9,6 +9,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(snaps) NOTIFICATIONS_ROUTE, SNAPS_ROUTE, + PERMISSIONS, ///: END:ONLY_INCLUDE_IF(snaps) } from '../../../helpers/constants/routes'; import { lockMetamask } from '../../../store/actions'; @@ -180,6 +181,25 @@ export const GlobalMenu = ({ closeMenu, anchorElement, isOpen }) => { > {t('connectedSites')} + {process.env.MULTICHAIN ? ( + { + history.push(PERMISSIONS); + trackEvent({ + event: MetaMetricsEventName.NavPermissionsOpened, + category: MetaMetricsEventCategory.Navigation, + properties: { + location: METRICS_LOCATION, + }, + }); + closeMenu(); + }} + data-testid="global-menu-connected-sites" + > + {t('allPermissions')} + + ) : null} { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) diff --git a/ui/components/multichain/pages/all-connections/__snapshots__/all-connections.test.js.snap b/ui/components/multichain/pages/all-connections/__snapshots__/all-connections.test.js.snap deleted file mode 100644 index 1607c2f83a5f..000000000000 --- a/ui/components/multichain/pages/all-connections/__snapshots__/all-connections.test.js.snap +++ /dev/null @@ -1,234 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`All Connections render renders correctly 1`] = ` -
-
-
-

- Site Connections -

- -

- Snap Connections -

- - - -
-
-
-`; diff --git a/ui/components/multichain/pages/all-connections/all-connections.js b/ui/components/multichain/pages/all-connections/all-connections.js deleted file mode 100644 index 36410a3b2182..000000000000 --- a/ui/components/multichain/pages/all-connections/all-connections.js +++ /dev/null @@ -1,211 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { useHistory } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import { Header, Page } from '../page'; -import { - ButtonIcon, - ButtonIconSize, - IconName, - Text, -} from '../../../component-library'; -import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { - BackgroundColor, - Color, - TextAlign, - TextVariant, -} from '../../../../helpers/constants/design-system'; -import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'; -import { - getAllConnectedAccounts, - getConnectedSubjectsForAllAddresses, - getSnapsList, -} from '../../../../selectors'; -import { Tab, Tabs } from '../../../ui/tabs'; -import { ConnectionListItem } from './connection-list-item'; - -const TABS_THRESHOLD = 5; - -export const AllConnections = () => { - const t = useI18nContext(); - const history = useHistory(); - let totalConnections = 0; - const connectedSubjectsForAllAddresses = useSelector( - getConnectedSubjectsForAllAddresses, - ); - const connectedAddresses = useSelector(getAllConnectedAccounts); - const connectedSnapsData = useSelector(getSnapsList); - - const connectedSiteData = useMemo(() => { - const siteData = {}; - connectedAddresses.forEach((connectedAddress) => { - connectedSubjectsForAllAddresses[connectedAddress].forEach((app) => { - if (!siteData[app.origin]) { - siteData[app.origin] = { ...app, addresses: [] }; - } - siteData[app.origin].addresses.push(connectedAddress); - }); - }); - return siteData; - }, [connectedAddresses, connectedSubjectsForAllAddresses]); - - const sitesConnectionsList = useMemo(() => { - const sitesList = {}; - Object.keys(connectedSiteData).forEach((siteKey) => { - const siteData = connectedSiteData[siteKey]; - const { name, iconUrl, origin, subjectType, extensionId, addresses } = - siteData; - - if (!sitesList[name]) { - sitesList[name] = { - name, - iconUrl, - origin, - subjectType, - extensionId, - addresses: [], - }; - totalConnections += 1; - } - - sitesList[name].addresses.push(...addresses); - }); - return sitesList; - }, [connectedSiteData]); - - const snapsConnectionsList = useMemo(() => { - const snapsList = {}; - Object.keys(connectedSnapsData).forEach((snap) => { - const snapData = connectedSnapsData[snap]; - const { id, name, packageName, iconUrl, subjectType } = snapData; - - if (!snapsList[name]) { - snapsList[name] = { - id, - name, - iconUrl, - packageName, - subjectType, - }; - totalConnections += 1; - } - }); - return snapsList; - }, [connectedSnapsData]); - - const shouldShowTabsView = useMemo(() => { - return ( - totalConnections > TABS_THRESHOLD && - Object.keys(sitesConnectionsList).length > 0 && - Object.keys(snapsConnectionsList).length > 0 - ); - }, [totalConnections, sitesConnectionsList, snapsConnectionsList]); - - const handleConnectionClick = useCallback((connection) => { - // TODO: go to connection details page - console.log('connection clicked: ', connection); - }, []); - - const renderConnectionsList = (connectionList) => - Object.entries(connectionList).map(([itemKey, connection]) => { - return ( - handleConnectionClick(connection)} - /> - ); - }); - - return ( - history.push(DEFAULT_ROUTE)} - size={ButtonIconSize.Sm} - /> - } - > - - {t('allConnections')} - - - } - > - {shouldShowTabsView ? ( - - - {renderConnectionsList(sitesConnectionsList)} - - - {renderConnectionsList(snapsConnectionsList)} - - - ) : ( - <> - {Object.keys(sitesConnectionsList).length > 0 && ( - <> - - {t('siteConnections')} - - {renderConnectionsList(sitesConnectionsList)} - - )} - {Object.keys(snapsConnectionsList).length > 0 && ( - <> - - {t('snapConnections')} - - {renderConnectionsList(snapsConnectionsList)} - - )} - - )} - {totalConnections === 0 ? ( - - {/* TODO: get copy for this edge case */} - No Connected Sites or Snaps - - ) : null} - - ); -}; diff --git a/ui/components/multichain/pages/all-connections/all-connections.stories.js b/ui/components/multichain/pages/all-connections/all-connections.stories.js deleted file mode 100644 index 016691cd7c60..000000000000 --- a/ui/components/multichain/pages/all-connections/all-connections.stories.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { AllConnections } from './all-connections'; - -export default { - title: 'Components/Multichain/AllConnections', -}; -export const DefaultStory = () => ; - -DefaultStory.storyName = 'Default'; diff --git a/ui/components/multichain/pages/index.js b/ui/components/multichain/pages/index.js index a3471211eb26..a8e64d1fcf9b 100644 --- a/ui/components/multichain/pages/index.js +++ b/ui/components/multichain/pages/index.js @@ -1,2 +1,2 @@ export { Connections } from './connections'; -export { AllConnections } from './all-connections/all-connections'; +export { PermissionsPage } from './permissions-page/permissions-page'; diff --git a/ui/components/multichain/pages/index.scss b/ui/components/multichain/pages/index.scss index 427139ba1396..5bc43557f0bf 100644 --- a/ui/components/multichain/pages/index.scss +++ b/ui/components/multichain/pages/index.scss @@ -1,3 +1,3 @@ @import 'page/'; @import 'send/'; -@import 'all-connections/all-connections'; +@import 'permissions-page/permissions-page'; diff --git a/ui/components/multichain/pages/permissions-page/__snapshots__/permissions-page.test.js.snap b/ui/components/multichain/pages/permissions-page/__snapshots__/permissions-page.test.js.snap new file mode 100644 index 000000000000..9d8a12bf6245 --- /dev/null +++ b/ui/components/multichain/pages/permissions-page/__snapshots__/permissions-page.test.js.snap @@ -0,0 +1,271 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`All Connections render renders correctly 1`] = ` +
+
+
+
+
+ +
+
+

+ + Permissions + +

+
+
+
+
+

+ Site Connections +

+ +

+ Snap Connections +

+ + + +
+
+
+
+`; diff --git a/ui/components/multichain/pages/all-connections/connection-list-item.js b/ui/components/multichain/pages/permissions-page/connection-list-item.js similarity index 100% rename from ui/components/multichain/pages/all-connections/connection-list-item.js rename to ui/components/multichain/pages/permissions-page/connection-list-item.js diff --git a/ui/components/multichain/pages/all-connections/connection-list-item.stories.js b/ui/components/multichain/pages/permissions-page/connection-list-item.stories.js similarity index 100% rename from ui/components/multichain/pages/all-connections/connection-list-item.stories.js rename to ui/components/multichain/pages/permissions-page/connection-list-item.stories.js diff --git a/ui/components/multichain/pages/permissions-page/permissions-page.js b/ui/components/multichain/pages/permissions-page/permissions-page.js new file mode 100644 index 000000000000..6580c04b2ec4 --- /dev/null +++ b/ui/components/multichain/pages/permissions-page/permissions-page.js @@ -0,0 +1,204 @@ +import classnames from 'classnames'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { useHistory } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { Content, Header, Page } from '../page'; +import { + Box, + ButtonIcon, + ButtonIconSize, + IconName, + Text, +} from '../../../component-library'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { + BackgroundColor, + BlockSize, + Color, + Display, + FlexDirection, + JustifyContent, + TextAlign, + TextColor, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'; +import { + getOnboardedInThisUISession, + getShowPermissionsTour, + getConnectedSitesList, + getConnectedSnapsList, +} from '../../../../selectors'; +import { Tab, Tabs } from '../../../ui/tabs'; +import { ProductTour } from '../../product-tour-popover'; +import { hidePermissionsTour } from '../../../../store/actions'; +import { ConnectionListItem } from './connection-list-item'; + +const TABS_THRESHOLD = 5; + +export const PermissionsPage = () => { + const t = useI18nContext(); + const history = useHistory(); + const headerRef = useRef(); + const [totalConnections, setTotalConnections] = useState(0); + const sitesConnectionsList = useSelector(getConnectedSitesList); + const snapsConnectionsList = useSelector(getConnectedSnapsList); + const showPermissionsTour = useSelector(getShowPermissionsTour); + const onboardedInThisUISession = useSelector(getOnboardedInThisUISession); + + useEffect(() => { + setTotalConnections( + Object.keys(sitesConnectionsList).length + + Object.keys(snapsConnectionsList).length, + ); + }, [sitesConnectionsList, snapsConnectionsList]); + + const shouldShowTabsView = useMemo(() => { + return ( + totalConnections > TABS_THRESHOLD && + Object.keys(sitesConnectionsList).length > 0 && + Object.keys(snapsConnectionsList).length > 0 + ); + }, [totalConnections, sitesConnectionsList, snapsConnectionsList]); + + const handleConnectionClick = useCallback((connection) => { + // TODO: go to connection details page + console.log('connection clicked: ', connection); + }, []); + + const renderConnectionsList = (connectionList) => + Object.entries(connectionList).map(([itemKey, connection]) => { + return ( + handleConnectionClick(connection)} + /> + ); + }); + + return ( + +
history.push(DEFAULT_ROUTE)} + size={ButtonIconSize.Sm} + /> + } + > + + {t('permissions')} + +
+ {showPermissionsTour && !onboardedInThisUISession ? ( + + ) : null} + + + {shouldShowTabsView ? ( + + + {renderConnectionsList(sitesConnectionsList)} + + + {renderConnectionsList(snapsConnectionsList)} + + + ) : ( + <> + {Object.keys(sitesConnectionsList).length > 0 && ( + <> + + {t('siteConnections')} + + {renderConnectionsList(sitesConnectionsList)} + + )} + {Object.keys(snapsConnectionsList).length > 0 && ( + <> + + {t('snapConnections')} + + {renderConnectionsList(snapsConnectionsList)} + + )} + + )} + {totalConnections === 0 ? ( + + + {t('permissionsPageEmptyContent')} + + + {t('permissionsPageEmptySubContent')} + + + ) : null} + +
+ ); +}; diff --git a/ui/components/multichain/pages/all-connections/all-connections.scss b/ui/components/multichain/pages/permissions-page/permissions-page.scss similarity index 79% rename from ui/components/multichain/pages/all-connections/all-connections.scss rename to ui/components/multichain/pages/permissions-page/permissions-page.scss index 1d2c0ce7c2b9..4f3f5a7c3d86 100644 --- a/ui/components/multichain/pages/all-connections/all-connections.scss +++ b/ui/components/multichain/pages/permissions-page/permissions-page.scss @@ -1,4 +1,4 @@ -.all-connections { +.permissions-page { &__tabs { padding: 0 16px; diff --git a/ui/components/multichain/pages/permissions-page/permissions-page.stories.js b/ui/components/multichain/pages/permissions-page/permissions-page.stories.js new file mode 100644 index 000000000000..9a016d81b760 --- /dev/null +++ b/ui/components/multichain/pages/permissions-page/permissions-page.stories.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { PermissionsPage } from './permissions-page'; + +export default { + title: 'Components/Multichain/PermissionsPage', +}; +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/multichain/pages/all-connections/all-connections.test.js b/ui/components/multichain/pages/permissions-page/permissions-page.test.js similarity index 88% rename from ui/components/multichain/pages/all-connections/all-connections.test.js rename to ui/components/multichain/pages/permissions-page/permissions-page.test.js index e689ab0d8f07..6b26ad5e089c 100644 --- a/ui/components/multichain/pages/all-connections/all-connections.test.js +++ b/ui/components/multichain/pages/permissions-page/permissions-page.test.js @@ -2,7 +2,7 @@ import React from 'react'; import configureStore from '../../../../store/store'; import mockState from '../../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; -import { AllConnections } from './all-connections'; +import { PermissionsPage } from './permissions-page'; mockState.metamask.subjectMetadata = { 'https://metamask.github.io': { @@ -84,16 +84,16 @@ describe('All Connections', () => { describe('render', () => { it('renders correctly', () => { const { container, getByTestId } = renderWithProvider( - , + , store, ); expect(container).toMatchSnapshot(); - expect(getByTestId('all-connections')).toBeInTheDocument(); + expect(getByTestId('permissions-page')).toBeInTheDocument(); }); it('renders sections when user has 5 or less connections', () => { - const { getByTestId } = renderWithProvider(, store); + const { getByTestId } = renderWithProvider(, store); expect(getByTestId('sites-connections')).toBeInTheDocument(); expect(getByTestId('snaps-connections')).toBeInTheDocument(); }); @@ -134,16 +134,16 @@ describe('All Connections', () => { }, }; store = configureStore(mockState); - const { getByTestId } = renderWithProvider(, store); - expect(getByTestId('all-connections-sites-tab')).toBeInTheDocument(); - expect(getByTestId('all-connections-snaps-tab')).toBeInTheDocument(); + const { getByTestId } = renderWithProvider(, store); + expect(getByTestId('permissions-page-sites-tab')).toBeInTheDocument(); + expect(getByTestId('permissions-page-snaps-tab')).toBeInTheDocument(); }); it('renders no connections message when user has no connections', () => { mockState.metamask.snaps = {}; mockState.metamask.subjectMetadata = {}; mockState.metamask.subjects = {}; store = configureStore(mockState); - const { getByTestId } = renderWithProvider(, store); + const { getByTestId } = renderWithProvider(, store); expect(getByTestId('no-connections')).toBeInTheDocument(); }); }); diff --git a/ui/components/multichain/product-tour-popover/index.scss b/ui/components/multichain/product-tour-popover/index.scss index c8b597d7c42a..731a97a25932 100644 --- a/ui/components/multichain/product-tour-popover/index.scss +++ b/ui/components/multichain/product-tour-popover/index.scss @@ -9,6 +9,11 @@ right: 6px !important; } + &__permissions-page-tour { + left: auto !important; //important required for permissions page popup center alignment, overrides .multichain-product-tour-menu -7px left styling + top: 0 !important; //important required for permissions page styling, since the anchorElement is inside Page Content + } + &__arrow, &__arrow::before { position: absolute; diff --git a/ui/components/multichain/product-tour-popover/product-tour-popover.js b/ui/components/multichain/product-tour-popover/product-tour-popover.js index 3da95feef3c9..411821a4da08 100644 --- a/ui/components/multichain/product-tour-popover/product-tour-popover.js +++ b/ui/components/multichain/product-tour-popover/product-tour-popover.js @@ -111,7 +111,9 @@ export const ProductTour = ({ color={TextColor.infoInverse} variant={TextVariant.bodyMd} > - {currentStep}/{totalSteps} + {currentStep && totalSteps + ? { currentStep } / { totalSteps } + : null} )} {process.env.MULTICHAIN && ( - + )} @@ -474,14 +473,14 @@ export default class Routes extends Component { return true; } - const isAllConnectionsPage = Boolean( + const isPermissionsPage = Boolean( matchPath(location.pathname, { - path: ALL_CONNECTIONS, + path: PERMISSIONS, exact: false, }), ); - if (isAllConnectionsPage) { + if (isPermissionsPage) { return true; } diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 50434c17d9e2..2085fc30ab99 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -1122,6 +1122,41 @@ export const getAllConnectedAccounts = createDeepEqualSelector( return Object.keys(connectedSubjects); }, ); +export const getConnectedSitesList = createDeepEqualSelector( + getConnectedSubjectsForAllAddresses, + getAllConnectedAccounts, + (connectedSubjectsForAllAddresses, connectedAddresses) => { + const sitesList = {}; + connectedAddresses.forEach((connectedAddress) => { + connectedSubjectsForAllAddresses[connectedAddress].forEach((app) => { + const siteKey = app.origin; + + if (sitesList[siteKey]) { + sitesList[siteKey].addresses.push(connectedAddress); + } else { + sitesList[siteKey] = { ...app, addresses: [connectedAddress] }; + } + }); + }); + + return sitesList; + }, +); + +export const getConnectedSnapsList = createDeepEqualSelector( + getSnapsList, + (snapsData) => { + const snapsList = {}; + + Object.values(snapsData).forEach((snap) => { + if (!snapsList[snap.name]) { + snapsList[snap.name] = snap; + } + }); + + return snapsList; + }, +); export const getMemoizedCurrentChainId = createDeepEqualSelector( getCurrentChainId, @@ -1384,6 +1419,10 @@ export function getShowBetaHeader(state) { return state.metamask.showBetaHeader; } +export function getShowPermissionsTour(state) { + return state.metamask.showPermissionsTour; +} + export function getShowProductTour(state) { return state.metamask.showProductTour; } diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 6003470c4f0e..75842a701992 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -4570,6 +4570,10 @@ export function hideBetaHeader() { return submitRequestToBackground('setShowBetaHeader', [false]); } +export function hidePermissionsTour() { + return submitRequestToBackground('setShowPermissionsTour', [false]); +} + export function hideProductTour() { return submitRequestToBackground('setShowProductTour', [false]); } From 8042a689b4a16b15416be78227a70141364afb66 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 19 Feb 2024 11:05:55 -0600 Subject: [PATCH 18/49] nit: Localize LavaDome copy warning (#23031) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** I noticed the text for account details key isn't localized. This PR localizes it. ## **Related issues** N/A ## **Manual testing steps** N/A ## **Screenshots/Recordings** ### **Before** ### **After** ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/_locales/en/messages.json | 3 +++ .../multichain/account-details/account-details-key.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 403509343c6a..b6df11414bc6 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2265,6 +2265,9 @@ "lastSold": { "message": "Last sold" }, + "lavaDomeCopyWarning": { + "message": "For your safety, selecting this text is not available right now." + }, "layer1Fees": { "message": "Layer 1 fees" }, diff --git a/ui/components/multichain/account-details/account-details-key.js b/ui/components/multichain/account-details/account-details-key.js index fdcecd7084a1..81240f27547c 100644 --- a/ui/components/multichain/account-details/account-details-key.js +++ b/ui/components/multichain/account-details/account-details-key.js @@ -69,7 +69,7 @@ export const AccountDetailsKey = ({ accountName, onClose, privateKey }) => { {showSelectDisableWarn && ( - For your safety, selecting this text is not available right now. + {t('lavaDomeCopyWarning')} )} From 001bc0cd9752c9b82b5d5d25f6586f8f335b71b4 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 19 Feb 2024 11:06:03 -0600 Subject: [PATCH 19/49] nit: useAccountTotalBalance code improvement (#23030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Small code improvement. We can simply define the object with its properties instead of adding them one by one. ## **Related issues** Fixes: N/A ## **Manual testing steps** N/A ## **Screenshots/Recordings** ### **Before** N/A ### **After** N/A ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ui/hooks/useAccountTotalFiatBalance.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/hooks/useAccountTotalFiatBalance.js b/ui/hooks/useAccountTotalFiatBalance.js index d0c55b8a8a5a..ce7eb852ccad 100644 --- a/ui/hooks/useAccountTotalFiatBalance.js +++ b/ui/hooks/useAccountTotalFiatBalance.js @@ -83,10 +83,11 @@ export const useAccountTotalFiatBalance = ( }); // Create an object with native token info. NOTE: Native token info is fetched from a separate controller - const nativeTokenValues = {}; - nativeTokenValues.iconUrl = primaryTokenImage; - nativeTokenValues.symbol = nativeCurrency; - nativeTokenValues.fiatBalance = nativeFiat; + const nativeTokenValues = { + iconUrl: primaryTokenImage, + symbol: nativeCurrency, + fiatBalance: nativeFiat, + }; // To match the list of detected tokens with the entire token list to find the image for tokens const findMatchingTokens = (array1, array2) => { From 2cbedb4405d10537d36e795bde5902787137f055 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 19 Feb 2024 11:06:21 -0600 Subject: [PATCH 20/49] Fix #22987 - UX - Multichain - Send flow - Select NFTs tab in Asset Picker when an NFT is selected (#23033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** When the user is in the new send flow and an NFT has been selected as the asset to send, if the user re-opens the AssetPicker, the NFTs tab should display immediately ## **Related issues** Fixes: #22987 ## **Manual testing steps** 1. Start the new send flow 2. Choose a recipient 3. Click the asset picker, choose an NFT as the asset 4. Click the asset picker again 5. See the NFT tab display by default ## **Screenshots/Recordings** ### **Before** N/A ### **After** https://github.com/MetaMask/metamask-extension/assets/46655/5e4bf3ce-05cd-4955-af7a-289d050f6cba ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../asset-picker-modal/asset-picker-modal.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx index 3fc0682ac77a..7a6cff5fa267 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx @@ -232,6 +232,8 @@ export function AssetPickerModal({ onClose(); }; + const defaultActiveTabKey = asset?.type === AssetType.NFT ? 'nfts' : 'tokens'; + return ( - + { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore From 7107841a322bfed5d3ec1075b5e9c0259cf1965f Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Mon, 19 Feb 2024 19:58:48 +0100 Subject: [PATCH 21/49] test: fix several ppom testcases due to description change (#23052) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** It seems that the description response for the malicious mocked requests has changed in a recent update. This PR aims to fix the e2e tests. This also reveals the strong need for using a stable version of the cdn, by: - Either using the cdn test version of ppom (stable), like we used to do --> that was changed due to some issue Blockaid had on their end - Or mocking the cdn responses and keep them always the same --> to investigate if this is doable, since the responses that we get are in a "weird format" (see example below) ![Screenshot from 2024-02-19 19-23-25](https://github.com/MetaMask/metamask-extension/assets/54408225/54589804-2c9f-40c7-8f67-2ff800f0b0be) Another scenario to explore for some of these specs, would be to see if we can inject the files directly in the ppomDB. In the same way we initialize the wallet with a certain state, we could initialize the wallet with the ppomDB prefilled in storage. This PR however, aims to unblock ci in the fastes possible way ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/23053 ## **Manual testing steps** 1. Check ci or run them locally ## **Screenshots/Recordings** ### **Before** ![Screenshot from 2024-02-19 19-04-44](https://github.com/MetaMask/metamask-extension/assets/54408225/248ae58c-fb40-4531-8028-a3e218ed44b3) ### **After** ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [x] I've included manual testing steps - [x] I've included screenshots/recordings if applicable - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../ppom-blockaid-alert-erc20-approval.spec.js | 2 +- .../ppom-blockaid-alert-erc20-transfer.spec.js | 2 +- .../ppom-blockaid-alert-networks-support.spec.js | 13 +++++-------- test/e2e/tests/ppom-blockaid-alert.spec.js | 3 ++- .../ppom-blockaid-setApprovalForAll-farming.spec.js | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js b/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js index 169372e5dbcf..b3391121bb5b 100644 --- a/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js @@ -232,7 +232,7 @@ describe('PPOM Blockaid Alert - Malicious ERC20 Approval @no-mmi', function () { 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.'; + 'If you approve this request, you might lose your assets.'; // Click TestDapp button to send JSON-RPC request await driver.clickElement('#maliciousApprovalButton'); diff --git a/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js b/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js index 4935a5240bcd..4891e510b053 100644 --- a/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js @@ -180,7 +180,7 @@ describe('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { 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.'; + 'If you approve this request, you might lose your assets.'; await unlockWallet(driver); await openDapp(driver); diff --git a/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js b/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js index c3c0018205ec..d5adeba44fb7 100644 --- a/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js @@ -65,12 +65,9 @@ describe('PPOM Blockaid Alert - Multiple Networks Support @no-mmi', function () async ({ driver }) => { const expectedTitle = 'This is a deceptive request'; - const expectedDescriptionMainnet = + const expectedDescription = 'If you approve this request, you might lose your assets.'; - const expectedDescriptionArbitrum = - 'If you approve this request, a third party known for scams will take all your assets.'; - await unlockWallet(driver); await openDapp(driver); @@ -97,8 +94,8 @@ describe('PPOM Blockaid Alert - Multiple Networks Support @no-mmi', function () `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: approval_farming\n`, ); assert( - bannerAlertText.includes(expectedDescriptionMainnet), - `Unexpected banner alert description. Expected: ${expectedDescriptionMainnet} \nExpected reason: approval_farming\n`, + bannerAlertText.includes(expectedDescription), + `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: approval_farming\n`, ); await driver.clickElement({ text: 'Reject', tag: 'button' }); @@ -144,8 +141,8 @@ describe('PPOM Blockaid Alert - Multiple Networks Support @no-mmi', function () `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: raw_native_token_transfer\n`, ); assert( - bannerAlertText.includes(expectedDescriptionArbitrum), - `Unexpected banner alert description. Expected: ${expectedDescriptionArbitrum} \nExpected reason: raw_native_token_transfer\n`, + bannerAlertText.includes(expectedDescription), + `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: raw_native_token_transfer\n`, ); }, ); diff --git a/test/e2e/tests/ppom-blockaid-alert.spec.js b/test/e2e/tests/ppom-blockaid-alert.spec.js index 1285665f977b..dcd2691b8be2 100644 --- a/test/e2e/tests/ppom-blockaid-alert.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert.spec.js @@ -289,7 +289,8 @@ describe('Confirmation Security Alert - Blockaid @no-mmi', function () { ); }); - it('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/tests/ppom-blockaid-setApprovalForAll-farming.spec.js b/test/e2e/tests/ppom-blockaid-setApprovalForAll-farming.spec.js index 780ecc273d4f..97a8653ab3c7 100644 --- a/test/e2e/tests/ppom-blockaid-setApprovalForAll-farming.spec.js +++ b/test/e2e/tests/ppom-blockaid-setApprovalForAll-farming.spec.js @@ -270,7 +270,7 @@ describe('PPOM Blockaid Alert - Set Approval to All @no-mmi', function () { 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.'; + 'If you approve this request, you might lose your assets.'; // Click TestDapp button to send JSON-RPC request await driver.clickElement('#maliciousSetApprovalForAll'); From c7e232e505d78e147ae1d0b082f584c6378fab0a Mon Sep 17 00:00:00 2001 From: Harika Jetpoluru <153644847+hjetpoluru@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:54:41 -0500 Subject: [PATCH 22/49] test: Added manual test for hex data toggle and corrected the change language format (#22998) Added manual test for show hex data toggle and corrected the change language format --------- Co-authored-by: seaona <54408225+seaona@users.noreply.github.com> --- .../17. settings/advanced/show-hex-data.csv | 13 +++++++++++++ .../17. settings/general/change-language.csv | 18 ++++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 test/scenarios/17. settings/advanced/show-hex-data.csv diff --git a/test/scenarios/17. settings/advanced/show-hex-data.csv b/test/scenarios/17. settings/advanced/show-hex-data.csv new file mode 100644 index 000000000000..8f6be090fcef --- /dev/null +++ b/test/scenarios/17. settings/advanced/show-hex-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),, +3,Click on the right corner setting(three dots) and click on the Settings from menu.,,Settings page appears, +4,Click on the Advanced tab.,,Advanced settings page appears, +5,Validate that the 'Show hex data' is default to off.,,The toggle is off by default, +6,Toggle the Hex Data switch.,,The switch is turned on as expected, +7,Click on the MetaMask logo,,Home page appears, +7,Navigate to the Send transaction in the home menu.,,Send transaction appears, +8,"Enter an address ",0x56F2e03c8D30649818c022a9759CF43B240D08B3,Address is entered and the Hex Data text box is shown as expected, +9,"Enter the amount and hex data ",amount 0.1 and hex data '0xabc',Amount and hex data are entered as expected, +10,Click on the next page button.,,The next page is shown, +11," Validate that the hex data is shown in the tab.",,The hex data is shown as expected, \ No newline at end of file diff --git a/test/scenarios/17. settings/general/change-language.csv b/test/scenarios/17. settings/general/change-language.csv index 7e75b32b1bd8..374c9125f3bf 100644 --- a/test/scenarios/17. settings/general/change-language.csv +++ b/test/scenarios/17. settings/general/change-language.csv @@ -1,15 +1,13 @@ -Verify that the application handles different local times appropriately - 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),, 3,Click on the right corner setting(three dots).,,General settings page appears, 4,Check the default language selected in the drop down For example :- local time in the Mac machine is set to 3:30 pm EST,,Default selected language should be English, - -Verify that the user can scroll through the language dropdown after resizing the browser window. - -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),, -3,Click on the right corner setting(three dots).,,General settings page appears, -4,Check the language drop down has a scroll functionality and resize the browser window,,Drop down works as expected and the scroll is working, \ No newline at end of file +5,Check the language drop down has a scroll functionality and resize the browser window,,Drop down works as expected and the scroll is working, +6,User selects Spanish language and verify that changing the language from the default,,Spanish changes the language of the application, +7,Verify that language persists with page refresh sessions and logout,,The language persists, +8,Select Dansk language and verify that navigating to a different page does not affect,,Danish the language of the application, +9,Navigate to the send screen and enter invalid account and verify that error messages are updated with the selected language,,Error messages are updated with the selected language change, +10,Select मानक हिन्दी language and verify that tooltips are updated for the home screen for account and bridge,,Tooltips are updated with the selected language change, +11,Select Magyar language and verify that hypertext are updated in nft tab,,Hypertext are updated with the selected language change, +12,User selects العربية language and verify that page indent with the selected language change,,Page indent with the selected language change, From ef9e63af06879dbd63a61d1c84b9fd10f3c486d2 Mon Sep 17 00:00:00 2001 From: Howard Braham Date: Mon, 19 Feb 2024 11:56:53 -0800 Subject: [PATCH 23/49] Hackathon project: Codespaces (#22358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** A MetaMask development environment that's entirely in the browser. You can try it right now by clicking on this button: [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/22358?quickstart=1) Right-click on the VNC desktop to launch Chrome or Firefox with MetaMask pre-installed. ### USE CASES - Review a PR without stopping what you’re working on - Develop on a Chromebook or iPad - Repro a flaky test that's only flaky on CircleCI, not locally - Get up and running quickly as a new external contributor / team member - Give a programming test for candidates - Allow PM to easily test a branch --------- Co-authored-by: Mike B <32695229+plasmacorral@users.noreply.github.com> Co-authored-by: Dan J Miller Co-authored-by: Brad Decker --- .circleci/config.yml | 5 +- .depcheckrc.yml | 1 + .devcontainer/devcontainer.json | 58 ++++ .devcontainer/download-builds.ts | 153 ++++++++ .devcontainer/first-run-notice.txt | 22 ++ .devcontainer/install.sh | 440 ++++++++++++++++++++++++ .devcontainer/launch-firefox.ts | 10 + .devcontainer/setup-browsers.ts | 27 ++ .github/CODEOWNERS | 4 + .github/pull-request-template.md | 2 + .github/workflows/codespaces.yml | 27 ++ .github/workflows/fitness-functions.yml | 29 +- .gitignore | 2 +- README.md | 41 ++- package.json | 6 +- test/e2e/webdriver/chrome.js | 3 + yarn.lock | 283 ++++++++++++++- 17 files changed, 1101 insertions(+), 12 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/download-builds.ts create mode 100644 .devcontainer/first-run-notice.txt create mode 100755 .devcontainer/install.sh create mode 100644 .devcontainer/launch-firefox.ts create mode 100644 .devcontainer/setup-browsers.ts create mode 100644 .github/workflows/codespaces.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ad5cdd5384c..f13f3177de7b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,6 @@ orbs: codecov: codecov/codecov@3.2.2 slack: circleci/slack@4.12.5 - rc_branch_only: &rc_branch_only filters: branches: @@ -481,6 +480,8 @@ jobs: - run: name: build:debug command: find dist/ -type f -exec md5sum {} \; | sort -k 2 + - store_artifacts: + path: builds - persist_to_workspace: root: . paths: @@ -698,6 +699,8 @@ jobs: - run: name: Move test zips to 'builds-test' to avoid conflict with production build command: mv ./builds ./builds-test + - store_artifacts: + path: builds-test - persist_to_workspace: root: . paths: diff --git a/.depcheckrc.yml b/.depcheckrc.yml index 064ce190a26d..7a048564edc9 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -37,6 +37,7 @@ ignores: - 'source-map-explorer' - 'playwright' - 'wait-on' + - 'tsx' #used in .devcontainer # development tool - 'nyc' # storybook diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000000..b23e99bbbe59 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,58 @@ +{ + "customizations": { + "vscode": { + "extensions": [ + "eamodio.gitlens", + "github.codespaces", + "github.copilot", + "github.copilot-chat", + "ms-azuretools.vscode-docker", + "rvest.vs-code-prettier-eslint" + ], + "settings": { + "editor.formatOnSave": true, + "git.autofetch": true, + "git.ignoreRebaseWarning": true, + "git.rebaseWhenSync": true, + "gitlens.showWelcomeOnInstall": false + }, + "tasks": [ + { + "label": "Open noVNC new tab", + "type": "shell", + "command": "xdg-open https://$CODESPACE_NAME-6080.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN", + "problemMatcher": [] + } + ] + } + }, + + "forwardPorts": [5901, 6080], + + "image": "mcr.microsoft.com/devcontainers/universal:2-linux", + + "name": "MM Extension Codespace", + + "otherPortsAttributes": { "onAutoForward": "ignore" }, + + "portsAttributes": { + "5901": { + "label": "local VNC", + "onAutoForward": "ignore" + }, + "6080": { + "label": "noVNC web", + "onAutoForward": "openPreview" + } + }, + + "postAttachCommand": "/usr/local/share/desktop-init.sh && git pull --rebase; yarn download-builds", + + // This is a working Infura key, but it's on the Free Plan and has very limited requests per second + // If you want to use your own INFURA_PROJECT_ID, follow the instructions in README.md + "postCreateCommand": "if [ -z \"$INFURA_PROJECT_ID\" ]; then echo 'INFURA_PROJECT_ID=3d110a0fce9e49b08d2ee584e19a05ba' > .metamaskrc; fi", + + "runArgs": ["--shm-size=1g"], + + "updateContentCommand": "sudo .devcontainer/install.sh && yarn --immutable && yarn tsx .devcontainer/setup-browsers.ts && echo 'export DISPLAY=:1' >> ~/.bashrc" +} diff --git a/.devcontainer/download-builds.ts b/.devcontainer/download-builds.ts new file mode 100644 index 000000000000..c533cebf5fa0 --- /dev/null +++ b/.devcontainer/download-builds.ts @@ -0,0 +1,153 @@ +import { execSync } from 'child_process'; +import util from 'util'; + +const exec = util.promisify(require('node:child_process').exec); + +function getGitBranch() { + const gitOutput = execSync('git status').toString(); + + const branchRegex = /On branch (?.*)\n/; + return gitOutput.match(branchRegex)?.groups?.branch || 'develop'; +} + +async function getCircleJobs(branch: string) { + let response = await fetch( + `https://circleci.com/api/v2/project/gh/MetaMask/metamask-extension/pipeline?branch=${branch}`, + ); + + const pipelineId = (await response.json()).items[0].id; + + console.log('pipelineId:', pipelineId); + + response = await fetch( + `https://circleci.com/api/v2/pipeline/${pipelineId}/workflow`, + ); + + const workflowId = (await response.json()).items[0].id; + + console.log('workflowId:', workflowId); + + response = await fetch( + `https://circleci.com/api/v2/workflow/${workflowId}/job`, + ); + + const jobs = (await response.json()).items; + + return jobs; +} + +async function getBuilds(branch: string, jobNames: string[]) { + const jobs = await getCircleJobs(branch); + let builds = [] as any[]; + + for (const jobName of jobNames) { + const jobId = jobs.find((job: any) => job.name === jobName).job_number; + + console.log(`jobName: ${jobName}, jobId: ${jobId}`); + + const response = await fetch( + `https://circleci.com/api/v2/project/gh/MetaMask/metamask-extension/${jobId}/artifacts`, + ); + + const artifacts = (await response.json()).items; + + if (!artifacts || artifacts.length === 0) { + return []; + } + + builds = builds.concat( + artifacts.filter((artifact: any) => artifact.path.endsWith('.zip')), + ); + } + + return builds; +} + +function getVersionNumber(builds: any[]) { + for (const build of builds) { + const versionRegex = /metamask-chrome-(?\d+\.\d+\.\d+).zip/; + + const versionNumber = build.path.match(versionRegex)?.groups?.version; + + if (versionNumber) { + return versionNumber; + } + } +} + +async function downloadBuilds(builds: any[]) { + if (!builds || builds.length === 0) { + console.log( + 'No builds found on CircleCI for the current branch, you will have to build the Extension yourself', + ); + return; + } + + const buildPromises = [] as Promise[]; + + for (const build of builds) { + if ( + build.path.startsWith('builds/') || + build.path.startsWith('builds-test/') + ) { + const { url } = build; + + console.log('downloading', build.path); + + buildPromises.push(exec(`curl -L --create-dirs -o ${build.path} ${url}`)); + } + } + + await Promise.all(buildPromises); + + console.log('downloads complete'); +} + +function unzipBuilds(folder: 'builds' | 'builds-test', versionNumber: string) { + if (!versionNumber) { + return; + } + + if (process.platform === 'win32') { + execSync(`rmdir /s /q dist & mkdir dist\\chrome & mkdir dist\\firefox`); + } else { + execSync('sudo rm -rf dist && mkdir -p dist'); + } + + for (const browser of ['chrome', 'firefox']) { + if (process.platform === 'win32') { + execSync( + `tar -xf ${folder}/metamask-${browser}-${versionNumber}.zip -C dist/${browser}`, + ); + } else { + execSync( + `unzip ${folder}/metamask-${browser}-${versionNumber}.zip -d dist/${browser}`, + ); + } + } + + console.log(`unzipped ${folder} into ./dist`); +} + +async function main(jobNames: string[]) { + const branch = getGitBranch(); + + const builds = await getBuilds(branch, jobNames); + + console.log('builds', builds); + + await downloadBuilds(builds); + + const versionNumber = getVersionNumber(builds); + const folder = builds[0].path.split('/')[0]; + + unzipBuilds(folder, versionNumber); +} + +let args = process.argv.slice(2); + +if (!args || args.length === 0) { + args = ['prep-build']; +} + +main(args); diff --git a/.devcontainer/first-run-notice.txt b/.devcontainer/first-run-notice.txt new file mode 100644 index 000000000000..7b093dbef40e --- /dev/null +++ b/.devcontainer/first-run-notice.txt @@ -0,0 +1,22 @@ +Welcome to MetaMask Codespaces! + +## Quickstart Instructions + +1. A "Simple Browser" will open inside the browser with noVNC -- click Connect + - Optional steps: + - Click the button at the upper-right of the Simple Browser tab to open the noVNC window in its own tab + - Open the noVNC sidebar on the left, click the gear icon, change the Scaling Mode to Remote Resizing +2. Wait about 20 extra seconds on the first launch, for the scripts to finish +3. Right-click on the noVNC desktop to launch Chrome or Firefox with MetaMask pre-installed +4. Change some code, then run `yarn start` to build in dev mode +5. After a minute or two, it will finish building, and you can see your changes in the noVNC desktop + +## Tips to keep your Codespaces usage lower + +- You are billed for both time spent running, and for storage used +- Codespaces pause after 30 minutes of inactivity, and auto-delete after 30 days of inactivity +- You can manage your Codespaces here: https://github.com/codespaces + - You may want to manually pause them before the 30 minute timeout + - If you have several idle Codespaces hanging around for several days, you can quickly run out of storage quota. + You should delete the ones you do not plan to use anymore, and probably keep only 1 or 2 in the long-term. + It's also possible to re-use old Codespaces and switch the branch, instead of creating new ones and deleting the old ones. \ No newline at end of file diff --git a/.devcontainer/install.sh b/.devcontainer/install.sh new file mode 100755 index 000000000000..8d198a9db794 --- /dev/null +++ b/.devcontainer/install.sh @@ -0,0 +1,440 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# MIT License +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE +#------------------------------------------------------------------------------------------------------------- +# Docs: https://github.com/devcontainers/features/blob/main/src/desktop-lite/README.md +# Maintainer: The VS Code and Codespaces Teams +# +# Original file source: https://github.com/devcontainers/features/blob/main/src/desktop-lite/install.sh +# Adapted by the MetaMask Codespaces team in 2023 to support noVNC 1.4.0 +# +# shellcheck disable=SC1091,SC2086 + +NOVNC_VERSION="1.4.0" +VNC_PASSWORD='' +NOVNC_PORT="${WEBPORT:-6080}" +VNC_PORT="${VNCPORT:-5901}" + +INSTALL_NOVNC="${INSTALL_NOVNC:-"true"}" +USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" + +WEBSOCKETIFY_VERSION=0.10.0 + +package_list=" + tigervnc-standalone-server \ + tigervnc-common \ + fluxbox \ + dbus-x11 \ + x11-utils \ + x11-xserver-utils \ + xdg-utils \ + fbautostart \ + at-spi2-core \ + xterm \ + eterm \ + nautilus\ + mousepad \ + seahorse \ + gnome-icon-theme \ + gnome-keyring \ + libx11-dev \ + libxkbfile-dev \ + libsecret-1-dev \ + libgbm-dev \ + libnotify4 \ + libnss3 \ + libxss1 \ + libasound2 \ + xfonts-base \ + xfonts-terminus \ + fonts-noto \ + fonts-wqy-microhei \ + fonts-droid-fallback \ + htop \ + ncdu \ + curl \ + ca-certificates\ + unzip \ + nano \ + locales" + +# Packages to attempt to install if essential tools are missing (ie: vncpasswd). +# This is useful, at least, for Ubuntu 22.04 (jammy) +package_list_additional=" + tigervnc-tools" + +set -e + +# Clean up +rm -rf /var/lib/apt/lists/* + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +# Add default Fluxbox config files if none are already present +fluxbox_apps="$(cat \ +<< 'EOF' +[transient] (role=GtkFileChooserDialog) + [Dimensions] {70% 70%} + [Position] (CENTER) {0 0} +[end] +EOF +)" + +fluxbox_init="$(cat \ +<< 'EOF' +session.configVersion: 13 +session.menuFile: ~/.fluxbox/menu +session.keyFile: ~/.fluxbox/keys +session.styleFile: /usr/share/fluxbox/styles/qnx-photon +session.screen0.workspaces: 1 +session.screen0.workspacewarping: false +session.screen0.toolbar.widthPercent: 100 +session.screen0.strftimeFormat: %a %l:%M %p +session.screen0.toolbar.tools: RootMenu, clock, iconbar, systemtray +session.screen0.workspaceNames: One, +session.screen0.rootCommand: fbsetbg -c /workspaces/metamask-extension/app/images/icon-512.png +EOF +)" + +fluxbox_menu="$(cat \ +<< 'EOF' +[begin] ( Application Menu ) + [exec] (File Manager) { nautilus ~ } <> + [exec] (Text Editor) { mousepad } <> + [exec] (Terminal) { tilix -w ~ -e $(readlink -f /proc/$$/exe) -il } <> + [exec] (Chrome) { chrome-mm --disable-gpu --disable-dev-shm-usage } <> + [exec] (Firefox) { firefox-mm } <> + [submenu] (System) {} + [exec] (Set Resolution) { tilix -t "Set Resolution" -e bash /usr/local/bin/set-resolution } <> + [exec] (Edit Application Menu) { mousepad ~/.fluxbox/menu } <> + [exec] (Passwords and Keys) { seahorse } <> + [exec] (Top Processes) { tilix -t "Top" -e htop } <> + [exec] (Disk Utilization) { tilix -t "Disk Utilization" -e ncdu / } <> + [exec] (Editres) {editres} <> + [exec] (Xfontsel) {xfontsel} <> + [exec] (Xkill) {xkill} <> + [exec] (Xrefresh) {xrefresh} <> + [end] + [config] (Configuration) + [workspaces] (Workspaces) +[end] +EOF +)" + +# Copy config files if the don't already exist +copy_fluxbox_config() { + local target_dir="$1" + mkdir -p "${target_dir}/.fluxbox" + touch "${target_dir}/.Xmodmap" + if [ ! -e "${target_dir}/.fluxbox/apps" ]; then + echo "${fluxbox_apps}" > "${target_dir}/.fluxbox/apps" + fi + if [ ! -e "${target_dir}/.fluxbox/init" ]; then + echo "${fluxbox_init}" > "${target_dir}/.fluxbox/init" + fi + if [ ! -e "${target_dir}/.fluxbox/menu" ]; then + echo "${fluxbox_menu}" > "${target_dir}/.fluxbox/menu" + fi +} + +apt_get_update() +{ + if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update -y + fi +} + +# Checks if packages are installed and installs them if not +check_packages() { + if ! dpkg -s "$@" > /dev/null 2>&1; then + apt_get_update + apt-get -y install --no-install-recommends "$@" + fi +} + +########################## +# Install starts here # +########################## + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +apt_get_update + +# On older Ubuntu, Tilix is in a PPA. on Debian stretch its in backports. +if [[ -z $(apt-cache --names-only search ^tilix$) ]]; then + . /etc/os-release + if [ "${ID}" = "ubuntu" ]; then + check_packages apt-transport-https software-properties-common + add-apt-repository -y ppa:webupd8team/terminix + elif [ "${VERSION_CODENAME}" = "stretch" ]; then + echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/stretch-backports.list + fi + apt-get update + if [[ -z $(apt-cache --names-only search ^tilix$) ]]; then + echo "(!) WARNING: Tilix not available on ${ID} ${VERSION_CODENAME} architecture $(uname -m). Skipping." + else + package_list="${package_list} tilix" + fi +else + package_list="${package_list} tilix" +fi + +# Install X11, fluxbox and VS Code dependencies +check_packages ${package_list} + +# On newer versions of Ubuntu (22.04), +# we need an additional package that isn't provided in earlier versions +if ! type vncpasswd > /dev/null 2>&1; then + check_packages ${package_list_additional} +fi + +# Install Emoji font if available in distro - Available in Debian 10+, Ubuntu 18.04+ +if dpkg-query -W fonts-noto-color-emoji > /dev/null 2>&1 && ! dpkg -s fonts-noto-color-emoji > /dev/null 2>&1; then + apt-get -y install --no-install-recommends fonts-noto-color-emoji +fi + +# Check at least one locale exists +if ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + locale-gen +fi + +# Install the Cascadia Code fonts - https://github.com/microsoft/cascadia-code +if [ ! -d "/usr/share/fonts/truetype/cascadia" ]; then + curl -sSL https://github.com/microsoft/cascadia-code/releases/download/v2008.25/CascadiaCode-2008.25.zip -o /tmp/cascadia-fonts.zip + unzip /tmp/cascadia-fonts.zip -d /tmp/cascadia-fonts + mkdir -p /usr/share/fonts/truetype/cascadia + mv /tmp/cascadia-fonts/ttf/* /usr/share/fonts/truetype/cascadia/ + rm -rf /tmp/cascadia-fonts.zip /tmp/cascadia-fonts +fi + +# Install noVNC +if [ "${INSTALL_NOVNC}" = "true" ] && [ ! -d "/usr/local/novnc" ]; then + mkdir -p /usr/local/novnc + curl -sSL https://github.com/novnc/noVNC/archive/v${NOVNC_VERSION}.zip -o /tmp/novnc-install.zip + unzip /tmp/novnc-install.zip -d /usr/local/novnc + cp /usr/local/novnc/noVNC-${NOVNC_VERSION}/vnc.html /usr/local/novnc/noVNC-${NOVNC_VERSION}/index.html + curl -sSL https://github.com/novnc/websockify/archive/v${WEBSOCKETIFY_VERSION}.zip -o /tmp/websockify-install.zip + unzip /tmp/websockify-install.zip -d /usr/local/novnc + ln -s /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION} /usr/local/novnc/noVNC-${NOVNC_VERSION}/utils/websockify + rm -f /tmp/websockify-install.zip /tmp/novnc-install.zip + + # Install noVNC dependencies and use them. + check_packages python3-minimal python3-numpy + sed -i -E 's/^python /python3 /' /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION}/run +fi + +# Set up folders for scripts and init files +mkdir -p /var/run/dbus /usr/local/etc/vscode-dev-containers/ +sudo cp .devcontainer/first-run-notice.txt /usr/local/etc/vscode-dev-containers/ + +# Script to change resolution of desktop +cat << EOF > /usr/local/bin/set-resolution +#!/bin/bash +RESOLUTION=\${1:-\${VNC_RESOLUTION:-1920x1080}} +DPI=\${2:-\${VNC_DPI:-96}} +IGNORE_ERROR=\${3:-"false"} +if [ -z "\$1" ]; then + echo -e "**Current Settings **\n" + xrandr + echo -n -e "\nEnter new resolution (WIDTHxHEIGHT, blank for \${RESOLUTION}, Ctrl+C to abort).\n> " + read NEW_RES + if [ "\${NEW_RES}" != "" ]; then + RESOLUTION=\${NEW_RES} + fi + if ! echo "\${RESOLUTION}" | grep -E '[0-9]+x[0-9]+' > /dev/null; then + echo -e "\nInvalid resolution format!\n" + exit 1 + fi + if [ -z "\$2" ]; then + echo -n -e "\nEnter new DPI (blank for \${DPI}, Ctrl+C to abort).\n> " + read NEW_DPI + if [ "\${NEW_DPI}" != "" ]; then + DPI=\${NEW_DPI} + fi + fi +fi + +xrandr --fb \${RESOLUTION} --dpi \${DPI} > /dev/null 2>&1 + +if [ \$? -ne 0 ] && [ "\${IGNORE_ERROR}" != "true" ]; then + echo -e "\nFAILED TO SET RESOLUTION!\n" + exit 1 +fi + +echo -e "\nSuccess!\n" +EOF + +# Container ENTRYPOINT script +cat << EOF > /usr/local/share/desktop-init.sh +#!/bin/bash + +user_name="${USERNAME}" +group_name="$(id -gn ${USERNAME})" +LOG=/tmp/container-init.log + +export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-"autolaunch:"}" +export DISPLAY=:1 +export VNC_RESOLUTION="${VNC_RESOLUTION:-1440x768x16}" +export LANG="${LANG:-"en_US.UTF-8"}" +export LANGUAGE="${LANGUAGE:-"en_US.UTF-8"}" + +# Execute the command it not already running +startInBackgroundIfNotRunning() +{ + log "Starting \$1." + echo -e "\n** \$(date) **" | sudoIf tee -a /tmp/\$1.log > /dev/null + if ! pgrep -x \$1 > /dev/null; then + keepRunningInBackground "\$@" + while ! pgrep -x \$1 > /dev/null; do + sleep 1 + done + log "\$1 started." + else + echo "\$1 is already running." | sudoIf tee -a /tmp/\$1.log > /dev/null + log "\$1 is already running." + fi +} + +# Keep command running in background +keepRunningInBackground() +{ + (\$2 bash -c "while :; do echo [\\\$(date)] Process started.; \$3; echo [\\\$(date)] Process exited!; sleep 5; done 2>&1" | sudoIf tee -a /tmp/\$1.log > /dev/null & echo "\$!" | sudoIf tee /tmp/\$1.pid > /dev/null) +} + +# Use sudo to run as root when required +sudoIf() +{ + if [ "\$(id -u)" -ne 0 ]; then + sudo "\$@" + else + "\$@" + fi +} + +# Use sudo to run as non-root user if not already running +sudoUserIf() +{ + if [ "\$(id -u)" -eq 0 ] && [ "\${user_name}" != "root" ]; then + sudo -u \${user_name} "\$@" + else + "\$@" + fi +} + +# Log messages +log() +{ + echo -e "[\$(date)] \$@" | sudoIf tee -a \$LOG > /dev/null +} + +log "** SCRIPT START **" + +# Start dbus. +log 'Running "/etc/init.d/dbus start".' +if [ -f "/var/run/dbus/pid" ] && ! pgrep -x dbus-daemon > /dev/null; then + sudoIf rm -f /var/run/dbus/pid +fi +sudoIf /etc/init.d/dbus start 2>&1 | sudoIf tee -a /tmp/dbus-daemon-system.log > /dev/null +while ! pgrep -x dbus-daemon > /dev/null; do + sleep 1 +done + +# Startup tigervnc server and fluxbox +sudoIf rm -rf /tmp/.X11-unix /tmp/.X*-lock +mkdir -p /tmp/.X11-unix +sudoIf chmod 1777 /tmp/.X11-unix +sudoIf chown root:\${group_name} /tmp/.X11-unix +if [ "\$(echo "\${VNC_RESOLUTION}" | tr -cd 'x' | wc -c)" = "1" ]; then VNC_RESOLUTION=\${VNC_RESOLUTION}x16; fi +screen_geometry="\${VNC_RESOLUTION%*x*}" +screen_depth="\${VNC_RESOLUTION##*x}" +startInBackgroundIfNotRunning "Xtigervnc" sudoUserIf "tigervncserver \${DISPLAY} -geometry \${screen_geometry} -depth \${screen_depth} -rfbport ${VNC_PORT} -dpi \${VNC_DPI:-96} -localhost -desktop fluxbox -fg -SecurityTypes none" + +# Spin up noVNC if installed and not running. +if [ -d "/usr/local/novnc" ] && [ "\$(ps -ef | grep /usr/local/novnc/noVNC*/utils/novnc_proxy | grep -v grep)" = "" ]; then + keepRunningInBackground "noVNC" sudoIf "/usr/local/novnc/noVNC*/utils/novnc_proxy --listen ${NOVNC_PORT} --vnc localhost:${VNC_PORT}" + log "noVNC started." +else + log "noVNC is already running or not installed." +fi + +# Set fox as wallpaper +fbsetbg -c /workspaces/metamask-extension/app/images/icon-512.png + +git config --global pull.rebase true + +# Run whatever was passed in +log "Executing \"\$@\"." +exec "\$@" +log "** SCRIPT EXIT **" +EOF + +echo "${VNC_PASSWORD}" | vncpasswd -f > /usr/local/etc/vscode-dev-containers/vnc-passwd +chmod +x /usr/local/share/desktop-init.sh /usr/local/bin/set-resolution +chgrp -R codespace . +chown -R codespace . + +# Set up fluxbox config +copy_fluxbox_config "/root" +if [ "${USERNAME}" != "root" ]; then + copy_fluxbox_config "/home/${USERNAME}" + chown -R ${USERNAME} /home/${USERNAME}/.Xmodmap /home/${USERNAME}/.fluxbox +fi + +# Clean up +rm -rf /var/lib/apt/lists/* + +cat << EOF + + +You now have a working desktop! Connect to in one of the following ways: + +- Forward port ${NOVNC_PORT} and use a web browser start the noVNC client (recommended) +- Forward port ${VNC_PORT} using VS Code client and connect using a VNC Viewer + +In both cases, use the password "${VNC_PASSWORD}" when connecting + +(*) Done! + +EOF \ No newline at end of file diff --git a/.devcontainer/launch-firefox.ts b/.devcontainer/launch-firefox.ts new file mode 100644 index 000000000000..c00719fd15b8 --- /dev/null +++ b/.devcontainer/launch-firefox.ts @@ -0,0 +1,10 @@ +import { Builder } from 'selenium-webdriver'; +import Firefox from 'selenium-webdriver/firefox'; + +const options: any = new Firefox.Options().setAcceptInsecureCerts(true); + +const builder = new Builder().forBrowser('firefox').setFirefoxOptions(options); + +const driver: any = builder.build(); + +driver.installAddon('./dist/firefox', true); diff --git a/.devcontainer/setup-browsers.ts b/.devcontainer/setup-browsers.ts new file mode 100644 index 000000000000..82a60693b7a8 --- /dev/null +++ b/.devcontainer/setup-browsers.ts @@ -0,0 +1,27 @@ +import { execSync } from 'child_process'; + +function addBrowserToPath(browserName: string) { + const seleniumOutput = execSync( + `node_modules/selenium-webdriver/bin/linux/selenium-manager --browser ${browserName}`, + ).toString(); + + let browserCommand = seleniumOutput.split('Browser path: ')[1]; + browserCommand = browserCommand.slice(0, -1); // cut off the newline + + execSync(`sudo ln -sf ${browserCommand} /usr/local/bin/${browserName}-ln`); +} + +addBrowserToPath('chrome'); +addBrowserToPath('firefox'); + +execSync( + `sudo bash -c "echo -e '#! /bin/bash\n/usr/local/bin/chrome-ln --load-extension=${process.cwd()}/dist/chrome' > /usr/local/bin/chrome-mm"`, +); + +execSync('sudo chmod 777 /usr/local/bin/chrome-mm'); + +execSync( + `sudo bash -c "echo -e '#! /bin/bash\ncd /workspaces/metamask-extension\nyarn tsx .devcontainer/launch-firefox.ts' > /usr/local/bin/firefox-mm"`, +); + +execSync('sudo chmod 777 /usr/local/bin/firefox-mm'); diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7073870aba1f..dcc43149b8b0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -40,3 +40,7 @@ privacy-snapshot.json @MetaMask/extension-privacy-reviewers # that agreement and can only be approved by those with the knowledge # and responsibility to publish libraries under the MetaMask name. .github/CODEOWNERS @MetaMask/library-admins @kumavis + +# For now, restricting approvals inside the .devcontainer folder to devs +# who were involved with the Codespaces project. +.devcontainer/ @MetaMask/library-admins @HowardBraham @plasmacorral @brad-decker diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index d85eb69ba4f9..0c046fb560d8 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -6,6 +6,8 @@ Write a short description of the changes included in this pull request, also inc 2. What is the improvement/solution? --> +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/PR?quickstart=1) + ## **Related issues** Fixes: diff --git a/.github/workflows/codespaces.yml b/.github/workflows/codespaces.yml new file mode 100644 index 000000000000..68f538dee512 --- /dev/null +++ b/.github/workflows/codespaces.yml @@ -0,0 +1,27 @@ +name: 'Codespaces: update yarn cache' + +on: + push: + branches: + - 'codespaces**' + - 'develop' + paths: + - '**/yarn.lock' + +jobs: + yarn-cache: + name: Generate cache image + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 # This retrieves only the latest commit. + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - run: yarn --immutable diff --git a/.github/workflows/fitness-functions.yml b/.github/workflows/fitness-functions.yml index 87e0bff2c96a..f80d27ad0577 100644 --- a/.github/workflows/fitness-functions.yml +++ b/.github/workflows/fitness-functions.yml @@ -10,12 +10,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn @@ -33,3 +33,28 @@ jobs: # then saved to a file called "diff". git diff "$(git merge-base "origin/$BASE_REF" HEAD)" HEAD -- . > ./diff npm run fitness-functions -- "ci" "./diff" + + update-codespaces-badge: + runs-on: ubuntu-latest + permissions: + pull-requests: write + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + + # This use of an ENV variable is neccessary to avoid an injection attack, see: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions + BODY: ${{ github.event.pull_request.body }} + + steps: + - name: Replace the text in the Codespaces badge with the PR number + run: | + #!/bin/bash + + # Bash replace syntax + NEW_BODY="${BODY/PR?quickstart=1/$PR_NUMBER?quickstart=1}" + + # If the body has changed as a result of the replace, use the GitHub API to update the PR + if [ "$NEW_BODY" != "$BODY" ]; then + gh api /repos/MetaMask/metamask-extension/pulls/${{github.event.pull_request.number}} -f body="$NEW_BODY" + fi diff --git a/.gitignore b/.gitignore index 51d7cff64d71..642526e93a27 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ storybook-build/ coverage/ jest-coverage/ dist -builds/ +builds*/ builds.zip development/ts-migration-dashboard/build diff --git a/README.md b/README.md index 0d802f5a6542..aa24e4e5c396 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,38 @@ To learn how to develop MetaMask-compatible applications, visit our [Developer D To learn how to contribute to the MetaMask project itself, visit our [Internal Docs](https://github.com/MetaMask/metamask-extension/tree/develop/docs). -## Building locally +## GitHub Codespaces quickstart + +As an alternative to building on your local machine, there is a new option to get a development environment up and running in less than 5 minutes by using GitHub Codespaces. Please note that there is a [Limited Free Monthly Quota](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces), and after that GitHub will start charging you. + +_Note: You are billed for both time spent running, and for storage used_ + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension?quickstart=1) + +1. Start by clicking the button above +2. A new browser tab will open with a remote version of Visual Studio Code (this will take a few minutes to load) +3. A "Simple Browser" will open inside the browser with noVNC -- click Connect + - Optional steps: + - Click the button at the upper-right of the Simple Browser tab to open the noVNC window in its own tab + - Open the noVNC sidebar on the left, click the gear icon, change the Scaling Mode to Remote Resizing +4. Wait about 20 extra seconds on the first launch, for the scripts to finish +5. Right-click on the noVNC desktop to launch Chrome or Firefox with MetaMask pre-installed +6. Change some code, then run `yarn start` to build in dev mode +7. After a minute or two, it will finish building, and you can see your changes in the noVNC desktop + +### Tips to keep your Codespaces usage lower + +- You are billed for both time spent running, and for storage used +- Codespaces pause after 30 minutes of inactivity, and auto-delete after 30 days of inactivity +- You can manage your Codespaces here: https://github.com/codespaces + - You may want to manually pause them before the 30 minute timeout + - If you have several idle Codespaces hanging around for several days, you can quickly run out of storage quota. You should delete the ones you do not plan to use anymore, and probably keep only 1 or 2 in the long-term. It's also possible to re-use old Codespaces and switch the branch, instead of creating new ones and deleting the old ones. + +### Codespaces on a fork + +If you are not a MetaMask Internal Developer, or are otherwise developing on a fork, the default Infura key will be on the Free Plan and have very limited requests per second. If you want to use your own Infura key, follow the `.metamaskrc` and `INFURA_PROJECT_ID` instructions in the section [Building on your local machine](#building-on-your-local-machine). + +## Building on your local machine - Install [Node.js](https://nodejs.org) version 20 - If you are using [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) (recommended) running `nvm use` will automatically choose the right node version for you. @@ -25,6 +56,7 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D - Optionally, replace the `PASSWORD` value with your development wallet password to avoid entering it each time you open the app. - Run `yarn install` to install the dependencies. - Build the project to the `./dist/` folder with `yarn dist`. + - Optionally, you may run `yarn start` to run dev mode. - Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built. - See the [build system readme](./development/build/README.md) for build system usage information. @@ -33,7 +65,6 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D - [How to add custom build to Chrome](./docs/add-to-chrome.md) - [How to add custom build to Firefox](./docs/add-to-firefox.md) - ## Git Hooks To get quick feedback from our shared code quality fitness functions before committing the code, you can install our git hooks with Husky. @@ -111,16 +142,18 @@ For example, to run the `account-details` tests using Chrome, with debug logging #### Running specific builds types e2e test -Different build types have different e2e tests sets. In order to run them look in the `packaje.json` file. You will find: +Different build types have different e2e tests sets. In order to run them look in the `package.json` file. You will find: + ```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": "ENABLE_MV3=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js", ``` - #### Note: Running MMI e2e tests + When running e2e on an MMI build you need to know that there are 2 separated set of tests: + - 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. - MMI runs another specific set of e2e legacy tests which are better documented [here](test/e2e/mmi/README.md) diff --git a/package.json b/package.json index d783deb453c7..fd34d0a6d336 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,8 @@ "close-release-bug-report-issue": "ts-node ./.github/scripts/close-release-bug-report-issue.ts", "check-template-and-add-labels": "ts-node ./.github/scripts/check-template-and-add-labels.ts", "audit": "yarn npm audit --recursive --environment production --severity moderate --ignore '@metamask/types (deprecation)'", + "download-builds": "tsx .devcontainer/download-builds.ts prep-build", + "download-builds:test": "tsx .devcontainer/download-builds.ts prep-build-test", "master-sync": "node development/master-sync.js" }, "resolutions": { @@ -574,6 +576,7 @@ "terser": "^5.7.0", "through2": "^4.0.2", "ts-node": "^10.9.2", + "tsx": "^4.7.1", "ttest": "^2.1.1", "typescript": "~5.3.3", "vinyl": "^2.2.1", @@ -658,7 +661,8 @@ "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>usb": false, "@metamask/ethjs-query>babel-runtime>core-js": false, "@storybook/test-runner>@swc/core": false, - "@lavamoat/lavadome-react>@lavamoat/preinstall-always-fail": false + "@lavamoat/lavadome-react>@lavamoat/preinstall-always-fail": false, + "tsx>esbuild": false } }, "packageManager": "yarn@4.0.2" diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index 0a74ec09089f..e238f7b50521 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -20,6 +20,8 @@ class ChromeDriver { `--proxy-server=${HTTPS_PROXY_HOST}`, // Set proxy in the way that doesn't interfere with Selenium Manager '--disable-features=OptimizationGuideModelDownloading,OptimizationHintsFetching,OptimizationTargetPredicition,OptimizationHints,NetworkTimeServiceQuerying', // Stop chrome from calling home so much (auto-downloads of AI models; time sync) '--disable-component-update', // Stop chrome from calling home so much (auto-update) + `--disable-gpu`, + `--disable-dev-shm-usage`, ]; if (process.env.MULTIPROVIDER) { @@ -29,6 +31,7 @@ class ChromeDriver { } else { args.push(`load-extension=${process.cwd()}/dist/chrome`); } + if (openDevToolsForTabs) { args.push('--auto-open-devtools-for-tabs'); } diff --git a/yarn.lock b/yarn.lock index 4ba24f6122ad..cb6d990fbe01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1933,6 +1933,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/aix-ppc64@npm:0.19.12" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm64@npm:0.18.20" @@ -1940,6 +1947,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm64@npm:0.19.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm@npm:0.18.20" @@ -1947,6 +1961,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm@npm:0.19.12" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-x64@npm:0.18.20" @@ -1954,6 +1975,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-x64@npm:0.19.12" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-arm64@npm:0.18.20" @@ -1961,6 +1989,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-arm64@npm:0.19.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-x64@npm:0.18.20" @@ -1968,6 +2003,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-x64@npm:0.19.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-arm64@npm:0.18.20" @@ -1975,6 +2017,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-arm64@npm:0.19.12" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-x64@npm:0.18.20" @@ -1982,6 +2031,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-x64@npm:0.19.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm64@npm:0.18.20" @@ -1989,6 +2045,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm64@npm:0.19.12" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm@npm:0.18.20" @@ -1996,6 +2059,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm@npm:0.19.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ia32@npm:0.18.20" @@ -2003,6 +2073,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ia32@npm:0.19.12" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-loong64@npm:0.18.20" @@ -2010,6 +2087,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-loong64@npm:0.19.12" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-mips64el@npm:0.18.20" @@ -2017,6 +2101,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-mips64el@npm:0.19.12" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ppc64@npm:0.18.20" @@ -2024,6 +2115,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ppc64@npm:0.19.12" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-riscv64@npm:0.18.20" @@ -2031,6 +2129,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-riscv64@npm:0.19.12" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-s390x@npm:0.18.20" @@ -2038,6 +2143,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-s390x@npm:0.19.12" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-x64@npm:0.18.20" @@ -2045,6 +2157,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-x64@npm:0.19.12" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/netbsd-x64@npm:0.18.20" @@ -2052,6 +2171,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/netbsd-x64@npm:0.19.12" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/openbsd-x64@npm:0.18.20" @@ -2059,6 +2185,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/openbsd-x64@npm:0.19.12" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/sunos-x64@npm:0.18.20" @@ -2066,6 +2199,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/sunos-x64@npm:0.19.12" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-arm64@npm:0.18.20" @@ -2073,6 +2213,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-arm64@npm:0.19.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-ia32@npm:0.18.20" @@ -2080,6 +2227,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-ia32@npm:0.19.12" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-x64@npm:0.18.20" @@ -2087,6 +2241,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-x64@npm:0.19.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -16044,6 +16205,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:~0.19.10": + version: 0.19.12 + resolution: "esbuild@npm:0.19.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.19.12" + "@esbuild/android-arm": "npm:0.19.12" + "@esbuild/android-arm64": "npm:0.19.12" + "@esbuild/android-x64": "npm:0.19.12" + "@esbuild/darwin-arm64": "npm:0.19.12" + "@esbuild/darwin-x64": "npm:0.19.12" + "@esbuild/freebsd-arm64": "npm:0.19.12" + "@esbuild/freebsd-x64": "npm:0.19.12" + "@esbuild/linux-arm": "npm:0.19.12" + "@esbuild/linux-arm64": "npm:0.19.12" + "@esbuild/linux-ia32": "npm:0.19.12" + "@esbuild/linux-loong64": "npm:0.19.12" + "@esbuild/linux-mips64el": "npm:0.19.12" + "@esbuild/linux-ppc64": "npm:0.19.12" + "@esbuild/linux-riscv64": "npm:0.19.12" + "@esbuild/linux-s390x": "npm:0.19.12" + "@esbuild/linux-x64": "npm:0.19.12" + "@esbuild/netbsd-x64": "npm:0.19.12" + "@esbuild/openbsd-x64": "npm:0.19.12" + "@esbuild/sunos-x64": "npm:0.19.12" + "@esbuild/win32-arm64": "npm:0.19.12" + "@esbuild/win32-ia32": "npm:0.19.12" + "@esbuild/win32-x64": "npm:0.19.12" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 861fa8eb2428e8d6521a4b7c7930139e3f45e8d51a86985cc29408172a41f6b18df7b3401e7e5e2d528cdf83742da601ddfdc77043ddc4f1c715a8ddb2d8a255 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -18218,7 +18459,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:2.3.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -18239,7 +18480,17 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 4c1ade961ded57cdbfbb5cac5106ec17bc8bccd62e16343c569a0ceeca83b9dfef87550b4dc5cbb89642da412b20c5071f304c8c464b80415446e8e155a038c0 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -18258,6 +18509,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "function-bind@npm:^1.1.1, function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" @@ -18522,7 +18782,7 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.0": +"get-tsconfig@npm:^4.7.0, get-tsconfig@npm:^4.7.2": version: 4.7.2 resolution: "get-tsconfig@npm:4.7.2" dependencies: @@ -24392,6 +24652,7 @@ __metadata: terser: "npm:^5.7.0" through2: "npm:^4.0.2" ts-node: "npm:^10.9.2" + tsx: "npm:^4.7.1" ttest: "npm:^2.1.1" typescript: "npm:~5.3.3" unicode-confusables: "npm:^0.1.1" @@ -32881,6 +33142,22 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^4.7.1": + version: 4.7.1 + resolution: "tsx@npm:4.7.1" + dependencies: + esbuild: "npm:~0.19.10" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.2" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 3a462b595f31ae58b31f9c6e8c450577dc87660b1225012bd972b6b58d7d2f6c4034728763ebc53bb731acff68de8b0fa50586e4c1ec4c086226f1788ccf9b7d + languageName: node + linkType: hard + "ttest@npm:^2.1.1": version: 2.1.1 resolution: "ttest@npm:2.1.1" From 7a76c2751ccf723fb4c85ec393aee17362825d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Regadas?= Date: Tue, 20 Feb 2024 11:08:52 +0000 Subject: [PATCH 24/49] MMI skipping until we move to sepolia (#23064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Skipping these tests until we finish moving to Sepolia. ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- test/e2e/mmi/specs/extension.visual.spec.ts | 2 +- test/e2e/mmi/specs/transactions.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/mmi/specs/extension.visual.spec.ts b/test/e2e/mmi/specs/extension.visual.spec.ts index cb0bb9d88896..1f2176cf6b0b 100644 --- a/test/e2e/mmi/specs/extension.visual.spec.ts +++ b/test/e2e/mmi/specs/extension.visual.spec.ts @@ -76,7 +76,7 @@ test.describe('MMI extension', () => { ); }); - test('Custodian token management', async ({ page, context }) => { + test.skip('Custodian token management', async ({ page, context }) => { // Define const to compare in assertions const arrayWithoutCustodianAccounts = ['Account 1']; const arrayWithCustodianAccounts = [ diff --git a/test/e2e/mmi/specs/transactions.spec.ts b/test/e2e/mmi/specs/transactions.spec.ts index ecaeff9434c4..4cd6dab45361 100644 --- a/test/e2e/mmi/specs/transactions.spec.ts +++ b/test/e2e/mmi/specs/transactions.spec.ts @@ -72,7 +72,7 @@ const sendTransaction = async ( }; test.describe('MMI send', () => { - test('Send a transaction from one account to another and confirm it from custody', async ({ + test.skip('Send a transaction from one account to another and confirm it from custody', async ({ page, context, }) => { From 54b3eec81f331ad4d635424ab30fa483a60cc325 Mon Sep 17 00:00:00 2001 From: Chloe Gao Date: Tue, 20 Feb 2024 15:18:37 +0100 Subject: [PATCH 25/49] resolve merge conflit --- app/scripts/lib/ppom/ppom-middleware.ts | 4 - lavamoat/browserify/beta/policy.json | 75 ---- lavamoat/browserify/desktop/policy.json | 75 ---- lavamoat/browserify/flask/policy.json | 75 ---- lavamoat/browserify/main/policy.json | 75 ---- lavamoat/browserify/mmi/policy.json | 75 ---- package.json | 3 - test/e2e/tests/nft/import-nft.spec.js | 8 - .../ppom-blockaid-alert-simple-send.spec.js | 6 - .../signature-request-siwe.js | 250 ----------- .../signature-request/signature-request.js | 393 ------------------ .../token-list-item/token-list-item.js | 8 - ...onfirm-page-container-content.component.js | 10 - .../blockaid-banner-alert.js | 30 -- .../blockaid-banner-alert.test.js | 10 - .../transaction-alerts/transaction-alerts.js | 17 - .../confirm-approve-content.component.js | 4 - .../confirm-transaction-base.component.js | 8 - .../confirm-transaction-base.container.js | 8 - .../token-allowance/token-allowance.js | 4 - yarn.lock | 21 - 21 files changed, 1159 deletions(-) delete mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe.js delete mode 100644 ui/components/app/signature-request/signature-request.js diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index 5cb14504566b..4f742e4af078 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -9,11 +9,7 @@ import { } from '../../../../shared/constants/security-provider'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import { SIGNING_METHODS } from '../../../../shared/constants/transaction'; -<<<<<<< HEAD -import PreferencesController from '../../controllers/preferences'; -======= import { PreferencesController } from '../../controllers/preferences'; ->>>>>>> origin/develop import { SecurityAlertResponse } from '../transaction/util'; const { sentry } = global as any; diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index f4ebd3e1ae55..c156a7b706b2 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -738,10 +738,6 @@ "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1491,23 +1487,6 @@ "uuid": true } }, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, -======= ->>>>>>> origin/develop "@metamask/logo": { "globals": { "addEventListener": true, @@ -1828,34 +1807,6 @@ "webpack>events": true } }, -<<<<<<< HEAD - "@metamask/signature-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/signature-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, -======= ->>>>>>> origin/develop "@metamask/smart-transactions-controller": { "globals": { "URLSearchParams": true, @@ -1894,10 +1845,6 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3500,28 +3447,6 @@ "@metamask/ppom-validator>elliptic": true } }, -<<<<<<< HEAD - "globalthis>define-properties": { - "packages": { - "globalthis>define-properties>define-data-property": true, - "globalthis>define-properties>has-property-descriptors": true, - "globalthis>define-properties>object-keys": true - } - }, - "globalthis>define-properties>define-data-property": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true - } - }, - "globalthis>define-properties>has-property-descriptors": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true - } - }, -======= ->>>>>>> origin/develop "gulp>vinyl-fs>object.assign": { "packages": { "@lavamoat/lavapack>json-stable-stringify>object-keys": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index 7bdd400db1a7..0388d2497a8c 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -738,10 +738,6 @@ "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1568,23 +1564,6 @@ "uuid": true } }, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, -======= ->>>>>>> origin/develop "@metamask/logo": { "globals": { "addEventListener": true, @@ -1969,34 +1948,6 @@ "webpack>events": true } }, -<<<<<<< HEAD - "@metamask/signature-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/signature-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, -======= ->>>>>>> origin/develop "@metamask/smart-transactions-controller": { "globals": { "URLSearchParams": true, @@ -2035,10 +1986,6 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3757,28 +3704,6 @@ "@metamask/ppom-validator>elliptic": true } }, -<<<<<<< HEAD - "globalthis>define-properties": { - "packages": { - "globalthis>define-properties>define-data-property": true, - "globalthis>define-properties>has-property-descriptors": true, - "globalthis>define-properties>object-keys": true - } - }, - "globalthis>define-properties>define-data-property": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true - } - }, - "globalthis>define-properties>has-property-descriptors": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true - } - }, -======= ->>>>>>> origin/develop "gulp>vinyl-fs>object.assign": { "packages": { "@lavamoat/lavapack>json-stable-stringify>object-keys": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 5ae06626f7d1..88c78d04b032 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -738,10 +738,6 @@ "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1568,23 +1564,6 @@ "uuid": true } }, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, -======= ->>>>>>> origin/develop "@metamask/logo": { "globals": { "addEventListener": true, @@ -2005,34 +1984,6 @@ "webpack>events": true } }, -<<<<<<< HEAD - "@metamask/signature-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/signature-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, -======= ->>>>>>> origin/develop "@metamask/smart-transactions-controller": { "globals": { "URLSearchParams": true, @@ -2071,10 +2022,6 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3793,28 +3740,6 @@ "@metamask/ppom-validator>elliptic": true } }, -<<<<<<< HEAD - "globalthis>define-properties": { - "packages": { - "globalthis>define-properties>define-data-property": true, - "globalthis>define-properties>has-property-descriptors": true, - "globalthis>define-properties>object-keys": true - } - }, - "globalthis>define-properties>define-data-property": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true - } - }, - "globalthis>define-properties>has-property-descriptors": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true - } - }, -======= ->>>>>>> origin/develop "gulp>vinyl-fs>object.assign": { "packages": { "@lavamoat/lavapack>json-stable-stringify>object-keys": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 5721a7174bd1..a84465a1832d 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -738,10 +738,6 @@ "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1491,23 +1487,6 @@ "uuid": true } }, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, -======= ->>>>>>> origin/develop "@metamask/logo": { "globals": { "addEventListener": true, @@ -1928,34 +1907,6 @@ "webpack>events": true } }, -<<<<<<< HEAD - "@metamask/signature-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/signature-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, -======= ->>>>>>> origin/develop "@metamask/smart-transactions-controller": { "globals": { "URLSearchParams": true, @@ -1994,10 +1945,6 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3716,28 +3663,6 @@ "@metamask/ppom-validator>elliptic": true } }, -<<<<<<< HEAD - "globalthis>define-properties": { - "packages": { - "globalthis>define-properties>define-data-property": true, - "globalthis>define-properties>has-property-descriptors": true, - "globalthis>define-properties>object-keys": true - } - }, - "globalthis>define-properties>define-data-property": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true - } - }, - "globalthis>define-properties>has-property-descriptors": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true - } - }, -======= ->>>>>>> origin/develop "gulp>vinyl-fs>object.assign": { "packages": { "@lavamoat/lavapack>json-stable-stringify>object-keys": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 62780d695cb3..c2f3259316ee 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -870,10 +870,6 @@ "@metamask/address-book-controller>@metamask/controller-utils>@metamask/utils": true, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "browserify>buffer": true, "eslint>fast-deep-equal": true, "eth-ens-namehash": true, @@ -1623,23 +1619,6 @@ "uuid": true } }, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": { - "packages": { - "@metamask/ethjs>number-to-bn": true, - "bn.js": true - } - }, -======= ->>>>>>> origin/develop "@metamask/logo": { "globals": { "addEventListener": true, @@ -2024,34 +2003,6 @@ "webpack>events": true } }, -<<<<<<< HEAD - "@metamask/signature-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/signature-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, - "@metamask/utils": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true, - "ethereumjs-util": true - } - }, -======= ->>>>>>> origin/develop "@metamask/smart-transactions-controller": { "globals": { "URLSearchParams": true, @@ -2090,10 +2041,6 @@ "packages": { "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": true, "@metamask/controller-utils>@spruceid/siwe-parser": true, -<<<<<<< HEAD - "@metamask/logging-controller>@metamask/controller-utils>ethjs-unit": true, -======= ->>>>>>> origin/develop "@metamask/utils": true, "browserify>buffer": true, "eslint>fast-deep-equal": true, @@ -3812,28 +3759,6 @@ "@metamask/ppom-validator>elliptic": true } }, -<<<<<<< HEAD - "globalthis>define-properties": { - "packages": { - "globalthis>define-properties>define-data-property": true, - "globalthis>define-properties>has-property-descriptors": true, - "globalthis>define-properties>object-keys": true - } - }, - "globalthis>define-properties>define-data-property": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true - } - }, - "globalthis>define-properties>has-property-descriptors": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true - } - }, -======= ->>>>>>> origin/develop "gulp>vinyl-fs>object.assign": { "packages": { "@lavamoat/lavapack>json-stable-stringify>object-keys": true, diff --git a/package.json b/package.json index 0b8b2a32d82d..4f238f88a560 100644 --- a/package.json +++ b/package.json @@ -398,10 +398,7 @@ "@babel/preset-typescript": "^7.23.2", "@babel/register": "^7.22.15", "@lavamoat/allow-scripts": "^3.0.1", -<<<<<<< HEAD -======= "@lavamoat/lavadome-core": "0.0.10", ->>>>>>> origin/develop "@lavamoat/lavapack": "^6.1.0", "@metamask/auto-changelog": "^2.1.0", "@metamask/build-utils": "^1.0.0", diff --git a/test/e2e/tests/nft/import-nft.spec.js b/test/e2e/tests/nft/import-nft.spec.js index 3dcad74c95b1..be3254cfea52 100644 --- a/test/e2e/tests/nft/import-nft.spec.js +++ b/test/e2e/tests/nft/import-nft.spec.js @@ -103,10 +103,6 @@ describe('Import NFT', function () { assert.equal(await importedNft.isDisplayed(), true); assert.equal(await importedNftImage.isDisplayed(), true); -<<<<<<< HEAD - await driver.clickElement({ text: 'Tokens', tag: 'button' }); -======= ->>>>>>> origin/develop await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( '[data-testid="multichain-account-menu-popover-action-button"]', @@ -131,10 +127,6 @@ describe('Import NFT', function () { await waitForAccountRendered(driver); await driver.clickElement(accountOneSelector); -<<<<<<< HEAD - await driver.clickElement({ text: 'NFTs', tag: 'button' }); -======= ->>>>>>> origin/develop const nftIsStillDisplayed = await driver.isElementPresentAndVisible({ css: 'h5', text: 'TestDappNFTs', diff --git a/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js b/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js index ceaa20dbdc6d..bf99c19c12c8 100644 --- a/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js @@ -126,12 +126,6 @@ async function mockInfuraWithFailedResponses(mockServer) { * @see {@link https://wobbly-nutmeg-8a5.notion.site/MM-E2E-Testing-1e51b617f79240a49cd3271565c6e12d} */ describe('Simple Send Security Alert - Blockaid @no-mmi', function () { -<<<<<<< HEAD - if (process.env.MULTICHAIN) { - return; - } -======= ->>>>>>> origin/develop it('should not show security alerts for benign requests', async function () { await withFixtures( { diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe.js b/ui/components/app/signature-request-siwe/signature-request-siwe.js deleted file mode 100644 index cca280868ad3..000000000000 --- a/ui/components/app/signature-request-siwe/signature-request-siwe.js +++ /dev/null @@ -1,250 +0,0 @@ -import React, { useCallback, useContext, useState } from 'react'; -import PropTypes from 'prop-types'; -import { useSelector, useDispatch } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import log from 'loglevel'; -import { isValidSIWEOrigin } from '@metamask/controller-utils'; -import { ethErrors, serializeError } from 'eth-rpc-errors'; -import { BannerAlert, Text } from '../../component-library'; -import Popover from '../../ui/popover'; -import Checkbox from '../../ui/check-box'; -import Button from '../../ui/button'; -import { I18nContext } from '../../../contexts/i18n'; -import { PageContainerFooter } from '../../ui/page-container'; -import { isAddressLedger } from '../../../ducks/metamask/metamask'; -import { - accountsWithSendEtherInfoSelector, - getSubjectMetadata, - getTotalUnapprovedMessagesCount, - unconfirmedMessagesHashSelector, -} from '../../../selectors'; -import { getAccountByAddress, valuesFor } from '../../../helpers/utils/util'; -import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; -import { formatMessageParams } from '../../../../shared/modules/siwe'; -import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck'; - -import { - SEVERITIES, - TextVariant, -} from '../../../helpers/constants/design-system'; -import { - resolvePendingApproval, - rejectPendingApproval, - rejectAllMessages, - completedTx, - showModal, -} from '../../../store/actions'; - -import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; -import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; -import { getMostRecentOverviewPage } from '../../../ducks/history/history'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) -import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -///: END:ONLY_INCLUDE_IF -import LedgerInstructionField from '../ledger-instruction-field'; - -import SignatureRequestHeader from '../signature-request-header'; -import Header from './signature-request-siwe-header'; -import Message from './signature-request-siwe-message'; - -export default function SignatureRequestSIWE({ txData }) { - const dispatch = useDispatch(); - const history = useHistory(); - const t = useContext(I18nContext); - const allAccounts = useSelector(accountsWithSendEtherInfoSelector); - const subjectMetadata = useSelector(getSubjectMetadata); - const messagesCount = useSelector(getTotalUnapprovedMessagesCount); - const messagesList = useSelector(unconfirmedMessagesHashSelector); - const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); - - const { - msgParams: { - from, - origin, - siwe: { parsedMessage }, - }, - id, - } = txData; - - const isLedgerWallet = useSelector((state) => isAddressLedger(state, from)); - - const fromAccount = getAccountByAddress(allAccounts, from); - const targetSubjectMetadata = subjectMetadata[origin]; - - const isMatchingAddress = - from.toLowerCase() === parsedMessage.address.toLowerCase(); - - const isSIWEDomainValid = isValidSIWEOrigin(txData.msgParams); - - const [isShowingDomainWarning, setIsShowingDomainWarning] = useState(false); - const [hasAgreedToDomainWarning, setHasAgreedToDomainWarning] = - useState(false); - - const showSecurityProviderBanner = isSuspiciousResponse( - txData?.securityProviderResponse, - ); - - const onSign = useCallback(async () => { - try { - await dispatch(resolvePendingApproval(id, null)); - dispatch(completedTx(id)); - } catch (e) { - log.error(e); - } - }, [id, dispatch]); - - const onCancel = useCallback(async () => { - try { - await dispatch( - rejectPendingApproval( - id, - serializeError(ethErrors.provider.userRejectedRequest()), - ), - ); - } catch (e) { - log.error(e); - } - }, [dispatch, id]); - - const handleCancelAll = () => { - const unapprovedTxCount = messagesCount; - - dispatch( - showModal({ - name: 'REJECT_TRANSACTIONS', - unapprovedTxCount, - onSubmit: async () => { - await dispatch(rejectAllMessages(valuesFor(messagesList))); - dispatch(clearConfirmTransaction()); - history.push(mostRecentOverviewPage); - }, - }), - ); - }; - - const rejectNText = t('rejectRequestsN', [messagesCount]); - - return ( -
-
- -
- - - { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - - ///: END:ONLY_INCLUDE_IF - } - {showSecurityProviderBanner && ( - - )} - -
- - {!isMatchingAddress && ( - - {t('SIWEAddressInvalid', [ - parsedMessage.address, - fromAccount.address, - ])} - - )} - {isLedgerWallet && ( -
- -
- )} - {!isSIWEDomainValid && ( - - - {t('SIWEDomainInvalidTitle')} - {' '} - {t('SIWEDomainInvalidText')} - - )} - setIsShowingDomainWarning(true) - } - cancelText={t('cancel')} - submitText={t('signin')} - submitButtonType={isSIWEDomainValid ? 'primary' : 'danger-primary'} - /> - {messagesCount > 1 ? ( - - ) : null} - {isShowingDomainWarning && ( - setIsShowingDomainWarning(false)} - title={t('SIWEWarningTitle')} - subtitle={t('SIWEWarningSubtitle')} - className="signature-request-siwe__warning-popover" - footerClassName="signature-request-siwe__warning-popover__footer" - footer={ - setIsShowingDomainWarning(false)} - cancelText={t('cancel')} - cancelButtonType="default" - onSubmit={onSign} - submitText={t('confirm')} - submitButtonType="danger-primary" - disabled={!hasAgreedToDomainWarning} - /> - } - > -
- setHasAgreedToDomainWarning((checked) => !checked)} - /> - -
-
- )} -
- ); -} - -SignatureRequestSIWE.propTypes = { - /** - * The display content of transaction data - */ - txData: PropTypes.object.isRequired, -}; diff --git a/ui/components/app/signature-request/signature-request.js b/ui/components/app/signature-request/signature-request.js deleted file mode 100644 index 874f89485f2b..000000000000 --- a/ui/components/app/signature-request/signature-request.js +++ /dev/null @@ -1,393 +0,0 @@ -import React, { useContext, useState, useEffect } from 'react'; -import { - useDispatch, - useSelector, - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - shallowEqual, - ///: END:ONLY_INCLUDE_IF -} from 'react-redux'; -import PropTypes from 'prop-types'; -import { memoize } from 'lodash'; -import { ethErrors, serializeError } from 'eth-rpc-errors'; -///: BEGIN:ONLY_INCLUDE_IF(build-mmi) -import { showCustodianDeepLink } from '@metamask-institutional/extension'; -///: END:ONLY_INCLUDE_IF -import { - resolvePendingApproval, - completedTx, - rejectPendingApproval, -} from '../../../store/actions'; -import { - doesAddressRequireLedgerHidConnection, - getSubjectMetadata, - getTotalUnapprovedMessagesCount, - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - accountsWithSendEtherInfoSelector, - getSelectedAccount, - getAccountType, - ///: END:ONLY_INCLUDE_IF -} from '../../../selectors'; -import { - getProviderConfig, - isAddressLedger, -} from '../../../ducks/metamask/metamask'; -import { - sanitizeMessage, - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - getAccountByAddress, - shortenAddress, - ///: END:ONLY_INCLUDE_IF -} from '../../../helpers/utils/util'; -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { useRejectTransactionModal } from '../../../hooks/useRejectTransactionModal'; - -import { ConfirmPageContainerNavigation } from '../confirm-page-container'; -import SignatureRequestHeader from '../signature-request-header/signature-request-header'; -import SecurityProviderBannerMessage from '../security-provider-banner-message'; -import LedgerInstructionField from '../ledger-instruction-field'; -import ContractDetailsModal from '../modals/contract-details-modal'; -import { MetaMetricsContext } from '../../../contexts/metametrics'; -import { - MetaMetricsEventCategory, - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - MetaMetricsEventName, - ///: END:ONLY_INCLUDE_IF -} from '../../../../shared/constants/metametrics'; -import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider'; - -import { - TextAlign, - TextColor, - TextVariant, - Size, - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - IconColor, - BackgroundColor, - Display, - BlockSize, - ///: END:ONLY_INCLUDE_IF -} from '../../../helpers/constants/design-system'; -import { - ButtonVariant, - Button, - ButtonLink, - TagUrl, - Text, - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - Icon, - IconName, - Box, - ///: END:ONLY_INCLUDE_IF -} from '../../component-library'; - -///: BEGIN:ONLY_INCLUDE_IF(build-mmi) -// eslint-disable-next-line import/order -import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../shared/constants/app'; -import { getEnvironmentType } from '../../../../app/scripts/lib/util'; -import { mmiActionsFactory } from '../../../store/institutional/institution-background'; -import { showCustodyConfirmLink } from '../../../store/institutional/institution-actions'; -import { useMMICustodySignMessage } from '../../../hooks/useMMICustodySignMessage'; -///: END:ONLY_INCLUDE_IF -///: BEGIN:ONLY_INCLUDE_IF(blockaid) -import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -///: END:ONLY_INCLUDE_IF - -import Message from './signature-request-message'; -import Footer from './signature-request-footer'; - -const SignatureRequest = ({ txData }) => { - const trackEvent = useContext(MetaMetricsContext); - const dispatch = useDispatch(); - const t = useI18nContext(); - - const [hasScrolledMessage, setHasScrolledMessage] = useState(false); - const [showContractDetails, setShowContractDetails] = useState(false); - const [messageRootRef, setMessageRootRef] = useState(null); - const [messageIsScrollable, setMessageIsScrollable] = useState(false); - - const { - id, - type, - msgParams: { from, data, origin, version }, - } = txData; - - // not forwarded to component - const hardwareWalletRequiresConnection = useSelector((state) => - doesAddressRequireLedgerHidConnection(state, from), - ); - const { chainId, rpcPrefs } = useSelector(getProviderConfig); - const unapprovedMessagesCount = useSelector(getTotalUnapprovedMessagesCount); - const subjectMetadata = useSelector(getSubjectMetadata); - const isLedgerWallet = useSelector((state) => isAddressLedger(state, from)); - const { handleCancelAll } = useRejectTransactionModal(); - - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - // Used to show a warning if the signing account is not the selected account - // Largely relevant for contract wallet custodians - const selectedAccount = useSelector(getSelectedAccount); - const mmiActions = mmiActionsFactory(); - const accountType = useSelector(getAccountType); - const isNotification = getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION; - const allAccounts = useSelector( - accountsWithSendEtherInfoSelector, - shallowEqual, - ); - const { address } = getAccountByAddress(allAccounts, from) || {}; - const { custodySignFn } = useMMICustodySignMessage(); - ///: END:ONLY_INCLUDE_IF - - useEffect(() => { - setMessageIsScrollable( - messageRootRef?.scrollHeight > messageRootRef?.clientHeight, - ); - }, [messageRootRef]); - - const targetSubjectMetadata = subjectMetadata?.[origin] || null; - - const parseMessage = memoize((dataToParse) => { - const { - message, - domain = {}, - primaryType, - types, - } = JSON.parse(dataToParse); - const sanitizedMessage = sanitizeMessage(message, primaryType, types); - return { sanitizedMessage, domain, primaryType }; - }); - - const onSign = async () => { - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - if (accountType === 'custody') { - await custodySignFn(txData); - } - ///: END:ONLY_INCLUDE_IF - - await dispatch(resolvePendingApproval(id)); - completedTx(id); - - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: 'Confirm', - properties: { - action: 'Sign Request', - legacy_event: true, - type, - version, - }, - }); - }; - - const onCancel = async () => { - await dispatch( - rejectPendingApproval( - id, - serializeError(ethErrors.provider.userRejectedRequest()), - ), - ); - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: 'Cancel', - properties: { - action: 'Sign Request', - legacy_event: true, - type, - version, - }, - }); - }; - - const { - sanitizedMessage, - domain: { verifyingContract }, - primaryType, - } = parseMessage(data); - - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - useEffect(() => { - if (txData.custodyId) { - showCustodianDeepLink({ - dispatch, - mmiActions, - txId: undefined, - custodyId: txData.custodyId, - fromAddress: address, - isSignature: true, - closeNotification: isNotification, - onDeepLinkFetched: () => undefined, - onDeepLinkShown: () => { - trackEvent({ - category: MetaMetricsEventCategory.MMI, - event: MetaMetricsEventName.SignatureDeeplinkDisplayed, - }); - }, - showCustodyConfirmLink, - }); - } - }, [ - dispatch, - mmiActions, - txData.custodyId, - address, - isNotification, - trackEvent, - ]); - ///: END:ONLY_INCLUDE_IF - - return ( -
- -
- -
-
- { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - - ///: END:ONLY_INCLUDE_IF - } - {(txData?.securityProviderResponse?.flagAsDangerous !== undefined && - txData?.securityProviderResponse?.flagAsDangerous !== - SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS) || - (txData?.securityProviderResponse && - Object.keys(txData.securityProviderResponse).length === 0) ? ( - - ) : null} - { - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - selectedAccount.address === address ? null : ( - - - - {t('mismatchAccount', [ - shortenAddress(selectedAccount.address), - shortenAddress(address), - ])} - - - ) - ///: END:ONLY_INCLUDE_IF - } -
- -
- - {t('sigRequest')} - - - {t('signatureRequestGuidance')} - - {verifyingContract ? ( -
- -
- ) : null} -
- {isLedgerWallet ? ( -
- -
- ) : null} - setHasScrolledMessage(true)} - setMessageRootRef={setMessageRootRef} - messageRootRef={messageRootRef} - messageIsScrollable={messageIsScrollable} - primaryType={primaryType} - /> -
- {showContractDetails && ( - setShowContractDetails(false)} - isContractRequestingSignature - /> - )} - {unapprovedMessagesCount > 1 ? ( - - {t('rejectRequestsN', [unapprovedMessagesCount])} - - ) : null} -
- ); -}; - -SignatureRequest.propTypes = { - txData: PropTypes.object, -}; - -export default SignatureRequest; diff --git a/ui/components/multichain/token-list-item/token-list-item.js b/ui/components/multichain/token-list-item/token-list-item.js index d68090ec759b..d5066208bf13 100644 --- a/ui/components/multichain/token-list-item/token-list-item.js +++ b/ui/components/multichain/token-list-item/token-list-item.js @@ -221,9 +221,6 @@ export const TokenListItem = ({ variant={TextVariant.bodyMd} ellipsis > -<<<<<<< HEAD - {tokenTitle} -======= {isStakeable ? ( <> {tokenSymbol} {stakeableTitle} @@ -231,7 +228,6 @@ export const TokenListItem = ({ ) : ( tokenSymbol )} ->>>>>>> origin/develop ) : ( @@ -241,9 +237,6 @@ export const TokenListItem = ({ variant={TextVariant.bodyMd} ellipsis > -<<<<<<< HEAD - {tokenTitle} -======= {isStakeable ? ( {tokenSymbol} {stakeableTitle} @@ -251,7 +244,6 @@ export const TokenListItem = ({ ) : ( tokenSymbol )} ->>>>>>> origin/develop )} diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index bfa3c211b274..cebfa3460ad0 100644 --- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -9,15 +9,6 @@ import { BUTTON_VARIANT, ///: END:ONLY_INCLUDE_IF BannerAlert, -<<<<<<< HEAD:ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js -} from '../../../component-library'; -import { PageContainerFooter } from '../../../ui/page-container'; -import { - INSUFFICIENT_FUNDS_ERROR_KEY, - IS_SIGNING_OR_SUBMITTING, -} from '../../../../helpers/constants/error-keys'; -import { Severity } from '../../../../helpers/constants/design-system'; -======= } from '../../../../../components/component-library'; import { PageContainerFooter } from '../../../../../components/ui/page-container'; import { @@ -25,7 +16,6 @@ import { IS_SIGNING_OR_SUBMITTING, } from '../../../../../helpers/constants/error-keys'; import { Severity } from '../../../../../helpers/constants/design-system'; ->>>>>>> origin/develop:ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'; diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js index 9b6ba5f4fc15..6920cdacd142 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js +++ b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js @@ -7,14 +7,8 @@ import { NETWORK_TO_NAME_MAP } from '../../../../../../shared/constants/network' import { OverflowWrap, Severity, -<<<<<<< HEAD:ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js -} from '../../../../helpers/constants/design-system'; -import { I18nContext } from '../../../../contexts/i18n'; -import { useTransactionEventFragment } from '../../../../hooks/useTransactionEventFragment'; -======= } from '../../../../../helpers/constants/design-system'; import { I18nContext } from '../../../../../contexts/i18n'; ->>>>>>> origin/develop:ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js import { BlockaidReason, BlockaidResultType, @@ -143,26 +137,6 @@ function BlockaidBannerAlert({ txData, ...props }) { ); }; -<<<<<<< HEAD:ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js - const jsonData = JSON.stringify(reportData); - - const encodedData = zlib?.gzipSync?.(jsonData) ?? jsonData; - - const reportUrl = getReportUrl(encodedData); - - const onClickSupportLink = () => { - updateTransactionEventFragment( - { - properties: { - external_link_clicked: 'security_alert_support_link', - }, - }, - txData.id, - ); - }; - -======= ->>>>>>> origin/develop:ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js return ( >>>>>> origin/develop:ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.js onClickSupportLink={onClickSupportLink} {...props} /> diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js index a892e156d500..b34d9d9b0e0a 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js +++ b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js @@ -1,17 +1,11 @@ import React from 'react'; import * as Sentry from '@sentry/browser'; import { fireEvent, screen } from '@testing-library/react'; -<<<<<<< HEAD:ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js -import { renderWithProvider } from '../../../../../test/lib/render-helpers'; -import { Severity } from '../../../../helpers/constants/design-system'; -import configureStore from '../../../../store/store'; -======= import { renderWithProvider } from '../../../../../../test/lib/render-helpers'; import { Severity } from '../../../../../helpers/constants/design-system'; import configureStore from '../../../../../store/store'; ->>>>>>> origin/develop:ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js import { BlockaidReason, BlockaidResultType, @@ -25,11 +19,7 @@ jest.mock('zlib', () => ({ const mockUpdateTransactionEventFragment = jest.fn(); -<<<<<<< HEAD:ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js -jest.mock('../../../../hooks/useTransactionEventFragment', () => { -======= jest.mock('../../../hooks/useTransactionEventFragment', () => { ->>>>>>> origin/develop:ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js return { useTransactionEventFragment: () => { return { diff --git a/ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js b/ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js index 3bd47229b294..ece1f397831f 100644 --- a/ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js +++ b/ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js @@ -2,16 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { TransactionType } from '@metamask/transaction-controller'; -<<<<<<< HEAD:ui/components/app/transaction-alerts/transaction-alerts.js -import { PriorityLevels } from '../../../../shared/constants/gas'; -import { submittedPendingTransactionsSelector } from '../../../selectors'; -import { useGasFeeContext } from '../../../contexts/gasFee'; -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { BannerAlert, ButtonLink, Text } from '../../component-library'; -import SimulationErrorMessage from '../../ui/simulation-error-message'; -import { SEVERITIES } from '../../../helpers/constants/design-system'; -import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; -======= import { PriorityLevels } from '../../../../../shared/constants/gas'; import { submittedPendingTransactionsSelector } from '../../../../selectors'; import { useGasFeeContext } from '../../../../contexts/gasFee'; @@ -24,22 +14,15 @@ import { import SimulationErrorMessage from '../simulation-error-message'; import { SEVERITIES } from '../../../../helpers/constants/design-system'; import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url'; ->>>>>>> origin/develop:ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js import { isSuspiciousResponse } from '../../../../../shared/modules/security-provider.utils'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; ///: END:ONLY_INCLUDE_IF import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; -<<<<<<< HEAD:ui/components/app/transaction-alerts/transaction-alerts.js -import { getNativeCurrency } from '../../../ducks/metamask/metamask'; -import { parseStandardTokenTransactionData } from '../../../../shared/modules/transaction.utils'; -import { getTokenValueParam } from '../../../../shared/lib/metamask-controller-utils'; -======= import { getNativeCurrency } from '../../../../ducks/metamask/metamask'; import { parseStandardTokenTransactionData } from '../../../../../shared/modules/transaction.utils'; import { getTokenValueParam } from '../../../../../shared/lib/metamask-controller-utils'; ->>>>>>> origin/develop:ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js const TransactionAlerts = ({ userAcknowledgedGasMissing, diff --git a/ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.js index 22f2d854077e..3ec563ab4f44 100644 --- a/ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.js +++ b/ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -20,11 +20,7 @@ import { import { ConfirmPageContainerWarning } from '../../components/confirm-page-container/confirm-page-container-content'; import LedgerInstructionField from '../../components/ledger-instruction-field'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) -<<<<<<< HEAD:ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js -import BlockaidBannerAlert from '../../../components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -======= import BlockaidBannerAlert from '../../components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; ->>>>>>> origin/develop:ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.js ///: END:ONLY_INCLUDE_IF import { isSuspiciousResponse } from '../../../../../shared/modules/security-provider.utils'; diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js index c072ad70d400..e0d5bd682217 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js @@ -13,13 +13,8 @@ import { ETH_GAS_PRICE_FETCH_WARNING_KEY, GAS_PRICE_FETCH_FAILURE_ERROR_KEY, IS_SIGNING_OR_SUBMITTING, -<<<<<<< HEAD:ui/pages/confirm-transaction-base/confirm-transaction-base.component.js -} from '../../helpers/constants/error-keys'; -import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'; -======= } from '../../../helpers/constants/error-keys'; import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display'; ->>>>>>> origin/develop:ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import TextField from '../../../components/ui/text-field'; @@ -160,11 +155,8 @@ export default class ConfirmTransactionBase extends Component { updateTransaction: PropTypes.func, isUsingPaymaster: PropTypes.bool, isSigningOrSubmitting: PropTypes.bool, -<<<<<<< HEAD:ui/pages/confirm-transaction-base/confirm-transaction-base.component.js -======= useMaxValue: PropTypes.bool, maxValue: PropTypes.string, ->>>>>>> origin/develop:ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.component.js }; state = { diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js index b8573997ed9d..39ce79d51420 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js @@ -51,13 +51,8 @@ import { getUnapprovedTransactions, getInternalAccountByAddress, getApprovedAndSignedTransactions, -<<<<<<< HEAD:ui/pages/confirm-transaction-base/confirm-transaction-base.container.js -} from '../../selectors'; -import { getMostRecentOverviewPage } from '../../ducks/history/history'; -======= } from '../../../selectors'; import { getMostRecentOverviewPage } from '../../../ducks/history/history'; ->>>>>>> origin/develop:ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js import { isAddressLedger, updateGasFees, @@ -319,11 +314,8 @@ const mapStateToProps = (state, ownProps) => { keyringForAccount: keyring, isUsingPaymaster, isSigningOrSubmitting, -<<<<<<< HEAD:ui/pages/confirm-transaction-base/confirm-transaction-base.container.js -======= useMaxValue, maxValue, ->>>>>>> origin/develop:ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) accountType, isNoteToTraderSupported, diff --git a/ui/pages/confirmations/token-allowance/token-allowance.js b/ui/pages/confirmations/token-allowance/token-allowance.js index a1143fcb352d..a7c6b4b368b6 100644 --- a/ui/pages/confirmations/token-allowance/token-allowance.js +++ b/ui/pages/confirmations/token-allowance/token-allowance.js @@ -63,11 +63,7 @@ import { import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; ///: BEGIN:ONLY_INCLUDE_IF(blockaid) -<<<<<<< HEAD:ui/pages/token-allowance/token-allowance.js -import BlockaidBannerAlert from '../../components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -======= import BlockaidBannerAlert from '../components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; ->>>>>>> origin/develop:ui/pages/confirmations/token-allowance/token-allowance.js ///: END:ONLY_INCLUDE_IF import { ConfirmPageContainerNavigation } from '../components/confirm-page-container'; diff --git a/yarn.lock b/yarn.lock index 08834b3a5f8e..61cae79f646e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1640,11 +1640,7 @@ __metadata: languageName: node linkType: hard -<<<<<<< HEAD -"@babel/traverse@npm:7.23.9, @babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.5, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.7.2": -======= "@babel/traverse@npm:7.23.9, @babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.5, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.23.2": ->>>>>>> origin/develop version: 7.23.9 resolution: "@babel/traverse@npm:7.23.9" dependencies: @@ -3407,8 +3403,6 @@ __metadata: languageName: node linkType: hard -<<<<<<< HEAD -======= "@lavamoat/lavadome-core@npm:0.0.10": version: 0.0.10 resolution: "@lavamoat/lavadome-core@npm:0.0.10" @@ -3438,7 +3432,6 @@ __metadata: languageName: node linkType: hard ->>>>>>> origin/develop "@lavamoat/lavapack@npm:^6.1.0": version: 6.1.0 resolution: "@lavamoat/lavapack@npm:6.1.0" @@ -3453,8 +3446,6 @@ __metadata: through2: "npm:4.0.2" umd: "npm:3.0.3" checksum: faf4425e740c5abb9a441b6e872921943d48129f8f2120ce26672119824cea5dfcbcb7ae7113ece639a34c924794e3f6088564281672421bf31b40c25cfc93dc -<<<<<<< HEAD -======= languageName: node linkType: hard @@ -3462,7 +3453,6 @@ __metadata: version: 2.0.0 resolution: "@lavamoat/preinstall-always-fail@npm:2.0.0" checksum: a69c712e9a01029cacc8f77f7b9a944a285d9532583c09fc6050baef098d962d7dea18f17f446ca1f0ec3cd1eea07bfaedd583a704e016889cae1eba7f3552fd ->>>>>>> origin/develop languageName: node linkType: hard @@ -4538,11 +4528,7 @@ __metadata: languageName: node linkType: hard -<<<<<<< HEAD -"@metamask/message-manager@npm:^7.3.0, @metamask/message-manager@npm:^7.3.5": -======= "@metamask/message-manager@npm:^7.3.0, @metamask/message-manager@npm:^7.3.8": ->>>>>>> origin/develop version: 7.3.8 resolution: "@metamask/message-manager@npm:7.3.8" dependencies: @@ -24353,11 +24339,8 @@ __metadata: "@keystonehq/bc-ur-registry-eth": "npm:^0.19.1" "@keystonehq/metamask-airgapped-keyring": "npm:^0.13.1" "@lavamoat/allow-scripts": "npm:^3.0.1" -<<<<<<< HEAD -======= "@lavamoat/lavadome-core": "npm:0.0.10" "@lavamoat/lavadome-react": "npm:0.0.11" ->>>>>>> origin/develop "@lavamoat/lavapack": "npm:^6.1.0" "@lavamoat/snow": "npm:^2.0.1" "@material-ui/core": "npm:^4.11.0" @@ -32723,11 +32706,7 @@ __metadata: languageName: node linkType: hard -<<<<<<< HEAD -"through2@npm:^2.0.0, through2@npm:^2.0.1, through2@npm:^2.0.3, through2@npm:^2.0.5, through2@npm:~2.0.0, through2@npm:~2.0.3": -======= "through2@npm:^2.0.0, through2@npm:^2.0.1, through2@npm:^2.0.3, through2@npm:~2.0.0, through2@npm:~2.0.3": ->>>>>>> origin/develop version: 2.0.5 resolution: "through2@npm:2.0.5" dependencies: From e4a0f401703940b4f0d271f4a82c3a2006b487ac Mon Sep 17 00:00:00 2001 From: Chloe Gao Date: Tue, 20 Feb 2024 15:28:48 +0100 Subject: [PATCH 26/49] resolve merge conflit --- lavamoat/browserify/beta/policy.json | 6 ---- lavamoat/browserify/desktop/policy.json | 6 ---- lavamoat/browserify/flask/policy.json | 6 ---- lavamoat/browserify/main/policy.json | 6 ---- lavamoat/browserify/mmi/policy.json | 6 ---- lavamoat/build-system/policy.json | 33 -------------------- yarn.lock | 41 ++++++++++--------------- 7 files changed, 16 insertions(+), 88 deletions(-) diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index c156a7b706b2..36c8cc479675 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -4163,17 +4163,11 @@ "string.prototype.matchall>call-bind": { "packages": { "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>call-bind>set-function-length": true, "string.prototype.matchall>get-intrinsic": true } }, - "string.prototype.matchall>call-bind>es-define-property": { - "packages": { - "string.prototype.matchall>get-intrinsic": true - } - }, "string.prototype.matchall>call-bind>set-function-length": { "packages": { "string.prototype.matchall>call-bind>es-errors": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index 0388d2497a8c..d792a0114e82 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -4529,17 +4529,11 @@ "string.prototype.matchall>call-bind": { "packages": { "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>call-bind>set-function-length": true, "string.prototype.matchall>get-intrinsic": true } }, - "string.prototype.matchall>call-bind>es-define-property": { - "packages": { - "string.prototype.matchall>get-intrinsic": true - } - }, "string.prototype.matchall>call-bind>set-function-length": { "packages": { "string.prototype.matchall>call-bind>es-errors": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 88c78d04b032..9250120ac60f 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -4565,17 +4565,11 @@ "string.prototype.matchall>call-bind": { "packages": { "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>call-bind>set-function-length": true, "string.prototype.matchall>get-intrinsic": true } }, - "string.prototype.matchall>call-bind>es-define-property": { - "packages": { - "string.prototype.matchall>get-intrinsic": true - } - }, "string.prototype.matchall>call-bind>set-function-length": { "packages": { "string.prototype.matchall>call-bind>es-errors": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index a84465a1832d..6987a8dea4ff 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -4488,17 +4488,11 @@ "string.prototype.matchall>call-bind": { "packages": { "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>call-bind>set-function-length": true, "string.prototype.matchall>get-intrinsic": true } }, - "string.prototype.matchall>call-bind>es-define-property": { - "packages": { - "string.prototype.matchall>get-intrinsic": true - } - }, "string.prototype.matchall>call-bind>set-function-length": { "packages": { "string.prototype.matchall>call-bind>es-errors": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index c2f3259316ee..f8dce4372499 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -4560,17 +4560,11 @@ "string.prototype.matchall>call-bind": { "packages": { "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>call-bind>set-function-length": true, "string.prototype.matchall>get-intrinsic": true } }, - "string.prototype.matchall>call-bind>es-define-property": { - "packages": { - "string.prototype.matchall>get-intrinsic": true - } - }, "string.prototype.matchall>call-bind>set-function-length": { "packages": { "string.prototype.matchall>call-bind>es-errors": true, diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 0087d1f7f9c0..959d228d357f 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -3406,33 +3406,6 @@ "setTimeout": true } }, -<<<<<<< HEAD - "globalthis": { - "packages": { - "globalthis>define-properties": true - } - }, - "globalthis>define-properties": { - "packages": { - "globalthis>define-properties>define-data-property": true, - "globalthis>define-properties>has-property-descriptors": true, - "globalthis>define-properties>object-keys": true - } - }, - "globalthis>define-properties>define-data-property": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true, - "string.prototype.matchall>call-bind>es-errors": true, - "string.prototype.matchall>es-abstract>gopd": true - } - }, - "globalthis>define-properties>has-property-descriptors": { - "packages": { - "string.prototype.matchall>call-bind>es-define-property": true - } - }, -======= ->>>>>>> origin/develop "globby": { "builtin": { "fs.Stats": true, @@ -7541,17 +7514,11 @@ "string.prototype.matchall>call-bind": { "packages": { "browserify>has>function-bind": true, - "string.prototype.matchall>call-bind>es-define-property": true, "string.prototype.matchall>call-bind>es-errors": true, "string.prototype.matchall>call-bind>set-function-length": true, "string.prototype.matchall>get-intrinsic": true } }, - "string.prototype.matchall>call-bind>es-define-property": { - "packages": { - "string.prototype.matchall>get-intrinsic": true - } - }, "string.prototype.matchall>call-bind>set-function-length": { "packages": { "string.prototype.matchall>call-bind>es-errors": true, diff --git a/yarn.lock b/yarn.lock index 61cae79f646e..cb6d990fbe01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12756,15 +12756,14 @@ __metadata: linkType: hard "call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.5": - version: 1.0.7 - resolution: "call-bind@npm:1.0.7" + version: 1.0.6 + resolution: "call-bind@npm:1.0.6" dependencies: - es-define-property: "npm:^1.0.0" es-errors: "npm:^1.3.0" function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.1" - checksum: cd6fe658e007af80985da5185bff7b55e12ef4c2b6f41829a26ed1eef254b1f1c12e3dfd5b2b068c6ba8b86aba62390842d81752e67dcbaec4f6f76e7113b6b7 + get-intrinsic: "npm:^1.2.3" + set-function-length: "npm:^1.2.0" + checksum: d99d92dc414d13a03b8b6f2307fc2f0d16a135b523a14d804a2ba7aaa8aae8223cb40d058703c1e66eed11acaff2dc1bcd6358395fa0eb151d84a42c21dedb19 languageName: node linkType: hard @@ -14692,13 +14691,14 @@ __metadata: linkType: hard "define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.2": - version: 1.1.4 - resolution: "define-data-property@npm:1.1.4" + version: 1.1.2 + resolution: "define-data-property@npm:1.1.2" dependencies: - es-define-property: "npm:^1.0.0" es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.2" gopd: "npm:^1.0.1" - checksum: abdcb2505d80a53524ba871273e5da75e77e52af9e15b3aa65d8aad82b8a3a424dad7aee2cc0b71470ac7acf501e08defac362e8b6a73cdb4309f028061df4ae + has-property-descriptors: "npm:^1.0.1" + checksum: 19336750149644b2eb53d281ba685c3561abf98d2b0d2a173ee065bb388b977350df2a08c2597b3401bf0e89f313fc69d7582f0373931cc74df0777fb5202cd0 languageName: node linkType: hard @@ -15963,15 +15963,6 @@ __metadata: languageName: node linkType: hard -"es-define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "es-define-property@npm:1.0.0" - dependencies: - get-intrinsic: "npm:^1.2.4" - checksum: f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 - languageName: node - linkType: hard - "es-errors@npm:^1.3.0": version: 1.3.0 resolution: "es-errors@npm:1.3.0" @@ -18696,7 +18687,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3": version: 1.2.4 resolution: "get-intrinsic@npm:1.2.4" dependencies: @@ -19633,11 +19624,11 @@ __metadata: linkType: hard "has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.1": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" + version: 1.0.1 + resolution: "has-property-descriptors@npm:1.0.1" dependencies: - es-define-property: "npm:^1.0.0" - checksum: 2d8c9ab8cebb572e3362f7d06139a4592105983d4317e68f7adba320fe6ddfc8874581e0971e899e633fd5f72e262830edce36d5a0bc863dad17ad20572484b2 + get-intrinsic: "npm:^1.2.2" + checksum: 21a47bb080a24e79594aef1ce71e1a18a1c5ab4120308e218088f67ebb7f6f408847541e2d96e5bd00e90eef5c5a49e4ebbdc8fc2d5b365a2c379aef071642f0 languageName: node linkType: hard @@ -30893,7 +30884,7 @@ __metadata: languageName: node linkType: hard -"set-function-length@npm:^1.2.1": +"set-function-length@npm:^1.2.0": version: 1.2.1 resolution: "set-function-length@npm:1.2.1" dependencies: From 61b5a1917d364fb765d61c0b0b20a1917b43e300 Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Tue, 20 Feb 2024 07:58:26 -0700 Subject: [PATCH 27/49] Fix: settings search for Security Alerts; Feat: Add Blockaid and OpenSea "Security & privacy" settings search; Feat: Add Nicknames and Snaps "Experimental" settings search (#22967) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - Fixes issue where previously, searching "Security Alerts" would redirect to the Experimental Tab. Now it redirects to the "Security & privacy" tab - Adds Blockaid and OpenSea "Security & privacy" settings search - Adds "Experimental" setting searches. Prolongs e2e experimental test by adding experimental settings searches. There were references placed in experimental-tab.component.js, but no references created in constants/settings.js. This PR enables searching for the following settings: - Allow Nicknames - Snaps ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/22870 ## **Manual testing steps** 1. Go to Settings 2. Use search bar to search relevant settings ## **Screenshots/Recordings** ### **Before** ### **After** #### Fix Screenshot 2024-02-14 at 10 40 13 PM #### New searchable Blockaid and OpenSea Screenshot 2024-02-14 at 10 12 37 PM #### New searchable Nicknames ![Screenshot 2024-02-16 at 10 05 46 AM](https://github.com/MetaMask/metamask-extension/assets/20778143/af38f130-5184-429f-827a-e39caf1d1b18) #### New searchable Snaps ![Screenshot 2024-02-16 at 10 45 19 AM](https://github.com/MetaMask/metamask-extension/assets/20778143/6cfbd632-6e7e-403d-b7d8-d1fb24edca1c) ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- test/e2e/tests/settings-search.spec.js | 4 +- ui/helpers/constants/settings.js | 50 +++++++++++++++---- ui/helpers/utils/settings-search.test.js | 6 +-- .../__snapshots__/security-tab.test.js.snap | 1 + .../security-tab/security-tab.component.js | 7 ++- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/test/e2e/tests/settings-search.spec.js b/test/e2e/tests/settings-search.spec.js index bffe62f7d18f..73572a6da761 100644 --- a/test/e2e/tests/settings-search.spec.js +++ b/test/e2e/tests/settings-search.spec.js @@ -14,7 +14,7 @@ describe('Settings Search', function () { security: 'Reveal Secret', alerts: 'Browsing a website', networks: 'Ethereum Mainnet', - experimental: 'Security alerts', + experimental: 'Nicknames', about: 'Terms of Use', }; @@ -98,7 +98,7 @@ describe('Settings Search', function () { }, ); }); - it('should find element inside the Security tab', async function () { + it('should find element inside the "Security & privacy" tab', async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), diff --git a/ui/helpers/constants/settings.js b/ui/helpers/constants/settings.js index 73e63331ea0f..f4dce45a1ed5 100644 --- a/ui/helpers/constants/settings.js +++ b/ui/helpers/constants/settings.js @@ -263,11 +263,30 @@ export const SETTINGS_CONSTANTS = [ route: `${SECURITY_ROUTE}#proposed-nicknames`, icon: 'fa fa-lock', }, - /** - * settingsRefs 15-17 will be handled in a future PR - * - * @see {@link https://github.com/MetaMask/metamask-extension/pull/22967} - */ + // securityAndPrivacy settingsRefs[15] + { + tabMessage: (t) => t('securityAndPrivacy'), + sectionMessage: (t) => t('securityAlerts'), + descriptionMessage: (t) => t('securityAlertsDescription'), + route: `${SECURITY_ROUTE}#security-alerts`, + icon: 'fa fa-lock', + }, + // securityAndPrivacy settingsRefs[16] + { + tabMessage: (t) => t('securityAndPrivacy'), + sectionMessage: (t) => t('blockaid'), + descriptionMessage: (t) => t('blockaidMessage'), + route: `${SECURITY_ROUTE}#security-alerts-blockaid`, + icon: 'fa fa-lock', + }, + // securityAndPrivacy settingsRefs[17] + { + tabMessage: (t) => t('securityAndPrivacy'), + sectionMessage: (t) => t('openSeaLabel'), + descriptionMessage: (t) => t('openSeaMessage'), + route: `${SECURITY_ROUTE}#security-alerts-opensea`, + icon: 'fa fa-lock', + }, { tabMessage: (t) => t('alerts'), sectionMessage: (t) => t('alertSettingsUnconnectedAccount'), @@ -405,11 +424,24 @@ export const SETTINGS_CONSTANTS = [ route: `${ADVANCED_ROUTE}#restore-userdata`, icon: 'fas fa-upload', }, + // experimental settingsRefs[0] { tabMessage: (t) => t('experimental'), - sectionMessage: (t) => t('securityAlerts'), - descriptionMessage: (t) => t('securityAlertsDescription'), - route: `${EXPERIMENTAL_ROUTE}#security-alerts`, - icon: 'fa fa-flask', + sectionMessage: (t) => t('petnamesEnabledToggle'), + descriptionMessage: (t) => t('petnamesEnabledToggleDescription'), + route: `${EXPERIMENTAL_ROUTE}#nicknames`, + icon: 'fas fa-flask', + }, + + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + // since this route is only included with keyring-snaps feature flag, this needs to be the last settingsRef for the experimental tab + // experimental settingsRefs[1] + { + tabMessage: (t) => t('experimental'), + sectionMessage: (t) => t('snaps'), + descriptionMessage: (t) => t('addSnapAccountToggle'), + route: `${EXPERIMENTAL_ROUTE}#snaps`, + icon: 'fas fa-flask', }, + ///: END:ONLY_INCLUDE_IF ]; diff --git a/ui/helpers/utils/settings-search.test.js b/ui/helpers/utils/settings-search.test.js index d9ccc7aa4e5e..db70cd424873 100644 --- a/ui/helpers/utils/settings-search.test.js +++ b/ui/helpers/utils/settings-search.test.js @@ -159,10 +159,10 @@ describe('Settings Search Utils', () => { expect(getNumberOfSettingRoutesInTab(t, t('contacts'))).toStrictEqual(1); }); - it('returns "Security & Privacy" section count', () => { + it('returns "Security & privacy" section count', () => { expect( getNumberOfSettingRoutesInTab(t, t('securityAndPrivacy')), - ).toStrictEqual(15); + ).toStrictEqual(18); }); it('returns "Alerts" section count', () => { @@ -175,7 +175,7 @@ describe('Settings Search Utils', () => { it('returns "Experimental" section count', () => { expect(getNumberOfSettingRoutesInTab(t, t('experimental'))).toStrictEqual( - 1, + 2, ); }); diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index 9af844718645..a6c2d73e6633 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -34,6 +34,7 @@ exports[`Security Tab should match snapshot 1`] = `
+ diff --git a/ui/pages/settings/security-tab/security-tab.component.js b/ui/pages/settings/security-tab/security-tab.component.js index d6ea786b8a77..150702b80e25 100644 --- a/ui/pages/settings/security-tab/security-tab.component.js +++ b/ui/pages/settings/security-tab/security-tab.component.js @@ -201,7 +201,12 @@ export default class SecurityTab extends PureComponent { ref={this.settingsRefs[15]} className="settings-page__security-tab-sub-header" > - + + {t('securityAlerts')}
From 030786af59d5e8b5c10bfdc377b7dc718d415a8d Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Tue, 20 Feb 2024 08:29:12 -0700 Subject: [PATCH 28/49] fix: Blockaid disable disclosure scrollToBottom (#22932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Replaces default Disclosure scrollToBottom behavior to fix issue where opening Blockaid Banner Alert "See Details" would scroll to the bottom Adds tests for Disclosure component ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/21187 ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ![sdlg5Rty3W](https://github.com/MetaMask/metamask-extension/assets/20778143/6d74bb25-09f7-406f-a75d-73664f4d6e2d) ### **After** ![ZHn0VM0UwG](https://github.com/MetaMask/metamask-extension/assets/20778143/a12a31fe-90e1-4f5b-92ec-a83ad957b994) ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ...transaction-list-item-details.component.js | 6 +- .../__snapshots__/disclosure.test.js.snap | 41 ++++++++++ ui/components/ui/disclosure/disclosure.js | 24 ++++-- .../ui/disclosure/disclosure.test.js | 80 +++++++++++++++++++ ...ecurity-provider-banner-alert.test.js.snap | 2 + .../blockaid-banner-alert.test.js.snap | 6 ++ 6 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 ui/components/ui/disclosure/__snapshots__/disclosure.test.js.snap create mode 100644 ui/components/ui/disclosure/disclosure.test.js diff --git a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js index c5037ff5c93d..e2fe8769b969 100644 --- a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -380,7 +380,11 @@ export default class TransactionListItemDetails extends PureComponent { } {transactionGroup.initialTransaction.type !== TransactionType.incoming && ( - + +
+
+ + + Test Title + +
+ Test +
+
+
+
+`; + +exports[`Disclosure matches snapshot without title prop 1`] = ` +
+
+ Test +
+
+`; diff --git a/ui/components/ui/disclosure/disclosure.js b/ui/components/ui/disclosure/disclosure.js index 297646c0956a..30d4bbb76192 100644 --- a/ui/components/ui/disclosure/disclosure.js +++ b/ui/components/ui/disclosure/disclosure.js @@ -47,24 +47,32 @@ const renderSummaryByType = (variant, title, size) => { } }; -const Disclosure = ({ children, title, size, variant }) => { +const Disclosure = ({ + children, + isScrollToBottomOnOpen, + title, + size, + variant, +}) => { const disclosureFooterEl = useRef(null); const [open, setOpen] = useState(false); const scrollToBottom = () => { - disclosureFooterEl && - disclosureFooterEl.current && - disclosureFooterEl.current.scrollIntoView({ behavior: 'smooth' }); + disclosureFooterEl?.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { - if (open) { + if (isScrollToBottomOnOpen && open) { scrollToBottom(); } - }, [open]); + }, [isScrollToBottomOnOpen, open]); return ( -
setOpen((state) => !state)}> +
setOpen((state) => !state)} + > {title ? (
{renderSummaryByType(variant, title)} @@ -83,12 +91,14 @@ const Disclosure = ({ children, title, size, variant }) => { Disclosure.propTypes = { children: PropTypes.node.isRequired, + isScrollToBottomOnOpen: PropTypes.bool, size: PropTypes.string, title: PropTypes.string, variant: PropTypes.string, }; Disclosure.defaultProps = { + isScrollToBottomOnOpen: false, size: 'normal', title: null, variant: DisclosureVariant.Default, diff --git a/ui/components/ui/disclosure/disclosure.test.js b/ui/components/ui/disclosure/disclosure.test.js new file mode 100644 index 000000000000..b659e1aae8d1 --- /dev/null +++ b/ui/components/ui/disclosure/disclosure.test.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import Disclosure from './disclosure'; + +describe('Disclosure', () => { + it('matches snapshot without title prop', () => { + const { container } = render(Test); + expect(container).toMatchSnapshot(); + }); + + it('matches snapshot with title prop', () => { + const { container } = render( + + Test + , + ); + expect(container).toMatchSnapshot(); + }); + + it('renders content', () => { + const { container, getByText, rerender } = render( + Test, + ); + expect(getByText('Test Title')).toBeInTheDocument(); + expect(container.querySelector('.disclosure__content').textContent).toBe( + 'Test', + ); + + expect( + container.querySelector('.disclosure__content.normal'), + ).toBeInTheDocument(); + + rerender( + + Test + , + ); + + expect( + container.querySelector('.disclosure__content.small'), + ).toBeInTheDocument(); + }); + + describe('when clicking on disclosure', () => { + it('does not scroll down on open by default or when isScrollToBottomOnOpen is false', () => { + const mockScrollIntoView = jest.fn(); + const originalScrollIntoView = + window.HTMLElement.prototype.scrollIntoView; + window.HTMLElement.prototype.scrollIntoView = mockScrollIntoView; + + const { getByTestId } = render( + Test, + ); + const element = getByTestId('disclosure'); + fireEvent.click(element); + expect(mockScrollIntoView).not.toHaveBeenCalled(); + window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView; + }); + + it('scrolls down on open when isScrollToBottomOnOpen is true', () => { + const mockScrollIntoView = jest.fn(); + const originalScrollIntoView = + window.HTMLElement.prototype.scrollIntoView; + window.HTMLElement.prototype.scrollIntoView = mockScrollIntoView; + + const { getByTestId } = render( + + Test + , + ); + const element = getByTestId('disclosure'); + + fireEvent.click(element); + expect(mockScrollIntoView).toHaveBeenCalledWith({ + behavior: 'smooth', + }); + window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView; + }); + }); +}); diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap b/ui/pages/confirmations/components/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap index 5981db85d4c0..7a17475bdde3 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap +++ b/ui/pages/confirmations/components/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap @@ -25,6 +25,7 @@ exports[`Security Provider Banner Alert should match snapshot 1`] = `

Date: Tue, 20 Feb 2024 09:28:16 -0700 Subject: [PATCH 29/49] style:fix: Remove background behind setting icons (#22982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** removing the barely visible background behind setting icons ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/22981 ## **Manual testing steps** 1. Go to settings 2. Search a setting 3. Observe barely visible background behind icons in both light and dark mode ## **Screenshots/Recordings** ### **Before** Screenshot 2024-02-14 at 10 20 41 PM Screenshot 2024-02-14 at 10 48 25 PM ### **After** (ignore "Security" instead of "Security & Privacy" copy Screenshot 2024-02-14 at 10 49 04 PM Screenshot 2024-02-14 at 10 48 31 PM ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ui/pages/settings/index.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/pages/settings/index.scss b/ui/pages/settings/index.scss index c69b05861fbc..ec06ed6c994b 100644 --- a/ui/pages/settings/index.scss +++ b/ui/pages/settings/index.scss @@ -95,7 +95,6 @@ gap: 8px; &__icon { - background: var(--color-background-alternative); height: 15px; width: 15px; margin-right: 16px; From 3e3acf59bad480ecf69c85520e5ec75315e33cf2 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:30:49 +0100 Subject: [PATCH 30/49] test: add scenario sign typed v4 with hardware wallet (#23043) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** In this PR we add the scenario for the flow: sign typed v4 with hardware wallet. This task belongs to the effort of documenting manual QA flows[ in this Epic](https://github.com/MetaMask/metamask-extension/issues/21962). ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/22029 ## **Manual testing steps** 1. Check steps are coherent with the flow in the video Note: You can see the table rendered [here](https://github.com/MetaMask/metamask-extension/blob/test-scenario-sign4-hw/test/scenarios/13.%20sign/sign%20typed%20data%20v4%20with%20hardware%20wallet.csv) for an easier review ## **Screenshots/Recordings** Performing the steps defined in the scenario https://github.com/MetaMask/metamask-extension/assets/54408225/766be1d0-77dd-4d5c-8f4d-d2b2f3d384fb ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [x] I've included manual testing steps - [x] I've included screenshots/recordings if applicable - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ...ign typed data v4 with hardware wallet.csv | 21 +++++++++++++++++++ .../2. keyring/connect hardware wallet.csv | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv diff --git a/test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv b/test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv new file mode 100644 index 000000000000..e7a851260ecf --- /dev/null +++ b/test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv @@ -0,0 +1,21 @@ +Steps,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.", +3,"Click on account menu icon. Click ""Add account or hardware wallet"".",,,"The ""Add account"" modal is shown.", +4,"On ""Add account"" modal, click ""Add hardware wallet"" button.",,,"""Connect a hardware wallet"" screen is shown. User can choose between different options to connect a hardware: Ledger, Trezor, Lattice, or QR-based. ""Continue"" button is disabled.", +5,Choose an option to connect hardware wallet.,We need to have a hardware wallet set up to test this functionality.,"e.g. choose ""Ledger""","""Continue"" button is enabled.", +6,"Plug the hardware wallet directly into computer, then unlock it.",,Password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","If you use Ledger, you need to open the Ethereum app on Ledger. If you use Trezor, make sure you use the correct passphrase." +7,"Choose one or multiples accounts that user wants to connect. Then click ""Unlock"".",,,, +8,Click account menu icon to open accounts list.,,,"In accounts list, all selected hardware wallet accounts are shown, and they are all flagged with harware wallet name to be distinguished from other accounts.", +9,Select one hardware wallet account.,,,"The Ether balance for the selected hardware wallet account is shown on the overview. +The selected account address is shown on the overview.", +10,Open the test dapp in another tab.,,https://metamask.github.io/test-dapp/,, +11,Click Connect.,,,"The MetaMask popup is opened with the Connect with MetaMask screen displayed. +Your imported hardware wallet account is selected.", +12,Click Next and Connect with the hardware wallet account.,,,"The MetaMask popup is closed. +You are connected to the test dapp.", +13,"Click ""Sign Typed Data V4"".",,,"The info modal instructions for signing with hardware wallet is displayed. +The signature message is in JSON formatting.", +14,Accept the Signature in the hardware wallet device.,,,The signature hash is displayed on the test dapp., +15,Verify signed hash.,,,The signed address is correctly verified.,The address shown in the test dapp is the same as the hardware wallet account. diff --git a/test/scenarios/2. keyring/connect hardware wallet.csv b/test/scenarios/2. keyring/connect hardware wallet.csv index 636a6df04ab8..fa11b9f58ced 100644 --- a/test/scenarios/2. keyring/connect hardware wallet.csv +++ b/test/scenarios/2. keyring/connect hardware wallet.csv @@ -4,7 +4,7 @@ Step,Test steps,Preconditions,Test data,Expected result,Notes 3,"Keep Ethereum Mainnet as the selected network. Click on account menu icon. Click ""Add account or hardware wallet"".",,,"The ""Add account"" modal is shown.", 4,"On ""Add account"" modal, click ""Add hardware wallet"" button.",,,"""Connect a hardware wallet"" screen is shown. User can choose between different options to connect a hardware: Ledger, Trezor, Lattice, or QR-based. ""Continue"" button is disabled.", 5,Choose an option to connect hardware wallet.,We need to have a hardware wallet setted up to test this functionality.,"e.g. choose ""Ledger""","""Continue"" button is enabled.", -6,"Plug the hardware wallet directly into computer, then unlock it.",,password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","If you use Ledger, you need to open the Ethereum app on Ledger. If you use Trazor, make sure you use the correct passphrase." +6,"Plug the hardware wallet directly into computer, then unlock it.",,password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","If you use Ledger, you need to open the Ethereum app on Ledger. If you use Trezor, make sure you use the correct passphrase." 7,"Choose one or multiples accounts that user wants to connect. Then click ""Unlock"".",,,, 8,Click account menu icon to open accounts list.,,,"In accounts list, all selected hardware wallet accounts are shown, and they are all flagged with harware wallet name to be distinguished from other accounts.", 9,"Select one hardware wallet account. ",,,"The Ether balance for the selected hardware wallet account is shown on the overview. The selected account address is shown on the overview. The selected network is Ethereum Mainnet. ", \ No newline at end of file From c9a8cbe487bbd5fcef062450c2a86edc527c7546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Tue, 20 Feb 2024 17:40:17 +0100 Subject: [PATCH 31/49] [MMI] Rely in custodian environment name (envName) for method findCustodianByDisplayName (#23073) --- .../find-by-custodian-name.test.ts | 24 +++++++++---------- .../institutional/find-by-custodian-name.ts | 14 ++++------- .../confirm-add-custodian-token.js | 7 ++++-- ui/pages/institutional/custody/custody.js | 6 ++--- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/ui/helpers/utils/institutional/find-by-custodian-name.test.ts b/ui/helpers/utils/institutional/find-by-custodian-name.test.ts index 86cdb977e4ba..b0e880b46ae4 100644 --- a/ui/helpers/utils/institutional/find-by-custodian-name.test.ts +++ b/ui/helpers/utils/institutional/find-by-custodian-name.test.ts @@ -1,6 +1,6 @@ -import { findCustodianByDisplayName } from './find-by-custodian-name'; +import { findCustodianByEnvName } from './find-by-custodian-name'; -describe('findCustodianByDisplayName', () => { +describe('findCustodianByEnvName', () => { const custodians = [ { type: 'JSONRPC', @@ -33,21 +33,21 @@ describe('findCustodianByDisplayName', () => { version: 2, }, ]; - it('should return the custodian if the display name is found in custodianKey', () => { - const displayName = 'Qredo'; - const custodian = findCustodianByDisplayName(displayName, custodians); - expect(custodian?.name).toBe('Qredo'); + it('should return the custodian if the env name is found in custodianKey', () => { + const envName = 'Qredo'; + const custodian = findCustodianByEnvName(envName, custodians); + expect(custodian?.envName).toBe('qredo'); }); - it('should return the custodian if the display name is found in custodianDisplayName', () => { - const displayName = 'Saturn Custody'; - const custodian = findCustodianByDisplayName(displayName, custodians); - expect(custodian?.name).toContain('Saturn'); + it('should return the custodian if the env name is found in custodianDisplayName', () => { + const envName = 'Saturn Custody'; + const custodian = findCustodianByEnvName(envName, custodians); + expect(custodian?.envName).toContain('saturn'); }); it('should return null if no matching custodian is found', () => { - const displayName = 'Non-existent Custodian'; - const custodian = findCustodianByDisplayName(displayName, custodians); + const envName = 'Non-existent Custodian'; + const custodian = findCustodianByEnvName(envName, custodians); expect(custodian).toBeNull(); }); }); diff --git a/ui/helpers/utils/institutional/find-by-custodian-name.ts b/ui/helpers/utils/institutional/find-by-custodian-name.ts index 5109e0281c8d..fdbc4ab4bd1f 100644 --- a/ui/helpers/utils/institutional/find-by-custodian-name.ts +++ b/ui/helpers/utils/institutional/find-by-custodian-name.ts @@ -13,24 +13,20 @@ type Custodian = { version: number; }; -// TODO (Bernardo) - There can be multiple custodian with the same name, envName should be used instead -export function findCustodianByDisplayName( - displayName: string, +export function findCustodianByEnvName( + envName: string, custodians: Custodian[], ): Custodian | null { - const formatedDisplayName = displayName.toLowerCase(); + const formatedEnvName = envName.toLowerCase(); if (!custodians) { return null; } for (const custodian of custodians) { - const custodianName = custodian.name.toLowerCase(); + const custodianName = custodian.envName.toLowerCase(); - if ( - custodianName.length !== 0 && - formatedDisplayName.includes(custodianName) - ) { + if (custodianName.length !== 0 && formatedEnvName.includes(custodianName)) { return custodian; } } diff --git a/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.js b/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.js index 078e3f8038db..90b058bc77d1 100644 --- a/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.js +++ b/ui/pages/institutional/confirm-add-custodian-token/confirm-add-custodian-token.js @@ -31,7 +31,7 @@ import { MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { getInstitutionalConnectRequests } from '../../../ducks/institutional/institutional'; -import { findCustodianByDisplayName } from '../../../helpers/utils/institutional/find-by-custodian-name'; +import { findCustodianByEnvName } from '../../../helpers/utils/institutional/find-by-custodian-name'; const ConfirmAddCustodianToken = () => { const t = useContext(I18nContext); @@ -125,7 +125,10 @@ const ConfirmAddCustodianToken = () => { const custodianLabel = connectRequest.labels?.find((label) => label.key === 'service')?.value || t('custodian'); - const custodian = findCustodianByDisplayName(custodianLabel, custodians); + const custodian = findCustodianByEnvName( + connectRequest.environment, + custodians, + ); return ( diff --git a/ui/pages/institutional/custody/custody.js b/ui/pages/institutional/custody/custody.js index 90de53a860b4..405aa59cc364 100644 --- a/ui/pages/institutional/custody/custody.js +++ b/ui/pages/institutional/custody/custody.js @@ -53,7 +53,7 @@ import { } from '../../../../shared/constants/metametrics'; import PulseLoader from '../../../components/ui/pulse-loader/pulse-loader'; import ConfirmConnectCustodianModal from '../confirm-connect-custodian-modal'; -import { findCustodianByDisplayName } from '../../../helpers/utils/institutional/find-by-custodian-name'; +import { findCustodianByEnvName } from '../../../helpers/utils/institutional/find-by-custodian-name'; import { setSelectedAddress } from '../../../store/actions'; const GK8_DISPLAY_NAME = 'gk8'; @@ -125,8 +125,8 @@ const CustodyPage = () => { async function handleButtonClick(custodian) { try { - const custodianByDisplayName = findCustodianByDisplayName( - custodian.displayName, + const custodianByDisplayName = findCustodianByEnvName( + custodian.envName, custodians, ); From c4f8e764c45d847da446f9865c787eb63bc5db66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Oliv=C3=A9?= Date: Tue, 20 Feb 2024 17:57:34 +0100 Subject: [PATCH 32/49] =?UTF-8?q?[MMI]=C2=A0Adding=20capture=20exceptions?= =?UTF-8?q?=20to=20MMI=20controllers=20(#22994)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scripts/controllers/mmi-controller.js | 3 ++ app/scripts/metamask-controller.js | 1 + builds.yml | 1 + package.json | 4 +- yarn.lock | 52 +++++++++++------------ 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index 87577bf564aa..4fa767657a9e 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -113,6 +113,7 @@ export default class MMIController extends EventEmitter { custodyController: this.custodyController, trackTransactionEvent: this.trackTransactionEventFromCustodianEvent.bind(this), + captureException, }); } @@ -201,12 +202,14 @@ export default class MMIController extends EventEmitter { await this.mmiConfigurationController.storeConfiguration(); } catch (error) { log.error('Error while unlocking extension.', error); + captureException(error); } try { await this.transactionUpdateController.subscribeToEvents(); } catch (error) { log.error('Error while unlocking extension.', error); + captureException(error); } const mmiConfigData = diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 89065229840f..76e812c5fa1e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1396,6 +1396,7 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) this.custodyController = new CustodyController({ initState: initState.CustodyController, + captureException, }); this.institutionalFeaturesController = new InstitutionalFeaturesController({ initState: initState.InstitutionalFeaturesController, diff --git a/builds.yml b/builds.yml index 71847a1bbee6..27969b9161fb 100644 --- a/builds.yml +++ b/builds.yml @@ -111,6 +111,7 @@ buildTypes: - 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 + - SENTRY_DSN: SENTRY_MMI_DSN # For some reason, MMI uses this type of versioning # Leaving it on for backwards compatibility isPrerelease: true diff --git a/package.json b/package.json index fd34d0a6d336..ab99b795cb53 100644 --- a/package.json +++ b/package.json @@ -239,9 +239,9 @@ "@lavamoat/lavadome-react": "0.0.11", "@lavamoat/snow": "^2.0.1", "@material-ui/core": "^4.11.0", - "@metamask-institutional/custody-controller": "^0.2.20", + "@metamask-institutional/custody-controller": "^0.2.22", "@metamask-institutional/custody-keyring": "^1.0.10", - "@metamask-institutional/extension": "^0.3.13", + "@metamask-institutional/extension": "^0.3.16", "@metamask-institutional/institutional-features": "^1.2.11", "@metamask-institutional/portfolio-dashboard": "^1.4.0", "@metamask-institutional/rpc-allowlist": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index cb6d990fbe01..62046e1f0ed8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3615,20 +3615,20 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/custody-controller@npm:^0.2.19, @metamask-institutional/custody-controller@npm:^0.2.20": - version: 0.2.20 - resolution: "@metamask-institutional/custody-controller@npm:0.2.20" +"@metamask-institutional/custody-controller@npm:^0.2.22": + version: 0.2.22 + resolution: "@metamask-institutional/custody-controller@npm:0.2.22" dependencies: "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-keyring": "npm:^1.0.9" + "@metamask-institutional/custody-keyring": "npm:^1.0.10" "@metamask-institutional/sdk": "npm:^0.1.24" "@metamask-institutional/types": "npm:^1.0.4" "@metamask/obs-store": "npm:^8.0.0" - checksum: 8b6a25b4d870ea9bd3f235d45c82226cecbbef3bbbe6b635114e28642c847dbd1258982d0b0501b0e4109de0d16024c4c192dcd7ff018d281f7e075f1de23b1d + checksum: e5ee0ce9dfca87ddaff977a48e4914263de9f930797ad9adc0568381ec2a4373f8e307bafc1162c0215eccfa93b8b42ce684bac4b294758c53164958e44a7d69 languageName: node linkType: hard -"@metamask-institutional/custody-keyring@npm:^1.0.10, @metamask-institutional/custody-keyring@npm:^1.0.8, @metamask-institutional/custody-keyring@npm:^1.0.9": +"@metamask-institutional/custody-keyring@npm:^1.0.10, @metamask-institutional/custody-keyring@npm:^1.0.8": version: 1.0.10 resolution: "@metamask-institutional/custody-keyring@npm:1.0.10" dependencies: @@ -3644,20 +3644,20 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/extension@npm:^0.3.13": - version: 0.3.13 - resolution: "@metamask-institutional/extension@npm:0.3.13" +"@metamask-institutional/extension@npm:^0.3.16": + version: 0.3.16 + resolution: "@metamask-institutional/extension@npm:0.3.16" dependencies: "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-controller": "npm:^0.2.19" - "@metamask-institutional/custody-keyring": "npm:^1.0.8" + "@metamask-institutional/custody-controller": "npm:^0.2.22" + "@metamask-institutional/custody-keyring": "npm:^1.0.10" "@metamask-institutional/portfolio-dashboard": "npm:^1.4.0" "@metamask-institutional/sdk": "npm:^0.1.24" - "@metamask-institutional/transaction-update": "npm:^0.1.34" + "@metamask-institutional/transaction-update": "npm:^0.1.36" "@metamask-institutional/types": "npm:^1.0.4" jest-create-mock-instance: "npm:^2.0.0" jest-fetch-mock: "npm:3.0.3" - checksum: a32e93bb8e2d31850b900f0077b502e5f6afc786dd9da621f1a8ed040ad800972838167a2db1991510d1e029ea9c9365e5fe3c2a0c421738a3f0a2b698886661 + checksum: 099b941dba62a503829e8806a6892ff49672492379f3d6230f6c562cf3e70da7ffc077598ff2acf8fe286a6fa8df3d8eaa63e9d40eca943fed3c609150b43af2 languageName: node linkType: hard @@ -3706,17 +3706,17 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/transaction-update@npm:^0.1.32, @metamask-institutional/transaction-update@npm:^0.1.34": - version: 0.1.34 - resolution: "@metamask-institutional/transaction-update@npm:0.1.34" +"@metamask-institutional/transaction-update@npm:^0.1.32, @metamask-institutional/transaction-update@npm:^0.1.36": + version: 0.1.36 + resolution: "@metamask-institutional/transaction-update@npm:0.1.36" dependencies: - "@metamask-institutional/custody-keyring": "npm:^1.0.8" + "@metamask-institutional/custody-keyring": "npm:^1.0.10" "@metamask-institutional/sdk": "npm:^0.1.24" "@metamask-institutional/types": "npm:^1.0.4" - "@metamask-institutional/websocket-client": "npm:^0.1.36" + "@metamask-institutional/websocket-client": "npm:^0.1.38" "@metamask/obs-store": "npm:^8.0.0" ethereumjs-util: "npm:^7.1.5" - checksum: 9dbf4374d8c7698f85fc6513641f7fc73d802afb0ef1e89e1dc3b3bc74d716705d8370bac7c2f20dfdeefd2a739da22c8903946f1972d5b51e6db0d23211fe04 + checksum: e35fad2e51a541679a36d3f8db11a16114a2516fed7c740ff77560a35cacf67880c8d90e57ba13fd9f179136a9fbdd108b3b58f0dc38c64a85797e8738749ad4 languageName: node linkType: hard @@ -3727,15 +3727,15 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/websocket-client@npm:^0.1.36": - version: 0.1.36 - resolution: "@metamask-institutional/websocket-client@npm:0.1.36" +"@metamask-institutional/websocket-client@npm:^0.1.38": + version: 0.1.38 + resolution: "@metamask-institutional/websocket-client@npm:0.1.38" dependencies: - "@metamask-institutional/custody-keyring": "npm:^1.0.8" + "@metamask-institutional/custody-keyring": "npm:^1.0.10" "@metamask-institutional/sdk": "npm:^0.1.24" "@metamask-institutional/types": "npm:^1.0.4" mock-socket: "npm:^9.2.1" - checksum: a8b56e681a2bcc858067a8b5fd7c4e586459f3cee9e4165f2cf1e08c278139b015fc9195b961ddb0bf8fc5169afa362c596624f7caaea1a8d59c491ac3973c28 + checksum: 6b1cb6798f58f83b128e55348fbb738d3cfb54c76d557731a8fd1bd3c1dde5604d958699d8c030ede225017fdc8977112d2397d161a5f9da6d9fced8452494e8 languageName: node linkType: hard @@ -24335,9 +24335,9 @@ __metadata: "@lavamoat/lavapack": "npm:^6.1.0" "@lavamoat/snow": "npm:^2.0.1" "@material-ui/core": "npm:^4.11.0" - "@metamask-institutional/custody-controller": "npm:^0.2.20" + "@metamask-institutional/custody-controller": "npm:^0.2.22" "@metamask-institutional/custody-keyring": "npm:^1.0.10" - "@metamask-institutional/extension": "npm:^0.3.13" + "@metamask-institutional/extension": "npm:^0.3.16" "@metamask-institutional/institutional-features": "npm:^1.2.11" "@metamask-institutional/portfolio-dashboard": "npm:^1.4.0" "@metamask-institutional/rpc-allowlist": "npm:^1.0.0" From d3f0a3240cee571c69bad88930c4131f2609af02 Mon Sep 17 00:00:00 2001 From: Olusegun Akintayo Date: Tue, 20 Feb 2024 18:09:56 +0100 Subject: [PATCH 33/49] Confirm button should be red when request is flagged as malicious on extension (#23004) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** On extension, when a transaction or signature request is flagged as malicious, the Confirm or Sign button should be red. ## **Related issues** Fixes: [#1994](https://github.com/MetaMask/MetaMask-planning/issues/1994) ## **Manual testing steps** 1. Run MM 2. Open testdapp 3. Perform blockaid transactions and see that the confirm/sign button shows the primary (blue) color 4. Checkout this branch 5. Repeat 2-3, but this time notice the confirm/sign button shows the danger (red) color ## **Screenshots/Recordings** ### **Before** https://github.com/MetaMask/metamask-extension/assets/44811/32b6cf67-76e1-4e44-9944-af20486822cf ### **After** Uploading after_button_red.mov… ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [x] I've included manual testing steps - [x] I've included screenshots/recordings if applicable - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [x] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../page-container-footer.component.test.js | 21 +++++++++++++++++++ ...onfirm-page-container-content.component.js | 10 +++++++++ .../confirm-page-container.component.js | 7 ++++++- .../signature-request-original.component.js | 6 ++++++ .../signature-request-siwe.js | 9 +++++++- .../signature-request-footer.component.js | 9 +++++++- .../signature-request/signature-request.js | 11 +++++++++- .../token-allowance/token-allowance.js | 6 ++++++ 8 files changed, 75 insertions(+), 4 deletions(-) diff --git a/ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js b/ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js index 7c9bc6758cf7..69eaa48860fb 100644 --- a/ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js +++ b/ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js @@ -51,5 +51,26 @@ describe('Page Footer', () => { expect(props.onSubmit).toHaveBeenCalled(); }); + + it('has danger class defined if type is danger', () => { + const { queryByTestId } = renderWithProvider( + , + ); + + const submitButton = queryByTestId('page-container-footer-next'); + + expect(submitButton.className).toContain('danger'); + }); + + it('has danger-primary class defined if type is danger-primary', () => { + const { queryByTestId } = renderWithProvider( + , + ); + + const submitButton = queryByTestId('page-container-footer-next'); + + console.log(submitButton.className); + expect(submitButton.className).toContain('danger-primary'); + }); }); }); diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index cebfa3460ad0..87d826069f9e 100644 --- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -17,6 +17,7 @@ import { } from '../../../../../helpers/constants/error-keys'; import { Severity } from '../../../../../helpers/constants/design-system'; +import { BlockaidResultType } from '../../../../../../shared/constants/security-provider'; import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'; export default class ConfirmPageContainerContent extends Component { @@ -64,6 +65,7 @@ export default class ConfirmPageContainerContent extends Component { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) noteComponent: PropTypes.node, ///: END:ONLY_INCLUDE_IF + txData: PropTypes.object, }; renderContent() { @@ -181,6 +183,7 @@ export default class ConfirmPageContainerContent extends Component { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) openBuyCryptoInPdapp, ///: END:ONLY_INCLUDE_IF + txData, } = this.props; const { t } = this.context; @@ -191,6 +194,12 @@ export default class ConfirmPageContainerContent extends Component { const showIsSigningOrSubmittingError = errorKey === IS_SIGNING_OR_SUBMITTING; + const submitButtonType = + txData?.securityAlertResponse?.result_type === + BlockaidResultType.Malicious + ? 'danger-primary' + : 'primary'; + return (
{unapprovedTxCount > 1 ? ( {rejectNText} diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js index 0b77d2708202..841a12d0e232 100644 --- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js +++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js @@ -61,6 +61,7 @@ import { } from '../../../../../shared/constants/metametrics'; ///: END:ONLY_INCLUDE_IF +import { BlockaidResultType } from '../../../../../shared/constants/security-provider'; import { ConfirmPageContainerHeader, ConfirmPageContainerContent, @@ -196,6 +197,9 @@ const ConfirmPageContainer = (props) => { collectionBalance, ]); + const isMaliciousRequest = + txData.securityAlertResponse?.result_type === BlockaidResultType.Malicious; + return (
@@ -348,7 +352,8 @@ const ConfirmPageContainer = (props) => { onSubmit={topLevelHandleSubmit} submitText={t('confirm')} submitButtonType={ - isSetApproveForAll && isApprovalOrRejection + (isSetApproveForAll && isApprovalOrRejection) || + isMaliciousRequest ? 'danger-primary' : 'primary' } diff --git a/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js b/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js index e0c63ebe51d4..4ea8bc7704ab 100644 --- a/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js +++ b/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js @@ -58,6 +58,7 @@ import SnapLegacyAuthorshipHeader from '../../../../components/app/snaps/snap-le ///: BEGIN:ONLY_INCLUDE_IF(build-flask) import InsightWarnings from '../../../../components/app/snaps/insight-warnings'; ///: END:ONLY_INCLUDE_IF +import { BlockaidResultType } from '../../../../../shared/constants/security-provider'; import SignatureRequestOriginalWarning from './signature-request-original-warning'; export default class SignatureRequestOriginal extends Component { @@ -323,6 +324,10 @@ export default class SignatureRequestOriginal extends Component { } = this.props; const { t } = this.context; + const submitButtonType = + txData.securityAlertResponse?.result_type === BlockaidResultType.Malicious + ? 'danger-primary' + : 'primary'; return ( ); }; diff --git a/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js b/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js index 01dd866790de..38f5abf5d4fc 100644 --- a/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js +++ b/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js @@ -47,6 +47,7 @@ import SignatureRequestHeader from '../signature-request-header'; ///: BEGIN:ONLY_INCLUDE_IF(build-flask) import InsightWarnings from '../../../../components/app/snaps/insight-warnings'; ///: END:ONLY_INCLUDE_IF +import { BlockaidResultType } from '../../../../../shared/constants/security-provider'; import Header from './signature-request-siwe-header'; import Message from './signature-request-siwe-message'; @@ -137,6 +138,12 @@ export default function SignatureRequestSIWE({ const rejectNText = t('rejectRequestsN', [messagesCount]); + const submitButtonType = + txData.securityAlertResponse?.result_type === + BlockaidResultType.Malicious || !isSIWEDomainValid + ? 'danger-primary' + : 'primary'; + return ( <>
@@ -211,7 +218,7 @@ export default function SignatureRequestSIWE({ }} cancelText={t('cancel')} submitText={t('signin')} - submitButtonType={isSIWEDomainValid ? 'primary' : 'danger-primary'} + submitButtonType={submitButtonType} /> {messagesCount > 1 ? (
`; -exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResponse.result_type is 'Failed 1`] = ` +exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResponse.result_type is 'Error 1`] = `
) : null; - const isFailedResultType = resultType === BlockaidResultType.Failed; + const isFailedResultType = resultType === BlockaidResultType.Errored; const severity = resultType === BlockaidResultType.Malicious diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js index b34d9d9b0e0a..4abd67e10685 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js +++ b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js @@ -70,13 +70,13 @@ describe('Blockaid Banner Alert', () => { expect(container.querySelector('.mm-banner-alert')).toBeNull(); }); - it(`should render '${Severity.Warning}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Failed}`, () => { + it(`should render '${Severity.Warning}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Errored}`, () => { const { container } = renderWithProvider( , @@ -140,13 +140,13 @@ describe('Blockaid Banner Alert', () => { expect(getByText('This is a deceptive request')).toBeInTheDocument(); }); - it(`should render title, "This is a suspicious request", when the reason is "${BlockaidReason.failed}"`, () => { + it(`should render title, "This is a suspicious request", when the reason is "${BlockaidReason.errored}"`, () => { const { getByText } = renderWithProvider( , @@ -248,14 +248,14 @@ describe('Blockaid Banner Alert', () => { }); describe('when constructing the Blockaid Report URL', () => { - describe(`when result_type='${BlockaidResultType.Failed}'`, () => { + describe(`when result_type='${BlockaidResultType.Errored}'`, () => { it('should pass the classification as "error" and the resultType as "Error"', () => { const { getByRole } = renderWithProvider( , @@ -276,7 +276,7 @@ describe('Blockaid Banner Alert', () => { 'If you approve this request, a third party known for scams might take all your assets.', [BlockaidReason.blurFarming]: 'If you approve this request, someone can steal your assets listed on Blur.', - [BlockaidReason.failed]: + [BlockaidReason.errored]: 'Because of an error, this request was not verified by the security provider. Proceed with caution.', [BlockaidReason.maliciousDomain]: "You're interacting with a malicious domain. If you approve this request, you might lose your assets.", From be604fced1ff3c39fcc578a366ec08b2d6bd8c85 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Wed, 21 Feb 2024 08:52:47 +0100 Subject: [PATCH 35/49] test: add scenario sign typed v3 with hardware wallet (#23063) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** In this PR we add the scenario for the flow: sign typed v3 with hardware wallet. Note: sign typed data v3 only work with QR-based wallets, not with Trezor nor Ledger. This task belongs to the effort of documenting manual QA flows[ in this Epic](https://github.com/MetaMask/metamask-extension/issues/21962). ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/22028 ## **Manual testing steps** Check steps are coherent with the flow in the video (at the end of the video the signature is closed due to a current identified [issue](https://github.com/MetaMask/metamask-extension/issues/23074)) Note: You can see the table rendered [here](https://github.com/MetaMask/metamask-extension/blob/1c387482f46acd9c1fd16b26f44acea9817152d6/test/scenarios/13.%20sign/sign%20typed%20data%20v3%20with%20hardware%20wallet.csv) for an easier review ## **Screenshots/Recordings** https://github.com/MetaMask/metamask-extension/assets/54408225/322eda2a-5e5d-411b-bd17-29dd46cb137b ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [x] I've included manual testing steps - [x] I've included screenshots/recordings if applicable - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- ...ign typed data v3 with hardware wallet.csv | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv diff --git a/test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv b/test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv new file mode 100644 index 000000000000..cebeaffd5c02 --- /dev/null +++ b/test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv @@ -0,0 +1,25 @@ +Steps,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.", +3,"Click on account menu icon. Click ""Add account or hardware wallet"".",,,"The ""Add account"" modal is shown.", +4,"On ""Add account"" modal, click ""Add hardware wallet"" button.",,,"""Connect a hardware wallet"" screen is shown. User can choose between different options to connect a hardware: Ledger, Trezor, Lattice, or QR-based. ""Continue"" button is disabled.", +5,Choose an option to connect hardware wallet.,We need to have a QR-based hardware wallet set up to test this functionality.,"e.g. choose ""QR-based""","""Continue"" button is enabled.", +6,"Unlock the QR-based wallet and sync it with MetaMask.",,Password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","Note: using Ledger or Trezor won't work for this flow, since they don't support Sign Typed Data V3." +7,"Choose one or multiples accounts that user wants to connect. Then click ""Unlock"".",,,, +8,Click account menu icon to open accounts list.,,,"In accounts list, all selected hardware wallet accounts are shown, and they are all flagged with harware wallet name to be distinguished from other accounts.", +9,Select one hardware wallet account.,,,"The Ether balance for the selected hardware wallet account is shown on the overview. +The selected account address is shown on the overview.", +10,Open the test dapp in another tab.,,https://metamask.github.io/test-dapp/,, +11,Click Connect.,,,"The MetaMask popup is opened with the Connect with MetaMask screen displayed. +Your imported hardware wallet account is selected.", +12,Click Next and Connect with the hardware wallet account.,,,"The MetaMask popup is closed. +You are connected to the test dapp.", +13,"Click ""Sign Typed Data V3"".",,,"The MetaMask popup is open. +The signature message is displayed in JSON formatting.", +14,"Click ""Sign"".",,,The QR code modal with the signature request is displayed., +15,"Scan the QR code with the QR-based wallet.",,,The signature QR code is displayed in the QR-based wallet., +15,"Click ""Get Signature"".",,,The computer camera is opened., +16,Scan the Signature from the QR-based wallet.,,,"The signature hash is displayed in the test-dapp result. +The MetaMask popup is closed.", +17,Verify signed hash.,,,The signed address is correctly verified.,The address shown in the test dapp is the same as the hardware wallet account. From cb14ec537747fe75c975259aea73a8c45eea90ed Mon Sep 17 00:00:00 2001 From: Nidhi Kumari Date: Wed, 21 Feb 2024 15:52:04 +0530 Subject: [PATCH 36/49] UX: Show same balance on eth overview and account list item (#23059) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR is to ensure total balance shows up for both eth overview and account list item ## **Related issues** Fixes: #23058 ## **Manual testing steps** 1. Go to Eth Overview section, on a non test network. Look at the eth overview balance 2. Account list item should show same balance as eth overview 3. Total balance logic works only for NON-TEST Networks ## **Screenshots/Recordings** ### **Before** https://github.com/MetaMask/metamask-extension/assets/39872794/aaab32c5-cbb8-43e2-b78d-6d3ee71535ee ### **After** https://github.com/MetaMask/metamask-extension/assets/39872794/54d61617-33e1-4b99-a699-cfd372d21b72 ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [x] I've included manual testing steps - [x] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: David Walsh --- test/e2e/tests/account-token-list.spec.js | 35 ++++++++----------- .../app/wallet-overview/eth-overview.js | 17 ++++++--- .../account-list-item.test.js.snap | 4 +-- .../account-list-item/account-list-item.js | 16 ++++++--- .../__snapshots__/your-accounts.test.tsx.snap | 4 +-- ui/selectors/selectors.js | 5 +++ 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/test/e2e/tests/account-token-list.spec.js b/test/e2e/tests/account-token-list.spec.js index 582a5cd59e90..1285027ed3e3 100644 --- a/test/e2e/tests/account-token-list.spec.js +++ b/test/e2e/tests/account-token-list.spec.js @@ -2,27 +2,24 @@ const { strict: assert } = require('assert'); const { withFixtures, defaultGanacheOptions, - unlockWallet, + logInWithBalanceValidation, } = require('../helpers'); + const FixtureBuilder = require('../fixture-builder'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); describe('Settings', function () { - const smartContract = SMART_CONTRACTS.ERC1155; it('Should match the value of token list item and account list item for eth conversion', async function () { await withFixtures( { - dapp: true, fixtures: new FixtureBuilder().build(), - defaultGanacheOptions, - smartContract, + ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement('[data-testid="home__asset-tab"]'); - const tokenValue = '0 ETH'; + const tokenValue = '25 ETH'; const tokenListAmount = await driver.findElement( '[data-testid="multichain-token-list-item-value"]', ); @@ -33,7 +30,7 @@ describe('Settings', function () { '.multichain-account-list-item .multichain-account-list-item__avatar-currency .currency-display-component__text', ); - assert.equal(await accountTokenValue.getText(), '0', 'ETH'); + assert.equal(await accountTokenValue.getText(), '25', 'ETH'); }, ); }); @@ -41,14 +38,12 @@ describe('Settings', function () { it('Should match the value of token list item and account list item for fiat conversion', async function () { await withFixtures( { - dapp: true, fixtures: new FixtureBuilder().build(), - defaultGanacheOptions, - smartContract, + ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement( '[data-testid="account-options-menu-button"]', @@ -65,18 +60,16 @@ describe('Settings', function () { ); await driver.clickElement('[data-testid="home__asset-tab"]'); - const tokenValue = '0 ETH'; const tokenListAmount = await driver.findElement( - '[data-testid="multichain-token-list-item-value"]', + '.eth-overview__primary-container', ); - assert.equal(await tokenListAmount.getText(), tokenValue); - + assert.equal(await tokenListAmount.getText(), '$42,500.00\nUSD'); await driver.clickElement('[data-testid="account-menu-icon"]'); const accountTokenValue = await driver.waitForSelector( - '.multichain-account-list-item .multichain-account-list-item__avatar-currency .currency-display-component__text', + '.multichain-account-list-item .multichain-account-list-item__asset', ); - assert.equal(await accountTokenValue.getText(), '0', 'ETH'); + assert.equal(await accountTokenValue.getText(), '$42,500.00USD'); }, ); }); diff --git a/ui/components/app/wallet-overview/eth-overview.js b/ui/components/app/wallet-overview/eth-overview.js index 585386ece792..dece08d61f14 100644 --- a/ui/components/app/wallet-overview/eth-overview.js +++ b/ui/components/app/wallet-overview/eth-overview.js @@ -34,6 +34,7 @@ import { getShouldHideZeroBalanceTokens, getCurrentNetwork, getSelectedAccountCachedBalance, + getShowFiatInTestnets, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) getSwapsDefaultToken, getCurrentKeyring, @@ -106,10 +107,15 @@ const EthOverview = ({ className, showAddress }) => { selectedAddress, shouldHideZeroBalanceTokens, ); + const showFiatInTestnets = useSelector(getShowFiatInTestnets); + const showFiat = + TEST_NETWORKS.includes(currentNetwork?.nickname) && !showFiatInTestnets; - const balanceToUse = TEST_NETWORKS.includes(currentNetwork?.nickname) - ? balance - : totalWeiBalance; + let balanceToUse = totalWeiBalance; + + if (showFiat) { + balanceToUse = balance; + } const isSwapsChain = useSelector(getIsSwapsChain); @@ -196,7 +202,10 @@ const EthOverview = ({ className, showAddress }) => { ? PRIMARY : SECONDARY } - showFiat={!TEST_NETWORKS.includes(currentNetwork?.nickname)} + showFiat={ + !showFiat || + !TEST_NETWORKS.includes(currentNetwork?.nickname) + } ethNumberOfDecimals={4} hideTitle /> diff --git a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap index cc754b6c907f..0acee96e7f7d 100644 --- a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap +++ b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap @@ -71,12 +71,12 @@ exports[`AccountListItem renders AccountListItem component and shows account nam >
- $3.31 + $880.18 { setAccountListItemMenuElement(ref); }; - + const showFiatInTestnets = useSelector(getShowFiatInTestnets); + const showFiat = + TEST_NETWORKS.includes(currentNetwork?.nickname) && !showFiatInTestnets; const { totalWeiBalance, orderedTokenList } = useAccountTotalFiatBalance( identity.address, ); - const balanceToTranslate = TEST_NETWORKS.includes(currentNetwork?.nickname) - ? totalWeiBalance - : identity.balance; + let balanceToTranslate = totalWeiBalance; + if (showFiat) { + balanceToTranslate = identity.balance; + } // If this is the selected item in the Account menu, // scroll the item into view @@ -209,7 +213,9 @@ export const AccountListItem = ({ ethNumberOfDecimals={MAXIMUM_CURRENCY_DECIMALS} value={balanceToTranslate} type={PRIMARY} - showFiat={!TEST_NETWORKS.includes(currentNetwork?.nickname)} + showFiat={ + !showFiat || !TEST_NETWORKS.includes(currentNetwork?.nickname) + } /> diff --git a/ui/components/multichain/pages/send/components/__snapshots__/your-accounts.test.tsx.snap b/ui/components/multichain/pages/send/components/__snapshots__/your-accounts.test.tsx.snap index 04ea196e505b..d213c98faec4 100644 --- a/ui/components/multichain/pages/send/components/__snapshots__/your-accounts.test.tsx.snap +++ b/ui/components/multichain/pages/send/components/__snapshots__/your-accounts.test.tsx.snap @@ -74,12 +74,12 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` >
- $537,761.36 + $880.18 { return state.metamask.useSafeChainsListValidation; }; +export function getShowFiatInTestnets(state) { + const { showFiatInTestnets } = getPreferences(state); + return showFiatInTestnets; +} + /** * To get the useCurrencyRateCheck flag which to check if the user prefers currency conversion * From a13de550347f0202a10903dd09efde820da0a0ad Mon Sep 17 00:00:00 2001 From: Harika Jetpoluru <153644847+hjetpoluru@users.noreply.github.com> Date: Wed, 21 Feb 2024 08:05:22 -0500 Subject: [PATCH 37/49] Fix test failure caused by previous revert - 22946 (#23076) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** PR #22762, an e2e test for the update network, was functioning correctly but started failing after being merged into the develop branch due to changes introduced in PR #22832. This PR addresses and fixes the test failure caused by the previous revert in PR #22946. Note:- The changes from the develop branch had to be reverted due to time constraints. ## **Related issues** #22949 ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've clearly explained what problem this PR is solving and how it is solved. - [x] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [x] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- test/e2e/tests/network/update-network.spec.ts | 113 ++++++++++++++++++ .../add-network-modal.test.js.snap | 4 + .../networks-form/networks-form.js | 4 + 3 files changed, 121 insertions(+) create mode 100644 test/e2e/tests/network/update-network.spec.ts diff --git a/test/e2e/tests/network/update-network.spec.ts b/test/e2e/tests/network/update-network.spec.ts new file mode 100644 index 000000000000..db5d2a43402b --- /dev/null +++ b/test/e2e/tests/network/update-network.spec.ts @@ -0,0 +1,113 @@ +import { strict as assert } from 'assert'; +import { Suite } from 'mocha'; +import FixtureBuilder from '../../fixture-builder'; +import { + defaultGanacheOptions, + unlockWallet, + withFixtures, +} from '../../helpers'; +import { Driver } from '../../webdriver/driver'; + +const selectors = { + accountOptionsMenuButton: '[data-testid="account-options-menu-button"]', + informationSymbol: '[data-testid="info-tooltip"]', + settingsOption: { text: 'Settings', tag: 'div' }, + networkOption: { text: 'Networks', tag: 'div' }, + generalOption: { text: 'General', tag: 'div' }, + ethereumNetwork: { text: 'Ethereum Mainnet', tag: 'div' }, + deleteButton: { text: 'Delete', tag: 'button' }, + cancelButton: { text: 'Cancel', tag: 'button' }, + saveButton: { text: 'Save', tag: 'button' }, + updatedNetworkDropDown: { tag: 'span', text: 'Update Network' }, + errorMessageInvalidUrl: { + tag: 'h6', + text: 'URLs require the appropriate HTTP/HTTPS prefix.', + }, + networkNameInputField: '[data-testid="network-form-network-name"]', + rpcUrlInputField: '[data-testid="network-form-rpc-url"]', + chainIdInputField: '[data-testid="network-form-chain-id"]', + errorContainer: '.settings-tab__error', +}; + +const inputData = { + networkName: 'Update Network', + rpcUrl: 'test', + chainId: '0x539', +}; + +async function navigateToEditNetwork(driver: Driver) { + await driver.clickElement(selectors.accountOptionsMenuButton); + await driver.clickElement(selectors.settingsOption); + await driver.clickElement(selectors.networkOption); +} +describe('Update Network:', function (this: Suite) { + it('update network details and validate the ui elements', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + + async ({ driver }: { driver: Driver }) => { + await unlockWallet(driver); + await navigateToEditNetwork(driver); + await driver.fill( + selectors.networkNameInputField, + inputData.networkName, + ); + await driver.fill(selectors.chainIdInputField, inputData.chainId); + await driver.clickElement(selectors.saveButton); + + // Validate the network name is updated + const updatedNetworkNamePresent = await driver.isElementPresent( + selectors.updatedNetworkDropDown, + ); + assert.equal( + updatedNetworkNamePresent, + true, + 'Network name is not updated', + ); + + await navigateToEditNetwork(driver); + await driver.fill(selectors.rpcUrlInputField, inputData.rpcUrl); + + // Validate the error message that appears for the invalid url format + const errorMessage = await driver.isElementPresent( + selectors.errorMessageInvalidUrl, + ); + assert.equal( + errorMessage, + true, + 'Error message for the invalid url did not appear', + ); + + // Validate the Save button is disabled for the invalid url format + const saveButton = await driver.findElement(selectors.saveButton); + const saveButtonEnabled = await saveButton.isEnabled(); + assert.equal(saveButtonEnabled, false, 'Save button is enabled'); + + // Validate the information symbol appears for chain id + const informationSymbolAppears = await driver.isElementPresent( + selectors.informationSymbol, + ); + assert.equal( + informationSymbolAppears, + true, + 'Information symbol did not appear for chain id', + ); + + await driver.clickElement(selectors.ethereumNetwork); + + // Validate the Save,Cancel Delete button is not present for the default network + await driver.assertElementNotPresent(selectors.deleteButton); + await driver.assertElementNotPresent(selectors.cancelButton); + await driver.assertElementNotPresent(selectors.saveButton); + + // Validate the error does not appear for updating the network name and chain id + await driver.clickElement(selectors.generalOption); + await driver.assertElementNotPresent(selectors.errorContainer); + }, + ); + }); +}); \ No newline at end of file diff --git a/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap b/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap index cf1180fdc481..9b0bd89d8cfb 100644 --- a/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap +++ b/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap @@ -60,6 +60,7 @@ exports[`Add Network Modal should render 1`] = `
@@ -90,6 +91,7 @@ exports[`Add Network Modal should render 1`] = `
@@ -144,6 +146,7 @@ exports[`Add Network Modal should render 1`] = `
@@ -201,6 +204,7 @@ exports[`Add Network Modal should render 1`] = `
diff --git a/ui/pages/settings/networks-tab/networks-form/networks-form.js b/ui/pages/settings/networks-tab/networks-form/networks-form.js index f7b892b894a0..e71f6bf3a0a7 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.js @@ -709,6 +709,7 @@ const NetworksForm = ({ titleText={t('networkName')} value={networkName} disabled={viewOnly} + dataTestId="network-form-network-name" />
Date: Wed, 21 Feb 2024 21:42:11 +0530 Subject: [PATCH 38/49] UX MULTICHAIN: added connect account in account list menu item (#22941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR is to add the connect account button in account list menu item ## **Related issues** Fixes:[https://github.com/MetaMask/MetaMask-planning/issues/1967]( https://github.com/MetaMask/MetaMask-planning/issues/1967) ## **Manual testing steps** 1. Go to account list menu 2. Click on connect account, if account is not connected it should connect the account ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-02-14 at 3 35 44 PM](https://github.com/MetaMask/metamask-extension/assets/39872794/d7bea860-bf44-4182-affe-14233446e322) ### **After** ![Screenshot 2024-02-14 at 8 04 17 PM](https://github.com/MetaMask/metamask-extension/assets/39872794/4a892b89-f608-4112-b897-d05de1ed0e94) ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: David Walsh --- app/_locales/en/messages.json | 3 ++ app/images/icons/user-circle-link.svg | 5 ++++ test/data/mock-state.json | 7 +++++ .../component-library/icon/icon.types.ts | 1 + .../account-list-item-menu.js | 29 ++++++++++++++++++- .../account-list-item-menu.test.js | 19 +++++++++++- .../account-list-item/account-list-item.js | 15 ++++++++++ .../account-list-item.stories.js | 1 + .../account-list-item.test.js | 3 ++ .../account-list-menu/account-list-menu.js | 1 + .../multichain/pages/send/send.test.js | 3 ++ .../templates/remove-snap-account.test.js | 3 ++ 12 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 app/images/icons/user-circle-link.svg diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 9591a91da069..3f250997e913 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -800,6 +800,9 @@ "connect": { "message": "Connect" }, + "connectAccount": { + "message": "Connect account" + }, "connectAccountOrCreate": { "message": "Connect account or create new" }, diff --git a/app/images/icons/user-circle-link.svg b/app/images/icons/user-circle-link.svg new file mode 100644 index 000000000000..acf97fc5f66b --- /dev/null +++ b/app/images/icons/user-circle-link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 06ab82c99d9f..2b47b4412c65 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -2,6 +2,13 @@ "DNS": { "resolution": null }, + "activeTab": { + "id": 113, + "title": "E2E Test Dapp", + "origin": "https://metamask.github.io", + "protocol": "https:", + "url": "https://metamask.github.io/test-dapp/" + }, "appState": { "networkDropdownOpen": false, "importNftsModal": { diff --git a/ui/components/component-library/icon/icon.types.ts b/ui/components/component-library/icon/icon.types.ts index 7ad8e2d5fd62..e5ba342768c4 100644 --- a/ui/components/component-library/icon/icon.types.ts +++ b/ui/components/component-library/icon/icon.types.ts @@ -156,6 +156,7 @@ export enum IconName { TrendDown = 'trend-down', TrendUp = 'trend-up', UserCircleAdd = 'user-circle-add', + UserCircleLink = 'user-circle-link', UserCircleRemove = 'user-circle-remove', UserCircle = 'user-circle', User = 'user', diff --git a/ui/components/multichain/account-list-item-menu/account-list-item-menu.js b/ui/components/multichain/account-list-item-menu/account-list-item-menu.js index 20f40a79526c..bd375e69db81 100644 --- a/ui/components/multichain/account-list-item-menu/account-list-item-menu.js +++ b/ui/components/multichain/account-list-item-menu/account-list-item-menu.js @@ -12,6 +12,7 @@ import { getAccountTypeForKeyring, getPinnedAccountsList, getHiddenAccountsList, + getOriginOfCurrentTab, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) getMetaMaskAccountsOrdered, ///: END:ONLY_INCLUDE_IF @@ -22,6 +23,7 @@ import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils import { findKeyringForAddress } from '../../../ducks/metamask/metamask'; import { MenuItem } from '../../ui/menu'; import { + Box, IconName, ModalFocus, Popover, @@ -34,11 +36,12 @@ import { MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { + addPermittedAccount, showModal, updateAccountsList, updateHiddenAccountsList, } from '../../../store/actions'; -import { TextVariant } from '../../../helpers/constants/design-system'; +import { Display, TextVariant } from '../../../helpers/constants/design-system'; import { formatAccountType } from '../../../helpers/utils/metrics'; import { AccountDetailsMenuItem, ViewExplorerMenuItem } from '..'; @@ -53,6 +56,7 @@ export const AccountListItemMenu = ({ isOpen, isPinned, isHidden, + isConnected, }) => { const t = useI18nContext(); const trackEvent = useContext(MetaMetricsContext); @@ -69,6 +73,7 @@ export const AccountListItemMenu = ({ const pinnedAccountList = useSelector(getPinnedAccountsList); const hiddenAccountList = useSelector(getHiddenAccountsList); + const shouldRenderConnectAccount = process.env.MULTICHAIN && !isConnected; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) const isCustodial = keyring?.type ? /Custody/u.test(keyring.type) : false; @@ -159,6 +164,8 @@ export const AccountListItemMenu = ({ dispatch(updateHiddenAccountsList(updatedHiddenAccountList)); }; + const activeTabOrigin = useSelector(getOriginOfCurrentTab); + return (
+ {shouldRenderConnectAccount ? ( + + { + dispatch( + addPermittedAccount(activeTabOrigin, identity.address), + ); + onClose(); + }} + iconName={IconName.UserCircleLink} + > + {t('connectAccount')} + + + ) : null}
+`; diff --git a/ui/components/multichain/badge-status/badge-status.stories.tsx b/ui/components/multichain/badge-status/badge-status.stories.tsx new file mode 100644 index 000000000000..b6cc485ef0e6 --- /dev/null +++ b/ui/components/multichain/badge-status/badge-status.stories.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { + BackgroundColor, + BorderColor, + Color, +} from '../../../helpers/constants/design-system'; +import { BadgeStatus } from './badge-status'; + +export default { + title: 'Components/Multichain/BadgeStatus', + component: BadgeStatus, + argTypes: { + badgeBackgroundColor: { + control: 'text', + }, + badgeBorderColor: { + control: 'text', + }, + text: { + control: 'text', + }, + address: { + control: 'text', + }, + isConnectedAndNotActive: { + control: 'boolean', + }, + }, + args: { + badgeBackgroundColor: BackgroundColor.successDefault, + badgeBorderColor: BackgroundColor.backgroundDefault, + address: '0x1', + text: 'Tooltip', + }, +}; + +const Template = (args) => { + return ; +}; + +export const DefaultStory = Template.bind({}); + +export const NotConnectedStory = Template.bind({}); +NotConnectedStory.args = { + badgeBackgroundColor: Color.borderMuted, + badgeBorderColor: BackgroundColor.backgroundDefault, +}; + +export const ConnectedNotActiveStory = Template.bind({}); +ConnectedNotActiveStory.args = { + badgeBackgroundColor: BackgroundColor.backgroundDefault, + badgeBorderColor: BorderColor.successDefault, + isConnectedAndNotActive: true, +}; diff --git a/ui/components/multichain/badge-status/badge-status.test.js b/ui/components/multichain/badge-status/badge-status.test.js new file mode 100644 index 000000000000..7c1212444c69 --- /dev/null +++ b/ui/components/multichain/badge-status/badge-status.test.js @@ -0,0 +1,39 @@ +import React from 'react'; +import mockState from '../../../../test/data/mock-state.json'; + +import { + BackgroundColor, + BorderColor, +} from '../../../helpers/constants/design-system'; +import configureStore from '../../../store/store'; +import { renderWithProvider } from '../../../../test/jest'; +import { BadgeStatus } from './badge-status'; + +describe('Badge Status', () => { + const render = (props = {}, state = {}) => { + const store = configureStore({ + metamask: { + ...mockState.metamask, + providerConfig: { + chainId: '0x99', + }, + ...state, + }, + }); + const DEFAULT_PROPS = { + badgeBackgroundColor: BackgroundColor.backgroundDefault, + badgeBorderColor: BorderColor.successDefault, + isConnectedAndNotActive: true, + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + text: 'Not Connected', + }; + return renderWithProvider( + , + store, + ); + }; + it('should render correctly', () => { + const { container } = render({}, { useBlockie: true }); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/components/multichain/badge-status/badge-status.tsx b/ui/components/multichain/badge-status/badge-status.tsx new file mode 100644 index 000000000000..bccc3d229669 --- /dev/null +++ b/ui/components/multichain/badge-status/badge-status.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import classNames from 'classnames'; +import { useSelector } from 'react-redux'; +import { + AlignItems, + BackgroundColor, + BorderColor, + BorderRadius, + Display, + JustifyContent, +} from '../../../helpers/constants/design-system'; +import { + AvatarAccount, + AvatarAccountSize, + AvatarAccountVariant, + BadgeWrapper, + Box, + BoxProps, +} from '../../component-library'; +import { getUseBlockie } from '../../../selectors'; +import Tooltip from '../../ui/tooltip'; +import { BadgeStatusProps } from './badge-status.types'; + +export const BadgeStatus: React.FC = ({ + className = '', + badgeBackgroundColor = BackgroundColor.backgroundAlternative, + badgeBorderColor = BorderColor.borderMuted, + address, + isConnectedAndNotActive = false, + text, + ...props +}): JSX.Element => { + const useBlockie = useSelector(getUseBlockie); + + return ( + )} + > + + + } + > + + + + + ); +}; diff --git a/ui/components/multichain/badge-status/badge-status.types.tsx b/ui/components/multichain/badge-status/badge-status.types.tsx new file mode 100644 index 000000000000..5038148a370f --- /dev/null +++ b/ui/components/multichain/badge-status/badge-status.types.tsx @@ -0,0 +1,30 @@ +import { + BackgroundColor, + BorderColor, +} from '../../../helpers/constants/design-system'; +import type { StyleUtilityProps } from '../../component-library/box'; + +export interface BadgeStatusProps extends StyleUtilityProps { + /** * Additional class name for the ImportTokenLink component. */ + className?: string; + /** + * Border color based on the connection status + */ + badgeBorderColor?: BorderColor; + /** + * Background Color of Badge + */ + badgeBackgroundColor?: BackgroundColor; + /** + * Connection status message on Tooltip + */ + text: string; + /** + * To determine connection status + */ + isConnectedAndNotActive: boolean; + /** + * Address for AvatarAccount + */ + address: string; +} diff --git a/ui/components/multichain/badge-status/index.scss b/ui/components/multichain/badge-status/index.scss new file mode 100644 index 000000000000..02e5d35efed3 --- /dev/null +++ b/ui/components/multichain/badge-status/index.scss @@ -0,0 +1,30 @@ +.multichain-badge-status { + padding: 0; + + &__badge { + height: 16px; + width: 16px; + z-index: 1; + } + + .mm-badge-wrapper__badge-container { //Need to override the zIndex, can't do it with badgeProps. + z-index: 1; + } + + &__badge-not-connected { + height: 10px; + width: 10px; + } + + &__badge-not-connected::after { + content: ''; + position: absolute; + top: -3px; + left: -3px; + right: -3px; + bottom: -3px; + background: var(--color-background-default); + z-index: -1; + border-radius: 50%; + } +} diff --git a/ui/components/multichain/badge-status/index.ts b/ui/components/multichain/badge-status/index.ts new file mode 100644 index 000000000000..ad04f877e934 --- /dev/null +++ b/ui/components/multichain/badge-status/index.ts @@ -0,0 +1 @@ +export { BadgeStatus } from './badge-status'; diff --git a/ui/components/multichain/multichain-components.scss b/ui/components/multichain/multichain-components.scss index d98e6fd7a139..64d9a1c5682d 100644 --- a/ui/components/multichain/multichain-components.scss +++ b/ui/components/multichain/multichain-components.scss @@ -21,6 +21,7 @@ @import 'network-list-menu'; @import 'product-tour-popover'; @import 'nft-item'; +@import 'badge-status'; @import 'import-tokens-modal'; @import 'asset-list-conversion-button'; @import 'asset-picker-amount'; From 2ae88571064aaa1e056a1031b05021f44fda0bdf Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Thu, 22 Feb 2024 19:11:27 +0530 Subject: [PATCH 48/49] feat: PPOM version update to fetch files only when transaction is received (#23001) --- app/scripts/lib/setupSentry.js | 1 - lavamoat/browserify/flask/policy.json | 4 +--- lavamoat/browserify/main/policy.json | 4 +--- package.json | 2 +- .../errors-after-init-opt-in-background-state.json | 1 - .../errors-after-init-opt-in-ui-state.json | 1 - yarn.lock | 11 ++++++----- 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 4b3d37c7567d..9a0eb9e9199d 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -171,7 +171,6 @@ export const SENTRY_BACKGROUND_STATE = { seedPhraseBackedUp: true, }, PPOMController: { - chainStatus: true, securityAlertsEnabled: false, storageMetadata: [], versionFileETag: false, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 48d55e61df29..73cf28268c16 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1775,10 +1775,8 @@ "@metamask/ppom-validator": { "globals": { "URL": true, - "clearInterval": true, "console.error": true, - "crypto": true, - "setInterval": true + "crypto": true }, "packages": { "@metamask/controller-utils": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index a41298b186e0..475459af8e30 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1698,10 +1698,8 @@ "@metamask/ppom-validator": { "globals": { "URL": true, - "clearInterval": true, "console.error": true, - "crypto": true, - "setInterval": true + "crypto": true }, "packages": { "@metamask/controller-utils": true, diff --git a/package.json b/package.json index fcb7a05a9e57..6d8f589a6a83 100644 --- a/package.json +++ b/package.json @@ -285,7 +285,7 @@ "@metamask/phishing-controller": "^8.0.0", "@metamask/polling-controller": "^4.0.0", "@metamask/post-message-stream": "^8.0.0", - "@metamask/ppom-validator": "^0.24.0", + "@metamask/ppom-validator": "^0.26.0", "@metamask/providers": "^14.0.2", "@metamask/queued-request-controller": "^0.3.0", "@metamask/rate-limit-controller": "^3.0.0", 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 5ab8c852daf9..d3269fd906e7 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 @@ -130,7 +130,6 @@ }, "PPOMController": { "storageMetadata": {}, - "chainStatus": { "0x539": { "chainId": "0x539", "versionInfo": [] } }, "versionFileETag": "string" }, "PermissionController": { "subjects": "object" }, 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 71cc07f1197f..de685a700d37 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 @@ -222,7 +222,6 @@ "pendingApprovalCount": "number", "approvalFlows": "object", "storageMetadata": {}, - "chainStatus": { "0x539": { "chainId": "0x539", "versionInfo": [] } }, "versionFileETag": "string" }, "send": "object", diff --git a/yarn.lock b/yarn.lock index e3a8ab6277c6..f194eae49234 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4957,19 +4957,20 @@ __metadata: languageName: node linkType: hard -"@metamask/ppom-validator@npm:^0.24.0": - version: 0.24.0 - resolution: "@metamask/ppom-validator@npm:0.24.0" +"@metamask/ppom-validator@npm:^0.26.0": + version: 0.26.0 + resolution: "@metamask/ppom-validator@npm:0.26.0" dependencies: "@metamask/base-controller": "npm:^3.0.0" "@metamask/controller-utils": "npm:^8.0.1" "@metamask/network-controller": "npm:^17.0.0" + "@metamask/utils": "npm:^8.3.0" await-semaphore: "npm:^0.1.3" crypto-js: "npm:^4.2.0" elliptic: "npm:^6.5.4" eslint-plugin-n: "npm:^16.6.2" json-rpc-random-id: "npm:^1.0.1" - checksum: cb488ccaffd119820536450519a0fffbdcb710a082de05a26d7e8a0f782b3024a747c36c5f16b9ca84343a0b234d3d0ebfd3e493bcb858e966129a3f40d218a0 + checksum: 9857444051a62aa3e6424102d9b8b808cb708c98f499c1e6628eba1635d15fba6c38c22d25954ec734979a5fef267c4aedd8cd290b39eece5c4f5f46fda644be languageName: node linkType: hard @@ -24679,7 +24680,7 @@ __metadata: "@metamask/phishing-warning": "npm:^3.0.3" "@metamask/polling-controller": "npm:^4.0.0" "@metamask/post-message-stream": "npm:^8.0.0" - "@metamask/ppom-validator": "npm:^0.24.0" + "@metamask/ppom-validator": "npm:^0.26.0" "@metamask/providers": "npm:^14.0.2" "@metamask/queued-request-controller": "npm:^0.3.0" "@metamask/rate-limit-controller": "npm:^3.0.0" From d87b63188e3d29aad3a0295e8f3c7e741ec17e51 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Thu, 22 Feb 2024 14:43:26 +0100 Subject: [PATCH 49/49] Feat: add optimism arbitrum goerli deprecation warning (#23071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR adds a deprecation warning when user switches to Arbitrum goerli or OP goerli testnetwork ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to home page 2. Click Add network 3. Click Add network Manually 4. Fill in the input fields with Arbitrum goerli or OP goerli testnet RPC and chainId (or use [chainList](https://chainlist.org/?chain=421613&search=arbitrum&testnets=true)) 5. Switch to the entered network 6. You should see a deprecation warning on the home page ## **Screenshots/Recordings** ### **Before** https://github.com/MetaMask/metamask-extension/assets/10994169/e1a5630e-d80e-41ae-8fc6-8b47c9351837 ### **After** https://github.com/MetaMask/metamask-extension/assets/10994169/5b463cc3-a3ef-4f68-a551-5abdb3c652eb ## **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 and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- shared/constants/network.ts | 9 +- test/e2e/tests/deprecated-networks.spec.js | 180 +++++++++++++++++- .../deprecated-networks.js | 2 +- 3 files changed, 188 insertions(+), 3 deletions(-) diff --git a/shared/constants/network.ts b/shared/constants/network.ts index f08aac55d7a0..816d4578b48a 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -145,6 +145,7 @@ export const CHAIN_IDS = { GNOSIS: '0x64', ZKSYNC_ERA: '0x144', TEST_ETH: '0x539', + ARBITRUM_GOERLI: '0x66eed', } as const; const CHAINLIST_CHAIN_IDS_MAP = { @@ -203,7 +204,12 @@ const CHAINLIST_CHAIN_IDS_MAP = { // To add a deprecation warning to a network, add it to the array // `DEPRECATED_NETWORKS` and as a new case to `getDeprecationWarningCopy() in // `ui/components/ui/deprecated-networks/deprecated-networks.js`. -export const DEPRECATED_NETWORKS = [CHAIN_IDS.AURORA, CHAIN_IDS.GOERLI]; +export const DEPRECATED_NETWORKS = [ + CHAIN_IDS.AURORA, + CHAIN_IDS.GOERLI, + CHAIN_IDS.ARBITRUM_GOERLI, + CHAIN_IDS.OPTIMISM_TESTNET, +]; /** * The largest possible chain ID we can handle. @@ -848,6 +854,7 @@ export const BUYABLE_CHAINS_MAP: { | typeof CHAIN_IDS.SEPOLIA | typeof CHAIN_IDS.GNOSIS | typeof CHAIN_IDS.AURORA + | typeof CHAIN_IDS.ARBITRUM_GOERLI >]: BuyableChainSettings; } = { [CHAIN_IDS.MAINNET]: { diff --git a/test/e2e/tests/deprecated-networks.spec.js b/test/e2e/tests/deprecated-networks.spec.js index 36ae0519f796..3acf8507a05c 100644 --- a/test/e2e/tests/deprecated-networks.spec.js +++ b/test/e2e/tests/deprecated-networks.spec.js @@ -1,6 +1,12 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../fixture-builder'); -const { withFixtures, unlockWallet } = require('../helpers'); +const { + withFixtures, + unlockWallet, + openDapp, + WINDOW_TITLES, +} = require('../helpers'); +const { CHAIN_IDS } = require('../../../shared/constants/network'); describe('Deprecated networks', function () { it('When selecting the Goerli test network, the users should see a warning message', async function () { @@ -31,4 +37,176 @@ describe('Deprecated networks', function () { }, ); }); + + it('Should show deprecation warning when switching to Arbitrum goerli testnet', async function () { + const TEST_CHAIN_ID = CHAIN_IDS.ARBITRUM_GOERLI; + 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.fullTitle(), + testSpecificMock: mockRPCURLAndChainId, + }, + async ({ driver }) => { + await unlockWallet(driver); + + await openDapp(driver); + await driver.executeScript(` + var params = [{ + chainId: "${TEST_CHAIN_ID}", + chainName: "Arbitrum Goerli", + nativeCurrency: { + name: "", + symbol: "ETH", + decimals: 18 + }, + rpcUrls: ["https://responsive-rpc.url/"], + blockExplorerUrls: [ "http://localhost:8080/api/customRPC" ] + }] + window.ethereum.request({ + method: 'wallet_addEthereumChain', + params + }) + `); + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + const [extension] = windowHandles; + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Dialog, + windowHandles, + ); + + await driver.clickElement({ + tag: 'button', + text: 'Approve', + }); + + const switchNetworkBtn = await driver.findElement({ + tag: 'button', + text: 'Switch network', + }); + + await switchNetworkBtn.click(); + + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindow(extension); + const deprecationWarningText = + 'Because of updates to the Ethereum system, the Goerli test network will be phased out soon.'; + const isDeprecationWarningDisplayed = await driver.isElementPresent({ + text: deprecationWarningText, + }); + + assert.equal( + isDeprecationWarningDisplayed, + true, + 'Goerli deprecation warning is not displayed', + ); + }, + ); + }); + + it('Should show deprecation warning when switching to Optimism goerli testnet', async function () { + const TEST_CHAIN_ID = CHAIN_IDS.OPTIMISM_TESTNET; + 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.fullTitle(), + testSpecificMock: mockRPCURLAndChainId, + }, + async ({ driver }) => { + await unlockWallet(driver); + + await openDapp(driver); + await driver.executeScript(` + var params = [{ + chainId: "${TEST_CHAIN_ID}", + chainName: "Optimism Goerli", + nativeCurrency: { + name: "", + symbol: "ETH", + decimals: 18 + }, + rpcUrls: ["https://responsive-rpc.url/"], + blockExplorerUrls: [ "http://localhost:8080/api/customRPC" ] + }] + window.ethereum.request({ + method: 'wallet_addEthereumChain', + params + }) + `); + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + const [extension] = windowHandles; + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Dialog, + windowHandles, + ); + + await driver.clickElement({ + tag: 'button', + text: 'Approve', + }); + + const switchNetworkBtn = await driver.findElement({ + tag: 'button', + text: 'Switch network', + }); + + await switchNetworkBtn.click(); + + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindow(extension); + const deprecationWarningText = + 'Because of updates to the Ethereum system, the Goerli test network will be phased out soon.'; + const isDeprecationWarningDisplayed = await driver.isElementPresent({ + text: deprecationWarningText, + }); + + assert.equal( + isDeprecationWarningDisplayed, + true, + 'Goerli deprecation warning is not displayed', + ); + }, + ); + }); }); diff --git a/ui/components/ui/deprecated-networks/deprecated-networks.js b/ui/components/ui/deprecated-networks/deprecated-networks.js index e6f250502cc4..869e316f163e 100644 --- a/ui/components/ui/deprecated-networks/deprecated-networks.js +++ b/ui/components/ui/deprecated-networks/deprecated-networks.js @@ -61,7 +61,7 @@ function getDeprecationWarningCopy(t, currentChainID) { if (currentChainID === CHAIN_IDS.AURORA) { bannerAlertDescription = t('deprecatedAuroraNetworkMsg'); actionBtnLinkURL = 'https://mainnet.aurora.dev/'; - } else if (currentChainID === CHAIN_IDS.GOERLI) { + } else if (DEPRECATED_NETWORKS.includes(currentChainID)) { bannerAlertDescription = t('deprecatedGoerliNtwrkMsg'); actionBtnLinkURL = 'https://github.com/eth-clients/goerli#goerli-goerlitzer-testnet';