From f00dd59a7803d4d83f1e5cb12ba878af6ba957eb Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:32:21 +0200 Subject: [PATCH 001/137] Remove `keyringMemStore` from `DetectTokensController` (#20728) * refactor: remove keyringMemStore from DetectTokensController * fix: use getState to get initial lock state * test: add case for lock event --- app/scripts/controllers/detect-tokens.js | 43 +++++----- app/scripts/controllers/detect-tokens.test.js | 83 ++++++++++++------- app/scripts/metamask-controller.js | 8 +- 3 files changed, 80 insertions(+), 54 deletions(-) diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 46719aebcced..86c8cc222856 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -28,7 +28,6 @@ export default class DetectTokensController { * @param config.interval * @param config.preferences * @param config.network - * @param config.keyringMemStore * @param config.tokenList * @param config.tokensController * @param config.assetsContractController @@ -40,7 +39,6 @@ export default class DetectTokensController { interval = DEFAULT_INTERVAL, preferences, network, - keyringMemStore, tokenList, tokensController, assetsContractController = null, @@ -52,7 +50,6 @@ export default class DetectTokensController { this.preferences = preferences; this.interval = interval; this.network = network; - this.keyringMemStore = keyringMemStore; this.tokenList = tokenList; this.useTokenDetection = this.preferences?.store.getState().useTokenDetection; @@ -91,6 +88,8 @@ export default class DetectTokensController { this.restartTokenDetection({ chainId: this.chainId }); } }); + + this.#registerKeyringHandlers(); } /** @@ -236,26 +235,6 @@ export default class DetectTokensController { }, interval); } - /** - * In setter when isUnlocked is updated to true, detectNewTokens and restart polling - * - * @type {object} - */ - set keyringMemStore(keyringMemStore) { - if (!keyringMemStore) { - return; - } - this._keyringMemStore = keyringMemStore; - this._keyringMemStore.subscribe(({ isUnlocked }) => { - if (this.isUnlocked !== isUnlocked) { - this.isUnlocked = isUnlocked; - if (isUnlocked) { - this.restartTokenDetection(); - } - } - }); - } - /** * @type {object} */ @@ -275,4 +254,22 @@ export default class DetectTokensController { return this.isOpen && this.isUnlocked; } /* eslint-enable accessor-pairs */ + + /** + * Constructor helper to register listeners on the keyring + * locked state changes + */ + #registerKeyringHandlers() { + const { isUnlocked } = this.messenger.call('KeyringController:getState'); + this.isUnlocked = isUnlocked; + + this.messenger.subscribe('KeyringController:unlock', () => { + this.isUnlocked = true; + this.restartTokenDetection(); + }); + + this.messenger.subscribe('KeyringController:lock', () => { + this.isUnlocked = false; + }); + } } diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index 8d1c182c249e..c7306dc83295 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -1,7 +1,6 @@ import { strict as assert } from 'assert'; import sinon from 'sinon'; import nock from 'nock'; -import { ObservableStore } from '@metamask/obs-store'; import BigNumber from 'bignumber.js'; import { ControllerMessenger } from '@metamask/base-controller'; import { @@ -16,25 +15,30 @@ import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import DetectTokensController from './detect-tokens'; import PreferencesController from './preferences'; -function buildMessenger() { - return new ControllerMessenger().getRestricted({ - name: 'DetectTokensController', - allowedEvents: ['NetworkController:stateChange'], - }); -} - describe('DetectTokensController', function () { let sandbox, assetsContractController, - keyringMemStore, network, preferences, provider, tokensController, - tokenListController; + tokenListController, + messenger; const noop = () => undefined; + const getRestrictedMessenger = () => { + return messenger.getRestricted({ + name: 'DetectTokensController', + allowedActions: ['KeyringController:getState'], + allowedEvents: [ + 'NetworkController:stateChange', + 'KeyringController:lock', + 'KeyringController:unlock', + ], + }); + }; + const networkControllerProviderConfig = { getAccounts: noop, }; @@ -198,7 +202,11 @@ describe('DetectTokensController', function () { .reply(200, { error: 'ChainId 3 is not supported' }) .persist(); - keyringMemStore = new ObservableStore({ isUnlocked: false }); + messenger = new ControllerMessenger(); + messenger.registerActionHandler('KeyringController:getState', () => ({ + isUnlocked: true, + })); + const networkControllerMessenger = new ControllerMessenger(); network = new NetworkController({ messenger: networkControllerMessenger, @@ -263,7 +271,11 @@ describe('DetectTokensController', function () { it('should poll on correct interval', async function () { const stub = sinon.stub(global, 'setInterval'); - new DetectTokensController({ messenger: buildMessenger(), interval: 1337 }); // eslint-disable-line no-new + // eslint-disable-next-line no-new + new DetectTokensController({ + messenger: getRestrictedMessenger(), + interval: 1337, + }); assert.strictEqual(stub.getCall(0).args[1], 1337); stub.restore(); }); @@ -272,10 +284,9 @@ describe('DetectTokensController', function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ - messenger: buildMessenger(), + messenger: getRestrictedMessenger(), preferences, network, - keyringMemStore, tokenList: tokenListController, tokensController, assetsContractController, @@ -309,10 +320,9 @@ describe('DetectTokensController', function () { }); await tokenListController.start(); const controller = new DetectTokensController({ - messenger: buildMessenger(), + messenger: getRestrictedMessenger(), preferences, network, - keyringMemStore, tokenList: tokenListController, tokensController, assetsContractController, @@ -333,10 +343,9 @@ describe('DetectTokensController', function () { sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ - messenger: buildMessenger(), + messenger: getRestrictedMessenger(), preferences, network, - keyringMemStore, tokenList: tokenListController, tokensController, assetsContractController, @@ -385,10 +394,9 @@ describe('DetectTokensController', function () { sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ - messenger: buildMessenger(), + messenger: getRestrictedMessenger(), preferences, network, - keyringMemStore, tokenList: tokenListController, tokensController, assetsContractController, @@ -444,10 +452,9 @@ describe('DetectTokensController', function () { it('should trigger detect new tokens when change address', async function () { sandbox.useFakeTimers(); const controller = new DetectTokensController({ - messenger: buildMessenger(), + messenger: getRestrictedMessenger(), preferences, network, - keyringMemStore, tokenList: tokenListController, tokensController, assetsContractController, @@ -464,10 +471,9 @@ describe('DetectTokensController', function () { it('should trigger detect new tokens when submit password', async function () { sandbox.useFakeTimers(); const controller = new DetectTokensController({ - messenger: buildMessenger(), + messenger: getRestrictedMessenger(), preferences, network, - keyringMemStore, tokenList: tokenListController, tokensController, assetsContractController, @@ -475,18 +481,38 @@ describe('DetectTokensController', function () { controller.isOpen = true; controller.selectedAddress = '0x0'; const stub = sandbox.stub(controller, 'detectNewTokens'); - await controller._keyringMemStore.updateState({ isUnlocked: true }); + + messenger.publish('KeyringController:unlock'); + sandbox.assert.called(stub); + assert.equal(controller.isUnlocked, true); + }); + + it('should not be active after lock event is emitted', async function () { + sandbox.useFakeTimers(); + const controller = new DetectTokensController({ + messenger: getRestrictedMessenger(), + preferences, + network, + tokenList: tokenListController, + tokensController, + assetsContractController, + }); + controller.isOpen = true; + + messenger.publish('KeyringController:lock'); + + assert.equal(controller.isUnlocked, false); + assert.equal(controller.isActive, false); }); it('should not trigger detect new tokens when not unlocked', async function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ - messenger: buildMessenger(), + messenger: getRestrictedMessenger(), preferences, network, - keyringMemStore, tokenList: tokenListController, tokensController, assetsContractController, @@ -505,10 +531,9 @@ describe('DetectTokensController', function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ - messenger: buildMessenger(), + messenger: getRestrictedMessenger(), preferences, network, - keyringMemStore, tokensController, assetsContractController, }); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ee08fe92682b..fd5c6427755f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1142,7 +1142,12 @@ export default class MetamaskController extends EventEmitter { const detectTokensControllerMessenger = this.controllerMessenger.getRestricted({ name: 'DetectTokensController', - allowedEvents: ['NetworkController:stateChange'], + allowedActions: ['KeyringController:getState'], + allowedEvents: [ + 'NetworkController:stateChange', + 'KeyringController:lock', + 'KeyringController:unlock', + ], }); this.detectTokensController = new DetectTokensController({ messenger: detectTokensControllerMessenger, @@ -1150,7 +1155,6 @@ export default class MetamaskController extends EventEmitter { tokensController: this.tokensController, assetsContractController: this.assetsContractController, network: this.networkController, - keyringMemStore: this.keyringController.memStore, tokenList: this.tokenListController, trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind( this.metaMetricsController, From b3180fe6bf47f44e5cf14c045726ff33c34d606b Mon Sep 17 00:00:00 2001 From: Marina Boboc <120041701+benjisclowder@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:35:35 +0300 Subject: [PATCH 002/137] update sentry documentation (#20813) --- development/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/development/README.md b/development/README.md index 560defc57ad2..d00880addb83 100644 --- a/development/README.md +++ b/development/README.md @@ -76,24 +76,26 @@ To debug in a production Sentry environment: Errors reported whilst using the extension will be displayed in Sentry's `Issues` page. +To debug in test build we need to comment out the IF condition https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/lib/setupSentry.js#L392 + ## Source Maps ### Debugging production builds using Source Maps To unbundle the extensions compiled and minified JavaScript using Source Maps: -- Open Chrome DevTools to inspect the `background.html` or `home.html` view +- Open Chrome DevTools to inspect the `background.html` or `home.html` view - Click on the `Sources` tab in Chrome DevTools - In the Sources tab, click on the `Page` panel - Expand the file directory in the Page panel until you see the source files you're after -- Select a source file in the Page panel +- Select a source file in the Page panel ``` chrome-extension://{EXTENSION_ID}/common-0.js ``` - Double click the source file to open it in the Workspace - Right click in the body of the source file and select `Add source map...` - Enter the path to the corresponding source map file, and Click `Add` -``` +``` file:///{LOCAL_FILE_SYSTEM}/metamask-extension/dist/sourcemaps/common-0.js.map ``` - Repeat the steps above as necessary adding all the relevant source map files From c0e121e7c3985b002230e9e8daab17352094993c Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Mon, 18 Sep 2023 10:13:26 +0100 Subject: [PATCH 003/137] Add name component (#20798) Add the Name component to display a saved petname or a proposed name, for raw values such as Ethereum account addresses. Add the nested NameDetails component when the Name component is clicked, which includes a modal containing a text field and dropdown of all available proposed names. Add the FormComboField component to provide a free entry text field with a dropdown of suggested options including both primary and secondary text. --- app/_locales/en/messages.json | 36 ++ lavamoat/browserify/beta/policy.json | 8 + lavamoat/browserify/desktop/policy.json | 8 + lavamoat/browserify/flask/policy.json | 8 + lavamoat/browserify/main/policy.json | 8 + lavamoat/browserify/mmi/policy.json | 8 + package.json | 1 + ui/components/app/app-components.scss | 2 + .../app/name/__snapshots__/name.test.tsx.snap | 95 +++++ ui/components/app/name/index.scss | 37 ++ ui/components/app/name/index.ts | 1 + .../__snapshots__/name-details.test.tsx.snap | 383 ++++++++++++++++++ .../app/name/name-details/index.scss | 15 + ui/components/app/name/name-details/index.ts | 1 + .../name/name-details/name-details.test.tsx | 221 ++++++++++ .../app/name/name-details/name-details.tsx | 203 ++++++++++ ui/components/app/name/name.stories.tsx | 170 ++++++++ ui/components/app/name/name.test.tsx | 210 ++++++++++ ui/components/app/name/name.tsx | 115 ++++++ .../form-combo-field.test.tsx.snap | 133 ++++++ .../form-combo-field.stories.tsx | 105 +++++ .../form-combo-field.test.tsx | 109 +++++ .../ui/form-combo-field/form-combo-field.tsx | 238 +++++++++++ ui/components/ui/form-combo-field/index.scss | 57 +++ ui/components/ui/form-combo-field/index.ts | 1 + ui/components/ui/ui-components.scss | 1 + ui/hooks/useName.test.ts | 145 +++++++ ui/hooks/useName.ts | 25 ++ ui/selectors/selectors.js | 13 + ui/store/actions.ts | 31 ++ yarn.lock | 11 + 31 files changed, 2399 insertions(+) create mode 100644 ui/components/app/name/__snapshots__/name.test.tsx.snap create mode 100644 ui/components/app/name/index.scss create mode 100644 ui/components/app/name/index.ts create mode 100644 ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap create mode 100644 ui/components/app/name/name-details/index.scss create mode 100644 ui/components/app/name/name-details/index.ts create mode 100644 ui/components/app/name/name-details/name-details.test.tsx create mode 100644 ui/components/app/name/name-details/name-details.tsx create mode 100644 ui/components/app/name/name.stories.tsx create mode 100644 ui/components/app/name/name.test.tsx create mode 100644 ui/components/app/name/name.tsx create mode 100644 ui/components/ui/form-combo-field/__snapshots__/form-combo-field.test.tsx.snap create mode 100644 ui/components/ui/form-combo-field/form-combo-field.stories.tsx create mode 100644 ui/components/ui/form-combo-field/form-combo-field.test.tsx create mode 100644 ui/components/ui/form-combo-field/form-combo-field.tsx create mode 100644 ui/components/ui/form-combo-field/index.scss create mode 100644 ui/components/ui/form-combo-field/index.ts create mode 100644 ui/hooks/useName.test.ts create mode 100644 ui/hooks/useName.ts diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 6092721aa172..a948be94930b 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -714,6 +714,10 @@ "coingecko": { "message": "CoinGecko" }, + "comboNoOptions": { + "message": "No options found", + "description": "Default text shown in the combo field dropdown if no options." + }, "configureSnapPopupDescription": { "message": "You're now leaving MetaMask to configure this snap." }, @@ -2368,6 +2372,38 @@ "name": { "message": "Name" }, + "nameAddressLabel": { + "message": "Address", + "description": "Label above address field in name component modal." + }, + "nameInstructionsNew": { + "message": "You are interacting with an unknown contract address. If you trust this author, set a personal display name to identify it going forward.", + "description": "Instruction text in name component modal when value is not recognised." + }, + "nameInstructionsSaved": { + "message": "Interactions with this address will always be identified using this personal display name.", + "description": "Instruction text in name component modal when value is saved." + }, + "nameLabel": { + "message": "Display name", + "description": "Label above name input field in name component modal." + }, + "nameModalTitleNew": { + "message": "Unknown address", + "description": "Title of the modal created by the name component when value is not recognised." + }, + "nameModalTitleSaved": { + "message": "Saved address", + "description": "Title of the modal created by the name component when value is saved." + }, + "nameNoProposedNames": { + "message": "No proposed names found", + "description": "Text shown in the proposed name dropdown if none found." + }, + "nameSetPlaceholder": { + "message": "Set a personal display name...", + "description": "Placeholder text for name input field in name component modal." + }, "nativeToken": { "message": "The native token on this network is $1. It is the token used for gas fees.", "description": "$1 represents the name of the native token on the current network" diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 0530d84fa850..e25e02879fd4 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1677,6 +1677,14 @@ "browserify>url": true } }, + "@metamask/name-controller": { + "globals": { + "fetch": true + }, + "packages": { + "@metamask/base-controller": true + } + }, "@metamask/network-controller": { "globals": { "URL": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index bdd2ba925bb9..7ede24c7bbb7 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -1828,6 +1828,14 @@ "browserify>url": true } }, + "@metamask/name-controller": { + "globals": { + "fetch": true + }, + "packages": { + "@metamask/base-controller": true + } + }, "@metamask/network-controller": { "globals": { "URL": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 2f97fd964747..d807734aedf6 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1828,6 +1828,14 @@ "browserify>url": true } }, + "@metamask/name-controller": { + "globals": { + "fetch": true + }, + "packages": { + "@metamask/base-controller": true + } + }, "@metamask/network-controller": { "globals": { "URL": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 8f1f171aa469..9eae2c148db8 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1677,6 +1677,14 @@ "browserify>url": true } }, + "@metamask/name-controller": { + "globals": { + "fetch": true + }, + "packages": { + "@metamask/base-controller": true + } + }, "@metamask/network-controller": { "globals": { "URL": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 578f541b1d3e..da430864f696 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1818,6 +1818,14 @@ "browserify>url": true } }, + "@metamask/name-controller": { + "globals": { + "fetch": true + }, + "packages": { + "@metamask/base-controller": true + } + }, "@metamask/network-controller": { "globals": { "URL": true, diff --git a/package.json b/package.json index 63bd13ec55a4..66a2804d8f03 100644 --- a/package.json +++ b/package.json @@ -256,6 +256,7 @@ "@metamask/logo": "^3.1.1", "@metamask/message-manager": "^7.3.0", "@metamask/metamask-eth-abis": "^3.0.0", + "@metamask/name-controller": "^1.0.0", "@metamask/network-controller": "^12.1.1", "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index c4dc26714527..c5c610d5dc51 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -95,6 +95,8 @@ @import 'network-account-balance-header/index'; @import 'approve-content-card/index'; @import 'transaction-alerts/transaction-alerts'; +@import 'name/index'; +@import 'name/name-details/index'; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) @import '../institutional/interactive-replacement-token-notification/index'; @import '../institutional/confirm-remove-jwt-modal/index'; diff --git a/ui/components/app/name/__snapshots__/name.test.tsx.snap b/ui/components/app/name/__snapshots__/name.test.tsx.snap new file mode 100644 index 000000000000..5769227e4286 --- /dev/null +++ b/ui/components/app/name/__snapshots__/name.test.tsx.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Name renders address with proposed name 1`] = ` +
+
+
+ + + 0xc0f...4978 + + + “ + TestProposedName + ” + +
+
+
+`; + +exports[`Name renders address with proposed name according to source priority 1`] = ` +
+
+
+ + + 0xc0f...4978 + + + “ + TestProposedName + ” + +
+
+
+`; + +exports[`Name renders address with saved name 1`] = ` +
+
+
+ + + TestName + +
+
+
+`; + +exports[`Name renders address without proposed name 1`] = ` +
+
+
+ + + 0xc0f...4979 + +
+
+
+`; diff --git a/ui/components/app/name/index.scss b/ui/components/app/name/index.scss new file mode 100644 index 000000000000..becff5641d2a --- /dev/null +++ b/ui/components/app/name/index.scss @@ -0,0 +1,37 @@ +.name { + border-radius: 36px; + padding: 6px 9px 6px 9px; + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 12px; + + &__missing { + background-color: var(--color-warning-muted); + } + + &__saved { + background-color: var(--color-info-muted); + } + + &__missing &__icon { + color: var(--color-warning-default); + } + + &__saved &__icon { + color: var(--color-info-default); + } + + &__value, + &__proposed { + color: var(--color-warning-default); + } + + &__name { + color: var(--color-info-default); + } + + &__proposed { + font-style: italic; + } +} diff --git a/ui/components/app/name/index.ts b/ui/components/app/name/index.ts new file mode 100644 index 000000000000..f3e2e4fe0a86 --- /dev/null +++ b/ui/components/app/name/index.ts @@ -0,0 +1 @@ +export { default } from './name'; diff --git a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap new file mode 100644 index 000000000000..2f4fc518007d --- /dev/null +++ b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap @@ -0,0 +1,383 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NameDetails renders with no saved name 1`] = ` + +
+
+
+
+
+
-

- Linea Goerli -

+

+ Linea Goerli +

+

+ + Lineascan.build + +

+