From 3cde03584a7c3b027cd5f97cb30f406ce62a00e1 Mon Sep 17 00:00:00 2001 From: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Fri, 28 Jun 2024 14:21:44 +0200 Subject: [PATCH 01/78] test: Fix flaky test: "Custom network customNetwork should add mainnet network" (#24895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR addresses the flakiness in e2e test "Custom network customNetwork should add mainnet network." Root Cause: The flakiness happens due to occasionally hitting the API rate limit while fetching the token symbol, resulting in the warning message `"Ticker symbol verification data is currently unavailable..."`, as indicated by the failure screenshots in the CI job. However, the test is intended to verify the presence of a different warning: `"The token symbol doesn't match the network or chainID..."`. Both warnings share the same data-testid but differ in text. Consequently, when the first warning appears, the test mistakenly identifies it as the second warning, leading to test failure. Fix Implemented: The solution for this test is to refine the selector for the specific warning message we aim to verify. By making the selector more precise, we ensure that the test accurately checks for the intended warning, thereby eliminating the flakiness caused by the confusion between the two warnings. ![Screenshot 2024-05-30 at 13 55 57](https://github.com/MetaMask/metamask-extension/assets/105063779/85e9dd36-6be1-4960-8920-f2e9f5d505a0) ![Screenshot 2024-05-30 at 11 57 16](https://github.com/MetaMask/metamask-extension/assets/105063779/5e47af8d-4c0f-4b31-a05e-0b9f9da04106) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/24895?quickstart=1) ## **Related issues** Fixes: #24634 ## **Manual testing steps** 1. Run the test several times yarn test:e2e:single test/e2e/tests/network/add-custom-network.spec.js --browser=firefox --leave-running --retryUntilFailure --retries=10 2. Check ci jobs ## **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 completed the PR template to the best of my ability - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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: seaona <54408225+seaona@users.noreply.github.com> --- test/e2e/tests/network/add-custom-network.spec.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/network/add-custom-network.spec.js b/test/e2e/tests/network/add-custom-network.spec.js index 0cd18c13a9bf..fd133fa47e47 100644 --- a/test/e2e/tests/network/add-custom-network.spec.js +++ b/test/e2e/tests/network/add-custom-network.spec.js @@ -88,6 +88,14 @@ const selectors = { }, suggestedTicker: '[data-testid="network-form-ticker-suggestion"]', tickerWarning: '[data-testid="network-form-ticker-warning"]', + suggestedTickerForXDAI: { + css: '[data-testid="network-form-ticker-suggestion"]', + text: 'Suggested ticker symbol: XDAI', + }, + tickerWarningTokenSymbol: { + css: '[data-testid="network-form-ticker-warning"]', + text: "This token symbol doesn't match the network name or chain ID entered.", + }, tickerButton: { text: 'PETH', tag: 'button' }, networkAdded: { text: 'Network added successfully!', tag: 'h4' }, @@ -735,11 +743,11 @@ describe('Custom network', function () { await driver.fill(selectors.explorerInputField, 'https://test.com'); const suggestedTicker = await driver.isElementPresent( - selectors.suggestedTicker, + selectors.suggestedTickerForXDAI, ); const tickerWarning = await driver.isElementPresent( - selectors.tickerWarning, + selectors.tickerWarningTokenSymbol, ); assert.equal(suggestedTicker, false); From 1661b212645a0d159a90a5b0d75e54ecc73090aa Mon Sep 17 00:00:00 2001 From: Javier Briones <1674192+jvbriones@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:55:33 +0200 Subject: [PATCH 02/78] ci: add SonarCloud scan job (#25421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Add a SonarCloud scan job ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] 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. ## **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. --- .github/workflows/sonar.yml | 22 ++++++---------------- sonar-project.properties | 26 +++++++++++--------------- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 61d549728d99..f5e1a0552dd1 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -1,18 +1,11 @@ name: Sonar on: - workflow_call: - secrets: - SONAR_TOKEN: - required: true -# pull_request: -# branches: -# - develop -# types: -# - opened -# - reopened -# - synchronize -# - labeled -# - unlabeled + push: + branches: + - develop + pull_request: + branches: + - develop jobs: sonarcloud: @@ -25,8 +18,5 @@ jobs: - name: SonarCloud Scan # This is SonarSource/sonarcloud-github-action@v2.0.0 uses: SonarSource/sonarcloud-github-action@4b4d7634dab97dcee0b75763a54a6dc92a9e6bc1 - with: - args: > - -Dsonar.javascript.lcov.reportPaths=tests/coverage/lcov.info env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/sonar-project.properties b/sonar-project.properties index de14094b965e..0455fa9634e2 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,18 +1,14 @@ -sonar.projectKey=metamask-extension -sonar.organization=consensys +sonar.projectKey=metamask-extension-private +sonar.organization=metamask -# This is the name and version displayed in the SonarCloud UI. -sonar.projectName=MetaMask Extension -#sonar.projectVersion=1.0 +# Source +sonar.sources=app,development,offscreen,shared,types,ui +sonar.exclusions=**/*.test.**,**/*.spec.**,app/images -# Root for sonar analysis. -sonar.sources=app/ +# Tests +sonar.tests=app,test,development,offscreen,shared,types,ui +sonar.test.inclusions=**/*.test.**,**/*.spec.** +sonar.javascript.lcov.reportPaths=tests/coverage/lcov.info -# Excluded project files from analysis. -#sonar.exclusions= - -# Inclusions for test files. -sonar.test.inclusions=**.test.** - -# Encoding of the source code. Default is default system encoding -sonar.sourceEncoding=UTF-8 +# Fail CI job if quality gate failures +sonar.qualitygate.wait=false \ No newline at end of file From fdc898e1670d1c973a165a1a09743f9a227d668a Mon Sep 17 00:00:00 2001 From: David Walsh Date: Fri, 28 Jun 2024 09:54:36 -0500 Subject: [PATCH 03/78] test: UX: Multichain: Add test for mutiple dapp confirmation order (#25536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR ensures no dapp-specified transactions get dropped when triggering transactions on dapps, switching to a dapp on another chain, and triggering another transaction. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25536?quickstart=1) ## **Related issues** Supercedes: https://github.com/MetaMask/metamask-extension/pull/25459 Fixes: ## **Manual testing steps** 1. Open `Tab 1`, connect to `Uniswap` on `Ethereum Mainnet` 2. Open `Tab 2`, connect to `PancakeSwap` on `BNB Chain` 3. Open `Tab 3`, connect to `Test Dapp` on `Sepolia` 4. Initiate a swap on `Tab 1` and `Tab 2` *BUT DO NOT CONFIRM IT, JUST MOVE ON TO THE NEXT TAB* 5. Initiate a send on `Tab 3` *BUT DO NOT CONFIRM IT* 6. On the confirmation screen, you should still see the first confirmation from `Tab 1` (`Uniswap`) on `Ethereum Mainnet`; confirm or reject it. See the confirmation window close 7. A new confirmation popup should come up with the `PancakeSwap`/ `Tab 2` confirmation on `BNB` chain; confirm or reject it. See the confirmation window close 8. See one last confirmation screen pop up for the `Tab 3` / `Test Dapp` send on `Sepolia`. Confirm or reject it. ## **Screenshots/Recordings** ### **Before** ### **After** Video of equivalent, manual test of this E2E https://github.com/MetaMask/metamask-extension/assets/46655/cc4578f9-602c-4c2e-835d-79e4ca8ed762 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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/helpers.js | 2 + test/e2e/tests/request-queuing/ui.spec.js | 270 +++++++++++++++++++--- 2 files changed, 238 insertions(+), 34 deletions(-) diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 41951f973aa9..ba32c94cabec 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -664,6 +664,7 @@ const closeSRPReveal = async (driver) => { const DAPP_HOST_ADDRESS = '127.0.0.1:8080'; const DAPP_URL = `http://${DAPP_HOST_ADDRESS}`; const DAPP_ONE_URL = 'http://127.0.0.1:8081'; +const DAPP_TWO_URL = 'http://127.0.0.1:8082'; const openDapp = async (driver, contract = null, dappURL = DAPP_URL) => { return contract @@ -1121,6 +1122,7 @@ module.exports = { DAPP_HOST_ADDRESS, DAPP_URL, DAPP_ONE_URL, + DAPP_TWO_URL, TEST_SEED_PHRASE, TEST_SEED_PHRASE_TWO, PRIVATE_KEY, diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js index e85d87226e84..45429ad32263 100644 --- a/test/e2e/tests/request-queuing/ui.spec.js +++ b/test/e2e/tests/request-queuing/ui.spec.js @@ -1,4 +1,5 @@ const { strict: assert } = require('assert'); +const { Browser } = require('selenium-webdriver'); const FixtureBuilder = require('../../fixture-builder'); const { withFixtures, @@ -11,21 +12,33 @@ const { defaultGanacheOptions, switchToNotificationWindow, veryLargeDelayMs, + DAPP_TWO_URL, } = require('../../helpers'); const { PAGES } = require('../../webdriver/driver'); -async function openDappAndSwitchChain(driver, dappUrl, chainId) { - const notificationWindowIndex = chainId ? 4 : 3; +// Window handle adjustments will need to be made for Non-MV3 Firefox +// due to OffscreenDocument. Additionally Firefox continually bombs +// with a "NoSuchWindowError: Browsing context has been discarded" whenever +// we try to open a third dapp, so this test run in Firefox will +// validate two dapps instead of 3 +const IS_FIREFOX = process.env.SELENIUM_BROWSER === Browser.FIREFOX; +async function openDappAndSwitchChain( + driver, + dappUrl, + chainId, + notificationWindowIndex = 3, +) { // Open the dapp await openDapp(driver, undefined, dappUrl); - await driver.delay(regularDelayMs); // Connect to the dapp await driver.findClickableElement({ text: 'Connect', tag: 'button' }); await driver.clickElement('#connectButton'); await driver.delay(regularDelayMs); + await switchToNotificationWindow(driver, notificationWindowIndex); + await driver.clickElement({ text: 'Next', tag: 'button', @@ -62,39 +75,99 @@ async function openDappAndSwitchChain(driver, dappUrl, chainId) { } } -async function selectDappClickSendGetNetwork(driver, dappUrl) { +async function selectDappClickSend(driver, dappUrl) { await driver.switchToWindowWithUrl(dappUrl); - // Windows: MetaMask, TestDapp1, TestDapp2 - const expectedWindowHandles = 3; - await driver.waitUntilXWindowHandles(expectedWindowHandles); - const currentWindowHandles = await driver.getAllWindowHandles(); await driver.clickElement('#sendButton'); +} - // Under mv3, we don't need to add to the current number of window handles - // because the offscreen document returned by getAllWindowHandles provides - // an extra window handle - const newWindowHandles = await driver.waitUntilXWindowHandles( - process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined - ? currentWindowHandles.length - : currentWindowHandles.length + 1, - ); - const [newNotificationWindowHandle] = newWindowHandles.filter( - (h) => !currentWindowHandles.includes(h), - ); - await driver.switchToWindow(newNotificationWindowHandle); +async function switchToNotificationPopoverValidateDetails( + driver, + expectedDetails, +) { + // Switches to the MetaMask Dialog window for confirmation + const windowHandles = await driver.getAllWindowHandles(); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog, windowHandles); + // Get UI details const networkPill = await driver.findElement( '[data-testid="network-display"]', ); const networkText = await networkPill.getText(); - await driver.clickElement({ css: 'button', text: 'Reject' }); - return networkText; + const originElement = await driver.findElement( + '.confirm-page-container-summary__origin bdi', + ); + const originText = await originElement.getText(); + + // Get state details + const notificationWindowState = await driver.executeScript(() => + window.stateHooks?.getCleanAppState?.(), + ); + const { chainId } = notificationWindowState.metamask.providerConfig; + + // Ensure accuracy + validateConfirmationDetails( + { networkText, originText, chainId }, + expectedDetails, + ); +} + +async function rejectTransaction(driver) { + await driver.clickElement({ tag: 'button', text: 'Reject' }); +} + +async function confirmTransaction(driver) { + await driver.clickElement({ tag: 'button', text: 'Confirm' }); +} + +function validateConfirmationDetails( + { chainId, networkText, originText }, + expected, +) { + assert.equal(chainId, expected.chainId); + assert.equal(networkText, expected.networkText); + assert.equal(originText, expected.originText); +} + +async function switchToNetworkByName(driver, networkName) { + await driver.clickElement('[data-testid="network-display"]'); + await driver.clickElement(`[data-testid="${networkName}"]`); +} + +async function validateBalanceAndActivity( + driver, + expectedBalance, + expectedActivityEntries = 1, +) { + // Ensure the balance changed if the the transaction was confirmed + await driver.waitForSelector({ + css: '[data-testid="eth-overview__primary-currency"] .currency-display-component__text', + text: expectedBalance, + }); + + // Ensure there's an activity entry of "Send" and "Confirmed" + if (expectedActivityEntries) { + await driver.clickElement('[data-testid="account-overview__activity-tab"]'); + assert.equal( + ( + await driver.findElements({ + css: '[data-testid="activity-list-item-action"]', + text: 'Send', + }) + ).length, + expectedActivityEntries, + ); + assert.equal( + (await driver.findElements('.transaction-status-label--confirmed')) + .length, + expectedActivityEntries, + ); + } } describe('Request-queue UI changes', function () { it('UI should show network specific to domain @no-mmi', async function () { const port = 8546; - const chainId = 1338; + const chainId = 1338; // 0x53a await withFixtures( { dapp: true, @@ -126,7 +199,7 @@ describe('Request-queue UI changes', function () { await openDappAndSwitchChain(driver, DAPP_URL); // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a', 4); // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet await driver.switchToWindowWithTitle( @@ -134,22 +207,151 @@ describe('Request-queue UI changes', function () { ); await driver.findElement({ css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', + text: 'Localhost 8546', }); // Go to the first dapp, ensure it uses localhost - const dappOneNetworkPillText = await selectDappClickSendGetNetwork( - driver, - DAPP_URL, - ); - assert.equal(dappOneNetworkPillText, 'Localhost 8545'); + await selectDappClickSend(driver, DAPP_URL); + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + await rejectTransaction(driver); // Go to the second dapp, ensure it uses Ethereum Mainnet - const dappTwoNetworkPillText = await selectDappClickSendGetNetwork( - driver, - DAPP_ONE_URL, + await selectDappClickSend(driver, DAPP_ONE_URL); + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); + await rejectTransaction(driver); + }, + ); + }); + + it('handles three confirmations on three confirmations concurrently @no-mmi', async function () { + const port = 8546; + const chainId = 1338; // 0x53a + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + // Ganache for network 1 + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + // Ganache for network 3 + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 3 }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a', 4); + + if (!IS_FIREFOX) { + // Open the third dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8', 5); + } + + // Trigger a send confirmation on the first dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_URL); + + // Trigger a send confirmation on the second dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_ONE_URL); + + if (!IS_FIREFOX) { + // Trigger a send confirmation on the third dapp, do not confirm or reject + await selectDappClickSend(driver, DAPP_TWO_URL); + } + + // Switch to the Notification window, ensure first transaction still showing + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + + // Confirm transaction, wait for first confirmation window to close, second to display + await confirmTransaction(driver); + await driver.delay(veryLargeDelayMs); + + // Switch to the new Notification window, ensure second transaction showing + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); + + // Reject this transaction, wait for second confirmation window to close, third to display + await rejectTransaction(driver); + await driver.delay(veryLargeDelayMs); + + if (!IS_FIREFOX) { + // Switch to the new Notification window, ensure third transaction showing + await switchToNotificationPopoverValidateDetails(driver, { + chainId: '0x3e8', + networkText: 'Localhost 7777', + originText: DAPP_TWO_URL, + }); + + // Confirm transaction + await confirmTransaction(driver); + } + + // With first and last confirmations confirmed, and second rejected, + // Ensure only first and last network balances were affected + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Wait for transaction to be completed on final confirmation + await driver.delay(veryLargeDelayMs); + + if (!IS_FIREFOX) { + // Start on the last joined network, whose send transaction was just confirmed + await validateBalanceAndActivity(driver, '24.9998'); + } + + // Switch to second network, ensure full balance + await switchToNetworkByName(driver, 'Localhost 8546'); + await validateBalanceAndActivity(driver, '25', 0); + + // Turn on test networks in Networks menu so Localhost 8545 is available + await driver.clickElement('[data-testid="network-display"]'); + await driver.clickElement('.mm-modal-content__dialog .toggle-button'); + await driver.clickElement( + '.mm-modal-content__dialog button[aria-label="Close"]', ); - assert.equal(dappTwoNetworkPillText, 'Ethereum Mainnet'); + + // Switch to first network, whose send transaction was just confirmed + await switchToNetworkByName(driver, 'Localhost 8545'); + await validateBalanceAndActivity(driver, '24.9998'); }, ); }); From 1f74b08fc18b6462b9624e7d33a2da01191c5e9b Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Fri, 28 Jun 2024 08:18:35 -0700 Subject: [PATCH 04/78] feat: adding / deleting additional RPC URLs (#25452) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Initial UI for adding and deleting multiple RPC URLs. The add and delete buttons don't do anything yet. Just UI until the network controller gets upgraded. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25452?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. Build with ENABLE_NETWORK_UI_REDESIGN=1 yarn start 2. Open networks, right click edit one 3. Click RPC dropdown 4. Add and delete RPC endpoints ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/MetaMask/metamask-extension/assets/3500406/42047230-b258-4c1b-a37e-fb3c0f300913 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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 Co-authored-by: salimtb --- app/_locales/en/messages.json | 15 ++++ .../confirm-delete-rpc-url-modal.tsx | 72 +++++++++++++++++ ui/components/app/modals/modal.js | 14 ++++ .../add-rpc-url-modal/add-rpc-url-modal.tsx | 43 ++++++++++ .../network-list-menu/network-list-menu.js | 80 +++++++++++++------ ui/ducks/app/app.ts | 13 ++- ui/pages/home/home.component.js | 9 ++- ui/pages/home/home.container.js | 2 +- .../add-network-modal/index.js | 3 + ui/pages/routes/routes.component.js | 9 ++- ui/pages/routes/routes.container.js | 2 + ui/pages/settings/networks-tab/index.scss | 2 + .../networks-form/networks-form.js | 16 +++- .../networks-form/rpc-url-editor.tsx | 47 ++++++++--- ui/selectors/selectors.js | 4 + ui/store/actions.test.js | 2 + ui/store/actions.ts | 20 ++--- 17 files changed, 294 insertions(+), 59 deletions(-) create mode 100644 ui/components/app/modals/confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal.tsx create mode 100644 ui/components/multichain/network-list-menu/add-rpc-url-modal/add-rpc-url-modal.tsx diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f50a5fa84362..25f3c5436158 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -311,6 +311,9 @@ "message": "Can’t find a token? You can manually add any token by pasting its address. Token contract addresses can be found on $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Add URL" + }, "addingCustomNetwork": { "message": "Adding Network" }, @@ -320,6 +323,9 @@ "additionalNetworks": { "message": "Additional networks" }, + "additionalRpcUrl": { + "message": "Additional RPC URL" + }, "address": { "message": "Address" }, @@ -972,6 +978,9 @@ "confirmConnectionTitle": { "message": "Confirm connection to $1" }, + "confirmDeletion": { + "message": "Confirm deletion" + }, "confirmFieldPaymaster": { "message": "Fee paid by" }, @@ -984,6 +993,9 @@ "confirmRecoveryPhrase": { "message": "Confirm Secret Recovery Phrase" }, + "confirmRpcUrlDeletionMessage": { + "message": "Are you sure you want to delete the RPC URL? Your information will not be saved for this network." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Only confirm this transaction if you fully understand the content and trust the requesting site." }, @@ -1463,6 +1475,9 @@ "message": "Delete $1 network?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Delete RPC URL" + }, "deposit": { "message": "Deposit" }, diff --git a/ui/components/app/modals/confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal.tsx b/ui/components/app/modals/confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal.tsx new file mode 100644 index 000000000000..3eb9a9323048 --- /dev/null +++ b/ui/components/app/modals/confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; +import { + BlockSize, + Display, +} from '../../../../helpers/constants/design-system'; +import { + Box, + ButtonPrimary, + ButtonPrimarySize, + ButtonSecondary, + ButtonSecondarySize, + Modal, + ModalBody, + ModalContent, + ModalHeader, + ModalOverlay, +} from '../../../component-library'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { + hideModal, + setEditedNetwork, + toggleNetworkMenu, +} from '../../../../store/actions'; + +const ConfirmDeleteRpcUrlModal = () => { + const t = useI18nContext(); + const dispatch = useDispatch(); + return ( + { + dispatch(setEditedNetwork()); + dispatch(hideModal()); + }} + > + + + {t('confirmDeletion')} + + {t('confirmRpcUrlDeletionMessage')} + + { + dispatch(hideModal()); + dispatch(toggleNetworkMenu()); + }} + > + {t('back')} + + { + console.log('TODO: Delete RPc URL'); + }} + > + {t('deleteRpcUrl')} + + + + + + ); +}; + +export default ConfirmDeleteRpcUrlModal; diff --git a/ui/components/app/modals/modal.js b/ui/components/app/modals/modal.js index f3eb1a950a40..9d546e5de74d 100644 --- a/ui/components/app/modals/modal.js +++ b/ui/components/app/modals/modal.js @@ -38,6 +38,7 @@ import TransactionAlreadyConfirmed from './transaction-already-confirmed'; // Metamask Notifications import ConfirmTurnOffProfileSyncing from './confirm-turn-off-profile-syncing'; import TurnOnMetamaskNotifications from './turn-on-metamask-notifications/turn-on-metamask-notifications'; +import ConfirmDeleteRpcUrlModal from './confirm-delete-rpc-url-modal/confirm-delete-rpc-url-modal'; const modalContainerBaseStyle = { transform: 'translate3d(-50%, 0, 0px)', @@ -230,6 +231,19 @@ const MODALS = { }, }, + CONFIRM_DELETE_RPC_URL: { + contents: , + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, + EDIT_APPROVAL_PERMISSION: { contents: , mobileModalStyle: { diff --git a/ui/components/multichain/network-list-menu/add-rpc-url-modal/add-rpc-url-modal.tsx b/ui/components/multichain/network-list-menu/add-rpc-url-modal/add-rpc-url-modal.tsx new file mode 100644 index 000000000000..8e4269928bc8 --- /dev/null +++ b/ui/components/multichain/network-list-menu/add-rpc-url-modal/add-rpc-url-modal.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { + Box, + ButtonPrimary, + ButtonPrimarySize, + FormTextField, +} from '../../../component-library'; +import { + BlockSize, + Display, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +const AddRpcUrlModal = () => { + const t = useI18nContext(); + + return ( + + + + + {t('addUrl')} + + + ); +}; + +export default AddRpcUrlModal; diff --git a/ui/components/multichain/network-list-menu/network-list-menu.js b/ui/components/multichain/network-list-menu/network-list-menu.js index c481b163f8a0..3a1f279103d4 100644 --- a/ui/components/multichain/network-list-menu/network-list-menu.js +++ b/ui/components/multichain/network-list-menu/network-list-menu.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; import { useDispatch, useSelector } from 'react-redux'; @@ -15,6 +15,7 @@ import { toggleNetworkMenu, updateNetworksList, setNetworkClientIdForDomain, + setEditedNetwork, } from '../../../store/actions'; import { FEATURED_RPCS, @@ -32,6 +33,7 @@ import { getOriginOfCurrentTab, getUseRequestQueue, getNetworkConfigurations, + getEditedNetwork, } from '../../../selectors'; import ToggleButton from '../../ui/toggle-button'; import { @@ -70,6 +72,7 @@ import { getLocalNetworkMenuRedesignFeatureFlag } from '../../../helpers/utils/f import AddNetworkModal from '../../../pages/onboarding-flow/add-network-modal'; import PopularNetworkList from './popular-network-list/popular-network-list'; import NetworkListSearch from './network-list-search/network-list-search'; +import AddRpcUrlModal from './add-rpc-url-modal/add-rpc-url-modal'; const ACTION_MODES = { // Displays the search box and network list @@ -78,14 +81,13 @@ const ACTION_MODES = { ADD: 'add', // Displays the Edit form EDIT: 'edit', + // Displays the page for adding an additional RPC URL + ADD_RPC: 'add_rpc', }; export const NetworkListMenu = ({ onClose }) => { const t = useI18nContext(); - const [actionMode, setActionMode] = useState(ACTION_MODES.LIST); - const [modalTitle, setModalTitle] = useState(t('networkMenuHeading')); - const [networkToEdit, setNetworkToEdit] = useState(null); const nonTestNetworks = useSelector(getNonTestNetworks); const testNetworks = useSelector(getTestNetworks); const showTestNetworks = useSelector(getShowTestNetworks); @@ -114,6 +116,19 @@ export const NetworkListMenu = ({ onClose }) => { const orderedNetworksList = useSelector(getOrderedNetworksList); + const editedNetwork = useSelector(getEditedNetwork); + + const [actionMode, setActionMode] = useState( + editedNetwork ? ACTION_MODES.EDIT : ACTION_MODES.LIST, + ); + + const networkToEdit = useMemo(() => { + const network = [...nonTestNetworks, ...testNetworks].find( + (n) => n.id === editedNetwork?.networkConfigurationId, + ); + return network ? { ...network, label: network.nickname } : undefined; + }, [editedNetwork, nonTestNetworks, testNetworks]); + const networkConfigurationChainIds = Object.values(networkConfigurations).map( (net) => net.chainId, ); @@ -259,12 +274,12 @@ export const NetworkListMenu = ({ onClose }) => { const getOnEditCallback = (network) => { return () => { - const networkToUse = { - ...network, - label: network.nickname, - }; - setModalTitle(network.nickname); - setNetworkToEdit(networkToUse); + dispatch( + setEditedNetwork({ + networkConfigurationId: network.id, + nickname: network.nickname, + }), + ); setActionMode(ACTION_MODES.EDIT); }; }; @@ -518,7 +533,6 @@ export const NetworkListMenu = ({ onClose }) => { category: MetaMetricsEventCategory.Network, }); setActionMode(ACTION_MODES.ADD); - setModalTitle(t('addCustomNetwork')); }} > {t('addNetwork')} @@ -528,20 +542,38 @@ export const NetworkListMenu = ({ onClose }) => { ); } else if (actionMode === ACTION_MODES.ADD) { return ; + } else if (actionMode === ACTION_MODES.EDIT) { + return ( + setActionMode(ACTION_MODES.ADD_RPC)} + /> + ); + } else if (actionMode === ACTION_MODES.ADD_RPC) { + return ; } - return ( - - ); + return null; // Unreachable, but satisfies linter }; - const headerAdditionalProps = - actionMode === ACTION_MODES.LIST - ? {} - : { onBack: () => setActionMode(ACTION_MODES.LIST) }; + // Modal back button + let onBack; + if (actionMode === ACTION_MODES.EDIT || actionMode === ACTION_MODES.ADD) { + onBack = () => setActionMode(ACTION_MODES.LIST); + } else if (actionMode === ACTION_MODES.ADD_RPC) { + onBack = () => setActionMode(ACTION_MODES.EDIT); + } + + // Modal title + let title; + if (actionMode === ACTION_MODES.LIST) { + title = t('networkMenuHeading'); + } else if (actionMode === ACTION_MODES.ADD) { + title = t('addCustomNetwork'); + } else { + title = editedNetwork.nickname; + } return ( @@ -560,9 +592,9 @@ export const NetworkListMenu = ({ onClose }) => { paddingRight={4} paddingBottom={6} onClose={onClose} - {...headerAdditionalProps} + onBack={onBack} > - {modalTitle} + {title} {renderListNetworks()} diff --git a/ui/ducks/app/app.ts b/ui/ducks/app/app.ts index dc5fa3ba64f6..a16508c9a45a 100644 --- a/ui/ducks/app/app.ts +++ b/ui/ducks/app/app.ts @@ -82,7 +82,13 @@ type AppState = { newNftAddedMessage: string; removeNftMessage: string; newNetworkAddedName: string; - editedNetwork: string; + editedNetwork: + | { + networkConfigurationId: string; + nickname: string; + editCompleted: boolean; + } + | undefined; newNetworkAddedConfigurationId: string; selectedNetworkConfigurationId: string; sendInputCurrencySwitched: boolean; @@ -163,7 +169,7 @@ const initialState: AppState = { newNftAddedMessage: '', removeNftMessage: '', newNetworkAddedName: '', - editedNetwork: '', + editedNetwork: undefined, newNetworkAddedConfigurationId: '', selectedNetworkConfigurationId: '', sendInputCurrencySwitched: false, @@ -489,10 +495,9 @@ export default function reduceApp( }; } case actionConstants.SET_EDIT_NETWORK: { - const { nickname } = action.payload; return { ...appState, - editedNetwork: nickname, + editedNetwork: action.payload, }; } case actionConstants.SET_NEW_TOKENS_IMPORTED: diff --git a/ui/pages/home/home.component.js b/ui/pages/home/home.component.js index cb177340501d..33ca2b67a181 100644 --- a/ui/pages/home/home.component.js +++ b/ui/pages/home/home.component.js @@ -80,6 +80,7 @@ import { ///: END:ONLY_INCLUDE_IF } from '../../../shared/lib/ui-utils'; import { AccountOverview } from '../../components/multichain/account-overview'; +import { setEditedNetwork } from '../../store/actions'; ///: BEGIN:ONLY_INCLUDE_IF(build-beta) import BetaHomeFooter from './beta/beta-home-footer.component'; ///: END:ONLY_INCLUDE_IF @@ -185,7 +186,7 @@ export default class Home extends PureComponent { showOutdatedBrowserWarning: PropTypes.bool.isRequired, setOutdatedBrowserWarningLastShown: PropTypes.func.isRequired, newNetworkAddedName: PropTypes.string, - editedNetwork: PropTypes.string, + editedNetwork: PropTypes.object, // This prop is used in the `shouldCloseNotificationPopup` function // eslint-disable-next-line react/no-unused-prop-types isSigningQRHardwareTransaction: PropTypes.bool.isRequired, @@ -499,7 +500,7 @@ export default class Home extends PureComponent { setRemoveNftMessage(''); setNewTokensImported(''); // Added this so we dnt see the notif if user does not close it setNewTokensImportedError(''); - clearEditedNetwork({}); + setEditedNetwork(); }; const autoHideDelay = 5 * SECOND; @@ -606,7 +607,7 @@ export default class Home extends PureComponent { } /> ) : null} - {editedNetwork ? ( + {editedNetwork?.editCompleted ? ( - {t('newNetworkEdited', [editedNetwork])} + {t('newNetworkEdited', [editedNetwork.nickname])} { dispatch(setNewNetworkAdded({})); }, clearEditedNetwork: () => { - dispatch(setEditedNetwork({})); + dispatch(setEditedNetwork()); }, setActiveNetwork: (networkConfigurationId) => { dispatch(setActiveNetwork(networkConfigurationId)); diff --git a/ui/pages/onboarding-flow/add-network-modal/index.js b/ui/pages/onboarding-flow/add-network-modal/index.js index c031739b68b6..b35785e4f2ac 100644 --- a/ui/pages/onboarding-flow/add-network-modal/index.js +++ b/ui/pages/onboarding-flow/add-network-modal/index.js @@ -19,6 +19,7 @@ export default function AddNetworkModal({ isNewNetworkFlow = false, addNewNetwork = true, networkToEdit = null, + onRpcUrlAdd, }) { const dispatch = useDispatch(); const t = useI18nContext(); @@ -50,6 +51,7 @@ export default function AddNetworkModal({ networksToRender={[]} cancelCallback={closeCallback} submitCallback={closeCallback} + onRpcUrlAdd={onRpcUrlAdd} isNewNetworkFlow={isNewNetworkFlow} {...additionalProps} /> @@ -62,6 +64,7 @@ AddNetworkModal.propTypes = { isNewNetworkFlow: PropTypes.bool, addNewNetwork: PropTypes.bool, networkToEdit: PropTypes.object, + onRpcUrlAdd: PropTypes.func, }; AddNetworkModal.defaultProps = { diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index bec55f91a15e..9fe51d046eec 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -209,6 +209,7 @@ export default class Routes extends Component { newPrivacyPolicyToastShownDate: PropTypes.number, setSurveyLinkLastClickedOrClosed: PropTypes.func.isRequired, setNewPrivacyPolicyToastShownDate: PropTypes.func.isRequired, + clearEditedNetwork: PropTypes.func.isRequired, setNewPrivacyPolicyToastClickedOrClosed: PropTypes.func.isRequired, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) isShowKeyringSnapRemovalResultModal: PropTypes.bool.isRequired, @@ -804,6 +805,7 @@ export default class Routes extends Component { switchedNetworkDetails, clearSwitchedNetworkDetails, networkMenuRedesign, + clearEditedNetwork, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) isShowKeyringSnapRemovalResultModal, hideShowKeyringSnapRemovalResultModal, @@ -886,7 +888,12 @@ export default class Routes extends Component { toggleAccountMenu()} /> ) : null} {isNetworkMenuOpen ? ( - toggleNetworkMenu()} /> + { + toggleNetworkMenu(); + clearEditedNetwork(); + }} + /> ) : null} {networkMenuRedesign ? : null} {accountDetailsAddress ? ( diff --git a/ui/pages/routes/routes.container.js b/ui/pages/routes/routes.container.js index d4334e00b4de..da6d62636d5f 100644 --- a/ui/pages/routes/routes.container.js +++ b/ui/pages/routes/routes.container.js @@ -52,6 +52,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) hideKeyringRemovalResultModal, ///: END:ONLY_INCLUDE_IF + setEditedNetwork, } from '../../store/actions'; import { pageChanged } from '../../ducks/history/history'; import { prepareToLeaveSwaps } from '../../ducks/swaps/swaps'; @@ -176,6 +177,7 @@ function mapDispatchToProps(dispatch) { dispatch(setNewPrivacyPolicyToastClickedOrClosed()), setNewPrivacyPolicyToastShownDate: (date) => dispatch(setNewPrivacyPolicyToastShownDate(date)), + clearEditedNetwork: () => dispatch(setEditedNetwork()), ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) hideShowKeyringSnapRemovalResultModal: () => dispatch(hideKeyringRemovalResultModal()), diff --git a/ui/pages/settings/networks-tab/index.scss b/ui/pages/settings/networks-tab/index.scss index 99a4840aead0..86e792d988c4 100644 --- a/ui/pages/settings/networks-tab/index.scss +++ b/ui/pages/settings/networks-tab/index.scss @@ -7,10 +7,12 @@ &__rpc-dropdown { cursor: pointer; + word-break: break-all; } &__rpc-item { position: relative; + word-break: break-all; } &__rpc-item:hover { 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 298a063f98b6..c5bdaeeb1fc5 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.js @@ -120,6 +120,7 @@ const NetworksForm = ({ selectedNetwork, cancelCallback, submitCallback, + onRpcUrlAdd, }) => { const t = useI18nContext(); const dispatch = useDispatch(); @@ -795,7 +796,13 @@ const NetworksForm = ({ }, }); if (networkMenuRedesign) { - dispatch(setEditedNetwork({ nickname: networkName })); + dispatch( + setEditedNetwork({ + networkConfigurationId, + nickname: networkName, + editCompleted: true, + }), + ); } } @@ -925,8 +932,12 @@ const NetworksForm = ({ ))} ) : null} + {networkMenuRedesign ? ( - + ) : ( { +import { showModal, toggleNetworkMenu } from '../../../../store/actions'; + +export const RpcUrlEditor = ({ + currentRpcUrl, + onRpcUrlAdd, +}: { + currentRpcUrl: string; + onRpcUrlAdd: () => void; +}) => { // TODO: real endpoints const dummyRpcUrls = [ currentRpcUrl, - 'https://dummy.mainnet.public.blastapi.io', - 'https://dummy.io/v3/blockchain/node/dummy', + 'https://mainnet.public.blastapi.io', + 'https://infura.foo.bar.baz/123456789', ]; const t = useI18nContext(); + const dispatch = useDispatch(); const rpcDropdown = useRef(null); - const [isOpen, setIsOpen] = useState(false); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [currentRpcEndpoint, setCurrentRpcEndpoint] = useState(currentRpcUrl); return ( @@ -48,7 +58,7 @@ export const RpcUrlEditor = ({ currentRpcUrl }: { currentRpcUrl: string }) => { {t('defaultRpcUrl')} setIsOpen(!isOpen)} + onClick={() => setIsDropdownOpen(!isDropdownOpen)} className="networks-tab__rpc-dropdown" display={Display.Flex} justifyContent={JustifyContent.spaceBetween} @@ -60,7 +70,7 @@ export const RpcUrlEditor = ({ currentRpcUrl }: { currentRpcUrl: string }) => { > {currentRpcEndpoint} @@ -69,19 +79,24 @@ export const RpcUrlEditor = ({ currentRpcUrl }: { currentRpcUrl: string }) => { paddingTop={2} paddingBottom={2} paddingLeft={0} + matchWidth={true} paddingRight={0} className="networks-tab__rpc-popover" referenceElement={rpcDropdown.current} position={PopoverPosition.Bottom} - isOpen={isOpen} + isOpen={isDropdownOpen} > {dummyRpcUrls.map((rpcEndpoint) => ( setCurrentRpcEndpoint(rpcEndpoint)} + onClick={() => { + setCurrentRpcEndpoint(rpcEndpoint); + setIsDropdownOpen(false); + }} className={classnames('networks-tab__rpc-item', { 'networks-tab__rpc-item--selected': rpcEndpoint === currentRpcEndpoint, @@ -103,19 +118,25 @@ export const RpcUrlEditor = ({ currentRpcUrl }: { currentRpcUrl: string }) => { {rpcEndpoint} alert('TODO: delete confirmation modal')} + onClick={(e: React.MouseEvent) => { + e.stopPropagation(); + dispatch(toggleNetworkMenu()); + dispatch( + showModal({ + name: 'CONFIRM_DELETE_RPC_URL', + }), + ); + }} /> ))} alert('TODO: add RPC modal')} + onClick={onRpcUrlAdd} padding={4} display={Display.Flex} alignItems={AlignItems.center} diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index cd049873dfa4..faec9242d616 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -2026,6 +2026,10 @@ export function getNewNetworkAdded(state) { return state.appState.newNetworkAddedName; } +/** + * @param state + * @returns {{ networkConfigurationId: string; nickname: string; editCompleted: boolean} | undefined} + */ export function getEditedNetwork(state) { return state.appState.editedNetwork; } diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index 813153d33ca3..aa161ba781a1 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -1395,6 +1395,8 @@ describe('Actions', () => { const newNetworkAddedDetails = { nickname: 'test-chain', + networkConfigurationId: 'testNetworkConfigurationId', + editCompleted: true, }; store.dispatch(actions.setEditedNetwork(newNetworkAddedDetails)); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 4a75d4828212..186e59ccb8df 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -4139,16 +4139,16 @@ export function setNewNetworkAdded({ }; } -export function setEditedNetwork({ - nickname, -}: { - networkConfigurationId: string; - nickname: string; -}): PayloadAction { - return { - type: actionConstants.SET_EDIT_NETWORK, - payload: { nickname }, - }; +export function setEditedNetwork( + payload: + | { + networkConfigurationId: string; + nickname: string; + editCompleted: boolean; + } + | undefined = undefined, +): PayloadAction { + return { type: actionConstants.SET_EDIT_NETWORK, payload }; } export function setNewNftAddedMessage( From 4a9c48084e7cd55a7dbd7258ec28fea8919a8a1e Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Fri, 28 Jun 2024 12:16:16 -0500 Subject: [PATCH 05/78] fix: add eth_signTypedData and eth_signTypedData_v3 to `methodsRequiringNetworkSwitch` (#25562) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Though signature requests like `personal_sign`, `eth_signTypedData`, `eth_signTypedData_v3` and `eth_signTypedData_v4` do not depend on state of network connections, these confirmations do use nicknames/addressbook state which is dependent on globally selected network state for parsing signatures and injecting nicknaming where possible. The queueing system introduced with Amon Hen v1 (v12.0.0 release) introduces certain conditions in which these signature confirmations will be rendered on the wrong network. Though this doesn't actually result in faulty signatures, we should switch to the appropriate/expected network for the UX reasons described above. Adding `eth_signTypedData_v3` and `eth_signTypedData` to the `methodsRequiringNetworkSwitch` array will cause the network to switch to the selected network for the requesting origin before initializing the confirmation. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25562?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/25528 See this slack thread: https://consensys.slack.com/archives/C06FXU326RL/p1719429561925649?thread_ts=1719415715.492249&cid=C06FXU326RL ## **Manual testing steps** See videos below 👇 ## **Screenshots/Recordings** ### **Before** https://github.com/MetaMask/metamask-extension/assets/34557516/3e32fce7-046c-4856-893d-a85083877327 ### **After** https://github.com/MetaMask/metamask-extension/assets/34557516/306d88aa-f5f3-4eae-a8e9-30b621c02626 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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: seaona <54408225+seaona@users.noreply.github.com> --- shared/constants/methods-tags.ts | 2 + .../dapp1-send-dapp2-signTypedData.spec.js | 148 ++++++++++++++++++ .../network-account-balance-header.js | 1 + .../confirm-add-suggested-nft.test.js.snap | 2 + .../signature-request-header.test.js.snap | 1 + .../signature-request-original.test.js.snap | 1 + .../signature-request-siwe.test.js.snap | 1 + .../signature-request.test.js.snap | 2 + .../__snapshots__/index.test.js.snap | 1 + .../token-allowance.test.js.snap | 1 + 10 files changed, 160 insertions(+) create mode 100644 test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js diff --git a/shared/constants/methods-tags.ts b/shared/constants/methods-tags.ts index a770fd733cd2..89cce5f67c8d 100644 --- a/shared/constants/methods-tags.ts +++ b/shared/constants/methods-tags.ts @@ -12,6 +12,8 @@ export const methodsRequiringNetworkSwitch = [ 'wallet_switchEthereumChain', 'wallet_addEthereumChain', 'wallet_watchAsset', + 'eth_signTypedData', + 'eth_signTypedData_v3', 'eth_signTypedData_v4', 'personal_sign', ] as const; diff --git a/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js b/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js new file mode 100644 index 000000000000..cd197970baea --- /dev/null +++ b/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js @@ -0,0 +1,148 @@ +const FixtureBuilder = require('../../fixture-builder'); +const { + withFixtures, + openDapp, + unlockWallet, + DAPP_URL, + DAPP_ONE_URL, + regularDelayMs, + defaultGanacheOptions, + WINDOW_TITLES, +} = require('../../helpers'); + +describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () { + it('should queue signTypedData tx after eth_sendTransaction confirmation and signTypedData confirmation should target the correct network after eth_sendTransaction is confirmed @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Connect to dapp 2 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + // switch chain for Dapp One + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x3e8' }], + }); + + // Initiate switchEthereumChain on Dapp one + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + // eth_sendTransaction request + await driver.clickElement('#sendButton'); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + // signTypedData request + await driver.clickElement('#signTypedData'); + + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Check correct network on the send confirmation. + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 7777', + }); + + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Check correct network on the signTypedData confirmation. + await driver.findElement({ + css: '[data-testid="signature-request-network-display"]', + text: 'Localhost 8545', + }); + }, + ); + }); +}); diff --git a/ui/components/app/network-account-balance-header/network-account-balance-header.js b/ui/components/app/network-account-balance-header/network-account-balance-header.js index 98c91acfe81a..9137d53f1f14 100644 --- a/ui/components/app/network-account-balance-header/network-account-balance-header.js +++ b/ui/components/app/network-account-balance-header/network-account-balance-header.js @@ -68,6 +68,7 @@ export default function NetworkAccountBalanceHeader({ variant={TextVariant.bodySm} as="h6" color={TextColor.textAlternative} + data-testid="signature-request-network-display" > {networkName} diff --git a/ui/pages/confirm-add-suggested-nft/__snapshots__/confirm-add-suggested-nft.test.js.snap b/ui/pages/confirm-add-suggested-nft/__snapshots__/confirm-add-suggested-nft.test.js.snap index eca476209a9c..908df23e73fa 100644 --- a/ui/pages/confirm-add-suggested-nft/__snapshots__/confirm-add-suggested-nft.test.js.snap +++ b/ui/pages/confirm-add-suggested-nft/__snapshots__/confirm-add-suggested-nft.test.js.snap @@ -83,6 +83,7 @@ exports[`ConfirmAddSuggestedNFT Component should match snapshot 1`] = ` >
Ethereum Mainnet
@@ -295,6 +296,7 @@ exports[`ConfirmAddSuggestedNFT Component should match snapshot 1`] = ` >
Ethereum Mainnet
diff --git a/ui/pages/confirmations/components/signature-request-header/__snapshots__/signature-request-header.test.js.snap b/ui/pages/confirmations/components/signature-request-header/__snapshots__/signature-request-header.test.js.snap index 005650a69853..c2df41b7f906 100644 --- a/ui/pages/confirmations/components/signature-request-header/__snapshots__/signature-request-header.test.js.snap +++ b/ui/pages/confirmations/components/signature-request-header/__snapshots__/signature-request-header.test.js.snap @@ -71,6 +71,7 @@ exports[`SignatureRequestHeader should match snapshot 1`] = ` >
goerli
diff --git a/ui/pages/confirmations/components/signature-request-original/__snapshots__/signature-request-original.test.js.snap b/ui/pages/confirmations/components/signature-request-original/__snapshots__/signature-request-original.test.js.snap index 9ed331fefe28..c9d4d342a59b 100644 --- a/ui/pages/confirmations/components/signature-request-original/__snapshots__/signature-request-original.test.js.snap +++ b/ui/pages/confirmations/components/signature-request-original/__snapshots__/signature-request-original.test.js.snap @@ -147,6 +147,7 @@ exports[`SignatureRequestOriginal should match snapshot 1`] = ` >
goerli
diff --git a/ui/pages/confirmations/components/signature-request-siwe/__snapshots__/signature-request-siwe.test.js.snap b/ui/pages/confirmations/components/signature-request-siwe/__snapshots__/signature-request-siwe.test.js.snap index a1219a561ba7..5d6a208872d2 100644 --- a/ui/pages/confirmations/components/signature-request-siwe/__snapshots__/signature-request-siwe.test.js.snap +++ b/ui/pages/confirmations/components/signature-request-siwe/__snapshots__/signature-request-siwe.test.js.snap @@ -144,6 +144,7 @@ exports[`SignatureRequestSIWE (Sign in with Ethereum) should match snapshot 1`] >
goerli
diff --git a/ui/pages/confirmations/components/signature-request/__snapshots__/signature-request.test.js.snap b/ui/pages/confirmations/components/signature-request/__snapshots__/signature-request.test.js.snap index 5aabba67b7d2..30011e96a607 100644 --- a/ui/pages/confirmations/components/signature-request/__snapshots__/signature-request.test.js.snap +++ b/ui/pages/confirmations/components/signature-request/__snapshots__/signature-request.test.js.snap @@ -144,6 +144,7 @@ exports[`Signature Request Component render should match snapshot when we are us >
Localhost 8545
@@ -916,6 +917,7 @@ exports[`Signature Request Component render should match snapshot when we want t >
Localhost 8545
diff --git a/ui/pages/confirmations/confirm-signature-request/__snapshots__/index.test.js.snap b/ui/pages/confirmations/confirm-signature-request/__snapshots__/index.test.js.snap index 1e600744a876..a10be0a7a2b2 100644 --- a/ui/pages/confirmations/confirm-signature-request/__snapshots__/index.test.js.snap +++ b/ui/pages/confirmations/confirm-signature-request/__snapshots__/index.test.js.snap @@ -144,6 +144,7 @@ exports[`Confirm Signature Request Component render should match snapshot 1`] = >
Goerli test network
diff --git a/ui/pages/confirmations/token-allowance/__snapshots__/token-allowance.test.js.snap b/ui/pages/confirmations/token-allowance/__snapshots__/token-allowance.test.js.snap index d8ff7862a6da..91ac3c735f84 100644 --- a/ui/pages/confirmations/token-allowance/__snapshots__/token-allowance.test.js.snap +++ b/ui/pages/confirmations/token-allowance/__snapshots__/token-allowance.test.js.snap @@ -163,6 +163,7 @@ exports[`TokenAllowancePage when mounted should match snapshot 1`] = ` >
mainnet
From 58082f542b950f979ac362d7aa428f8181e80b9b Mon Sep 17 00:00:00 2001 From: micaelae <100321200+micaelae@users.noreply.github.com> Date: Fri, 28 Jun 2024 14:31:37 -0700 Subject: [PATCH 06/78] chore: add bridge controller, store and api utils (#25044) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR doesn't have user-facing effects, just setting up state management for subsequent feature PRs. Changes include * a new bridge controller that manages background state for the cross-chain swaps experience * basic redux slice for bridging. Note that the slice extends swaps, since we plan to decouple the frontend components for now but merge the experiences later on * e2e tests for current bridge button behavior * new logic for fetching and setting bridge feature flags [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25044?quickstart=1) ## **Related issues** Fixes: [METABRIDGE-889](https://consensyssoftware.atlassian.net/browse/METABRIDGE-889) ## **Manual testing steps** 1. Load extension 2. Verify that Bridge button behavior has not changed ## **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 completed the PR template to the best of my ability - [X] 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. ## **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. --- .eslintrc.js | 1 + .metamaskrc.dist | 1 + app/scripts/controllers/bridge.test.ts | 29 ++++ app/scripts/controllers/bridge.ts | 36 +++++ app/scripts/lib/setupSentry.js | 7 + app/scripts/metamask-controller.js | 8 ++ builds.yml | 1 + shared/constants/bridge.ts | 9 ++ test/e2e/mock-e2e.js | 14 +- .../bridge-click-from-asset-overview.spec.ts | 42 ++++++ .../bridge-click-from-eth-overview.spec.ts | 27 ++++ test/e2e/tests/bridge/bridge-test-utils.ts | 128 ++++++++++++++++++ ...rs-after-init-opt-in-background-state.json | 7 + .../errors-after-init-opt-in-ui-state.json | 6 + test/jest/mock-store.js | 19 +++ ui/ducks/bridge/actions.ts | 9 ++ ui/ducks/bridge/bridge.test.ts | 31 +++++ ui/ducks/bridge/bridge.ts | 25 ++++ ui/ducks/index.js | 2 + ui/ducks/swaps/swaps.js | 1 + ui/hooks/bridge/useBridging.ts | 15 ++ ui/pages/bridge/bridge.util.test.ts | 59 ++++++++ ui/pages/bridge/bridge.util.ts | 72 ++++++++++ ui/store/actions.test.js | 21 +++ ui/store/actions.ts | 11 ++ 25 files changed, 580 insertions(+), 1 deletion(-) create mode 100644 app/scripts/controllers/bridge.test.ts create mode 100644 app/scripts/controllers/bridge.ts create mode 100644 test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts create mode 100644 test/e2e/tests/bridge/bridge-click-from-eth-overview.spec.ts create mode 100644 test/e2e/tests/bridge/bridge-test-utils.ts create mode 100644 ui/ducks/bridge/actions.ts create mode 100644 ui/ducks/bridge/bridge.test.ts create mode 100644 ui/ducks/bridge/bridge.ts create mode 100644 ui/hooks/bridge/useBridging.ts create mode 100644 ui/pages/bridge/bridge.util.test.ts create mode 100644 ui/pages/bridge/bridge.util.ts diff --git a/.eslintrc.js b/.eslintrc.js index 9f7fed5928ed..0aea1e739e3f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -281,6 +281,7 @@ module.exports = { 'app/scripts/controllers/mmi-controller.test.ts', 'app/scripts/metamask-controller.actions.test.js', 'app/scripts/detect-multiple-instances.test.js', + 'app/scripts/controllers/bridge.test.ts', 'app/scripts/controllers/swaps.test.js', 'app/scripts/controllers/metametrics.test.js', 'app/scripts/controllers/permissions/**/*.test.js', diff --git a/.metamaskrc.dist b/.metamaskrc.dist index c7431cc0719b..429addc860be 100644 --- a/.metamaskrc.dist +++ b/.metamaskrc.dist @@ -6,6 +6,7 @@ INFURA_PROJECT_ID=00000000000 ;PASSWORD=METAMASK PASSWORD ;SEGMENT_WRITE_KEY= +;BRIDGE_USE_DEV_APIS= ;SWAPS_USE_DEV_APIS= ;PORTFOLIO_URL= ;TRANSACTION_SECURITY_PROVIDER= diff --git a/app/scripts/controllers/bridge.test.ts b/app/scripts/controllers/bridge.test.ts new file mode 100644 index 000000000000..a6001f7aa0d7 --- /dev/null +++ b/app/scripts/controllers/bridge.test.ts @@ -0,0 +1,29 @@ +import BridgeController from './bridge'; + +const EMPTY_INIT_STATE = { + bridgeState: { + bridgeFeatureFlags: { + extensionSupport: false, + }, + }, +}; + +describe('BridgeController', function () { + let bridgeController: BridgeController; + + beforeAll(function () { + bridgeController = new BridgeController(); + }); + + it('constructor should setup correctly', function () { + expect(bridgeController.store.getState()).toStrictEqual(EMPTY_INIT_STATE); + }); + + it('setBridgeFeatureFlags should set the bridge feature flags', function () { + const featureFlagsResponse = { extensionSupport: true }; + bridgeController.setBridgeFeatureFlags(featureFlagsResponse); + expect( + bridgeController.store.getState().bridgeState.bridgeFeatureFlags, + ).toStrictEqual(featureFlagsResponse); + }); +}); diff --git a/app/scripts/controllers/bridge.ts b/app/scripts/controllers/bridge.ts new file mode 100644 index 000000000000..23323371ea4c --- /dev/null +++ b/app/scripts/controllers/bridge.ts @@ -0,0 +1,36 @@ +import { ObservableStore } from '@metamask/obs-store'; + +export enum BridgeFeatureFlagsKey { + EXTENSION_SUPPORT = 'extensionSupport', +} + +export type BridgeFeatureFlags = { + [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: boolean; +}; + +const initialState = { + bridgeState: { + bridgeFeatureFlags: { + [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: false, + }, + }, +}; + +export default class BridgeController { + store = new ObservableStore(initialState); + + resetState = () => { + this.store.updateState({ + bridgeState: { + ...initialState.bridgeState, + }, + }); + }; + + setBridgeFeatureFlags = (bridgeFeatureFlags: BridgeFeatureFlags) => { + const { bridgeState } = this.store.getState(); + this.store.updateState({ + bridgeState: { ...bridgeState, bridgeFeatureFlags }, + }); + }; +} diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 92173fc5f72c..a6b47eef9376 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -120,6 +120,13 @@ export const SENTRY_BACKGROUND_STATE = { MultichainBalancesController: { balances: false, }, + BridgeController: { + bridgeState: { + bridgeFeatureFlags: { + extensionSupport: false, + }, + }, + }, CronjobController: { jobs: false, }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 360e30a89303..b94c010feda1 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -321,6 +321,7 @@ import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verific import { updateSecurityAlertResponse } from './lib/ppom/ppom-util'; import createEvmMethodsToNonEvmAccountReqFilterMiddleware from './lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware'; import { isEthAddress } from './lib/multichain/address'; +import BridgeController from './controllers/bridge'; export const METAMASK_CONTROLLER_EVENTS = { // Fired after state changes that impact the extension badge (unapproved msg count) @@ -1973,6 +1974,7 @@ export default class MetamaskController extends EventEmitter { }, initState.SwapsController, ); + this.bridgeController = new BridgeController(); this.smartTransactionsController = new SmartTransactionsController( { getNetworkClientById: this.networkController.getNetworkClientById.bind( @@ -2202,6 +2204,7 @@ export default class MetamaskController extends EventEmitter { EncryptionPublicKeyController: this.encryptionPublicKeyController, SignatureController: this.signatureController, SwapsController: this.swapsController.store, + BridgeController: this.bridgeController.store, EnsController: this.ensController, ApprovalController: this.approvalController, PPOMController: this.ppomController, @@ -3022,6 +3025,7 @@ export default class MetamaskController extends EventEmitter { appMetadataController, permissionController, preferencesController, + bridgeController, swapsController, tokensController, smartTransactionsController, @@ -3627,6 +3631,10 @@ export default class MetamaskController extends EventEmitter { setSwapsQuotesPollingLimitEnabled: swapsController.setSwapsQuotesPollingLimitEnabled.bind(swapsController), + // Bridge + setBridgeFeatureFlags: + bridgeController.setBridgeFeatureFlags.bind(bridgeController), + // Smart Transactions fetchSmartTransactionFees: smartTransactionsController.getFees.bind( smartTransactionsController, diff --git a/builds.yml b/builds.yml index aa93f756db49..0e83d86303b0 100644 --- a/builds.yml +++ b/builds.yml @@ -135,6 +135,7 @@ features: # env object supports both declarations (- FOO), and definitions (- FOO: BAR). # Variables that were declared have to be defined somewhere in the load chain before usage env: + - BRIDGE_USE_DEV_APIS: false - SWAPS_USE_DEV_APIS: false - PORTFOLIO_URL: https://portfolio.metamask.io - TOKEN_ALLOWANCE_IMPROVEMENTS: false diff --git a/shared/constants/bridge.ts b/shared/constants/bridge.ts index e02c992bbbba..6ac9cfd4245e 100644 --- a/shared/constants/bridge.ts +++ b/shared/constants/bridge.ts @@ -1,5 +1,6 @@ import { CHAIN_IDS } from './network'; +// TODO read from feature flags export const ALLOWED_BRIDGE_CHAIN_IDS = [ CHAIN_IDS.MAINNET, CHAIN_IDS.BSC, @@ -11,3 +12,11 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [ CHAIN_IDS.LINEA_MAINNET, CHAIN_IDS.BASE, ]; + +const BRIDGE_DEV_API_BASE_URL = 'https://bridge.dev-api.cx.metamask.io'; +const BRIDGE_PROD_API_BASE_URL = 'https://bridge.api.cx.metamask.io'; +export const BRIDGE_API_BASE_URL = process.env.BRIDGE_USE_DEV_APIS + ? BRIDGE_DEV_API_BASE_URL + : BRIDGE_PROD_API_BASE_URL; + +export const BRIDGE_CLIENT_ID = 'extension'; diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index f3279377114d..50bd7633f8d3 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -629,6 +629,15 @@ async function setupMocking( return [...privacyReport].sort(); } + /** + * Excludes hosts from the privacyReport if they are refered to by the MetaMask Portfolio + * in a different tab. This is because the Portfolio is a separate application + * + * @param request + */ + const portfolioRequestsMatcher = (request) => + request.headers.referer === 'https://portfolio.metamask.io/'; + /** * Listen for requests and add the hostname to the privacy report if it did * not previously exist. This is used to track which hosts are requested @@ -638,7 +647,10 @@ async function setupMocking( * operation. See the browserAPIRequestDomains regex above. */ server.on('request-initiated', (request) => { - if (request.headers.host.match(browserAPIRequestDomains) === null) { + if ( + request.headers.host.match(browserAPIRequestDomains) === null && + !portfolioRequestsMatcher(request) + ) { privacyReport.add(request.headers.host); } }); diff --git a/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts b/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts new file mode 100644 index 000000000000..24f0bc0fb233 --- /dev/null +++ b/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts @@ -0,0 +1,42 @@ +import { Suite } from 'mocha'; +import { withFixtures, logInWithBalanceValidation } from '../../helpers'; +import { Ganache } from '../../seeder/ganache'; +import GanacheContractAddressRegistry from '../../seeder/ganache-contract-address-registry'; +import { Driver } from '../../webdriver/driver'; +import { BridgePage, getBridgeFixtures } from './bridge-test-utils'; + +describe('Click bridge button from asset page @no-mmi', function (this: Suite) { + it('loads portfolio tab when flag is turned off', async function () { + await withFixtures( + getBridgeFixtures(this.test?.fullTitle()), + async ({ + driver, + ganacheServer, + contractRegistry, + }: { + driver: Driver; + ganacheServer: Ganache; + contractRegistry: GanacheContractAddressRegistry; + }) => { + const bridgePage = new BridgePage(driver); + await logInWithBalanceValidation(driver, ganacheServer); + + // ETH + await bridgePage.loadAssetPage(contractRegistry); + await bridgePage.load('coin-overview'); + await bridgePage.verifyPortfolioTab( + 'https://portfolio.metamask.io/bridge?metametricsId=null', + ); + + await bridgePage.reloadHome(); + + // TST + await bridgePage.loadAssetPage(contractRegistry, 'TST'); + await bridgePage.load('token-overview'); + await bridgePage.verifyPortfolioTab( + 'https://portfolio.metamask.io/bridge?metametricsId=null', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/bridge/bridge-click-from-eth-overview.spec.ts b/test/e2e/tests/bridge/bridge-click-from-eth-overview.spec.ts new file mode 100644 index 000000000000..0a6098e01592 --- /dev/null +++ b/test/e2e/tests/bridge/bridge-click-from-eth-overview.spec.ts @@ -0,0 +1,27 @@ +import { Suite } from 'mocha'; +import { withFixtures, logInWithBalanceValidation } from '../../helpers'; +import { Ganache } from '../../seeder/ganache'; +import { Driver } from '../../webdriver/driver'; +import { BridgePage, getBridgeFixtures } from './bridge-test-utils'; + +describe('Click bridge button from wallet overview @no-mmi', function (this: Suite) { + it('loads portfolio tab when flag is turned off', async function () { + await withFixtures( + getBridgeFixtures(this.test?.fullTitle()), + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer: Ganache; + }) => { + const bridgePage = new BridgePage(driver); + await logInWithBalanceValidation(driver, ganacheServer); + await bridgePage.load(); + await bridgePage.verifyPortfolioTab( + 'https://portfolio.metamask.io/bridge?metametricsId=null', + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/bridge/bridge-test-utils.ts b/test/e2e/tests/bridge/bridge-test-utils.ts new file mode 100644 index 000000000000..157876f43769 --- /dev/null +++ b/test/e2e/tests/bridge/bridge-test-utils.ts @@ -0,0 +1,128 @@ +import { strict as assert } from 'assert'; +import { Mockttp } from 'mockttp'; +import FixtureBuilder from '../../fixture-builder'; +import { + WINDOW_TITLES, + clickNestedButton, + generateGanacheOptions, +} from '../../helpers'; +import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import GanacheContractAddressRegistry from '../../seeder/ganache-contract-address-registry'; +import { Driver } from '../../webdriver/driver'; + +export class BridgePage { + driver: Driver; + + constructor(driver: Driver) { + this.driver = driver; + } + + load = async ( + location: + | 'wallet-overview' + | 'coin-overview' + | 'token-overview' = 'wallet-overview', + ) => { + let bridgeButtonTestIdPrefix; + switch (location) { + case 'wallet-overview': + bridgeButtonTestIdPrefix = 'eth'; + break; + case 'coin-overview': // native asset page + bridgeButtonTestIdPrefix = 'coin'; + break; + case 'token-overview': + default: + bridgeButtonTestIdPrefix = 'token'; + } + await this.driver.clickElement( + `[data-testid="${bridgeButtonTestIdPrefix}-overview-bridge"]`, + ); + }; + + reloadHome = async (shouldCloseWindow = true) => { + if (shouldCloseWindow) { + await this.driver.closeWindow(); + await this.driver.delay(2000); + await this.driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + } + await this.driver.navigate(); + }; + + loadAssetPage = async ( + contractRegistry: GanacheContractAddressRegistry, + symbol?: string, + ) => { + let tokenListItem; + + if (symbol) { + // Import token + const contractAddress = await contractRegistry.getContractAddress( + SMART_CONTRACTS.HST, + ); + await this.driver.clickElement({ + text: 'Import tokens', + tag: 'button', + }); + await clickNestedButton(this.driver, 'Custom token'); + await this.driver.fill( + '[data-testid="import-tokens-modal-custom-address"]', + contractAddress, + ); + await this.driver.waitForSelector( + '[data-testid="import-tokens-modal-custom-decimals"]', + ); + await this.driver.clickElement({ + text: 'Next', + tag: 'button', + }); + await this.driver.clickElement( + '[data-testid="import-tokens-modal-import-button"]', + ); + await this.driver.delay(2000); + tokenListItem = await this.driver.findElement({ text: symbol }); + } else { + tokenListItem = await this.driver.findElement( + '[data-testid="multichain-token-list-button"]', + ); + } + await tokenListItem.click(); + assert.ok((await this.driver.getCurrentUrl()).includes('asset')); + }; + + verifyPortfolioTab = async (url: string) => { + await this.driver.delay(4000); + await this.driver.switchToWindowWithTitle('MetaMask Portfolio - Bridge'); + assert.equal(await this.driver.getCurrentUrl(), url); + }; + + verifySwapPage = async () => { + const currentUrl = await this.driver.getCurrentUrl(); + assert.ok(currentUrl.includes('cross-chain/swaps')); + }; +} + +export const getBridgeFixtures = ( + title?: string, + testSpecificMock?: (server: Mockttp) => Promise, +) => { + return { + driverOptions: { + openDevToolsForTabs: true, + }, + fixtures: new FixtureBuilder({ inputChainId: CHAIN_IDS.MAINNET }) + .withNetworkControllerOnMainnet() + .withTokensControllerERC20() + .build(), + testSpecificMock, + smartContract: SMART_CONTRACTS.HST, + ganacheOptions: generateGanacheOptions({ + hardfork: 'london', + chain: { chainId: CHAIN_IDS.MAINNET }, + }), + title, + }; +}; diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 84fef8e26a90..a7169143d03e 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -59,6 +59,13 @@ "approvalFlows": "object" }, "AuthenticationController": { "isSignedIn": "boolean" }, + "BridgeController": { + "bridgeState": { + "bridgeFeatureFlags": { + "extensionSupport": "boolean" + } + } + }, "CronjobController": { "jobs": "object" }, "CurrencyController": { "currencyRates": { diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index 4b7972acd311..a79a2e543573 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -2,6 +2,7 @@ "DNS": "object", "activeTab": "object", "appState": "object", + "bridge": "object", "confirm": "object", "confirmAlerts": "object", "confirmTransaction": "object", @@ -60,6 +61,11 @@ }, "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, + "bridgeState": { + "bridgeFeatureFlags": { + "extensionSupport": "boolean" + } + }, "browserEnvironment": { "os": "string", "browser": "string" }, "popupGasPollTokens": "object", "notificationGasPollTokens": "object", diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index 365d9b00bdbd..85f617450136 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -666,3 +666,22 @@ export const createSwapsMockStore = () => { }, }; }; + +export const createBridgeMockStore = () => { + const swapsStore = createSwapsMockStore(); + return { + ...swapsStore, + bridge: { + toChain: null, + }, + metamask: { + ...swapsStore.metamask, + bridgeState: { + ...(swapsStore.metamask.bridgeState ?? {}), + bridgeFeatureFlags: { + extensionSupport: false, + }, + }, + }, + }; +}; diff --git a/ui/ducks/bridge/actions.ts b/ui/ducks/bridge/actions.ts new file mode 100644 index 000000000000..a9ce6f825b0f --- /dev/null +++ b/ui/ducks/bridge/actions.ts @@ -0,0 +1,9 @@ +import { swapsSlice } from '../swaps/swaps'; +import { bridgeSlice } from './bridge'; + +// Bridge actions + +// eslint-disable-next-line no-empty-pattern +const {} = swapsSlice.actions; + +export const { setToChain } = bridgeSlice.actions; diff --git a/ui/ducks/bridge/bridge.test.ts b/ui/ducks/bridge/bridge.test.ts new file mode 100644 index 000000000000..5aff92b54251 --- /dev/null +++ b/ui/ducks/bridge/bridge.test.ts @@ -0,0 +1,31 @@ +import nock from 'nock'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; + +import { createBridgeMockStore } from '../../../test/jest/mock-store'; +import { CHAIN_IDS } from '../../../shared/constants/network'; +import bridgeReducer from './bridge'; +import { setToChain } from './actions'; + +const middleware = [thunk]; + +describe('Ducks - Bridge', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const store = configureMockStore(middleware)(createBridgeMockStore()); + + afterEach(() => { + nock.cleanAll(); + }); + + describe('setToChain', () => { + it('calls the "bridge/setToChain" action', () => { + const state = store.getState().bridge; + const actionPayload = CHAIN_IDS.BSC; + store.dispatch(setToChain(actionPayload)); + const actions = store.getActions(); + expect(actions[0].type).toBe('bridge/setToChain'); + const newState = bridgeReducer(state, actions[0]); + expect(newState.toChain).toBe(actionPayload); + }); + }); +}); diff --git a/ui/ducks/bridge/bridge.ts b/ui/ducks/bridge/bridge.ts new file mode 100644 index 000000000000..2f6b4aabd775 --- /dev/null +++ b/ui/ducks/bridge/bridge.ts @@ -0,0 +1,25 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { swapsSlice } from '../swaps/swaps'; + +// Only states that are not in swaps slice +type BridgeState = { + toChain: string | null; +}; + +const initialState: BridgeState = { + toChain: null, +}; + +const bridgeSlice = createSlice({ + name: 'bridge', + initialState: { ...initialState }, + reducers: { + ...swapsSlice.reducer, + setToChain: (state, action) => { + state.toChain = action.payload; + }, + }, +}); + +export { bridgeSlice }; +export default bridgeSlice.reducer; diff --git a/ui/ducks/index.js b/ui/ducks/index.js index f72918460655..069bd385c093 100644 --- a/ui/ducks/index.js +++ b/ui/ducks/index.js @@ -10,6 +10,7 @@ import confirmTransactionReducer from './confirm-transaction/confirm-transaction import gasReducer from './gas/gas.duck'; import { invalidCustomNetwork, unconnectedAccount } from './alerts'; import swapsReducer from './swaps/swaps'; +import bridgeReducer from './bridge/bridge'; import historyReducer from './history/history'; import rampsReducer from './ramps/ramps'; import confirmAlertsReducer from './confirm-alerts/confirm-alerts'; @@ -28,6 +29,7 @@ export default combineReducers({ confirmTransaction: confirmTransactionReducer, swaps: swapsReducer, ramps: rampsReducer, + bridge: bridgeReducer, gas: gasReducer, localeMessages: localeMessagesReducer, }); diff --git a/ui/ducks/swaps/swaps.js b/ui/ducks/swaps/swaps.js index 59fadda433d5..9c15aec7435b 100644 --- a/ui/ducks/swaps/swaps.js +++ b/ui/ducks/swaps/swaps.js @@ -511,6 +511,7 @@ export { swapCustomGasModalLimitEdited, swapCustomGasModalClosed, setTransactionSettingsOpened, + slice as swapsSlice, }; export const navigateBackToBuildQuote = (history) => { diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts new file mode 100644 index 000000000000..9e6356d99941 --- /dev/null +++ b/ui/hooks/bridge/useBridging.ts @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { fetchBridgeFeatureFlags } from '../../pages/bridge/bridge.util'; +import { setBridgeFeatureFlags } from '../../store/actions'; + +const useBridging = () => { + const dispatch = useDispatch(); + + useEffect(() => { + fetchBridgeFeatureFlags().then((bridgeFeatureFlags) => { + dispatch(setBridgeFeatureFlags(bridgeFeatureFlags)); + }); + }, [dispatch, setBridgeFeatureFlags]); +}; +export default useBridging; diff --git a/ui/pages/bridge/bridge.util.test.ts b/ui/pages/bridge/bridge.util.test.ts new file mode 100644 index 000000000000..d693f92dc956 --- /dev/null +++ b/ui/pages/bridge/bridge.util.test.ts @@ -0,0 +1,59 @@ +import fetchWithCache from '../../../shared/lib/fetch-with-cache'; +import { fetchBridgeFeatureFlags } from './bridge.util'; + +jest.mock('../../../shared/lib/fetch-with-cache'); + +describe('Bridge utils', () => { + it('should fetch bridge feature flags successfully', async () => { + const mockResponse = { + 'extension-support': true, + }; + + (fetchWithCache as jest.Mock).mockResolvedValue(mockResponse); + + const result = await fetchBridgeFeatureFlags(); + + expect(fetchWithCache).toHaveBeenCalledWith({ + url: 'https://bridge.api.cx.metamask.io/getAllFeatureFlags', + fetchOptions: { + method: 'GET', + headers: { 'X-Client-Id': 'extension' }, + }, + cacheOptions: { cacheRefreshTime: 600000 }, + functionName: 'fetchBridgeFeatureFlags', + }); + + expect(result).toEqual({ extensionSupport: true }); + }); + + it('should use fallback bridge feature flags if response is unexpected', async () => { + const mockResponse = { + flag1: true, + flag2: false, + }; + + (fetchWithCache as jest.Mock).mockResolvedValue(mockResponse); + + const result = await fetchBridgeFeatureFlags(); + + expect(fetchWithCache).toHaveBeenCalledWith({ + url: 'https://bridge.api.cx.metamask.io/getAllFeatureFlags', + fetchOptions: { + method: 'GET', + headers: { 'X-Client-Id': 'extension' }, + }, + cacheOptions: { cacheRefreshTime: 600000 }, + functionName: 'fetchBridgeFeatureFlags', + }); + + expect(result).toEqual({ extensionSupport: false }); + }); + + it('should handle fetch error', async () => { + const mockError = new Error('Failed to fetch'); + + (fetchWithCache as jest.Mock).mockRejectedValue(mockError); + + await expect(fetchBridgeFeatureFlags()).rejects.toThrowError(mockError); + }); +}); diff --git a/ui/pages/bridge/bridge.util.ts b/ui/pages/bridge/bridge.util.ts new file mode 100644 index 000000000000..d7fae190e28f --- /dev/null +++ b/ui/pages/bridge/bridge.util.ts @@ -0,0 +1,72 @@ +import { + BridgeFeatureFlagsKey, + BridgeFeatureFlags, +} from '../../../app/scripts/controllers/bridge'; +import { + BRIDGE_API_BASE_URL, + BRIDGE_CLIENT_ID, +} from '../../../shared/constants/bridge'; +import { MINUTE } from '../../../shared/constants/time'; +import fetchWithCache from '../../../shared/lib/fetch-with-cache'; +import { validateData } from '../../../shared/lib/swaps-utils'; + +const CLIENT_ID_HEADER = { 'X-Client-Id': BRIDGE_CLIENT_ID }; +const CACHE_REFRESH_TEN_MINUTES = 10 * MINUTE; + +// Types copied from Metabridge API +enum BridgeFlag { + EXTENSION_SUPPORT = 'extension-support', +} + +type FeatureFlagResponse = { + [BridgeFlag.EXTENSION_SUPPORT]: boolean; +}; +// End of copied types + +type Validator = { + property: keyof T; + type: string; + validator: (value: unknown) => boolean; +}; + +const validateResponse = ( + validators: Validator[], + data: unknown, + urlUsed: string, +): data is T => { + return validateData(validators, data, urlUsed); +}; + +export async function fetchBridgeFeatureFlags(): Promise { + const url = `${BRIDGE_API_BASE_URL}/getAllFeatureFlags`; + const rawFeatureFlags = await fetchWithCache({ + url, + fetchOptions: { method: 'GET', headers: CLIENT_ID_HEADER }, + cacheOptions: { cacheRefreshTime: CACHE_REFRESH_TEN_MINUTES }, + functionName: 'fetchBridgeFeatureFlags', + }); + + if ( + validateResponse( + [ + { + property: BridgeFlag.EXTENSION_SUPPORT, + type: 'boolean', + validator: (v) => typeof v === 'boolean', + }, + ], + rawFeatureFlags, + url, + ) + ) { + return { + [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: + rawFeatureFlags[BridgeFlag.EXTENSION_SUPPORT], + }; + } + + return { + // TODO set default to true once bridging is live + [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: false, + }; +} diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index aa161ba781a1..255d0b26adce 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -2690,4 +2690,25 @@ describe('Actions', () => { expect(store.getActions()).toStrictEqual(expectedActions); }); }); + + describe('#setBridgeFeatureFlags', () => { + it('calls setBridgeFeatureFlags in the background', async () => { + const store = mockStore(); + background.setBridgeFeatureFlags = sinon + .stub() + .callsFake((_, cb) => cb()); + setBackgroundConnection(background); + + await store.dispatch( + actions.setBridgeFeatureFlags({ extensionSupport: true }), + ); + + expect(background.setBridgeFeatureFlags.callCount).toStrictEqual(1); + expect(background.setBridgeFeatureFlags.getCall(0).args[0]).toStrictEqual( + { + extensionSupport: true, + }, + ); + }); + }); }); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 186e59ccb8df..1bc6a437f121 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -113,6 +113,7 @@ import { import { ThemeType } from '../../shared/constants/preferences'; import { FirstTimeFlowType } from '../../shared/constants/onboarding'; import type { MarkAsReadNotificationsParam } from '../../app/scripts/controllers/metamask-notifications/types/notification/notification'; +import { BridgeFeatureFlags } from '../../app/scripts/controllers/bridge'; import * as actionConstants from './actionConstants'; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) import { updateCustodyState } from './institutional/institution-actions'; @@ -3889,6 +3890,16 @@ export function setInitialGasEstimate( }; } +// Bridge +export function setBridgeFeatureFlags( + featureFlags: BridgeFeatureFlags, +): ThunkAction { + return async (dispatch: MetaMaskReduxDispatch) => { + await submitRequestToBackground('setBridgeFeatureFlags', [featureFlags]); + await forceUpdateMetamaskState(dispatch); + }; +} + // Permissions export function requestAccountsPermissionWithId( From f353f0c316a18510a71ac3c4d45b765f78475028 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Sat, 29 Jun 2024 06:42:08 +0800 Subject: [PATCH 07/78] chore(deps): bump assets controller to v34.0.0 (#25540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR bumps `@metamask/assets-controllers` to `^34.0.0`. ## **Related issues** Fixes https://github.com/MetaMask/accounts-planning/issues/481 ## **Manual testing steps** This PR affects all assets related tokens 1. Test added and removing of and tokens 2. Transfer tokens 3. Turn on off token detection and see the detected tokens 4. Check if the token rate values on mainnet ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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: MetaMask Bot --- app/scripts/metamask-controller.js | 150 ++++++------------------ app/scripts/metamask-controller.test.js | 103 +++++++--------- lavamoat/browserify/beta/policy.json | 1 - lavamoat/browserify/flask/policy.json | 1 - lavamoat/browserify/main/policy.json | 1 - lavamoat/browserify/mmi/policy.json | 1 - package.json | 2 +- yarn.lock | 21 ++-- 8 files changed, 92 insertions(+), 188 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b94c010feda1..88491e2af62c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -524,12 +524,6 @@ export default class MetamaskController extends EventEmitter { this.networkController.getProviderAndBlockTracker().blockTracker; this.deprecatedNetworkVersions = {}; - const tokenListMessenger = this.controllerMessenger.getRestricted({ - name: 'TokenListController', - allowedEvents: ['NetworkController:stateChange'], - allowedActions: ['NetworkController:getNetworkClientById'], - }); - const accountsControllerMessenger = this.controllerMessenger.getRestricted({ name: 'AccountsController', allowedEvents: [ @@ -567,6 +561,12 @@ export default class MetamaskController extends EventEmitter { networkConfigurations: this.networkController.state.networkConfigurations, }); + const tokenListMessenger = this.controllerMessenger.getRestricted({ + name: 'TokenListController', + allowedActions: ['NetworkController:getNetworkClientById'], + allowedEvents: ['NetworkController:stateChange'], + }); + this.tokenListController = new TokenListController({ chainId: this.networkController.state.providerConfig.chainId, preventPollingOnNetworkRestart: !this.#isTokenListPollingRequired( @@ -604,49 +604,21 @@ export default class MetamaskController extends EventEmitter { allowedActions: [ 'ApprovalController:addRequest', 'NetworkController:getNetworkClientById', + 'AccountsController:getSelectedAccount', + 'AccountsController:getAccount', ], allowedEvents: [ 'NetworkController:networkDidChange', - 'AccountsController:selectedAccountChange', + 'AccountsController:selectedEvmAccountChange', 'PreferencesController:stateChange', 'TokenListController:stateChange', ], }); this.tokensController = new TokensController({ + state: initState.TokensController, + provider: this.provider, messenger: tokensControllerMessenger, chainId: this.networkController.state.providerConfig.chainId, - // TODO: The tokens controller currently does not support internalAccounts. This is done to match the behavior of the previous tokens controller subscription. - onPreferencesStateChange: (listener) => - this.controllerMessenger.subscribe( - `AccountsController:selectedAccountChange`, - (newlySelectedInternalAccount) => { - listener({ selectedAddress: newlySelectedInternalAccount.address }); - }, - ), - onNetworkDidChange: (cb) => - networkControllerMessenger.subscribe( - 'NetworkController:networkDidChange', - () => { - const networkState = this.networkController.state; - return cb(networkState); - }, - ), - onTokenListStateChange: (listener) => - this.controllerMessenger.subscribe( - `${this.tokenListController.name}:stateChange`, - listener, - ), - getNetworkClientById: this.networkController.getNetworkClientById.bind( - this.networkController, - ), - config: { - provider: this.provider, - selectedAddress: - initState.AccountsController?.internalAccounts?.accounts[ - initState.AccountsController?.internalAccounts?.selectedAccount - ]?.address ?? '', - }, - state: initState.TokensController, }); const nftControllerMessenger = this.controllerMessenger.getRestricted({ @@ -664,15 +636,9 @@ export default class MetamaskController extends EventEmitter { ], }); this.nftController = new NftController({ + state: initState.NftController, messenger: nftControllerMessenger, chainId: this.networkController.state.providerConfig.chainId, - onPreferencesStateChange: this.preferencesController.store.subscribe.bind( - this.preferencesController.store, - ), - onNetworkStateChange: networkControllerMessenger.subscribe.bind( - networkControllerMessenger, - 'NetworkController:stateChange', - ), getERC721AssetName: this.assetsContractController.getERC721AssetName.bind( this.assetsContractController, ), @@ -706,10 +672,6 @@ export default class MetamaskController extends EventEmitter { source, }, }), - getNetworkClientById: this.networkController.getNetworkClientById.bind( - this.networkController, - ), - state: initState.NftController, }); this.nftController.setApiKey(process.env.OPENSEA_KEY); @@ -718,14 +680,13 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.getRestricted({ name: 'NftDetectionController', allowedEvents: [ - 'PreferencesController:stateChange', 'NetworkController:stateChange', + 'PreferencesController:stateChange', ], allowedActions: [ 'ApprovalController:addRequest', 'NetworkController:getState', 'NetworkController:getNetworkClientById', - 'PreferencesController:getState', 'AccountsController:getSelectedAccount', ], }); @@ -733,21 +694,12 @@ export default class MetamaskController extends EventEmitter { this.nftDetectionController = new NftDetectionController({ messenger: nftDetectionControllerMessenger, chainId: this.networkController.state.providerConfig.chainId, - onNftsStateChange: (listener) => this.nftController.subscribe(listener), - onPreferencesStateChange: this.preferencesController.store.subscribe.bind( - this.preferencesController.store, - ), - onNetworkStateChange: networkControllerMessenger.subscribe.bind( - networkControllerMessenger, - 'NetworkController:stateChange', - ), getOpenSeaApiKey: () => this.nftController.openSeaApiKey, getBalancesInSingleCall: this.assetsContractController.getBalancesInSingleCall.bind( this.assetsContractController, ), addNft: this.nftController.addNft.bind(this.nftController), - getNftApi: this.nftController.getNftApi.bind(this.nftController), getNftState: () => this.nftController.state, // added this to track previous value of useNftDetection, should be true on very first initializing of controller[] disabled: @@ -755,11 +707,6 @@ export default class MetamaskController extends EventEmitter { undefined ? false // the detection is enabled by default : !this.preferencesController.store.getState().useNftDetection, - selectedAddress: - this.preferencesController.store.getState().selectedAddress, - getNetworkClientById: this.networkController.getNetworkClientById.bind( - this.networkController, - ), }); this.metaMetricsController = new MetaMetricsController({ @@ -953,55 +900,29 @@ export default class MetamaskController extends EventEmitter { fetchMultiExchangeRate, }); - const tokenRatesControllerMessenger = - this.controllerMessenger.getRestricted({ - name: 'TokenRatesController', - allowedEvents: [ - 'PreferencesController:stateChange', - 'TokensController:stateChange', - 'NetworkController:stateChange', - ], - allowedActions: [ - 'TokensController:getState', - 'NetworkController:getNetworkClientById', - 'NetworkController:getState', - 'PreferencesController:getState', - ], - }); + const tokenRatesMessenger = this.controllerMessenger.getRestricted({ + name: 'TokenRatesController', + allowedActions: [ + 'TokensController:getState', + 'NetworkController:getNetworkClientById', + 'NetworkController:getState', + 'AccountsController:getAccount', + 'AccountsController:getSelectedAccount', + ], + allowedEvents: [ + 'NetworkController:stateChange', + 'AccountsController:selectedEvmAccountChange', + 'PreferencesController:stateChange', + 'TokensController:stateChange', + ], + }); // token exchange rate tracker - this.tokenRatesController = new TokenRatesController( - { - messenger: tokenRatesControllerMessenger, - chainId: this.networkController.state.providerConfig.chainId, - ticker: this.networkController.state.providerConfig.ticker, - selectedAddress: this.accountsController.getSelectedAccount().address, - onTokensStateChange: (listener) => - this.tokensController.subscribe(listener), - onNetworkStateChange: networkControllerMessenger.subscribe.bind( - networkControllerMessenger, - 'NetworkController:stateChange', - ), - onPreferencesStateChange: (listener) => - this.controllerMessenger.subscribe( - `AccountsController:selectedAccountChange`, - (newlySelectedInternalAccount) => { - listener({ - selectedAddress: newlySelectedInternalAccount.address, - }); - }, - ), - tokenPricesService: new CodefiTokenPricesServiceV2(), - getNetworkClientById: this.networkController.getNetworkClientById.bind( - this.networkController, - ), - }, - { - allTokens: this.tokensController.state.allTokens, - allDetectedTokens: this.tokensController.state.allDetectedTokens, - }, - initState.TokenRatesController, - ); + this.tokenRatesController = new TokenRatesController({ + state: initState.TokenRatesController, + messenger: tokenRatesMessenger, + tokenPricesService: new CodefiTokenPricesServiceV2(), + }); this.preferencesController.store.subscribe( previousValueComparator((prevState, currState) => { @@ -1628,6 +1549,7 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.getRestricted({ name: 'TokenDetectionController', allowedActions: [ + 'AccountsController:getAccount', 'AccountsController:getSelectedAccount', 'KeyringController:getState', 'NetworkController:getNetworkClientById', @@ -1639,7 +1561,7 @@ export default class MetamaskController extends EventEmitter { 'TokensController:addDetectedTokens', ], allowedEvents: [ - 'AccountsController:selectedAccountChange', + 'AccountsController:selectedEvmAccountChange', 'KeyringController:lock', 'KeyringController:unlock', 'NetworkController:networkDidChange', diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 771378568020..1ad12736fe2a 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -662,59 +662,46 @@ describe('MetaMaskController', () => { it('should clear previous identities after vault restoration', async () => { jest.spyOn(metamaskController, 'getBalance').mockResolvedValue('0x0'); - let startTime = Date.now(); await metamaskController.createNewVaultAndRestore( 'foobar1337', TEST_SEED, ); - let endTime = Date.now(); - const firstVaultIdentities = cloneDeep( - metamaskController.getState().identities, + const firstVaultAccounts = cloneDeep( + metamaskController.accountsController.listAccounts(), ); - expect( - firstVaultIdentities[TEST_ADDRESS].lastSelected >= startTime && - firstVaultIdentities[TEST_ADDRESS].lastSelected <= endTime, - ).toStrictEqual(true); - delete firstVaultIdentities[TEST_ADDRESS].lastSelected; - expect(firstVaultIdentities).toStrictEqual({ - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, - }); + expect(firstVaultAccounts).toHaveLength(1); + expect(firstVaultAccounts[0].address).toBe(TEST_ADDRESS); - await metamaskController.preferencesController.setAccountLabel( - TEST_ADDRESS, + const selectedAccount = + metamaskController.accountsController.getSelectedAccount(); + metamaskController.accountsController.setAccountName( + selectedAccount.id, 'Account Foo', ); - const labelledFirstVaultIdentities = cloneDeep( - metamaskController.getState().identities, + const labelledFirstVaultAccounts = cloneDeep( + metamaskController.accountsController.listAccounts(), ); - delete labelledFirstVaultIdentities[TEST_ADDRESS].lastSelected; - expect(labelledFirstVaultIdentities).toStrictEqual({ - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, - }); - startTime = Date.now(); + expect(labelledFirstVaultAccounts[0].address).toBe(TEST_ADDRESS); + expect(labelledFirstVaultAccounts[0].metadata.name).toBe('Account Foo'); + await metamaskController.createNewVaultAndRestore( 'foobar1337', TEST_SEED_ALT, ); - endTime = Date.now(); - const secondVaultIdentities = cloneDeep( - metamaskController.getState().identities, + const secondVaultAccounts = cloneDeep( + metamaskController.accountsController.listAccounts(), ); + + expect(secondVaultAccounts).toHaveLength(1); expect( - secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected >= startTime && - secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected <= endTime, - ).toStrictEqual(true); - delete secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected; - expect(secondVaultIdentities).toStrictEqual({ - [TEST_ADDRESS_ALT]: { - address: TEST_ADDRESS_ALT, - name: DEFAULT_LABEL, - }, - }); + metamaskController.accountsController.getSelectedAccount().address, + ).toBe(TEST_ADDRESS_ALT); + expect(secondVaultAccounts[0].address).toBe(TEST_ADDRESS_ALT); + expect(secondVaultAccounts[0].metadata.name).toBe(DEFAULT_LABEL); }); it('should restore any consecutive accounts with balances without extra zero balance accounts', async () => { @@ -748,29 +735,29 @@ describe('MetaMaskController', () => { allDetectedTokens: { '0x1': { [TEST_ADDRESS_2]: [{}] } }, }); - const startTime = Date.now(); await metamaskController.createNewVaultAndRestore( 'foobar1337', TEST_SEED, ); // Expect first account to be selected - const identities = cloneDeep(metamaskController.getState().identities); - expect( - identities[TEST_ADDRESS].lastSelected >= startTime && - identities[TEST_ADDRESS].lastSelected <= Date.now(), - ).toStrictEqual(true); - - // Expect first 2 accounts to be restored - delete identities[TEST_ADDRESS].lastSelected; - expect(identities).toStrictEqual({ - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, - [TEST_ADDRESS_2]: { - address: TEST_ADDRESS_2, - name: 'Account 2', - lastSelected: expect.any(Number), - }, - }); + const accounts = cloneDeep( + metamaskController.accountsController.listAccounts(), + ); + + const selectedAccount = + metamaskController.accountsController.getSelectedAccount(); + + expect(selectedAccount.address).toBe(TEST_ADDRESS); + expect(accounts).toHaveLength(2); + expect(accounts[0].address).toBe(TEST_ADDRESS); + expect(accounts[0].metadata.name).toBe(DEFAULT_LABEL); + expect(accounts[1].address).toBe(TEST_ADDRESS_2); + expect(accounts[1].metadata.name).toBe('Account 2'); + // TODO: Handle last selected in the update of the next accounts controller. + // expect(accounts[1].metadata.lastSelected).toBeGreaterThan( + // accounts[0].metadata.lastSelected, + // ); }); }); @@ -1596,14 +1583,12 @@ describe('MetaMaskController', () => { symbol: 'FOO', }; - metamaskController.tokensController.update((state) => { - state.tokens = [ - { - address: '0x6b175474e89094c44da98b954eedeac495271d0f', - ...tokenData, - }, - ]; - }); + await metamaskController.tokensController.addTokens([ + { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + ...tokenData, + }, + ]); metamaskController.provider = provider; const tokenDetails = diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index d17b42a08fc8..6e8a55b7124a 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -800,7 +800,6 @@ "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, - "@metamask/snaps-utils": true, "@metamask/utils": true, "uuid": true } diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index d17b42a08fc8..6e8a55b7124a 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -800,7 +800,6 @@ "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, - "@metamask/snaps-utils": true, "@metamask/utils": true, "uuid": true } diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index d17b42a08fc8..6e8a55b7124a 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -800,7 +800,6 @@ "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, - "@metamask/snaps-utils": true, "@metamask/utils": true, "uuid": true } diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index b1576a7d4f79..08b8c7e19958 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -892,7 +892,6 @@ "@metamask/eth-snap-keyring": true, "@metamask/keyring-api": true, "@metamask/keyring-controller": true, - "@metamask/snaps-utils": true, "@metamask/utils": true, "uuid": true } diff --git a/package.json b/package.json index 0eb9a1d6fdc8..f9dbb8dbeb77 100644 --- a/package.json +++ b/package.json @@ -289,7 +289,7 @@ "@metamask/address-book-controller": "^4.0.1", "@metamask/announcement-controller": "^6.1.0", "@metamask/approval-controller": "^7.0.0", - "@metamask/assets-controllers": "^33.0.0", + "@metamask/assets-controllers": "^34.0.0", "@metamask/base-controller": "^5.0.1", "@metamask/browser-passworder": "^4.3.0", "@metamask/contract-metadata": "^2.5.0", diff --git a/yarn.lock b/yarn.lock index 6658ee69abfe..1b0c258c8e28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4751,14 +4751,15 @@ __metadata: languageName: node linkType: hard -"@metamask/accounts-controller@npm:^17.0.0": - version: 17.0.0 - resolution: "@metamask/accounts-controller@npm:17.0.0" +"@metamask/accounts-controller@npm:^17.0.0, @metamask/accounts-controller@npm:^17.1.0": + version: 17.1.1 + resolution: "@metamask/accounts-controller@npm:17.1.1" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@metamask/base-controller": "npm:^6.0.0" "@metamask/eth-snap-keyring": "npm:^4.3.1" "@metamask/keyring-api": "npm:^8.0.0" + "@metamask/keyring-controller": "npm:^17.1.0" "@metamask/snaps-sdk": "npm:^4.2.0" "@metamask/snaps-utils": "npm:^7.4.0" "@metamask/utils": "npm:^8.3.0" @@ -4769,7 +4770,7 @@ __metadata: peerDependencies: "@metamask/keyring-controller": ^17.0.0 "@metamask/snaps-controllers": ^8.1.1 - checksum: 10/49ff64d252a463e00d0ad1baad6ac1c2fea9660899c7519c4ce3bc52dcf856d62094b141aaa5ae358b2f26b58d919db4820317c72b66a221656e35a86e55d579 + checksum: 10/79c74f1e219d616ffa5754418b27e2a6f2704ed0690201b46fdcffba5b25297decee735587e373b65b31f6f1817ad1d0b3072525335f67c2da1b4ff25b077b9c languageName: node linkType: hard @@ -4824,9 +4825,9 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@npm:^33.0.0": - version: 33.0.0 - resolution: "@metamask/assets-controllers@npm:33.0.0" +"@metamask/assets-controllers@npm:^34.0.0": + version: 34.0.0 + resolution: "@metamask/assets-controllers@npm:34.0.0" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@ethersproject/address": "npm:^5.7.0" @@ -4834,7 +4835,7 @@ __metadata: "@ethersproject/contracts": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.0" "@metamask/abi-utils": "npm:^2.0.2" - "@metamask/accounts-controller": "npm:^17.0.0" + "@metamask/accounts-controller": "npm:^17.1.0" "@metamask/approval-controller": "npm:^7.0.0" "@metamask/base-controller": "npm:^6.0.0" "@metamask/contract-metadata": "npm:^2.4.0" @@ -4862,7 +4863,7 @@ __metadata: "@metamask/keyring-controller": ^17.0.0 "@metamask/network-controller": ^19.0.0 "@metamask/preferences-controller": ^13.0.0 - checksum: 10/cb35e1a170c10f64df023938108593df3c5686e92070593f230c1146cd78d5ed4fbff9027cb18920c11a1ac2dc1090ce718ed22ba61dfd337fe68c18f4c06164 + checksum: 10/181cdfbcceb71ffa6d9126d70ebe97cee43ddcc1a50554594cea073d127a3a9ddc0666615b462563e33700d32b9b405cecc8a44fbcd95c84eb3b6053546ab480 languageName: node linkType: hard @@ -25170,7 +25171,7 @@ __metadata: "@metamask/announcement-controller": "npm:^6.1.0" "@metamask/api-specs": "npm:^0.9.3" "@metamask/approval-controller": "npm:^7.0.0" - "@metamask/assets-controllers": "npm:^33.0.0" + "@metamask/assets-controllers": "npm:^34.0.0" "@metamask/auto-changelog": "npm:^2.1.0" "@metamask/base-controller": "npm:^5.0.1" "@metamask/browser-passworder": "npm:^4.3.0" From 3ea381d26963849a33d76d5b644c5f0044c9c778 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:17:11 +0200 Subject: [PATCH 08/78] chore: adds quality gate for rerunning e2e spec files that are new or have been modified (#24556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR adds a quality gate for new or modified e2e spec files. Whenever there is a PR which modifies or changes a test, this will be run more times, in order to prevent introducing a flakiness accidentally. It is done as follows: - Identifies any new or modified e2e file from inside the test/ folder using `git diff` and using these 2 filters: - `file.filename.startsWith('test/e2e/') &&` - `file.filename.endsWith('.spec.js') || file.filename.endsWith('.spec.ts') ` - Copies the given specs x5 times in the list of testpaths to execute -> this number is arbitrary, we could modify it to any value we want. The reason for taking this approach instead of changing the retrial number is to benefit of the parallelization, as @HowardBraham pointed out in a comment. - Since we already had a flag which could support the re-running successful tests, `--retry-until-failure` I just leveraged this into the `for` loop for each test, and if that testcase was identified as new/modified, the flag is added so the new tests fail fast without retrials ### Incremental git fetch depth within shallow clone We use git fetch with incremental depth as @danjm suggested. The ci environment uses a shallow clone, meaning we won't be able to succeed just by using git diff as it won't find the merge base. For fixing that, we start with a git fetch depth of 1, and keep incrementing the depth (1, 10, 100) it the error is `no merge base` up until 100. If the git diff still fails, we then do a full git fetch with the `unshallow` flag. - [Update] This is the working result with the last commit https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/89269/workflows/103b78a8-8f0d-4323-96b0-9e235c4cbc81/jobs/3296802 ![Screenshot from 2024-06-26 11-39-19](https://github.com/MetaMask/metamask-extension/assets/54408225/a2a89d6a-3a73-48ba-91a3-20aeadc38573) ### New ci Job The git diff is done in a new ci job which runs at the beginning in parallel of prep-deps. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/24556?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/24009 ## **Manual testing steps** 1. Check ci runs (notice previous runs had failing and changed tests on purpose, in order to try the different scenarios described below) ## **Screenshots/Recordings** =============================================== [UPDATE with the new code changes] - :green_circle: Case 1: A test has changed -> it's rerun 1+5 times and it's successful (it will be run in different buckets) https://github.com/MetaMask/metamask-extension/assets/54408225/c1456104-1f5f-4ef3-9364-4e435f8797f4 https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/89277/workflows/7fce0a2e-773f-46da-8ab9-1dbec7992b58/jobs/3297267/parallel-runs/10?filterBy=ALL - :green_circle: Case 2: A test has changed, but it has a mistake in the code (intentionally to simulate a flaky test) -> it fails immediately and there are no more retries. The rest of the tests, are retried if they failed as usual - Case for main test build test: https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/89277/workflows/7fce0a2e-773f-46da-8ab9-1dbec7992b58/jobs/3297267/artifacts - Case for flask specific test: https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/89277/workflows/7fce0a2e-773f-46da-8ab9-1dbec7992b58/jobs/3297277/artifacts - :green_circle: Case 3: A PR has no test spec files changed -> nothing different happens - ci run: check current ci ## **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 completed the PR template to the best of my ability - [ ] 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. ## **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: Mark Stacey Co-authored-by: Howard Braham --- .circleci/config.yml | 31 +++++++++ .circleci/scripts/git-diff-develop.ts | 99 +++++++++++++++++++++++++++ development/lib/retry.js | 20 +++--- test/e2e/changedFilesUtil.js | 44 ++++++++++++ test/e2e/run-all.js | 62 +++++++++++++++-- test/e2e/run-e2e-test.js | 6 +- 6 files changed, 244 insertions(+), 18 deletions(-) create mode 100644 .circleci/scripts/git-diff-develop.ts create mode 100644 test/e2e/changedFilesUtil.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 5ab0af8fb0cc..d9f0754335e7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -105,6 +105,9 @@ workflows: - prep-deps - check-pr-tag - prep-deps + - get-changed-files-with-git-diff: + requires: + - prep-deps - test-deps-audit: requires: - prep-deps @@ -187,41 +190,51 @@ workflows: - test-e2e-chrome: requires: - prep-build-test + - get-changed-files-with-git-diff - test-e2e-chrome-confirmation-redesign: requires: - prep-build-confirmation-redesign-test + - get-changed-files-with-git-diff - test-e2e-firefox: requires: - prep-build-test-mv2 + - get-changed-files-with-git-diff - test-e2e-firefox-confirmation-redesign: <<: *develop_master_rc_only requires: - prep-build-confirmation-redesign-test-mv2 + - get-changed-files-with-git-diff - test-e2e-chrome-rpc: requires: - prep-build-test + - get-changed-files-with-git-diff - test-api-specs: requires: - prep-build-test - test-e2e-chrome-multiple-providers: requires: - prep-build-test + - get-changed-files-with-git-diff - test-e2e-chrome-flask: requires: - prep-build-test-flask + - get-changed-files-with-git-diff - test-e2e-firefox-flask: <<: *develop_master_rc_only requires: - prep-build-test-flask-mv2 + - get-changed-files-with-git-diff - test-e2e-chrome-mmi: requires: - prep-build-test-mmi + - get-changed-files-with-git-diff - test-e2e-mmi-playwright - OPTIONAL: requires: - prep-build-test-mmi-playwright - test-e2e-chrome-rpc-mmi: requires: - prep-build-test-mmi + - get-changed-files-with-git-diff - test-e2e-chrome-vault-decryption: filters: branches: @@ -230,6 +243,7 @@ workflows: - /^Version-v(\d+)[.](\d+)[.](\d+)/ requires: - prep-build + - get-changed-files-with-git-diff - test-unit-jest-main: requires: - prep-deps @@ -472,6 +486,23 @@ jobs: - node_modules - build-artifacts + # This job is used for the e2e quality gate. + # It must be run before any job which uses the run-all.js script. + get-changed-files-with-git-diff: + executor: node-browsers-small + steps: + - run: *shallow-git-clone + - run: sudo corepack enable + - attach_workspace: + at: . + - run: + name: Get changed files with git diff + command: npx tsx .circleci/scripts/git-diff-develop.ts + - persist_to_workspace: + root: . + paths: + - changed-files + validate-lavamoat-allow-scripts: executor: node-browsers-small steps: diff --git a/.circleci/scripts/git-diff-develop.ts b/.circleci/scripts/git-diff-develop.ts new file mode 100644 index 000000000000..8b5680b17d3f --- /dev/null +++ b/.circleci/scripts/git-diff-develop.ts @@ -0,0 +1,99 @@ +import { hasProperty } from '@metamask/utils'; +import { exec as execCallback } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import { promisify } from 'util'; + +const exec = promisify(execCallback); + +/** + * Fetches the git repository with a specified depth. + * + * @param depth - The depth to use for the fetch command. + * @returns True if the fetch is successful, otherwise false. + */ +async function fetchWithDepth(depth: number): Promise { + try { + await exec(`git fetch --depth ${depth} origin develop`); + await exec(`git fetch --depth ${depth} origin ${process.env.CIRCLE_BRANCH}`); + return true; + } catch (error: unknown) { + console.error(`Failed to fetch with depth ${depth}:`, error); + return false; + } +} + +/** + * Attempts to fetch the necessary commits until the merge base is found. + * It tries different fetch depths and performs a full fetch if needed. + * + * @throws If an unexpected error occurs during the execution of git commands. + */ +async function fetchUntilMergeBaseFound() { + const depths = [1, 10, 100]; + for (const depth of depths) { + console.log(`Attempting git diff with depth ${depth}...`); + await fetchWithDepth(depth); + + try { + await exec(`git merge-base origin/HEAD HEAD`); + return; + } catch (error: unknown) { + if ( + error instanceof Error && + hasProperty(error, 'code') && + error.code === 1 + ) { + console.error(`Error 'no merge base' encountered with depth ${depth}. Incrementing depth...`); + } else { + throw error; + } + } + } + await exec(`git fetch --unshallow origin develop`); +} + +/** + * Performs a git diff command to get the list of files changed between the current branch and the origin. + * It first ensures that the necessary commits are fetched until the merge base is found. + * + * @returns The output of the git diff command, listing the changed files. + * @throws If unable to get the diff after fetching the merge base or if an unexpected error occurs. + */ +async function gitDiff(): Promise { + await fetchUntilMergeBaseFound(); + const { stdout: diffResult } = await exec(`git diff --name-only origin/HEAD...${process.env.CIRCLE_BRANCH}`); + if (!diffResult) { + throw new Error('Unable to get diff after full checkout.'); + } + return diffResult; +} + +/** + * Stores the output of git diff to a file. + * + * @returns Returns a promise that resolves when the git diff output is successfully stored. + */ +async function storeGitDiffOutput() { + try { + console.log("Attempting to get git diff..."); + const diffOutput = await gitDiff(); + console.log(diffOutput); + + // Create the directory + const outputDir = 'changed-files'; + fs.mkdirSync(outputDir, { recursive: true }); + + // Store the output of git diff + const outputPath = path.resolve(outputDir, 'changed-files.txt'); + fs.writeFileSync(outputPath, diffOutput); + + console.log(`Git diff results saved to ${outputPath}`); + process.exit(0); + } catch (error: any) { + console.error('An error occurred:', error.message); + process.exit(1); + } +} + +storeGitDiffOutput(); diff --git a/development/lib/retry.js b/development/lib/retry.js index e6e5dfc040af..813a63aa44e4 100644 --- a/development/lib/retry.js +++ b/development/lib/retry.js @@ -11,12 +11,12 @@ * @param {string} [args.rejectionMessage] - The message for the rejected promise * this function will return in the event of failure. (Default: "Retry limit * reached") - * @param {boolean} [args.retryUntilFailure] - Retries until the function fails. + * @param {boolean} [args.stopAfterOneFailure] - Retries until the function fails. * @param {Function} functionToRetry - The function that is run and tested for * failure. * @returns {Promise<* | null | Error>} a promise that either resolves with one of the following: * - If successful, resolves with the return value of functionToRetry. - * - If functionToRetry fails while retryUntilFailure is true, resolves with null. + * - If functionToRetry fails while stopAfterOneFailure is true, resolves with null. * - Otherwise it is rejected with rejectionMessage. */ async function retry( @@ -24,7 +24,7 @@ async function retry( retries, delay = 0, rejectionMessage = 'Retry limit reached', - retryUntilFailure = false, + stopAfterOneFailure = false, }, functionToRetry, ) { @@ -36,7 +36,7 @@ async function retry( try { const result = await functionToRetry(); - if (!retryUntilFailure) { + if (!stopAfterOneFailure) { return result; } } catch (error) { @@ -46,18 +46,22 @@ async function retry( console.error('error caught in retry():', error); } - if (attempts < retries) { - console.log('Ready to retry() again'); + if (stopAfterOneFailure) { + throw new Error('Test failed. No more retries will be performed'); } - if (retryUntilFailure) { - return null; + if (attempts < retries) { + console.log('Ready to retry() again'); } } finally { attempts += 1; } } + if (stopAfterOneFailure) { + return null; + } + throw new Error(rejectionMessage); } diff --git a/test/e2e/changedFilesUtil.js b/test/e2e/changedFilesUtil.js new file mode 100644 index 000000000000..5ead76203db0 --- /dev/null +++ b/test/e2e/changedFilesUtil.js @@ -0,0 +1,44 @@ +const fs = require('fs').promises; +const path = require('path'); + +const BASE_PATH = path.resolve(__dirname, '..', '..'); +const CHANGED_FILES_PATH = path.join( + BASE_PATH, + 'changed-files', + 'changed-files.txt', +); + +/** + * Reads the list of changed files from the git diff file. + * + * @returns {Promise} An array of changed file paths. + */ +async function readChangedFiles() { + try { + const data = await fs.readFile(CHANGED_FILES_PATH, 'utf8'); + const changedFiles = data.split('\n'); + return changedFiles; + } catch (error) { + console.error('Error reading from file:', error); + return ''; + } +} + +/** + * Filters the list of changed files to include only E2E test files within the 'test/e2e/' directory. + * + * @returns {Promise} An array of filtered E2E test file paths. + */ +async function filterE2eChangedFiles() { + const changedFiles = await readChangedFiles(); + const e2eChangedFiles = changedFiles + .filter( + (file) => + file.startsWith('test/e2e/') && + (file.endsWith('.spec.js') || file.endsWith('.spec.ts')), + ) + .map((file) => `${BASE_PATH}/${file}`); + return e2eChangedFiles; +} + +module.exports = { filterE2eChangedFiles }; diff --git a/test/e2e/run-all.js b/test/e2e/run-all.js index 0ff043261a7b..d52a37e9afe6 100644 --- a/test/e2e/run-all.js +++ b/test/e2e/run-all.js @@ -6,6 +6,7 @@ const { hideBin } = require('yargs/helpers'); const { runInShell } = require('../../development/lib/run-command'); const { exitWithError } = require('../../development/lib/exit-with-error'); const { loadBuildTypesConfig } = require('../../development/lib/build-type'); +const { filterE2eChangedFiles } = require('./changedFilesUtil'); // These tests should only be run on Flask for now. const FLASK_ONLY_TESTS = ['test-snap-namelookup.spec.js']; @@ -30,9 +31,47 @@ const getTestPathsForTestDir = async (testDir) => { return testPaths; }; +// Quality Gate Retries +const RETRIES_FOR_NEW_OR_CHANGED_TESTS = 5; + +/** + * Runs the quality gate logic to filter and append changed or new tests if present. + * + * @param {string} fullTestList - List of test paths to be considered. + * @param {string[]} changedOrNewTests - List of changed or new test paths. + * @returns {string} The updated full test list. + */ +async function applyQualityGate(fullTestList, changedOrNewTests) { + let qualityGatedList = fullTestList; + + if (changedOrNewTests.length > 0) { + // Filter to include only the paths present in fullTestList + const filteredTests = changedOrNewTests.filter((test) => + fullTestList.includes(test), + ); + + // If there are any filtered tests, append them to fullTestList + if (filteredTests.length > 0) { + const filteredTestsString = filteredTests.join('\n'); + for (let i = 0; i < RETRIES_FOR_NEW_OR_CHANGED_TESTS; i++) { + qualityGatedList += `\n${filteredTestsString}`; + } + } + } + + return qualityGatedList; +} + // For running E2Es in parallel in CI -function runningOnCircleCI(testPaths) { - const fullTestList = testPaths.join('\n'); +async function runningOnCircleCI(testPaths) { + const changedOrNewTests = await filterE2eChangedFiles(); + console.log('Changed or new test list:', changedOrNewTests); + + const fullTestList = await applyQualityGate( + testPaths.join('\n'), + changedOrNewTests, + ); + console.log('Full test list:', fullTestList); fs.writeFileSync('test/test-results/fullTestList.txt', fullTestList); @@ -46,7 +85,7 @@ function runningOnCircleCI(testPaths) { // Report if no tests found, exit gracefully if (result.indexOf('There were no tests found') !== -1) { console.log(`run-all.js info: Skipping this node because "${result}"`); - return []; + return { fullTestList: [] }; } // If there's no text file, it means this node has no tests, so exit gracefully @@ -54,13 +93,15 @@ function runningOnCircleCI(testPaths) { console.log( 'run-all.js info: Skipping this node because there is no myTestList.txt', ); - return []; + return { fullTestList: [] }; } // take the space-delimited result and split into an array - return fs + const myTestList = fs .readFileSync('test/test-results/myTestList.txt', { encoding: 'utf8' }) .split(' '); + + return { fullTestList: myTestList, changedOrNewTests }; } async function main() { @@ -204,8 +245,10 @@ async function main() { await fs.promises.mkdir('test/test-results/e2e', { recursive: true }); let myTestList; + let changedOrNewTests; if (process.env.CIRCLECI) { - myTestList = runningOnCircleCI(testPaths); + ({ fullTestList: myTestList, changedOrNewTests = [] } = + await runningOnCircleCI(testPaths)); } else { myTestList = testPaths; } @@ -217,7 +260,12 @@ async function main() { if (testPath !== '') { testPath = testPath.replace('\n', ''); // sometimes there's a newline at the end of the testPath console.log(`\nExecuting testPath: ${testPath}\n`); - await runInShell('node', [...args, testPath]); + + const isTestChangedOrNew = changedOrNewTests?.includes(testPath); + const qualityGateArg = isTestChangedOrNew + ? ['--stop-after-one-failure'] + : []; + await runInShell('node', [...args, ...qualityGateArg, testPath]); } } } diff --git a/test/e2e/run-e2e-test.js b/test/e2e/run-e2e-test.js index a4c0496dbda6..0acf0e571cdb 100644 --- a/test/e2e/run-e2e-test.js +++ b/test/e2e/run-e2e-test.js @@ -35,7 +35,7 @@ async function main() { 'Set how many times the test should be retried upon failure.', type: 'number', }) - .option('retry-until-failure', { + .option('stop-after-one-failure', { default: false, description: 'Retries until the test fails', type: 'boolean', @@ -73,7 +73,7 @@ async function main() { mmi, e2eTestPath, retries, - retryUntilFailure, + stopAfterOneFailure, leaveRunning, updateSnapshot, updatePrivacySnapshot, @@ -141,7 +141,7 @@ async function main() { const dir = 'test/test-results/e2e'; fs.mkdir(dir, { recursive: true }); - await retry({ retries, retryUntilFailure }, async () => { + await retry({ retries, stopAfterOneFailure }, async () => { await runInShell('yarn', [ 'mocha', `--config=${configFile}`, From df81e73bfc8ad13ee1cbe12e15709d254723e944 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:39:52 +0200 Subject: [PATCH 09/78] fix: failingt e2e `Click bridge button from asset page @no-mmi loads portfolio tab when flag is turned off` (#25607) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The test is failing since we are trying to manually add a token which is already added using fixtures. See ci failure [here](https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/89950/workflows/6dce2bee-4cdf-4118-9fea-9ddc10f25096/jobs/3330074/tests). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25607?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/pull/25607 ## **Manual testing steps** 1. Check ci ## **Screenshots/Recordings** ![image](https://github.com/MetaMask/metamask-extension/assets/54408225/4519217f-e01d-48ba-a515-b483fc952854) Failure and fix: https://github.com/MetaMask/metamask-extension/assets/54408225/423f628b-e902-444c-815b-1aa05f824aa8 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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. --- .../bridge-click-from-asset-overview.spec.ts | 3 ++- test/e2e/tests/bridge/bridge-test-utils.ts | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts b/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts index 24f0bc0fb233..766c4561fd56 100644 --- a/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts +++ b/test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts @@ -8,7 +8,8 @@ import { BridgePage, getBridgeFixtures } from './bridge-test-utils'; describe('Click bridge button from asset page @no-mmi', function (this: Suite) { it('loads portfolio tab when flag is turned off', async function () { await withFixtures( - getBridgeFixtures(this.test?.fullTitle()), + // withErc20 param is false, as we test it manually below + getBridgeFixtures(this.test?.fullTitle(), undefined, false), async ({ driver, ganacheServer, diff --git a/test/e2e/tests/bridge/bridge-test-utils.ts b/test/e2e/tests/bridge/bridge-test-utils.ts index 157876f43769..19f9fd8c71c7 100644 --- a/test/e2e/tests/bridge/bridge-test-utils.ts +++ b/test/e2e/tests/bridge/bridge-test-utils.ts @@ -108,15 +108,21 @@ export class BridgePage { export const getBridgeFixtures = ( title?: string, testSpecificMock?: (server: Mockttp) => Promise, + withErc20: boolean = true, ) => { + const fixtureBuilder = new FixtureBuilder({ + inputChainId: CHAIN_IDS.MAINNET, + }).withNetworkControllerOnMainnet(); + + if (withErc20) { + fixtureBuilder.withTokensControllerERC20(); + } + return { driverOptions: { openDevToolsForTabs: true, }, - fixtures: new FixtureBuilder({ inputChainId: CHAIN_IDS.MAINNET }) - .withNetworkControllerOnMainnet() - .withTokensControllerERC20() - .build(), + fixtures: fixtureBuilder.build(), testSpecificMock, smartContract: SMART_CONTRACTS.HST, ganacheOptions: generateGanacheOptions({ From 699ddccc76302df6130835dc6655077806bf6335 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Mon, 1 Jul 2024 15:06:25 +0100 Subject: [PATCH 10/78] feat: Remove blockaid migration BannerAlert (#25556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** On https://github.com/MetaMask/metamask-extension/pull/23743/, we introduced a BannerAlert that warns the user if they migrated from OpenSea to Blockaid security alerts and they are on a network that doesn't support them. Since enough time has passed, this PR removes that logic [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25556?quickstart=1) ## **Related issues** https://github.com/MetaMask/metamask-extension/pull/23743/ ## **Manual testing steps** See instructions on https://github.com/MetaMask/metamask-extension/pull/23743/ ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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/de/messages.json | 9 --- app/_locales/el/messages.json | 9 --- app/_locales/en/messages.json | 9 --- app/_locales/es/messages.json | 9 --- app/_locales/fr/messages.json | 9 --- app/_locales/hi/messages.json | 9 --- app/_locales/id/messages.json | 9 --- app/_locales/ja/messages.json | 9 --- app/_locales/ko/messages.json | 9 --- app/_locales/pt/messages.json | 9 --- app/_locales/ru/messages.json | 9 --- app/_locales/tl/messages.json | 9 --- app/_locales/tr/messages.json | 9 --- app/_locales/vi/messages.json | 9 --- app/_locales/zh_CN/messages.json | 9 --- app/scripts/controllers/preferences.js | 9 --- app/scripts/controllers/preferences.test.js | 17 ---- app/scripts/lib/setupSentry.js | 1 - app/scripts/metamask-controller.js | 4 - ...rs-after-init-opt-in-background-state.json | 1 - .../errors-after-init-opt-in-ui-state.json | 1 - ...migrate-opensea-to-blockaid-banner.spec.js | 81 ------------------- ...aid-unavailable-banner-alert.test.tsx.snap | 50 ------------ ...ckaid-unavailable-banner-alert.stories.tsx | 10 --- ...blockaid-unavailable-banner-alert.test.tsx | 39 --------- .../blockaid-unavailable-banner-alert.tsx | 52 ------------ .../index.ts | 1 - .../signature-request-original.component.js | 2 - .../signature-request/signature-request.js | 2 - .../confirm-transaction-base.component.js | 2 - .../token-allowance/token-allowance.js | 2 - ui/selectors/selectors.js | 8 -- ui/store/actions.ts | 17 ---- 33 files changed, 434 deletions(-) delete mode 100644 test/e2e/tests/ppom/migrate-opensea-to-blockaid-banner.spec.js delete mode 100644 ui/pages/confirmations/components/blockaid-unavailable-banner-alert/__snapshots__/blockaid-unavailable-banner-alert.test.tsx.snap delete mode 100644 ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.stories.tsx delete mode 100644 ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.test.tsx delete mode 100644 ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.tsx delete mode 100644 ui/pages/confirmations/components/blockaid-unavailable-banner-alert/index.ts diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 0160c032ed88..95383c4fb54c 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -3313,15 +3313,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Snaps entdecken" - }, - "openSeaToBlockaidDescription": { - "message": "Sicherheitsbenachrichtigungen sind in diesem Netzwerk nicht mehr verfügbar. Die Installation eines Snaps kann Ihre Sicherheit erhöhen." - }, - "openSeaToBlockaidTitle": { - "message": "Vorsicht!" - }, "operationFailed": { "message": "Vorgang fehlgeschlagen" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index a53b48a7be40..cdaf57f83bc7 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -3313,15 +3313,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Εξερευνήστε τα Snaps" - }, - "openSeaToBlockaidDescription": { - "message": "Οι ειδοποιήσεις ασφαλείας δεν είναι πλέον διαθέσιμες σε αυτό το δίκτυο. Η εγκατάσταση ενός Snap μπορεί να βελτιώσει την ασφάλειά σας." - }, - "openSeaToBlockaidTitle": { - "message": "Προσοχή!" - }, "operationFailed": { "message": "Η λειτουργία απέτυχε" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 25f3c5436158..b3ee9ef98b53 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3585,15 +3585,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Explore Snaps" - }, - "openSeaToBlockaidDescription": { - "message": "Security alerts are no longer available on this network. Installing a Snap may improve your security." - }, - "openSeaToBlockaidTitle": { - "message": "Heads up!" - }, "operationFailed": { "message": "Operation Failed" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 244c70313634..a0067a6f0464 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -3310,15 +3310,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Explorar Snaps" - }, - "openSeaToBlockaidDescription": { - "message": "Las alertas de seguridad ya no están disponibles en esta red. Instalar un Snap podría mejorar su seguridad." - }, - "openSeaToBlockaidTitle": { - "message": "¡Atención!" - }, "operationFailed": { "message": "Operación fallida" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 1b2a54f3c4c1..98e857113d67 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -3313,15 +3313,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Explorer les Snaps" - }, - "openSeaToBlockaidDescription": { - "message": "Les alertes de sécurité ne sont plus disponibles sur ce réseau. L’installation d’un Snap peut améliorer la sécurité." - }, - "openSeaToBlockaidTitle": { - "message": "Attention !" - }, "operationFailed": { "message": "L’opération a échoué" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index dc6318004958..6ac05bdf0ef4 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -3310,15 +3310,6 @@ "openSeaNew": { "message": "ओपनसी" }, - "openSeaToBlockaidBtnLabel": { - "message": "Snaps को एक्सप्लोर करें" - }, - "openSeaToBlockaidDescription": { - "message": "सुरक्षा एलर्ट अब इस नेटवर्क पर उपलब्ध नहीं हैं। Snap इंस्टॉल करने से आपकी सुरक्षा में सुधार हो सकता है।" - }, - "openSeaToBlockaidTitle": { - "message": "सतर्क रहें!" - }, "operationFailed": { "message": "प्रचालन नहीं हो पाया" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 0faef246fd39..10811a064ada 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -3313,15 +3313,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Jelajahi Snap" - }, - "openSeaToBlockaidDescription": { - "message": "Peringatan keamanan tidak lagi tersedia di jaringan ini. Menginstal Snap dapat meningkatkan keamanan Anda." - }, - "openSeaToBlockaidTitle": { - "message": "Perhatian!" - }, "operationFailed": { "message": "Pengoperasian Gagal" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 4308665d7bcb..d8624f6c9263 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -3310,15 +3310,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Snapを閲覧" - }, - "openSeaToBlockaidDescription": { - "message": "このネットワークでセキュリティアラートが使用できなくなりました。Snapをインストールすると、セキュリティが向上する可能性があります。" - }, - "openSeaToBlockaidTitle": { - "message": "ご注意!" - }, "operationFailed": { "message": "操作に失敗しました" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 464c3bf9e250..cde46c43aa8f 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -3310,15 +3310,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Snap 탐색" - }, - "openSeaToBlockaidDescription": { - "message": "이 네트워크에서 더 이상 보안 경고를 사용할 수 없습니다. Snap을 설치하면 보안을 강화할 수 있습니다." - }, - "openSeaToBlockaidTitle": { - "message": "주의하세요!" - }, "operationFailed": { "message": "작업에 실패했습니다" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index f201f475a4ec..923e4338aaf6 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -3313,15 +3313,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Explorar snaps" - }, - "openSeaToBlockaidDescription": { - "message": "Os alertas de segurança não estão mais disponíveis nesta rede. Instalar um snap pode melhorar sua segurança." - }, - "openSeaToBlockaidTitle": { - "message": "Atenção!" - }, "operationFailed": { "message": "Falha na operação" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 3c419a274034..af5435447c92 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -3313,15 +3313,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Обзор Snaps" - }, - "openSeaToBlockaidDescription": { - "message": "Оповещения безопасности больше не доступны в этой сети. Установка Snap может повысить вашу безопасность." - }, - "openSeaToBlockaidTitle": { - "message": "Внимание!" - }, "operationFailed": { "message": "Операция не удалась" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index b6d454c012af..e3b1c7853cde 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -3310,15 +3310,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Galugarin ang mga Snap" - }, - "openSeaToBlockaidDescription": { - "message": "Ang mga alerto sa seguridad ay hindi na available sa network na ito. Ang pag-install ng Snap ay maaaring magpahusay sa iyong seguridad." - }, - "openSeaToBlockaidTitle": { - "message": "Paalala!" - }, "operationFailed": { "message": "Nabigo ang Operasyon" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index cd6508892738..4efed1136ce9 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -3313,15 +3313,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Snap'leri Keşfet" - }, - "openSeaToBlockaidDescription": { - "message": "Güvenlik uyarıları artık bu ağda mevcut değil. Bir Snap yüklemek güvenliğinizi artırabilir." - }, - "openSeaToBlockaidTitle": { - "message": "Dikkat!" - }, "operationFailed": { "message": "İşlem Başarısız Oldu" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index ffc867cf6e76..05edfed2d544 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -3310,15 +3310,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "Khám phá các Snap" - }, - "openSeaToBlockaidDescription": { - "message": "Cảnh báo bảo mật không còn khả dụng trên mạng này. Cài đặt Snap có thể cải thiện khả năng bảo mật của bạn." - }, - "openSeaToBlockaidTitle": { - "message": "Chú ý!" - }, "operationFailed": { "message": "Thao tác thất bại" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 14c8e355be1f..07f5fa4b93d0 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -3310,15 +3310,6 @@ "openSeaNew": { "message": "OpenSea" }, - "openSeaToBlockaidBtnLabel": { - "message": "探索 Snap" - }, - "openSeaToBlockaidDescription": { - "message": "此网络不再提供安全提醒。安装 Snap 可能提高安全性。" - }, - "openSeaToBlockaidTitle": { - "message": "注意!" - }, "operationFailed": { "message": "操作失败" }, diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 5509d804b6fd..cfa5907a5194 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -51,7 +51,6 @@ export default class PreferencesController { eth_sign: false, }, useMultiAccountBalanceChecker: true, - hasDismissedOpenSeaToBlockaidBanner: false, useSafeChainsListValidation: true, // set to true means the dynamic list from the API is being used // set to false will be using the static list from contract-metadata @@ -191,14 +190,6 @@ export default class PreferencesController { this.store.updateState({ useMultiAccountBalanceChecker: val }); } - /** - * Setter for the `dismissOpenSeaToBlockaidBanner` property - * - */ - dismissOpenSeaToBlockaidBanner() { - this.store.updateState({ hasDismissedOpenSeaToBlockaidBanner: true }); - } - /** * Setter for the `useSafeChainsListValidation` property * diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index fc344ada1264..cd8e9d3fe896 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -308,23 +308,6 @@ describe('preferences controller', () => { }); }); - describe('dismissOpenSeaToBlockaidBanner', () => { - it('hasDismissedOpenSeaToBlockaidBanner should default to false', () => { - expect( - preferencesController.store.getState() - .hasDismissedOpenSeaToBlockaidBanner, - ).toStrictEqual(false); - }); - - it('should set the hasDismissedOpenSeaToBlockaidBanner property in state', () => { - preferencesController.dismissOpenSeaToBlockaidBanner(); - expect( - preferencesController.store.getState() - .hasDismissedOpenSeaToBlockaidBanner, - ).toStrictEqual(true); - }); - }); - describe('setUseSafeChainsListValidation', function () { it('should default to true', function () { const state = preferencesController.store.getState(); diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index a6b47eef9376..695b995ddaa8 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -271,7 +271,6 @@ export const SENTRY_BACKGROUND_STATE = { useRequestQueue: true, useTransactionSimulations: true, enableMV3TimestampSave: true, - hasDismissedOpenSeaToBlockaidBanner: true, }, PushPlatformNotificationsController: { fcmToken: false, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 88491e2af62c..7550b19e98d6 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2982,10 +2982,6 @@ export default class MetamaskController extends EventEmitter { preferencesController.setUseMultiAccountBalanceChecker.bind( preferencesController, ), - dismissOpenSeaToBlockaidBanner: - preferencesController.dismissOpenSeaToBlockaidBanner.bind( - preferencesController, - ), setUseSafeChainsListValidation: preferencesController.setUseSafeChainsListValidation.bind( preferencesController, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index a7169143d03e..69e4002b7e46 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -180,7 +180,6 @@ "dismissSeedBackUpReminder": true, "disabledRpcMethodPreferences": { "eth_sign": false }, "useMultiAccountBalanceChecker": true, - "hasDismissedOpenSeaToBlockaidBanner": false, "useSafeChainsListValidation": "boolean", "useTokenDetection": false, "useNftDetection": false, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index a79a2e543573..401f92d138a0 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -114,7 +114,6 @@ "dismissSeedBackUpReminder": true, "disabledRpcMethodPreferences": { "eth_sign": false }, "useMultiAccountBalanceChecker": true, - "hasDismissedOpenSeaToBlockaidBanner": false, "useSafeChainsListValidation": true, "useTokenDetection": false, "useNftDetection": false, diff --git a/test/e2e/tests/ppom/migrate-opensea-to-blockaid-banner.spec.js b/test/e2e/tests/ppom/migrate-opensea-to-blockaid-banner.spec.js deleted file mode 100644 index 77430a250f1e..000000000000 --- a/test/e2e/tests/ppom/migrate-opensea-to-blockaid-banner.spec.js +++ /dev/null @@ -1,81 +0,0 @@ -const FixtureBuilder = require('../../fixture-builder'); -const { - defaultGanacheOptions, - logInWithBalanceValidation, - withFixtures, - openDapp, - WINDOW_TITLES, -} = require('../../helpers'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); - -describe('Migrate Opensea to Blockaid Banner @no-mmi', function () { - const ONE_CLICK_CONFIRMATIONS_USING_BLOCKAID = [ - { name: 'Personal Sign signature', testDAppBtnId: 'personalSign' }, - { name: 'Sign Typed Data signature', testDAppBtnId: 'signTypedData' }, - { name: 'Sign Typed Data v3 signature', testDAppBtnId: 'signTypedDataV3' }, - { name: 'Sign Typed Data v4 signature', testDAppBtnId: 'signTypedDataV4' }, - { name: 'Sign Permit signature', testDAppBtnId: 'signPermit' }, - { name: 'Simple Send transaction', testDAppBtnId: 'sendButton' }, - { - name: 'Contract Interaction transaction', - testDAppBtnId: 'deployMultisigButton', - }, - ]; - - ONE_CLICK_CONFIRMATIONS_USING_BLOCKAID.forEach((confirmation) => { - it(`Shows up on ${confirmation.name} confirmations`, async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPreferencesController({ - hasMigratedFromOpenSeaToBlockaid: true, - }) - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - await openDapp(driver); - - await driver.clickElement(`#${confirmation.testDAppBtnId}`); - await driver.delay(2000); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ text: 'Heads up!', tag: 'p' }); - }, - ); - }); - }); - - it('Shows up on Token Approval transaction confirmations', async function () { - const smartContract = SMART_CONTRACTS.HST; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPreferencesController({ hasMigratedFromOpenSeaToBlockaid: true }) - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - }, - async ({ driver, contractRegistry, ganacheServer }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await logInWithBalanceValidation(driver, ganacheServer); - await openDapp(driver, contractAddress); - - await driver.clickElement({ text: 'Approve Tokens', tag: 'button' }); - await driver.delay(2000); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ text: 'Heads up!', tag: 'p' }); - }, - ); - }); -}); diff --git a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/__snapshots__/blockaid-unavailable-banner-alert.test.tsx.snap b/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/__snapshots__/blockaid-unavailable-banner-alert.test.tsx.snap deleted file mode 100644 index 780135bc2132..000000000000 --- a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/__snapshots__/blockaid-unavailable-banner-alert.test.tsx.snap +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should not render if user hasn't been migrated to blockaid 1`] = `
`; - -exports[` should render if user has been migrated to blockaid 1`] = ` -
-
- -
-

- Heads up! -

-

- Security alerts are no longer available on this network. Installing a Snap may improve your security. -

-

- - Explore Snaps - -

- -
-
-`; diff --git a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.stories.tsx b/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.stories.tsx deleted file mode 100644 index bbb1752a9da3..000000000000 --- a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.stories.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { BlockaidUnavailableBannerAlert } from './blockaid-unavailable-banner-alert'; - -export default { - title: 'Components/App/Confirm/BlockaidUnavailableBannerAlert', -}; - -export const DefaultStory = () => ; - -DefaultStory.storyName = 'Default'; diff --git a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.test.tsx b/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.test.tsx deleted file mode 100644 index dffe59df0211..000000000000 --- a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import mockState from '../../../../../test/data/mock-state.json'; -import configureStore from '../../../../store/store'; -import { renderWithProvider } from '../../../../../test/lib/render-helpers'; -import { BlockaidUnavailableBannerAlert } from './blockaid-unavailable-banner-alert'; - -describe('', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const render = (storeOverrides: Record = {}) => { - const store = configureStore({ - ...mockState.metamask, - metamask: { ...mockState.metamask }, - ...storeOverrides, - }); - - return renderWithProvider(, store); - }; - - it("should not render if user hasn't been migrated to blockaid", () => { - const { container } = render(); - - expect(container).toMatchSnapshot(); - }); - - it('should render if user has been migrated to blockaid', () => { - const { container } = render({ - metamask: { - hasMigratedFromOpenSeaToBlockaid: true, - }, - }); - - expect(container).toMatchSnapshot(); - }); -}); diff --git a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.tsx b/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.tsx deleted file mode 100644 index e7d5b254b477..000000000000 --- a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { - BannerAlert, - BannerAlertSeverity, -} from '../../../../components/component-library'; -import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { - getHasDismissedOpenSeaToBlockaidBanner, - getHasMigratedFromOpenSeaToBlockaid, - getIsNetworkSupportedByBlockaid, -} from '../../../../selectors'; -import { dismissOpenSeaToBlockaidBanner } from '../../../../store/actions'; - -export const BlockaidUnavailableBannerAlert = () => { - const dispatch = useDispatch(); - const t = useI18nContext(); - - const hasMigratedFromOpenSeaToBlockaid = useSelector( - getHasMigratedFromOpenSeaToBlockaid, - ); - const isNetworkSupportedByBlockaid = useSelector( - getIsNetworkSupportedByBlockaid, - ); - const hasDismissedOpenSeaToBlockaidBanner = useSelector( - getHasDismissedOpenSeaToBlockaidBanner, - ); - - const showOpenSeaToBlockaidBannerAlert = - hasMigratedFromOpenSeaToBlockaid && - !isNetworkSupportedByBlockaid && - !hasDismissedOpenSeaToBlockaidBanner; - - const handleCloseOpenSeaToBlockaidBannerAlert = () => { - dispatch(dismissOpenSeaToBlockaidBanner()); - }; - - return showOpenSeaToBlockaidBannerAlert ? ( - - ) : null; -}; diff --git a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/index.ts b/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/index.ts deleted file mode 100644 index c8ceb0873461..000000000000 --- a/ui/pages/confirmations/components/blockaid-unavailable-banner-alert/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BlockaidUnavailableBannerAlert } from './blockaid-unavailable-banner-alert'; 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 dbf451e0ff6f..b6e934a2cbea 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 @@ -50,7 +50,6 @@ import SignatureRequestHeader from '../signature-request-header'; import SnapLegacyAuthorshipHeader from '../../../../components/app/snaps/snap-legacy-authorship-header'; import InsightWarnings from '../../../../components/app/snaps/insight-warnings'; import { BlockaidResultType } from '../../../../../shared/constants/security-provider'; -import { BlockaidUnavailableBannerAlert } from '../blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert'; import SignatureRequestOriginalWarning from './signature-request-original-warning'; export default class SignatureRequestOriginal extends Component { @@ -151,7 +150,6 @@ export default class SignatureRequestOriginal extends Component { securityProviderResponse={txData.securityProviderResponse} /> )} - { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) this.props.selectedAccount.address === diff --git a/ui/pages/confirmations/components/signature-request/signature-request.js b/ui/pages/confirmations/components/signature-request/signature-request.js index fc812ef54634..d1a04c9c9c67 100644 --- a/ui/pages/confirmations/components/signature-request/signature-request.js +++ b/ui/pages/confirmations/components/signature-request/signature-request.js @@ -78,7 +78,6 @@ import { useMMICustodySignMessage } from '../../../../hooks/useMMICustodySignMes ///: END:ONLY_INCLUDE_IF import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; import InsightWarnings from '../../../../components/app/snaps/insight-warnings'; -import { BlockaidUnavailableBannerAlert } from '../blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert'; import Message from './signature-request-message'; import Footer from './signature-request-footer'; @@ -212,7 +211,6 @@ const SignatureRequest = ({ txData, warnings }) => { marginRight={4} marginBottom={4} /> - {(txData?.securityProviderResponse?.flagAsDangerous !== undefined && txData?.securityProviderResponse?.flagAsDangerous !== SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS) || 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 84c81acc7ccd..ca9e80fed643 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 @@ -69,7 +69,6 @@ import { isHardwareKeyring } from '../../../helpers/utils/hardware'; import FeeDetailsComponent from '../components/fee-details-component/fee-details-component'; import { SimulationDetails } from '../components/simulation-details'; import { fetchSwapsFeatureFlags } from '../../swaps/swaps.util'; -import { BlockaidUnavailableBannerAlert } from '../components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert'; export default class ConfirmTransactionBase extends Component { static contextTypes = { @@ -534,7 +533,6 @@ export default class ConfirmTransactionBase extends Component { return (
- diff --git a/ui/pages/confirmations/token-allowance/token-allowance.js b/ui/pages/confirmations/token-allowance/token-allowance.js index 56519615e00c..c091d390075e 100644 --- a/ui/pages/confirmations/token-allowance/token-allowance.js +++ b/ui/pages/confirmations/token-allowance/token-allowance.js @@ -72,7 +72,6 @@ import { ConfirmPageContainerWarning } from '../components/confirm-page-containe import CustomNonce from '../components/custom-nonce'; import FeeDetailsComponent from '../components/fee-details-component/fee-details-component'; import { BlockaidResultType } from '../../../../shared/constants/security-provider'; -import { BlockaidUnavailableBannerAlert } from '../components/blockaid-unavailable-banner-alert/blockaid-unavailable-banner-alert'; const ALLOWED_HOSTS = ['portfolio.metamask.io']; @@ -385,7 +384,6 @@ export default function TokenAllowance({ marginLeft={4} marginRight={4} /> - {isSuspiciousResponse(txData?.securityProviderResponse) && ( { - return (dispatch: MetaMaskReduxDispatch) => { - // skipping loading indication as it blips in the UI and looks weird - log.debug(`background.dismissOpenSeaToBlockaidBanner`); - callBackgroundMethod('dismissOpenSeaToBlockaidBanner', [], (err) => { - if (err) { - dispatch(displayWarning(err)); - } - }); - }; -} - export function setUseSafeChainsListValidation( val: boolean, ): ThunkAction { From fe12ae48f5ac9eb695db5458501ef2f4a32347f2 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Mon, 1 Jul 2024 16:34:06 +0200 Subject: [PATCH 11/78] fix(snaps): Fix alignment of install origin is `snap-authorship-expanded` (#25583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This fixes the expanded authorship install origin being left aligned when the text takes more than a line. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25583?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Install a snap with a long install origin 2. Go to the snap's settings page 3. Look at the install origin ## **Screenshots/Recordings** ### **Before** ![image](https://github.com/MetaMask/metamask-extension/assets/13910212/3d6689a0-8e78-458e-bfe4-28094f893f61) ### **After** ![Screenshot 2024-06-28 at 17 04 27](https://github.com/MetaMask/metamask-extension/assets/13910212/660431d2-434f-4df4-ae08-aad6fb9e2c27) ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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. --- .../snaps/snap-authorship-expanded/snap-authorship-expanded.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js b/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js index 8ab9c3a299ca..5339c8587d52 100644 --- a/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js +++ b/ui/components/app/snaps/snap-authorship-expanded/snap-authorship-expanded.js @@ -16,6 +16,7 @@ import { FontWeight, JustifyContent, OverflowWrap, + TextAlign, TextColor, TextVariant, } from '../../../../helpers/constants/design-system'; @@ -179,7 +180,7 @@ const SnapAuthorshipExpanded = ({ snapId, className, snap }) => { flexDirection={FlexDirection.Column} alignItems={AlignItems.flexEnd} > - {installOrigin.host} + {installOrigin.host} {t('installedOn', [ formatDate(installInfo.date, 'dd MMM yyyy'), From 03b5d5ef766f05f20ddf5efa90b129f7944d5af4 Mon Sep 17 00:00:00 2001 From: Bilal <44588480+BZahory@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:33:26 -0400 Subject: [PATCH 12/78] fix: tweak spacing in send asset picker (#25576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The token symbol send page's asset picker is not vertically aligned with other dropdowns on the page. This PR removes an unneeded margin introduced in #25470. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25576?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to send page 2. Ensure asset picker is spaced correctly and vertically aligned with other components ## **Screenshots/Recordings** ### **Before** Screenshot 2024-06-27 at 5 55 08 PM ### **After** Screenshot 2024-06-27 at 5 58 37 PM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [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. ## **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/__snapshots__/asset-picker.test.tsx.snap | 4 ++-- .../asset-picker-amount/asset-picker/asset-picker.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/components/multichain/asset-picker-amount/asset-picker/__snapshots__/asset-picker.test.tsx.snap b/ui/components/multichain/asset-picker-amount/asset-picker/__snapshots__/asset-picker.test.tsx.snap index 9eedf3168b8b..2662eb196a76 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker/__snapshots__/asset-picker.test.tsx.snap +++ b/ui/components/multichain/asset-picker-amount/asset-picker/__snapshots__/asset-picker.test.tsx.snap @@ -13,7 +13,7 @@ exports[`AssetPicker matches snapshot 1`] = ` class="mm-box mm-box--display-flex mm-box--gap-3 mm-box--align-items-center" >
} - marginRight={3} > Date: Tue, 2 Jul 2024 04:07:06 +0530 Subject: [PATCH 13/78] chore: Create a story for RestoreVaultPage component (#25284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR adds a Storybook story for the `RestoreVaultPage` component. The story allows for isolated testing and visualization of the `RestoreVaultPage` component within the Storybook UI. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25284?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. Go to the latest build of storybook in this PR 2. Navigate to the `RestoreVaultPage` component in the `Pages/Keychains` folder. ## **Screenshots/Recordings** Screenshot 2024-06-13 at 7 34 21 AM Screenshot 2024-06-13 at 7 34 48 AM ## **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 completed the PR template to the best of my ability - [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. ## **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. [Devin Run](https://preview.devin.ai/devin/6d166713059149e5816c5666c2c85ed9) Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ui/pages/keychains/restore-vault.stories.tsx | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 ui/pages/keychains/restore-vault.stories.tsx diff --git a/ui/pages/keychains/restore-vault.stories.tsx b/ui/pages/keychains/restore-vault.stories.tsx new file mode 100644 index 000000000000..ae87ad4ac70e --- /dev/null +++ b/ui/pages/keychains/restore-vault.stories.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Provider } from 'react-redux'; +import configureStore from 'redux-mock-store'; +import RestoreVaultPage from './restore-vault'; + +const mockStore = configureStore([]); +const store = mockStore({ + appState: { + isLoading: false, + }, +}); + +const meta: Meta = { + title: 'Pages/Keychains/RestoreVaultPage', + component: RestoreVaultPage, + decorators: [(Story) => ], + argTypes: { + createNewVaultAndRestore: { action: 'createNewVaultAndRestore' }, + leaveImportSeedScreenState: { action: 'leaveImportSeedScreenState' }, + history: { control: 'object' }, + isLoading: { control: 'boolean' }, + }, + args: { + createNewVaultAndRestore: () => {}, + leaveImportSeedScreenState: () => {}, + history: { push: () => {} }, + isLoading: false, + }, +}; + +export default meta; +type Story = StoryObj; + +export const DefaultStory: Story = {}; + +DefaultStory.storyName = 'Default'; From d9ce2dd04b76e39a1c87f8885e2877bab955d8d4 Mon Sep 17 00:00:00 2001 From: Devin <168687171+Devin-Apps@users.noreply.github.com> Date: Tue, 2 Jul 2024 04:08:44 +0530 Subject: [PATCH 14/78] refactor: Replace deprecated mixins with Text component in unlock-page.component.js (#25227) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This pull request replaces the deprecated mixins in the `unlock-page.component.js` and `index.scss` files with the `Text` component. The changes include updating the `unlock-page__title` class to use the `Text` component with appropriate properties and removing the deprecated mixin instance from the SCSS file. Devin Run Link: https://preview.devin.ai/devin/de079f9a40fd45adb09783a36409256c [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25227?quickstart=1) ## **Related issues** Partially Fixes: https://github.com/MetaMask/metamask-extension/issues/20496 ## **Manual testing steps** 1. Go to the latest build of storybook in this PR 2. Verify that the "UnlockPage" component renders correctly with the updated `Text` component 3. Ensure that the `unlock-page__title` class is replaced with `[data-testid="unlock-page-title"]` in the `test/e2e/tests/settings/auto-lock.spec.js` file ## **Screenshots/Recordings** ### **Before** ![](https://api.devin.ai/attachments/991317c8-2109-4a41-bf0c-5d3edd8add50/980098aa-0372-4728-9f89-f8eff532a8f2.png) ### **After** ![](https://api.devin.ai/attachments/49521e22-bbd6-4016-81a2-839816a3008f/0361fee1-e517-4e0e-8959-f22c6b2e1fac.png) ## **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 completed the PR template to the best of my ability - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: George Marshall --- test/e2e/tests/settings/auto-lock.spec.js | 4 +++- .../__snapshots__/unlock-page.test.js.snap | 3 ++- ui/pages/unlock-page/index.scss | 8 -------- ui/pages/unlock-page/unlock-page.component.js | 12 +++++++++++- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/test/e2e/tests/settings/auto-lock.spec.js b/test/e2e/tests/settings/auto-lock.spec.js index 400f2d45aab8..bded29fa5f39 100644 --- a/test/e2e/tests/settings/auto-lock.spec.js +++ b/test/e2e/tests/settings/auto-lock.spec.js @@ -42,7 +42,9 @@ describe('Auto-Lock Timer', function () { '[data-testid="advanced-setting-auto-lock"] button', ); // Verify the wallet is locked - const pageTitle = await driver.findElement('.unlock-page__title'); + const pageTitle = await driver.findElement( + '[data-testid="unlock-page-title"]', + ); const unlockButton = await driver.findElement('.unlock-page button'); assert.equal(await pageTitle.getText(), 'Welcome back!'); assert.equal(await unlockButton.isDisplayed(), true); diff --git a/ui/pages/unlock-page/__snapshots__/unlock-page.test.js.snap b/ui/pages/unlock-page/__snapshots__/unlock-page.test.js.snap index beb7e82699be..0a572596aa2c 100644 --- a/ui/pages/unlock-page/__snapshots__/unlock-page.test.js.snap +++ b/ui/pages/unlock-page/__snapshots__/unlock-page.test.js.snap @@ -19,7 +19,8 @@ exports[`Unlock Page should match snapshot 1`] = `

Welcome back!

diff --git a/ui/pages/unlock-page/index.scss b/ui/pages/unlock-page/index.scss index abb1f837bdef..7d98b84f96a9 100644 --- a/ui/pages/unlock-page/index.scss +++ b/ui/pages/unlock-page/index.scss @@ -35,14 +35,6 @@ } } - &__title { - @include design-system.H2; - - margin-top: 5px; - font-weight: 800; - color: var(--color-text-alternative); - } - &__form { width: 100%; margin: 56px 0 8px; diff --git a/ui/pages/unlock-page/unlock-page.component.js b/ui/pages/unlock-page/unlock-page.component.js index e97f0b2f3291..504873b1c1cc 100644 --- a/ui/pages/unlock-page/unlock-page.component.js +++ b/ui/pages/unlock-page/unlock-page.component.js @@ -1,6 +1,8 @@ import { EventEmitter } from 'events'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { Text } from '../../components/component-library'; +import { TextVariant, TextColor } from '../../helpers/constants/design-system'; import Button from '../../components/ui/button'; import TextField from '../../components/ui/text-field'; import Mascot from '../../components/ui/mascot'; @@ -176,7 +178,15 @@ export default class UnlockPage extends Component {
) : null}
-

{t('welcomeBack')}

+ + {t('welcomeBack')} +
{t('unlockMessage')}
Date: Tue, 2 Jul 2024 04:30:30 +0530 Subject: [PATCH 15/78] refactor: Replace deprecated mixins with Text component in selected-account.component.js (#25262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Replaced deprecated mixins with Text component in `selected-account.component.js`. The change aims to modernize the codebase by using the Text component from the design system, ensuring consistency and maintainability. Devin Run Link: https://preview.devin.ai/devin/6dcddd7b3ee2456ca004b34d033b0d82 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25262?quickstart=1) ## **Related issues** Partially Fixes: https://github.com/MetaMask/metamask-extension/issues/20496 ## **Manual testing steps** 1. Go to the latest build of storybook in this PR 2. Verify the "SelectedAccount" component displays correctly with the updated Text component. ## **Screenshots/Recordings** ### **Before** ![](https://api.devin.ai/attachments/ad6f5c71-8714-4f0d-9675-d36481abbafa/before_changes_selected-account.component.png) ### **After** Screenshot 2024-06-12 at 21 55 36 ## **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 completed the PR template to the best of my ability - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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: George Marshall Co-authored-by: Shreyasi Mandal --- ui/components/app/selected-account/index.scss | 21 ----------- .../selected-account.component.js | 35 +++++++++++++++---- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/ui/components/app/selected-account/index.scss b/ui/components/app/selected-account/index.scss index 5c2be851e62c..bf4b3887c4f8 100644 --- a/ui/components/app/selected-account/index.scss +++ b/ui/components/app/selected-account/index.scss @@ -11,27 +11,6 @@ width: 100%; } - &__name { - @include design-system.H5; - - width: 100%; - font-weight: 500; - color: var(--color-text-default); - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - text-align: center; - margin-bottom: 4px; - } - - &__address { - @include design-system.H7; - - color: var(--color-text-alternative); - display: flex; - align-items: center; - } - &__clickable { display: flex; flex-direction: column; diff --git a/ui/components/app/selected-account/selected-account.component.js b/ui/components/app/selected-account/selected-account.component.js index 0ab706deab69..ee13d7f786d3 100644 --- a/ui/components/app/selected-account/selected-account.component.js +++ b/ui/components/app/selected-account/selected-account.component.js @@ -11,8 +11,17 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; import CustodyLabels from '../../institutional/custody-labels/custody-labels'; ///: END:ONLY_INCLUDE_IF -import { Icon, IconName, IconSize } from '../../component-library'; -import { IconColor } from '../../../helpers/constants/design-system'; +import { Icon, IconName, IconSize, Text } from '../../component-library'; +import { + IconColor, + TextVariant, + TextColor, + TextAlign, + BlockSize, + Display, + FontWeight, + AlignItems, +} from '../../../helpers/constants/design-system'; import { COPY_OPTIONS } from '../../../../shared/constants/copy'; class SelectedAccount extends Component { @@ -108,10 +117,24 @@ class SelectedAccount extends Component { copyToClipboard(checksummedAddress, COPY_OPTIONS); }} > -
+ {selectedAccount.metadata.name} -
-
+ + { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) showCustodyLabels && @@ -132,7 +155,7 @@ class SelectedAccount extends Component { />
)} -
+
From b893161ad7afe76f5cefa1d37cc8009581836ec1 Mon Sep 17 00:00:00 2001 From: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:33:15 +0200 Subject: [PATCH 16/78] test: Initial PR for integrating the Page Object Model (POM) into e2e test suite (#25373) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The Page Object Model (POM) is a design pattern that promotes code reusability and maintainability in e2e testing by encapsulating UI element selectors and interactions within page objects. This initial PR is the beginning of integrating POM into e2e test suite, aiming to enhance test code quality. The entire implementation is done in TypeScript instead of JavaScript. Key Considerations for Implementation: - Easy Adaptation: ensure that the structure is straightforward and intuitive, thereby accelerating the development speed and facilitating easier migration. - Enhanced Logging: offer better insights during test execution and debugging, making it easier to investigate flaky test Structure: The POM structure is organized around distinct page objects for each application UI screen. (Common components, such as the HeaderNavbar, are directly integrated as part of a screen's component when interaction is required. This approach eliminates the need for explicit class extension and allows for the exclusion of the HeaderNavbar in screens where interaction with it is unnecessary.) Page functions and process: I've introduced page objects, each designed to encapsulate the elements and interactions specific to their respective pages. Additionally, I've implemented processes such as `loginWaitBalance` and `sendTransaction` to efficiently manage common test flows that require interactions across multiple screens. Processes should be defined for sequences that involve multiple page objects, facilitating broader testing objectives like completing transactions or logging in. These are typically actions that navigate through several screens. Functions within a class (page object) are best used for actions and verifications that are specific to a single page. This approach promotes the encapsulation and reusability of code for individual UI components or screens, making the tests more modular and maintainable. 3 Migrated Test Cases: Migrated 3 transaction test cases to the new POM structure, showcasing the improved test architecture and log information. These migrations serve as a POC, demonstrating the effectiveness of POM in our testing environment and provide the base for future migrations. Additionally, I'm eliminating CSS selectors that aren't robust and replacing them with more stable selectors in this PR. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25373?quickstart=1) ## **Related issues** Fixes: #24985 Relates to: #22464 ## **Manual testing steps** Tests should pass on CI. Code should be easy to understand. ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-06-14 at 12 01 32](https://github.com/MetaMask/metamask-extension/assets/105063779/e0a48e9e-d8e1-4508-8b3b-6e1923b65efc) ![Screenshot 2024-06-18 at 11 25 05](https://github.com/MetaMask/metamask-extension/assets/105063779/d10f9bc8-9a3c-4d80-a341-0d7a8fcf73fc) ### **After** ![Screenshot 2024-06-14 at 22 38 22](https://github.com/MetaMask/metamask-extension/assets/105063779/35b1b150-dc0c-436b-9062-af0a71dd48ef) ![Screenshot 2024-06-18 at 11 28 10](https://github.com/MetaMask/metamask-extension/assets/105063779/ddbb953c-7839-4d6c-ae1a-07fa1e825f7d) ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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/constants.ts | 3 + test/e2e/helpers.js | 18 +- test/e2e/page-objects/pages/header-navbar.ts | 18 ++ test/e2e/page-objects/pages/homepage.ts | 165 ++++++++++++++++++ test/e2e/page-objects/pages/login-page.ts | 45 +++++ .../pages/send/confirm-tx-page.ts | 58 ++++++ .../pages/send/send-token-page.ts | 131 ++++++++++++++ .../page-objects/processes/login.process.ts | 30 ++++ .../processes/send-transaction.process.ts | 43 +++++ test/e2e/tests/transaction/ens.spec.js | 124 ------------- test/e2e/tests/transaction/ens.spec.ts | 108 ++++++++++++ .../e2e/tests/transaction/simple-send.spec.js | 28 --- .../e2e/tests/transaction/simple-send.spec.ts | 32 ++++ .../stuck-approved-transaction.spec.js | 40 ----- .../stuck-approved-transaction.spec.ts | 28 +++ test/e2e/webdriver/README.md | 27 +++ test/e2e/webdriver/driver.js | 21 +++ .../domain-input-resolution-cell.tsx | 10 +- .../confirm-transaction-base.component.js | 5 +- .../add-recipient/domain-input.component.js | 5 +- 20 files changed, 738 insertions(+), 201 deletions(-) create mode 100644 test/e2e/page-objects/pages/header-navbar.ts create mode 100644 test/e2e/page-objects/pages/homepage.ts create mode 100644 test/e2e/page-objects/pages/login-page.ts create mode 100644 test/e2e/page-objects/pages/send/confirm-tx-page.ts create mode 100644 test/e2e/page-objects/pages/send/send-token-page.ts create mode 100644 test/e2e/page-objects/processes/login.process.ts create mode 100644 test/e2e/page-objects/processes/send-transaction.process.ts delete mode 100644 test/e2e/tests/transaction/ens.spec.js create mode 100644 test/e2e/tests/transaction/ens.spec.ts delete mode 100644 test/e2e/tests/transaction/simple-send.spec.js create mode 100644 test/e2e/tests/transaction/simple-send.spec.ts delete mode 100644 test/e2e/tests/transaction/stuck-approved-transaction.spec.js create mode 100644 test/e2e/tests/transaction/stuck-approved-transaction.spec.ts diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts index ad1689e570d6..86dda727a040 100644 --- a/test/e2e/constants.ts +++ b/test/e2e/constants.ts @@ -35,3 +35,6 @@ export const TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL = /* Address of the VerifyingPaymaster smart contract deployed to Ganache. */ export const VERIFYING_PAYMASTER = '0xbdbDEc38ed168331b1F7004cc9e5392A2272C1D7'; + +/* Default ganache ETH balance in decimal when first login */ +export const DEFAULT_GANACHE_ETH_BALANCE_DEC = '25'; diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index ba32c94cabec..226fe4f9f6ac 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -16,7 +16,10 @@ const { PAGES } = require('./webdriver/driver'); const GanacheSeeder = require('./seeder/ganache-seeder'); const { Bundler } = require('./bundler'); const { SMART_CONTRACTS } = require('./seeder/smart-contracts'); -const { ERC_4337_ACCOUNT } = require('./constants'); +const { + ERC_4337_ACCOUNT, + DEFAULT_GANACHE_ETH_BALANCE_DEC, +} = require('./constants'); const tinyDelayMs = 200; const regularDelayMs = tinyDelayMs * 2; @@ -730,25 +733,30 @@ const ACCOUNT_1 = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; const ACCOUNT_2 = '0x09781764c08de8ca82e156bbf156a3ca217c7950'; const defaultGanacheOptions = { - accounts: [{ secretKey: PRIVATE_KEY, balance: convertETHToHexGwei(25) }], + accounts: [ + { + secretKey: PRIVATE_KEY, + balance: convertETHToHexGwei(DEFAULT_GANACHE_ETH_BALANCE_DEC), + }, + ], }; const multipleGanacheOptions = { accounts: [ { secretKey: PRIVATE_KEY, - balance: convertETHToHexGwei(25), + balance: convertETHToHexGwei(DEFAULT_GANACHE_ETH_BALANCE_DEC), }, { secretKey: PRIVATE_KEY_TWO, - balance: convertETHToHexGwei(25), + balance: convertETHToHexGwei(DEFAULT_GANACHE_ETH_BALANCE_DEC), }, ], }; const generateGanacheOptions = ({ secretKey = PRIVATE_KEY, - balance = convertETHToHexGwei(25), + balance = convertETHToHexGwei(DEFAULT_GANACHE_ETH_BALANCE_DEC), ...otherProps }) => { const accounts = [ diff --git a/test/e2e/page-objects/pages/header-navbar.ts b/test/e2e/page-objects/pages/header-navbar.ts new file mode 100644 index 000000000000..add85e88b7b7 --- /dev/null +++ b/test/e2e/page-objects/pages/header-navbar.ts @@ -0,0 +1,18 @@ +import { Driver } from '../../webdriver/driver'; + +class HeaderNavbar { + private driver: Driver; + + private accountMenuButton: string; + + constructor(driver: Driver) { + this.driver = driver; + this.accountMenuButton = '[data-testid="account-menu-icon"]'; + } + + async openAccountMenu(): Promise { + await this.driver.clickElement(this.accountMenuButton); + } +} + +export default HeaderNavbar; diff --git a/test/e2e/page-objects/pages/homepage.ts b/test/e2e/page-objects/pages/homepage.ts new file mode 100644 index 000000000000..5e596021a449 --- /dev/null +++ b/test/e2e/page-objects/pages/homepage.ts @@ -0,0 +1,165 @@ +import { strict as assert } from 'assert'; +import { Driver } from '../../webdriver/driver'; +import { DEFAULT_GANACHE_ETH_BALANCE_DEC } from '../../constants'; +import HeaderNavbar from './header-navbar'; + +class HomePage { + private driver: Driver; + + private sendButton: string; + + private activityTab: string; + + private tokensTab: string; + + private balance: string; + + private completedTransactions: string; + + private confirmedTransactions: object; + + private transactionAmountsInActivity: string; + + public headerNavbar: HeaderNavbar; + + constructor(driver: Driver) { + this.driver = driver; + this.headerNavbar = new HeaderNavbar(driver); + this.sendButton = '[data-testid="eth-overview-send"]'; + this.activityTab = '[data-testid="account-overview__activity-tab"]'; + this.tokensTab = '[data-testid="account-overview__asset-tab"]'; + this.confirmedTransactions = { + text: 'Confirmed', + css: '.transaction-status-label--confirmed', + }; + this.balance = '[data-testid="eth-overview__primary-currency"]'; + this.completedTransactions = '[data-testid="activity-list-item"]'; + this.transactionAmountsInActivity = + '[data-testid="transaction-list-item-primary-currency"]'; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.sendButton, + this.activityTab, + this.tokensTab, + ]); + } catch (e) { + console.log('Timeout while waiting for home page to be loaded', e); + throw e; + } + console.log('Home page is loaded'); + } + + async check_expectedBalanceIsDisplayed( + expectedBalance: string = DEFAULT_GANACHE_ETH_BALANCE_DEC, + ): Promise { + try { + await this.driver.waitForSelector({ + css: this.balance, + text: `${expectedBalance} ETH`, + }); + } catch (e) { + const balance = await this.driver.waitForSelector(this.balance); + const currentBalance = parseFloat(await balance.getText()); + const errorMessage = `Expected balance ${expectedBalance} ETH, got balance ${currentBalance} ETH`; + console.log(errorMessage, e); + throw e; + } + console.log( + `Expected balance ${expectedBalance} ETH is displayed on homepage`, + ); + } + + async startSendFlow(): Promise { + await this.driver.clickElement(this.sendButton); + } + + async goToActivityList(): Promise { + console.log(`Open activity tab on homepage`); + await this.driver.clickElement(this.activityTab); + } + + /** + * This function checks if the specified number of confirmed transactions are displayed in the activity list on homepage. + * It waits up to 10 seconds for the expected number of confirmed transactions to be visible. + * + * @param expectedNumber - The number of confirmed transactions expected to be displayed in activity list. Defaults to 1. + * @returns A promise that resolves if the expected number of confirmed transactions is displayed within the timeout period. + */ + async check_confirmedTxNumberDisplayedInActivity( + expectedNumber: number = 1, + ): Promise { + console.log( + `Wait for ${expectedNumber} confirmed transactions to be displayed in activity list`, + ); + await this.driver.wait(async () => { + const confirmedTxs = await this.driver.findElements( + this.confirmedTransactions, + ); + return confirmedTxs.length === expectedNumber; + }, 10000); + console.log( + `${expectedNumber} confirmed transactions found in activity list on homepage`, + ); + } + + /** + * This function checks the specified number of completed transactions are displayed in the activity list on the homepage. + * It waits up to 10 seconds for the expected number of completed transactions to be visible. + * + * @param expectedNumber - The number of completed transactions expected to be displayed in the activity list. Defaults to 1. + * @returns A promise that resolves if the expected number of completed transactions is displayed within the timeout period. + */ + async check_completedTxNumberDisplayedInActivity( + expectedNumber: number = 1, + ): Promise { + console.log( + `Wait for ${expectedNumber} completed transactions to be displayed in activity list`, + ); + await this.driver.wait(async () => { + const completedTxs = await this.driver.findElements( + this.completedTransactions, + ); + return completedTxs.length === expectedNumber; + }, 10000); + console.log( + `${expectedNumber} completed transactions found in activity list on homepage`, + ); + } + + /** + * This function checks if a specified transaction amount at the specified index matches the expected one. + * + * @param expectedAmount - The expected transaction amount to be displayed. Defaults to '-1 ETH'. + * @param expectedNumber - The 1-based index of the transaction in the activity list whose amount is to be checked. + * Defaults to 1, indicating the first transaction in the list. + * @returns A promise that resolves if the transaction amount at the specified index matches the expected amount. + * The promise is rejected if the amounts do not match or if an error occurs during the process. + * @example + * // To check if the third transaction in the activity list displays an amount of '2 ETH' + * await check_txAmountInActivity('2 ETH', 3); + */ + async check_txAmountInActivity( + expectedAmount: string = '-1 ETH', + expectedNumber: number = 1, + ): Promise { + const transactionAmounts = await this.driver.findElements( + this.transactionAmountsInActivity, + ); + const transactionAmountsText = await transactionAmounts[ + expectedNumber - 1 + ].getText(); + assert.equal( + transactionAmountsText, + expectedAmount, + `${transactionAmountsText} is displayed as transaction amount instead of ${expectedAmount} for transaction ${expectedNumber}`, + ); + console.log( + `Amount for transaction ${expectedNumber} is displayed as ${expectedAmount}`, + ); + } +} + +export default HomePage; diff --git a/test/e2e/page-objects/pages/login-page.ts b/test/e2e/page-objects/pages/login-page.ts new file mode 100644 index 000000000000..2a3145ccdab5 --- /dev/null +++ b/test/e2e/page-objects/pages/login-page.ts @@ -0,0 +1,45 @@ +import { Driver } from '../../webdriver/driver'; + +class LoginPage { + private driver: Driver; + + private passwordInput: string; + + private unlockButton: string; + + private welcomeBackMessage: object; + + constructor(driver: Driver) { + this.driver = driver; + this.passwordInput = '[data-testid="unlock-password"]'; + this.unlockButton = '[data-testid="unlock-submit"]'; + this.welcomeBackMessage = { + css: '.unlock-page__title', + text: 'Welcome back!', + }; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.welcomeBackMessage, + this.passwordInput, + this.unlockButton, + ]); + } catch (e) { + console.log('Timeout while waiting for login page to be loaded', e); + throw e; + } + console.log('Login page is loaded'); + } + + async fillPassword(password: string): Promise { + await this.driver.fill(this.passwordInput, password); + } + + async clickUnlockButton(): Promise { + await this.driver.clickElement(this.unlockButton); + } +} + +export default LoginPage; diff --git a/test/e2e/page-objects/pages/send/confirm-tx-page.ts b/test/e2e/page-objects/pages/send/confirm-tx-page.ts new file mode 100644 index 000000000000..0b51f9c6c4aa --- /dev/null +++ b/test/e2e/page-objects/pages/send/confirm-tx-page.ts @@ -0,0 +1,58 @@ +import { Driver } from '../../../webdriver/driver'; + +class ConfirmTxPage { + private driver: Driver; + + private confirmButton: string; + + private totalFee: string; + + private transactionFee: string; + + constructor(driver: Driver) { + this.driver = driver; + this.confirmButton = '[data-testid="page-container-footer-next"]'; + this.transactionFee = '[data-testid="confirm-gas-display"]'; + this.totalFee = '[data-testid="confirm-page-total-amount"]'; + } + + /** + * Verifies that the confirm transaction page is fully loaded by checking for the presence of confirm button and the expected gas values. + * + * @param expectedGasFee - The expected gas fee value to be displayed on the page. + * @param expectedTotalFee - The expected total fee value to be displayed on the page. + * @returns A promise that resolves when all specified elements are verified to be present and contain the expected values, indicating the page has fully loaded. + */ + async check_pageIsLoaded( + expectedGasFee: string, + expectedTotalFee: string, + ): Promise { + try { + await Promise.all([ + this.driver.waitForSelector(this.confirmButton), + this.driver.waitForSelector({ + css: this.transactionFee, + text: `${expectedGasFee} ETH`, + }), + this.driver.waitForSelector({ + css: this.totalFee, + text: `${expectedTotalFee} ETH`, + }), + ]); + } catch (e) { + console.log( + `Timeout while waiting for confirm transaction screen to be loaded, expected gas fee is: ${expectedGasFee} and expected total fee is: ${expectedTotalFee}`, + e, + ); + throw e; + } + console.log('Confirm transaction page is loaded with expected gas value'); + } + + async confirmTx(): Promise { + console.log('Click confirm button to confirm transaction'); + await this.driver.clickElement(this.confirmButton); + } +} + +export default ConfirmTxPage; diff --git a/test/e2e/page-objects/pages/send/send-token-page.ts b/test/e2e/page-objects/pages/send/send-token-page.ts new file mode 100644 index 000000000000..b5b703814a5f --- /dev/null +++ b/test/e2e/page-objects/pages/send/send-token-page.ts @@ -0,0 +1,131 @@ +import { strict as assert } from 'assert'; +import { Driver } from '../../../webdriver/driver'; + +class SendTokenPage { + private driver: Driver; + + private inputRecipient: string; + + private inputAmount: string; + + private scanButton: string; + + private continueButton: object; + + private ensResolvedName: string; + + private ensAddressAsRecipient: string; + + private ensResolvedAddress: string; + + constructor(driver: Driver) { + this.driver = driver; + this.inputAmount = '[data-testid="currency-input"]'; + this.inputRecipient = '[data-testid="ens-input"]'; + this.scanButton = '[data-testid="ens-qr-scan-button"]'; + this.ensResolvedName = + '[data-testid="multichain-send-page__recipient__item__title"]'; + this.ensResolvedAddress = + '[data-testid="multichain-send-page__recipient__item__subtitle"]'; + this.ensAddressAsRecipient = '[data-testid="ens-input-selected"]'; + this.continueButton = { + text: 'Continue', + tag: 'button', + }; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.scanButton, + this.inputRecipient, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for send token screen to be loaded', + e, + ); + throw e; + } + console.log('Send token screen is loaded'); + } + + async fillRecipient(recipientAddress: string): Promise { + console.log( + `Fill recipient input with ${recipientAddress} on send token screen`, + ); + await this.driver.pasteIntoField(this.inputRecipient, recipientAddress); + } + + async fillAmount(amount: string): Promise { + console.log(`Fill amount input with ${amount} on send token screen`); + const inputAmount = await this.driver.waitForSelector(this.inputAmount); + await this.driver.pasteIntoField(this.inputAmount, amount); + // The return value is not ts-compatible, requiring a temporary any cast to access the element's value. This will be corrected with the driver function's ts migration. + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const inputValue = await (inputAmount as any).getProperty('value'); + assert.equal( + inputValue, + amount, + `Error when filling amount field on send token screen: the value entered is ${inputValue} instead of expected ${amount}.`, + ); + } + + async goToNextScreen(): Promise { + await this.driver.clickElement(this.continueButton); + } + + /** + * Verifies that an ENS domain correctly resolves to the specified Ethereum address on the send token screen. + * + * @param ensDomain - The ENS domain name expected to resolve (e.g., "test.eth"). + * @param address - The Ethereum address to which the ENS domain is expected to resolve. + * @returns A promise that resolves if the ENS domain successfully resolves to the specified address on send token screen. + */ + async check_ensAddressResolution( + ensDomain: string, + address: string, + ): Promise { + console.log( + `Check ENS domain resolution: '${ensDomain}' should resolve to address '${address}' on the send token screen.`, + ); + // check if ens domain is resolved as expected address + await this.driver.waitForSelector({ + text: ensDomain, + css: this.ensResolvedName, + }); + await this.driver.waitForSelector({ + text: address, + css: this.ensResolvedAddress, + }); + } + + /** + * Verifies that an address resolved via ENS can be selected as the recipient on the send token screen. + * + * @param ensDomain - The ENS domain name expected to resolve to the given address. + * @param address - The Ethereum address to which the ENS domain is expected to resolve. + * @returns A promise that resolves if the ENS domain can be successfully used as a recipient address on the send token screen. + */ + async check_ensAddressAsRecipient( + ensDomain: string, + address: string, + ): Promise { + // click to select the resolved adress + await this.driver.clickElement({ + text: ensDomain, + css: this.ensResolvedName, + }); + // user should be able to send token to the resolved address + await this.driver.waitForSelector({ + css: this.ensAddressAsRecipient, + text: ensDomain + address, + }); + console.log( + `ENS domain '${ensDomain}' resolved to address '${address}' and can be used as recipient on send token screen.`, + ); + } +} + +export default SendTokenPage; diff --git a/test/e2e/page-objects/processes/login.process.ts b/test/e2e/page-objects/processes/login.process.ts new file mode 100644 index 000000000000..93bc1d3c18d3 --- /dev/null +++ b/test/e2e/page-objects/processes/login.process.ts @@ -0,0 +1,30 @@ +import LoginPage from '../pages/login-page'; +import HomePage from '../pages/homepage'; +import { Driver } from '../../webdriver/driver'; +import { DEFAULT_GANACHE_ETH_BALANCE_DEC } from '../../constants'; +import { WALLET_PASSWORD } from '../../helpers'; + +/** + * This method unlocks the wallet and verifies that the user lands on the homepage with the expected balance. It is designed to be the initial step in setting up a test environment. + * + * @param driver - The webdriver instance. + * @param password - The password used to unlock the wallet. Defaults to WALLET_PASSWORD. + * @param expectedBalance - The expected balance to be displayed on the homepage after successful login. Defaults to DEFAULT_GANACHE_ETH_BALANCE_DEC, reflecting common usage in test setups. + */ +export const loginWithBalanceValidaiton = async ( + driver: Driver, + password: string = WALLET_PASSWORD, + expectedBalance: string = DEFAULT_GANACHE_ETH_BALANCE_DEC, +) => { + console.log('Navigate to unlock page and try to login with pasword'); + await driver.navigate(); + const loginPage = new LoginPage(driver); + await loginPage.check_pageIsLoaded(); + await loginPage.fillPassword(password); + await loginPage.clickUnlockButton(); + + // user should land on homepage after successfully logging in with password + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(expectedBalance); +}; diff --git a/test/e2e/page-objects/processes/send-transaction.process.ts b/test/e2e/page-objects/processes/send-transaction.process.ts new file mode 100644 index 000000000000..2ff94f49aced --- /dev/null +++ b/test/e2e/page-objects/processes/send-transaction.process.ts @@ -0,0 +1,43 @@ +import HomePage from '../pages/homepage'; +import ConfirmTxPage from '../pages/send/confirm-tx-page'; +import SendTokenPage from '../pages/send/send-token-page'; +import { Driver } from '../../webdriver/driver'; + +/** + * This function initiates the steps required to send a transaction from the homepage to final confirmation. + * + * @param driver - The webdriver instance. + * @param recipientAddress - The recipient address. + * @param amount - The amount of the asset to be sent in the transaction. + * @param gasfee - The expected transaction gas fee. + * @param totalfee - The expected total transaction fee. + */ +export const sendTransaction = async ( + driver: Driver, + recipientAddress: string, + amount: string, + gasfee: string, + totalfee: string, +): Promise => { + console.log( + `Start process to send amount ${amount} to recipient ${recipientAddress} on home screen`, + ); + // click send button on homepage to start process + const homePage = new HomePage(driver); + await homePage.startSendFlow(); + + // user should land on send token screen to fill recipient and amount + const sendToPage = new SendTokenPage(driver); + await sendToPage.check_pageIsLoaded(); + await sendToPage.fillRecipient(recipientAddress); + await sendToPage.fillAmount(amount); + await sendToPage.goToNextScreen(); + + // confirm transaction when user lands on confirm transaction screen + const confirmTxPage = new ConfirmTxPage(driver); + await confirmTxPage.check_pageIsLoaded(gasfee, totalfee); + await confirmTxPage.confirmTx(); + + // user should land on homepage after transaction is confirmed + await homePage.check_pageIsLoaded(); +}; diff --git a/test/e2e/tests/transaction/ens.spec.js b/test/e2e/tests/transaction/ens.spec.js deleted file mode 100644 index db399fef5a93..000000000000 --- a/test/e2e/tests/transaction/ens.spec.js +++ /dev/null @@ -1,124 +0,0 @@ -const { - defaultGanacheOptions, - withFixtures, - openActionMenuAndStartSendFlow, - unlockWallet, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('ENS', function () { - const sampleAddress = '1111111111111111111111111111111111111111'; - - // Having 2 versions of the address is a bug(#25286) - const shortSampleAddress = '0x1111...1111'; - const shortSampleAddresV2 = '0x11111...11111'; - - const sampleEnsDomain = 'test.eth'; - const infuraUrl = - 'https://mainnet.infura.io/v3/00000000000000000000000000000000'; - - async function mockInfura(mockServer) { - await mockServer - .forPost(infuraUrl) - .withJsonBodyIncluding({ method: 'eth_blockNumber' }) - .thenCallback(() => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: '1111111111111111', - result: '0x1', - }, - }; - }); - - await mockServer - .forPost(infuraUrl) - .withJsonBodyIncluding({ method: 'eth_getBalance' }) - .thenCallback(() => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: '1111111111111111', - result: '0x1', - }, - }; - }); - - await mockServer - .forPost(infuraUrl) - .withJsonBodyIncluding({ method: 'eth_getBlockByNumber' }) - .thenCallback(() => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: '1111111111111111', - result: {}, - }, - }; - }); - - await mockServer - .forPost(infuraUrl) - .withJsonBodyIncluding({ method: 'eth_call' }) - .thenCallback(() => { - return { - statusCode: 200, - json: { - jsonrpc: '2.0', - id: '1111111111111111', - result: `0x000000000000000000000000${sampleAddress}`, - }, - }; - }); - } - - it('domain resolves to a correct address', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().withNetworkControllerOnMainnet().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - testSpecificMock: mockInfura, - }, - async ({ driver }) => { - await unlockWallet(driver); - - await driver.assertElementNotPresent('.loading-overlay'); - - await openActionMenuAndStartSendFlow(driver); - - await driver.pasteIntoField( - '.ens-input__wrapper__input', - sampleEnsDomain, - ); - - await driver.waitForSelector({ - text: sampleEnsDomain, - css: '[data-testid="multichain-send-page__recipient__item__title"]', - }); - - await driver.waitForSelector({ - text: shortSampleAddress, - css: '.multichain-send-page__recipient__item__subtitle', - }); - - await driver.clickElement({ - text: sampleEnsDomain, - css: '[data-testid="multichain-send-page__recipient__item__title"]', - }); - - await driver.findElement({ - css: '.ens-input__selected-input__title', - text: 'test.eth', - }); - - await driver.findElement({ - text: shortSampleAddresV2, - }); - }, - ); - }); -}); diff --git a/test/e2e/tests/transaction/ens.spec.ts b/test/e2e/tests/transaction/ens.spec.ts new file mode 100644 index 000000000000..c669f1818004 --- /dev/null +++ b/test/e2e/tests/transaction/ens.spec.ts @@ -0,0 +1,108 @@ +import { Suite } from 'mocha'; +import { MockttpServer } from 'mockttp'; +import { + defaultGanacheOptions, + withFixtures, + WALLET_PASSWORD, +} from '../../helpers'; +import { Driver } from '../../webdriver/driver'; +import FixtureBuilder from '../../fixture-builder'; +import { loginWithBalanceValidaiton } from '../../page-objects/processes/login.process'; +import HomePage from '../../page-objects/pages/homepage'; +import SendTokenPage from '../../page-objects/pages/send/send-token-page'; + +describe('ENS', function (this: Suite) { + const sampleAddress: string = '1111111111111111111111111111111111111111'; + + // Having 2 versions of the address is a bug(#25286) + const shortSampleAddress = '0x1111...1111'; + const shortSampleAddresV2 = '0x11111...11111'; + + const sampleEnsDomain: string = 'test.eth'; + const infuraUrl: string = + 'https://mainnet.infura.io/v3/00000000000000000000000000000000'; + + async function mockInfura(mockServer: MockttpServer): Promise { + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'eth_blockNumber' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: '0x1', + }, + })); + + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'eth_getBalance' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: '0x1', + }, + })); + + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'eth_getBlockByNumber' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: {}, + }, + })); + + await mockServer + .forPost(infuraUrl) + .withJsonBodyIncluding({ method: 'eth_call' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: `0x000000000000000000000000${sampleAddress}`, + }, + })); + } + + it('domain resolves to a correct address', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().withNetworkControllerOnMainnet().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: mockInfura, + }, + async ({ driver }: { driver: Driver }) => { + await loginWithBalanceValidaiton(driver, WALLET_PASSWORD, '<0.000001'); + + // click send button on homepage to start send process + await new HomePage(driver).startSendFlow(); + + // fill ens address as recipient when user lands on send token screen + const sendToPage = new SendTokenPage(driver); + await sendToPage.check_pageIsLoaded(); + await sendToPage.fillRecipient(sampleEnsDomain); + + // verify that ens domain resolves to the correct address + await sendToPage.check_ensAddressResolution( + sampleEnsDomain, + shortSampleAddress, + ); + + // Verify the resolved ENS address can be used as the recipient address + await sendToPage.check_ensAddressAsRecipient( + sampleEnsDomain, + shortSampleAddresV2, + ); + }, + ); + }); +}); diff --git a/test/e2e/tests/transaction/simple-send.spec.js b/test/e2e/tests/transaction/simple-send.spec.js deleted file mode 100644 index 54e14af03a2b..000000000000 --- a/test/e2e/tests/transaction/simple-send.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -const { - defaultGanacheOptions, - withFixtures, - sendTransaction, - logInWithBalanceValidation, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Simple send', function () { - it('can send a simple transaction from one account to another', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer }) => { - await logInWithBalanceValidation(driver, ganacheServer); - - await sendTransaction( - driver, - '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', - '1', - ); - }, - ); - }); -}); diff --git a/test/e2e/tests/transaction/simple-send.spec.ts b/test/e2e/tests/transaction/simple-send.spec.ts new file mode 100644 index 000000000000..b92241cc47a5 --- /dev/null +++ b/test/e2e/tests/transaction/simple-send.spec.ts @@ -0,0 +1,32 @@ +import { Suite } from 'mocha'; +import { Driver } from '../../webdriver/driver'; +import { withFixtures, defaultGanacheOptions } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { loginWithBalanceValidaiton } from '../../page-objects/processes/login.process'; +import { sendTransaction } from '../../page-objects/processes/send-transaction.process'; +import HomePage from '../../page-objects/pages/homepage'; + +describe('Simple send eth', function (this: Suite) { + it('can send a simple transaction from one account to another', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await loginWithBalanceValidaiton(driver); + await sendTransaction( + driver, + '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', + '1', + '0.000042', + '1.000042', + ); + const homePage = new HomePage(driver); + await homePage.check_confirmedTxNumberDisplayedInActivity(); + await homePage.check_txAmountInActivity(); + }, + ); + }); +}); diff --git a/test/e2e/tests/transaction/stuck-approved-transaction.spec.js b/test/e2e/tests/transaction/stuck-approved-transaction.spec.js deleted file mode 100644 index 2d50a0d651c1..000000000000 --- a/test/e2e/tests/transaction/stuck-approved-transaction.spec.js +++ /dev/null @@ -1,40 +0,0 @@ -const { strict: assert } = require('assert'); -const { - withFixtures, - unlockWallet, - generateGanacheOptions, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); - -describe('Editing Confirm Transaction', function () { - it('approves a transaction stuck in approved state on boot', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder() - .withTransactionControllerApprovedTransaction() - .build(), - ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); - - const txValues = await driver.findElements( - '[data-testid="transaction-list-item-primary-currency"]', - ); - assert.equal(txValues.length, 1); - assert.ok(/-1\s*ETH/u.test(await txValues[0].getText())); - }, - ); - }); -}); diff --git a/test/e2e/tests/transaction/stuck-approved-transaction.spec.ts b/test/e2e/tests/transaction/stuck-approved-transaction.spec.ts new file mode 100644 index 000000000000..11a5159fc106 --- /dev/null +++ b/test/e2e/tests/transaction/stuck-approved-transaction.spec.ts @@ -0,0 +1,28 @@ +import { Suite } from 'mocha'; +import { withFixtures, generateGanacheOptions } from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { Driver } from '../../webdriver/driver'; +import { loginWithBalanceValidaiton } from '../../page-objects/processes/login.process'; +import HomePage from '../../page-objects/pages/homepage'; + +describe('Editing Confirm Transaction', function (this: Suite) { + it('approves a transaction stuck in approved state on boot', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withTransactionControllerApprovedTransaction() + .build(), + ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await loginWithBalanceValidaiton(driver); + + const homePage = new HomePage(driver); + await homePage.goToActivityList(); + await homePage.check_completedTxNumberDisplayedInActivity(); + await homePage.check_txAmountInActivity(); + }, + ); + }); +}); diff --git a/test/e2e/webdriver/README.md b/test/e2e/webdriver/README.md index 39d7e01fd348..02e0bf25859a 100644 --- a/test/e2e/webdriver/README.md +++ b/test/e2e/webdriver/README.md @@ -543,6 +543,33 @@ This organization helps provide a clear structure for understanding the various > ``` > +
waitForMultipleSelectors + +> **`waitForMultipleSelectors`** function is designed for scenarios where you need to wait for multiple elements to either become visible or be detached from the DOM before proceeding with further actions. It enhances test robustness by allowing for simultaneous waits on several conditions, making it particularly useful in complex web interactions, such as: +> +> - Waiting for all parts of a page to load before performing a comprehensive test. +> - Ensuring multiple UI components are removed after an action. +> +> [source](https://github.com/MetaMask/metamask-extension/blob/671c9975424a83904a4752dfb8a7cf728ae67355/test/e2e/webdriver/driver.js#L357) +> +> #### Arguments +> +> @param `{Array}` rawLocators - Array of element locators
+> @param `{number}` timeout - Optional parameter that specifies the maximum amount of time (in milliseconds) to wait for the condition to be met and desired state of the elements to wait for.
+> It defaults to 'visible', indicating that the function will wait until the elements are visible on the page.
+> The other supported state is 'detached', which means waiting until the elements are removed from the DOM. +> +> #### Returns +> @returns `{Promise>}` Promise resolving when all elements meet the state or timeout occurs.
+> @throws `{Error}` Will throw an error if any of the elements do not reach the specified state within the timeout period. + +> **Example** wait for multiple elements to load +> +> ```jsx +> await driver.waitForMultipleSelectors(['.selector1', '.selector2', '.selector3']); +> ``` +> +
waitForNonEmptyElement > **`waitForNonEmptyElement`** function is an asynchronous function designed to wait until a specified web element contains some text, i.e., it's not empty. This can be particularly useful in scenarios where the content of an element is dynamically loaded or updated, and you need to ensure the element has content before proceeding with further actions. This function is useful when you need to wait for a message, label, or any piece of information to appear in a UI element before performing further actions, such as: diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 5b76eb0d1c91..44ef803d4aef 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -343,6 +343,27 @@ class Driver { return wrapElementWithAPI(element, this); } + /** + * Waits for multiple elements that match the given locators to reach the specified state within the timeout period. + * + * @param {Array} rawLocators - Array of element locators + * @param {number} timeout - Optional parameter that specifies the maximum amount of time (in milliseconds) + * to wait for the condition to be met and desired state of the elements to wait for. + * It defaults to 'visible', indicating that the method will wait until the elements are visible on the page. + * The other supported state is 'detached', which means waiting until the elements are removed from the DOM. + * @returns {Promise>} Promise resolving when all elements meet the state or timeout occurs. + * @throws {Error} Will throw an error if any of the elements do not reach the specified state within the timeout period. + */ + async waitForMultipleSelectors( + rawLocators, + { timeout = this.timeout, state = 'visible' } = {}, + ) { + const promises = rawLocators.map((rawLocator) => + this.waitForSelector(rawLocator, { timeout, state }), + ); + return Promise.all(promises); + } + /** * Waits for an element that matches the given locator to become non-empty within the timeout period. * This is particularly useful for waiting for elements that are dynamically populated with content. diff --git a/ui/components/multichain/pages/send/components/domain-input-resolution-cell.tsx b/ui/components/multichain/pages/send/components/domain-input-resolution-cell.tsx index b320a4850cd8..54d80a4e6c5a 100644 --- a/ui/components/multichain/pages/send/components/domain-input-resolution-cell.tsx +++ b/ui/components/multichain/pages/send/components/domain-input-resolution-cell.tsx @@ -165,7 +165,10 @@ export const DomainInputResolutionCell = ({ )} {ellipsify(address)} - + {domainName && ( - + {ellipsify(address)} )} 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 ca9e80fed643..2ed27fe5ced9 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 @@ -585,7 +585,10 @@ export default class ConfirmTransactionBase extends Component { })} subTitle={t('transactionDetailGasTotalSubtitle')} subText={ -
+
diff --git a/ui/pages/confirmations/send/send-content/add-recipient/domain-input.component.js b/ui/pages/confirmations/send/send-content/add-recipient/domain-input.component.js index bb6e432f1a5d..1496f8251812 100644 --- a/ui/pages/confirmations/send/send-content/add-recipient/domain-input.component.js +++ b/ui/pages/confirmations/send/send-content/add-recipient/domain-input.component.js @@ -126,7 +126,10 @@ export default class DomainInput extends Component { > {hasSelectedAddress ? ( <> -
+
Date: Tue, 2 Jul 2024 12:27:24 +0200 Subject: [PATCH 17/78] fix: page object selector not found (#25624) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The e2e that are using page objects are failing due to this error: `Waiting for element to be located By(xpath, .//*[contains(concat(' ', normalize-space(./@class), ' '), ' unlock-page__title ')][(contains(string(.), 'Welcome back!') or contains(string(.), 'Welcomeback!'))])`. It looks like the selector is not correct. This PR uses the data test id to find the correct element. Explanation: [this PR](https://github.com/MetaMask/metamask-extension/pull/25227) removed the css selector, and [this other PR](https://github.com/MetaMask/metamask-extension/pull/25373) implemented page objects with the old selector -possibly the last PR was not updated, so the tests were green, but once the 2 have been merged, now this selector is not found and fails in the subsequent branches ci failure [here](https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/90108/workflows/5d04a531-048a-40e8-87f2-1d4d02f51291/jobs/3338274/tests#failed-test-0) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25624?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ![Screenshot from 2024-07-02 11-48-09](https://github.com/MetaMask/metamask-extension/assets/54408225/faf2a546-d8af-4ed2-884c-85c836994d86) ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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/page-objects/pages/login-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/page-objects/pages/login-page.ts b/test/e2e/page-objects/pages/login-page.ts index 2a3145ccdab5..b96600675040 100644 --- a/test/e2e/page-objects/pages/login-page.ts +++ b/test/e2e/page-objects/pages/login-page.ts @@ -14,7 +14,7 @@ class LoginPage { this.passwordInput = '[data-testid="unlock-password"]'; this.unlockButton = '[data-testid="unlock-submit"]'; this.welcomeBackMessage = { - css: '.unlock-page__title', + css: '[data-testid="unlock-page-title"]', text: 'Welcome back!', }; } From 03fc8a9d0d9267ed3dca9bd7a81c42936a110d56 Mon Sep 17 00:00:00 2001 From: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:29:33 +0200 Subject: [PATCH 18/78] chore: [Delivery] Update author mapping list for PR (#25606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Update author team mapping list for PR [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25606?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** Check new authur/team mapping ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- development/generate-rc-commits.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/development/generate-rc-commits.js b/development/generate-rc-commits.js index 4de83ccaebce..a09103c35db9 100644 --- a/development/generate-rc-commits.js +++ b/development/generate-rc-commits.js @@ -49,17 +49,16 @@ const authorTeams = { 'Niranjana Binoy', 'Victor Thomas', 'vthomas13', + 'seaona', + 'Norbert Elter', ], - DappAPI: ['tmashuang', 'jiexi', 'BelfordZ', 'Shane'], - 'Confirmation UX': [ + 'Wallet API': ['tmashuang', 'jiexi', 'BelfordZ', 'Shane'], + Confirmations: [ 'Pedro Figueiredo', 'Sylva Elendu', 'Olusegun Akintayo', 'Jyoti Puri', 'Ariella Vu', - 'seaona', - ], - 'Confirmation Systems': [ 'OGPoyraz', 'vinistevam', 'Matthew Walsh', @@ -67,8 +66,14 @@ const authorTeams = { 'Vinicius Stevam', 'Derek Brans', 'sleepytanya', + 'Priya', + ], + 'Design Systems': [ + 'georgewrmarshall', + 'Garrett Bear', + 'George Marshall', + 'Devin', ], - 'Design Systems': ['georgewrmarshall', 'Garrett Bear', 'George Marshall'], Snaps: [ 'David Drazic', 'hmalik88', @@ -84,7 +89,7 @@ const authorTeams = { Assets: ['salimtb', 'sahar-fehri', 'Brian Bergeron'], Linea: ['VGau', 'Victorien Gauch'], lavamoat: ['weizman', 'legobeat', 'kumavis', 'LeoTM'], - 'Shared Libraries': [ + 'Wallet Framework': [ 'Michele Esposito', 'Elliot Winkler', 'Gudahtt', @@ -98,10 +103,11 @@ const authorTeams = { 'Shane T', 'Bernardo Garces Chapero', ], - Swaps: ['Daniel', 'Davide Brocchetto', 'Nicolas Ferro'], + Swaps: ['Daniel', 'Davide Brocchetto', 'Nicolas Ferro', 'infiniteflower'], Devex: ['Thomas Huang', 'Alex Donesky', 'jiexi', 'Zachary Belford'], Notifications: ['Prithpal-Sooriya', 'Matteo Scurati', 'Prithpal Sooriya'], - Bridging: ['Bilal', 'micaelae'], + Bridging: ['Bilal', 'micaelae', 'Ethan Wessel'], + Ramps: ['George Weiler'], }; // Function to get PR labels From d721e7f7b1d7a915624fb14050a8b4037c524d96 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 2 Jul 2024 20:17:29 +0800 Subject: [PATCH 19/78] fix: account missing in connection page (#25500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR fixes a regression where the connected account is not showing up on the page. ## **Related issues** ## **Manual testing steps** 1. Go to a test dapp 2. Connect the account 3. Open the popup 4. Click on the connection icon and see the connected account. ## **Screenshots/Recordings** ### **Before** Screenshot 2024-06-25 at 15 30 55 ### **After** Screenshot 2024-06-25 at 15 31 01 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] 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. ## **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: Nidhi Kumari Co-authored-by: Charly Chevalier --- test/e2e/helpers.js | 9 +++++ .../tests/multichain/connection-page.spec.js | 33 +++++++++++++++++++ .../account-list-item.test.js.snap | 2 ++ .../account-list-item/account-list-item.js | 6 +++- .../app-header-unlocked-content.tsx | 2 ++ .../connected-site-menu.js | 2 +- .../__snapshots__/connections.test.tsx.snap | 1 + .../send/__snapshots__/send.test.js.snap | 1 + .../__snapshots__/your-accounts.test.tsx.snap | 6 ++++ .../remove-snap-account.test.js.snap | 1 + 10 files changed, 61 insertions(+), 2 deletions(-) diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 226fe4f9f6ac..dbdfdeb2446d 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -675,6 +675,14 @@ const openDapp = async (driver, contract = null, dappURL = DAPP_URL) => { : await driver.openNewPage(dappURL); }; +const openDappConnectionsPage = async (driver) => { + await driver.openNewPage( + `${driver.extensionUrl}/home.html#connections/${encodeURIComponent( + DAPP_URL, + )}`, + ); +}; + const createDappTransaction = async (driver, transaction) => { await openDapp( driver, @@ -1158,6 +1166,7 @@ module.exports = { importWrongSRPOnboardingFlow, testSRPDropdownIterations, openDapp, + openDappConnectionsPage, createDappTransaction, switchToOrOpenDapp, connectToDapp, diff --git a/test/e2e/tests/multichain/connection-page.spec.js b/test/e2e/tests/multichain/connection-page.spec.js index e5594de82840..2d1d2f2d106c 100644 --- a/test/e2e/tests/multichain/connection-page.spec.js +++ b/test/e2e/tests/multichain/connection-page.spec.js @@ -183,4 +183,37 @@ describe('Connections page', function () { }, ); }); + + // Skipped until issue where firefox connecting to dapp is resolved. + // it('shows that the account is connected to the dapp', async function () { + // await withFixtures( + // { + // dapp: true, + // fixtures: new FixtureBuilder().build(), + // title: this.test.fullTitle(), + // ganacheOptions: defaultGanacheOptions, + // }, + // async ({ driver, ganacheServer }) => { + // const ACCOUNT = '0x5CfE73b6021E818B776b421B1c4Db2474086a7e1'; + // const SHORTENED_ACCOUNT = shortenAddress(ACCOUNT); + // await logInWithBalanceValidation(driver, ganacheServer); + // await openDappConnectionsPage(driver); + // // Verify that there are no connected accounts + // await driver.assertElementNotPresent( + // '[data-testid="account-list-address"]', + // ); + + // await connectToDapp(driver); + // await openDappConnectionsPage(driver); + + // const account = await driver.findElement( + // '[data-testid="account-list-address"]', + // ); + // const accountAddress = await account.getText(); + + // // Dapp should contain single connected account address + // assert.strictEqual(accountAddress, SHORTENED_ACCOUNT); + // }, + // ); + // }); }); 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 0b2b13a29277..ae53a1353084 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 @@ -262,6 +262,7 @@ exports[`AccountListItem renders AccountListItem component and shows account nam >

bc1qn3s...5eker

@@ -557,6 +558,7 @@ exports[`AccountListItem renders AccountListItem component and shows account nam >

0x0DCD5...3E7bc

diff --git a/ui/components/multichain/account-list-item/account-list-item.js b/ui/components/multichain/account-list-item/account-list-item.js index c3e09207cb7a..2b48bcb79baf 100644 --- a/ui/components/multichain/account-list-item/account-list-item.js +++ b/ui/components/multichain/account-list-item/account-list-item.js @@ -320,7 +320,11 @@ export const AccountListItem = ({ justifyContent={JustifyContent.spaceBetween} > - + {shortenAddress(normalizeSafeAddress(account.address))} diff --git a/ui/components/multichain/app-header/app-header-unlocked-content.tsx b/ui/components/multichain/app-header/app-header-unlocked-content.tsx index 0cca531b5fc1..2be2d5ffd342 100644 --- a/ui/components/multichain/app-header/app-header-unlocked-content.tsx +++ b/ui/components/multichain/app-header/app-header-unlocked-content.tsx @@ -41,6 +41,7 @@ import { GlobalMenu } from '../global-menu'; import { getSelectedInternalAccount, getTestNetworkBackgroundColor, + getOriginOfCurrentTab, } from '../../../selectors'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { normalizeSafeAddress } from '../../../../app/scripts/lib/multichain/address'; @@ -80,6 +81,7 @@ export const AppHeaderUnlockedContent = ({ const t = useI18nContext(); const history = useHistory(); const dispatch = useDispatch(); + const origin = useSelector(getOriginOfCurrentTab); const [accountOptionsMenuOpen, setAccountOptionsMenuOpen] = useState(false); const testNetworkBackgroundColor = useSelector(getTestNetworkBackgroundColor); diff --git a/ui/components/multichain/connected-site-menu/connected-site-menu.js b/ui/components/multichain/connected-site-menu/connected-site-menu.js index c7811b3e819c..4f532710288e 100644 --- a/ui/components/multichain/connected-site-menu/connected-site-menu.js +++ b/ui/components/multichain/connected-site-menu/connected-site-menu.js @@ -137,5 +137,5 @@ ConnectedSiteMenu.propTypes = { /** * Disable the connected site menu if the account is non-evm */ - disabled: PropTypes.boolean, + disabled: PropTypes.bool, }; diff --git a/ui/components/multichain/pages/connections/__snapshots__/connections.test.tsx.snap b/ui/components/multichain/pages/connections/__snapshots__/connections.test.tsx.snap index faa7a4fac173..3864de9c921e 100644 --- a/ui/components/multichain/pages/connections/__snapshots__/connections.test.tsx.snap +++ b/ui/components/multichain/pages/connections/__snapshots__/connections.test.tsx.snap @@ -317,6 +317,7 @@ exports[`Connections Content should render correctly 1`] = ` >

0x0DCD5...3E7bc

diff --git a/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap b/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap index 59f5db9f8385..a3ee231d6ae2 100644 --- a/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap +++ b/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap @@ -489,6 +489,7 @@ exports[`SendPage render and initialization should render correctly even when a >

0x0

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 411ee70e4ea3..901e7def9c2c 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 @@ -265,6 +265,7 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` >

0x0DCD5...3E7bc

@@ -561,6 +562,7 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` >

0xEC1Ad...9251B

@@ -857,6 +859,7 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` >

0xc42ED...D8813

@@ -1162,6 +1165,7 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` >

0xeB9e6...64823

@@ -1458,6 +1462,7 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` >

0xb5526...FEe5D

@@ -1767,6 +1772,7 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` >

0xca8f1...Cf281

diff --git a/ui/pages/confirmations/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap b/ui/pages/confirmations/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap index 7a411efb53ed..e0305e1c6533 100644 --- a/ui/pages/confirmations/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap +++ b/ui/pages/confirmations/confirmation/templates/__snapshots__/remove-snap-account.test.js.snap @@ -361,6 +361,7 @@ exports[`remove-snap-account confirmation should match snapshot 1`] = ` >

0x0DCD5...3E7bc

From 4886a3ff5ce5b8da4c313b888b77b0ec47de93d5 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:18:46 +0200 Subject: [PATCH 20/78] chore: exclude running git diff job for the e2e quality gate in `develop`, `master` and release branches (#25605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR excludes running the git-diff script for the e2e quality gate added [here](https://github.com/MetaMask/metamask-extension/commit/3ea381d26963849a33d76d5b644c5f0044c9c778), for `develop` as well as for `master` and release branches. We don't want to run this in develop, master or release branch, since there is no point on re-running the new/changed tests there, as the changes are already in `develop` branch. This could add up more credits and also slow down ci in those branches. **Context**: in `develop` branch the current quality gate fails, since the `diffResult` is empty, and we were throwing the following [error](https://app.circleci.com/pipelines/github/MetaMask/metamask-extension/89939/workflows/dccfb6e0-741d-4416-bc01-8ab1a884c12b/jobs/3329380) as we were entering in the !diffResult condition. However, this PR fixes the issue on the higher level, by skipping entirely the git diff for the mentioned branches above. ``` await fetchUntilMergeBaseFound(); const { stdout: diffResult } = await exec(`git diff --name-only origin/HEAD...${process.env.CIRCLE_BRANCH}`); if (!diffResult) { throw new Error('Unable to get diff after full checkout.'); } ``` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25605?quickstart=1) ## **Related issues** Fixes: current develop ci ## **Manual testing steps** - ci should continue to work - once merged, fixes develop ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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. --- .circleci/config.yml | 12 +++++++++--- test/e2e/changedFilesUtil.js | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d9f0754335e7..aaed8f3c8bf2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,6 +53,13 @@ develop_master_rc_only: &develop_master_rc_only - master - /^Version-v(\d+)[.](\d+)[.](\d+)/ +exclude_develop_master_rc: &exclude_develop_master_rc + filters: + branches: + ignore: + - develop + - master + - /^Version-v(\d+)[.](\d+)[.](\d+)/ aliases: # Shallow Git Clone - &shallow-git-clone @@ -106,6 +113,7 @@ workflows: - check-pr-tag - prep-deps - get-changed-files-with-git-diff: + <<: *exclude_develop_master_rc requires: - prep-deps - test-deps-audit: @@ -203,7 +211,6 @@ workflows: <<: *develop_master_rc_only requires: - prep-build-confirmation-redesign-test-mv2 - - get-changed-files-with-git-diff - test-e2e-chrome-rpc: requires: - prep-build-test @@ -223,7 +230,6 @@ workflows: <<: *develop_master_rc_only requires: - prep-build-test-flask-mv2 - - get-changed-files-with-git-diff - test-e2e-chrome-mmi: requires: - prep-build-test-mmi @@ -243,7 +249,6 @@ workflows: - /^Version-v(\d+)[.](\d+)[.](\d+)/ requires: - prep-build - - get-changed-files-with-git-diff - test-unit-jest-main: requires: - prep-deps @@ -488,6 +493,7 @@ jobs: # This job is used for the e2e quality gate. # It must be run before any job which uses the run-all.js script. + # The job is skipped in develop, master or RC branches. get-changed-files-with-git-diff: executor: node-browsers-small steps: diff --git a/test/e2e/changedFilesUtil.js b/test/e2e/changedFilesUtil.js index 5ead76203db0..7beb7c94e9e7 100644 --- a/test/e2e/changedFilesUtil.js +++ b/test/e2e/changedFilesUtil.js @@ -20,7 +20,7 @@ async function readChangedFiles() { return changedFiles; } catch (error) { console.error('Error reading from file:', error); - return ''; + return []; } } From c6419813d24772e369f4e73ccad67c8e13abbf8d Mon Sep 17 00:00:00 2001 From: Mike B <32695229+plasmacorral@users.noreply.github.com> Date: Tue, 2 Jul 2024 08:30:28 -0400 Subject: [PATCH 21/78] test: add e2e to swap with snap account (#25558) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25558?quickstart=1) ## **Related issues** Fixes: [469](https://github.com/MetaMask/accounts-planning/issues/469) ## **Manual testing steps** 1. `yarn build:test:mv2` then `ENABLE_MV3=false yarn test:e2e:single test/e2e/accounts/snap-account-eth-swap.spec.ts --browser=firefox` 2. `yarn build:test` then `yarn test:e2e:single test/e2e/accounts/snap-account-eth-swap.spec.ts --browser=chrome` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] 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. ## **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. --- .../accounts/snap-account-eth-swap.spec.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/e2e/accounts/snap-account-eth-swap.spec.ts diff --git a/test/e2e/accounts/snap-account-eth-swap.spec.ts b/test/e2e/accounts/snap-account-eth-swap.spec.ts new file mode 100644 index 000000000000..bb4c3c7a5770 --- /dev/null +++ b/test/e2e/accounts/snap-account-eth-swap.spec.ts @@ -0,0 +1,48 @@ +import { withFixtures, defaultGanacheOptions, WINDOW_TITLES } from '../helpers'; +import { Driver } from '../webdriver/driver'; +import FixtureBuilder from '../fixture-builder'; +import { + buildQuote, + reviewQuote, + waitForTransactionToComplete, + checkActivityTransaction, +} from '../tests/swaps/shared'; +import { installSnapSimpleKeyring } from './common'; + +const DAI = 'DAI'; +const TEST_ETH = 'TESTETH'; + +describe('Snap Account - Swap', function () { + it('swaps ETH for DAI using a snap account', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await installSnapSimpleKeyring(driver, false); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await buildQuote(driver, { + amount: 0.001, + swapTo: DAI, + }); + await reviewQuote(driver, { + amount: 0.001, + swapFrom: TEST_ETH, + swapTo: DAI, + }); + await driver.clickElement({ text: 'Swap', tag: 'button' }); + await waitForTransactionToComplete(driver, { tokenName: 'DAI' }); + await checkActivityTransaction(driver, { + index: 0, + amount: '0.001', + swapFrom: TEST_ETH, + swapTo: DAI, + }); + }, + ); + }); +}); From 7d94797c50e8bde272b2f6083981517fdacfd7be Mon Sep 17 00:00:00 2001 From: Xiaoming Wang <7315988+dawnseeker8@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:59:30 +0100 Subject: [PATCH 22/78] fix: Fix issue 22837 about unknown error during ledger pair (#25462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Issue #22837 descrip that ledger sometimes will display `Unknown Error` during pairing. we have tried to replicate the issue and discover that it happen during ledger is lock and not open Eth app. this PR will replace the `Unknown Error` with more meaningful error message to guide user solve the issue. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25462?quickstart=1) ## **Related issues** Fixes: #22837 ## **Manual testing steps** 1. Setup wallet 2. Connect, unlock and open the Ethereum app on Ledger 3. Use the add hardware flow to add some Ledger accounts 4. Remove the Ledger accounts from full screen mode 5. Use add hardware flow to try and add any Ledger accounts 6. Select the `paired` with that ledger when it is available 7. ledger should be locked status or unlock ledger but not opening eth app. 8. you will see new error message `Unlock your Ledger device and open the ETH app` 9. After you unlock your ledger and open ETH app. and click paired again 10. you should be able to select accounts from next screen and import those accounts from ledger. ## **Screenshots/Recordings** ### **Before** https://recordit.co/kdCDL4laWo ### **After** https://github.com/MetaMask/metamask-extension/assets/7315988/f11a1867-ad3f-4796-8661-6d3469bed682 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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: chloeYue <105063779+chloeYue@users.noreply.github.com> --- ...r-bridge-keyring-npm-2.0.1-7a5d815b2d.patch | 18 ++++++++++++++++++ package.json | 2 +- yarn.lock | 17 +++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 .yarn/patches/@metamask-eth-ledger-bridge-keyring-npm-2.0.1-7a5d815b2d.patch diff --git a/.yarn/patches/@metamask-eth-ledger-bridge-keyring-npm-2.0.1-7a5d815b2d.patch b/.yarn/patches/@metamask-eth-ledger-bridge-keyring-npm-2.0.1-7a5d815b2d.patch new file mode 100644 index 000000000000..786d5cd1b226 --- /dev/null +++ b/.yarn/patches/@metamask-eth-ledger-bridge-keyring-npm-2.0.1-7a5d815b2d.patch @@ -0,0 +1,18 @@ +diff --git a/dist/ledger-keyring.js b/dist/ledger-keyring.js +index 2386b2e7fe36d1e65ef74f0a19d3b41450dcfa48..f999a0ab465cce7a450a5812f1d7aa6e39b74aed 100644 +--- a/dist/ledger-keyring.js ++++ b/dist/ledger-keyring.js +@@ -150,7 +150,12 @@ class LedgerKeyring extends events_1.EventEmitter { + }); + } + catch (error) { +- throw error instanceof Error ? error : new Error('Unknown error'); ++ ++ /** ++ * For Fixing issue 22837, when ledger is locked and didnt open the ethereum app in ledger, ++ * The extension will always show `unknown error`, below change will transform the error to something meaningful. ++ */ ++ throw error instanceof Error ? error : new Error('Unlock your Ledger device and open the ETH app'); + } + if (updateHdk && payload.chainCode) { + this.hdk.publicKey = buffer_1.Buffer.from(payload.publicKey, 'hex'); diff --git a/package.json b/package.json index f9dbb8dbeb77..391ffff36e58 100644 --- a/package.json +++ b/package.json @@ -298,7 +298,7 @@ "@metamask/ens-controller": "^10.0.1", "@metamask/eth-json-rpc-filters": "^7.0.0", "@metamask/eth-json-rpc-middleware": "^12.1.1", - "@metamask/eth-ledger-bridge-keyring": "^2.0.1", + "@metamask/eth-ledger-bridge-keyring": "patch:@metamask/eth-ledger-bridge-keyring@npm%3A2.0.1#~/.yarn/patches/@metamask-eth-ledger-bridge-keyring-npm-2.0.1-7a5d815b2d.patch", "@metamask/eth-query": "^4.0.0", "@metamask/eth-sig-util": "^7.0.1", "@metamask/eth-snap-keyring": "^4.3.1", diff --git a/yarn.lock b/yarn.lock index 1b0c258c8e28..87e407e69b99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5211,7 +5211,7 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-ledger-bridge-keyring@npm:^2.0.1": +"@metamask/eth-ledger-bridge-keyring@npm:2.0.1": version: 2.0.1 resolution: "@metamask/eth-ledger-bridge-keyring@npm:2.0.1" dependencies: @@ -5224,6 +5224,19 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-ledger-bridge-keyring@patch:@metamask/eth-ledger-bridge-keyring@npm%3A2.0.1#~/.yarn/patches/@metamask-eth-ledger-bridge-keyring-npm-2.0.1-7a5d815b2d.patch": + version: 2.0.1 + resolution: "@metamask/eth-ledger-bridge-keyring@patch:@metamask/eth-ledger-bridge-keyring@npm%3A2.0.1#~/.yarn/patches/@metamask-eth-ledger-bridge-keyring-npm-2.0.1-7a5d815b2d.patch::version=2.0.1&hash=322aa0" + dependencies: + "@ethereumjs/rlp": "npm:^4.0.0" + "@ethereumjs/tx": "npm:^4.1.1" + "@ethereumjs/util": "npm:^8.0.0" + "@metamask/eth-sig-util": "npm:^7.0.0" + hdkey: "npm:^2.1.0" + checksum: 10/0f8c86d1b4c323b8a79fa82e3df300034f8dea928569cc3560d1b3352e09e9397844b75b8642ca57866ef5e241b49bd190b8ba7d1b08efa248d9ca909485a674 + languageName: node + linkType: hard + "@metamask/eth-query@npm:^3.0.1": version: 3.0.1 resolution: "@metamask/eth-query@npm:3.0.1" @@ -25188,7 +25201,7 @@ __metadata: "@metamask/eslint-plugin-design-tokens": "npm:^1.1.0" "@metamask/eth-json-rpc-filters": "npm:^7.0.0" "@metamask/eth-json-rpc-middleware": "npm:^12.1.1" - "@metamask/eth-ledger-bridge-keyring": "npm:^2.0.1" + "@metamask/eth-ledger-bridge-keyring": "patch:@metamask/eth-ledger-bridge-keyring@npm%3A2.0.1#~/.yarn/patches/@metamask-eth-ledger-bridge-keyring-npm-2.0.1-7a5d815b2d.patch" "@metamask/eth-query": "npm:^4.0.0" "@metamask/eth-sig-util": "npm:^7.0.1" "@metamask/eth-snap-keyring": "npm:^4.3.1" From 548b54aae983ddc68af56156984f0ea49063ebed Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:14:17 +0200 Subject: [PATCH 23/78] chore: Update @metamask/smart-transactions-controller from 10.1.2 to 10.1.6 (#25611) --- lavamoat/browserify/beta/policy.json | 119 ++++++++------------------ lavamoat/browserify/flask/policy.json | 119 ++++++++------------------ lavamoat/browserify/main/policy.json | 119 ++++++++------------------ lavamoat/browserify/mmi/policy.json | 119 ++++++++------------------ package.json | 2 +- yarn.lock | 22 ++--- 6 files changed, 152 insertions(+), 348 deletions(-) diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 6e8a55b7124a..daddd541a5f0 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -941,11 +941,19 @@ "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller>@metamask/base-controller": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "uuid": true } }, + "@metamask/assets-controllers>@metamask/polling-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/assets-controllers>cockatiel": { "globals": { "AbortController": true, @@ -2199,8 +2207,8 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, @@ -2252,6 +2260,14 @@ "TextEncoder": true } }, + "@metamask/smart-transactions-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { "globals": { "URL": true, @@ -2305,10 +2321,10 @@ "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, "@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, @@ -2344,32 +2360,6 @@ "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -2377,62 +2367,11 @@ "setInterval": true }, "packages": { + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/eth-query": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/polling-controller": true, - "bn.js": true, - "browserify>buffer": true, - "uuid": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils>@ethereumjs/util": true, - "@metamask/utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "bn.js": true, "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils>@ethereumjs/util": { - "globals": { - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/polling-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, "uuid": true } }, @@ -2812,10 +2751,10 @@ "@metamask/base-controller": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/controller-utils": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2842,6 +2781,18 @@ "eth-ens-namehash": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 6e8a55b7124a..daddd541a5f0 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -941,11 +941,19 @@ "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller>@metamask/base-controller": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "uuid": true } }, + "@metamask/assets-controllers>@metamask/polling-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/assets-controllers>cockatiel": { "globals": { "AbortController": true, @@ -2199,8 +2207,8 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, @@ -2252,6 +2260,14 @@ "TextEncoder": true } }, + "@metamask/smart-transactions-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { "globals": { "URL": true, @@ -2305,10 +2321,10 @@ "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, "@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, @@ -2344,32 +2360,6 @@ "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -2377,62 +2367,11 @@ "setInterval": true }, "packages": { + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/eth-query": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/polling-controller": true, - "bn.js": true, - "browserify>buffer": true, - "uuid": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils>@ethereumjs/util": true, - "@metamask/utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "bn.js": true, "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils>@ethereumjs/util": { - "globals": { - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/polling-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, "uuid": true } }, @@ -2812,10 +2751,10 @@ "@metamask/base-controller": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/controller-utils": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2842,6 +2781,18 @@ "eth-ens-namehash": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 6e8a55b7124a..daddd541a5f0 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -941,11 +941,19 @@ "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller>@metamask/base-controller": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "uuid": true } }, + "@metamask/assets-controllers>@metamask/polling-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/assets-controllers>cockatiel": { "globals": { "AbortController": true, @@ -2199,8 +2207,8 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, @@ -2252,6 +2260,14 @@ "TextEncoder": true } }, + "@metamask/smart-transactions-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { "globals": { "URL": true, @@ -2305,10 +2321,10 @@ "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, "@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, @@ -2344,32 +2360,6 @@ "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -2377,62 +2367,11 @@ "setInterval": true }, "packages": { + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/eth-query": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/polling-controller": true, - "bn.js": true, - "browserify>buffer": true, - "uuid": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils>@ethereumjs/util": true, - "@metamask/utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "bn.js": true, "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils>@ethereumjs/util": { - "globals": { - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/polling-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, "uuid": true } }, @@ -2812,10 +2751,10 @@ "@metamask/base-controller": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/controller-utils": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2842,6 +2781,18 @@ "eth-ens-namehash": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 08b8c7e19958..0043b64f7534 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1033,11 +1033,19 @@ "setTimeout": true }, "packages": { - "@metamask/assets-controllers>@metamask/base-controller": true, + "@metamask/assets-controllers>@metamask/polling-controller>@metamask/base-controller": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "uuid": true } }, + "@metamask/assets-controllers>@metamask/polling-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/assets-controllers>cockatiel": { "globals": { "AbortController": true, @@ -2291,8 +2299,8 @@ }, "packages": { "@ethersproject/abi>@ethersproject/bytes": true, + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/eth-query": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/smart-transactions-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/controller-utils": true, @@ -2344,6 +2352,14 @@ "TextEncoder": true } }, + "@metamask/smart-transactions-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/smart-transactions-controller>@metamask/controller-utils": { "globals": { "URL": true, @@ -2397,10 +2413,10 @@ "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, "@metamask/rpc-errors": true, + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, @@ -2436,32 +2452,6 @@ "webpack>events": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -2469,62 +2459,11 @@ "setInterval": true }, "packages": { + "@metamask/assets-controllers>@metamask/polling-controller": true, "@metamask/eth-query": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/polling-controller": true, - "bn.js": true, - "browserify>buffer": true, - "uuid": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils>@ethereumjs/util": true, - "@metamask/utils": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, "bn.js": true, "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/controller-utils>@ethereumjs/util": { - "globals": { - "console.warn": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, - "@ethereumjs/tx>ethereum-cryptography": true, - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true, - "webpack>events": true - } - }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/polling-controller": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller>@metamask/base-controller": true, - "@metamask/snaps-utils>fast-json-stable-stringify": true, "uuid": true } }, @@ -2904,10 +2843,10 @@ "@metamask/base-controller": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/gas-fee-controller>@metamask/polling-controller": true, "@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/controller-utils": true, + "@metamask/user-operation-controller>@metamask/polling-controller": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -2934,6 +2873,18 @@ "eth-ens-namehash": true } }, + "@metamask/user-operation-controller>@metamask/polling-controller": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "uuid": true + } + }, "@metamask/utils": { "globals": { "TextDecoder": true, diff --git a/package.json b/package.json index 391ffff36e58..d57e25eaeaff 100644 --- a/package.json +++ b/package.json @@ -335,7 +335,7 @@ "@metamask/scure-bip39": "^2.0.3", "@metamask/selected-network-controller": "^15.0.2", "@metamask/signature-controller": "^16.0.0", - "@metamask/smart-transactions-controller": "^10.1.2", + "@metamask/smart-transactions-controller": "^10.1.6", "@metamask/snaps-controllers": "^9.2.0", "@metamask/snaps-execution-environments": "^6.5.0", "@metamask/snaps-rpc-methods": "^9.1.4", diff --git a/yarn.lock b/yarn.lock index 87e407e69b99..035e63c8c6a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6008,7 +6008,7 @@ __metadata: languageName: node linkType: hard -"@metamask/polling-controller@npm:^6.0.1, @metamask/polling-controller@npm:^6.0.2": +"@metamask/polling-controller@npm:^6.0.2": version: 6.0.2 resolution: "@metamask/polling-controller@npm:6.0.2" dependencies: @@ -6239,25 +6239,25 @@ __metadata: languageName: node linkType: hard -"@metamask/smart-transactions-controller@npm:^10.1.2": - version: 10.1.2 - resolution: "@metamask/smart-transactions-controller@npm:10.1.2" +"@metamask/smart-transactions-controller@npm:^10.1.6": + version: 10.1.6 + resolution: "@metamask/smart-transactions-controller@npm:10.1.6" dependencies: "@babel/runtime": "npm:^7.24.1" "@ethereumjs/tx": "npm:^5.2.1" "@ethereumjs/util": "npm:^9.0.2" "@ethersproject/bytes": "npm:^5.7.0" - "@metamask/base-controller": "npm:^5.0.1" - "@metamask/controller-utils": "npm:^9.1.0" + "@metamask/base-controller": "npm:^6.0.0" + "@metamask/controller-utils": "npm:^11.0.0" "@metamask/eth-query": "npm:^4.0.0" - "@metamask/network-controller": "npm:^18.1.2" - "@metamask/polling-controller": "npm:^6.0.1" - "@metamask/transaction-controller": "npm:^30.0.0" + "@metamask/network-controller": "npm:^19.0.0" + "@metamask/polling-controller": "npm:^8.0.0" + "@metamask/transaction-controller": "npm:^32.0.0" bignumber.js: "npm:^9.0.1" events: "npm:^3.3.0" fast-json-patch: "npm:^3.1.0" lodash: "npm:^4.17.21" - checksum: 10/178ce39935c31d431bd9ef9c8a9388292ac0216c32d1721b5ceeafd7f1fb7a467e6921fbf6ad947b0be68f3a6383b325838116e5701b71226e97c35f2beef6b3 + checksum: 10/e914a35ba822ad9bad877c9a5f5496b85fbe9b8dc6b682cc46134c18fdfef29fc5cad1af4c8bac4c2feddfe97fbf9b78faa021da0ea08559bdcc05bef3b49d85 languageName: node linkType: hard @@ -25240,7 +25240,7 @@ __metadata: "@metamask/scure-bip39": "npm:^2.0.3" "@metamask/selected-network-controller": "npm:^15.0.2" "@metamask/signature-controller": "npm:^16.0.0" - "@metamask/smart-transactions-controller": "npm:^10.1.2" + "@metamask/smart-transactions-controller": "npm:^10.1.6" "@metamask/snaps-controllers": "npm:^9.2.0" "@metamask/snaps-execution-environments": "npm:^6.5.0" "@metamask/snaps-rpc-methods": "npm:^9.1.4" From 95407cc9e6cc6bb1783576843f11aa6a77b788b1 Mon Sep 17 00:00:00 2001 From: Mike B <32695229+plasmacorral@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:30:43 -0400 Subject: [PATCH 24/78] fix: Remove unused fixtures and fix test name in smart swaps disabled spec (#25616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Test name was not being captured correctly and recently came to appreciate that some of the snap account related fixtures were not actually needed for this test. Updated test name and removed `accountSnapFixtures` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25616?quickstart=1) ## **Related issues** Fixes: [505](https://github.com/MetaMask/accounts-planning/issues/505) ## **Manual testing steps** Firefox: `yarn build:test:mv2` then `ENABLE_MV3=false yarn test:e2e:single test/e2e/accounts/smart-swap-disabled.spec.ts --browser=firefox` Chrome: `yarn build:test` then `yarn test:e2e:single test/e2e/accounts/smart-swap-disabled.spec.ts --browser=chrome` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] 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. ## **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/accounts/smart-swap-disabled.spec.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/e2e/accounts/smart-swap-disabled.spec.ts b/test/e2e/accounts/smart-swap-disabled.spec.ts index e12f7bcae7c2..4e6ce9b2c6ef 100644 --- a/test/e2e/accounts/smart-swap-disabled.spec.ts +++ b/test/e2e/accounts/smart-swap-disabled.spec.ts @@ -1,17 +1,17 @@ -import { title } from 'process'; import { Suite } from 'mocha'; -import { withFixtures } from '../helpers'; +import { withFixtures, defaultGanacheOptions } from '../helpers'; import { Driver } from '../webdriver/driver'; -import { - accountSnapFixtures, - installSnapSimpleKeyring, - makeNewAccountAndSwitch, -} from './common'; +import FixtureBuilder from '../fixture-builder'; +import { installSnapSimpleKeyring, makeNewAccountAndSwitch } from './common'; -describe('Smart Swaps', function (this: Suite) { - it('should be disabled for snap accounts', async function () { +describe('Snap Account - Smart Swaps', function (this: Suite) { + it('checks if smart swaps are disabled for snap accounts', async function () { await withFixtures( - accountSnapFixtures(title), + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, async ({ driver }: { driver: Driver }) => { await installSnapSimpleKeyring(driver, false); await makeNewAccountAndSwitch(driver); From 05904ca445f0e98e190d26ec64a6aa23386e5e80 Mon Sep 17 00:00:00 2001 From: Kate Johnson <91970214+k-g-j@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:38:07 -0400 Subject: [PATCH 25/78] feat: added BTC variant to ramps-card and illustration image (#25615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Updates the Ramps banners for BTC accounts. Added - `BTC` to `RAMPS_CARD_VARIANT_TYPES` with `illustractionSrc`, `gradient`, `title`, and `body` in the `RAMPS_CARD_VARIANTS` - [BTC illustration](https://www.figma.com/design/vsHVGgHiWo37Qs2EyMMy0K/Empty-Account-Contextual-Buy-Button?node-id=354-8198&t=61v1CV61FZV68ssr-0) to `app/images` - `BtcBanner` to `RampsMetaMaskEntry` enum [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25615?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/accounts-planning/issues/496 ## **Manual testing steps** N/A @georgeweiler is working on subsequent PR to include BTC in the list of buyable tokens, which will render the new banner in that flow. ## **Screenshots/Recordings** Screenshot 2024-07-01 at 3 50 59 PM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [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. ## **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/images/ramps-card-btc-illustration.png | Bin 0 -> 22142 bytes .../multichain/ramps-card/ramps-card.js | 9 +++++++++ .../multichain/ramps-card/ramps-card.stories.js | 5 +++++ ui/hooks/ramps/useRamps/useRamps.ts | 1 + 4 files changed, 15 insertions(+) create mode 100644 app/images/ramps-card-btc-illustration.png diff --git a/app/images/ramps-card-btc-illustration.png b/app/images/ramps-card-btc-illustration.png new file mode 100644 index 0000000000000000000000000000000000000000..4c5cee4e40ac7db44cf47e8bbfc048f9e5ffab7c GIT binary patch literal 22142 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 { diff --git a/ui/components/multichain/ramps-card/ramps-card.stories.js b/ui/components/multichain/ramps-card/ramps-card.stories.js index b280e63591d8..2a4dce444c7e 100644 --- a/ui/components/multichain/ramps-card/ramps-card.stories.js +++ b/ui/components/multichain/ramps-card/ramps-card.stories.js @@ -35,3 +35,8 @@ export const ActivityStory = (args) => ( ); ActivityStory.storyName = 'Activity'; + +export const BTCStory = (args) => ( + +); +BTCStory.storyName = 'BTC'; diff --git a/ui/hooks/ramps/useRamps/useRamps.ts b/ui/hooks/ramps/useRamps/useRamps.ts index aa8ccb1c76d2..55b1d1e222d9 100644 --- a/ui/hooks/ramps/useRamps/useRamps.ts +++ b/ui/hooks/ramps/useRamps/useRamps.ts @@ -14,6 +14,7 @@ export enum RampsMetaMaskEntry { NftBanner = 'ext_buy_banner_nfts', TokensBanner = 'ext_buy_banner_tokens', ActivityBanner = 'ext_buy_banner_activity', + BtcBanner = 'ext_buy_banner_btc', } const portfolioUrl = process.env.PORTFOLIO_URL; From 6557b599118710ebfa2641f9e8f8bb58cc9b4cf2 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Tue, 2 Jul 2024 20:09:18 +0530 Subject: [PATCH 26/78] fix: decimal places displayed on token value on permit pages (#25410) --- .../confirmations/signatures/permit.spec.ts | 2 +- .../__snapshots__/typed-sign.test.tsx.snap | 2 +- .../permit-simulation.test.tsx.snap | 2 +- .../permit-simulation.test.tsx | 5 +- .../permit-simulation/permit-simulation.tsx | 10 +++- .../info/typed-sign/typed-sign.test.tsx | 6 +++ .../confirm/info/typed-sign/typed-sign.tsx | 21 ++++++++- .../components/confirm/row/dataTree.tsx | 24 +++++++++- .../row/typed-sign-data/typedSignData.tsx | 11 +++-- .../components/confirm/utils.test.ts | 46 +++++++++++-------- .../confirmations/components/confirm/utils.ts | 11 +++++ 11 files changed, 110 insertions(+), 30 deletions(-) diff --git a/test/e2e/tests/confirmations/signatures/permit.spec.ts b/test/e2e/tests/confirmations/signatures/permit.spec.ts index 8320fd748f63..03941dbecc3f 100644 --- a/test/e2e/tests/confirmations/signatures/permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/permit.spec.ts @@ -83,7 +83,7 @@ async function assertInfoValues(driver: Driver) { css: '.name__value', text: '0x5B38D...eddC4', }); - const value = driver.findElement({ text: '3000' }); + const value = driver.findElement({ text: '3,000' }); const nonce = driver.findElement({ text: '0' }); const deadline = driver.findElement({ text: '02 August 1971, 16:53' }); diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/__snapshots__/typed-sign.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/typed-sign/__snapshots__/typed-sign.test.tsx.snap index 328fe4209390..c06db18f9268 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/__snapshots__/typed-sign.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/__snapshots__/typed-sign.test.tsx.snap @@ -401,7 +401,7 @@ exports[`TypedSignInfo correctly renders permit sign type 1`] = ` class="mm-box mm-text mm-text--body-md mm-box--color-inherit" style="white-space: pre-wrap;" > - 3000 + 3,000

diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap index 111bc553477f..9b67671cbb06 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap @@ -70,7 +70,7 @@ exports[`PermitSimulation renders component correctly 1`] = `

- 3000 + 30.00

diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx index ea0ffcc47bc1..daca28ece848 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx @@ -15,7 +15,10 @@ describe('PermitSimulation', () => { }, }; const mockStore = configureMockStore([])(state); - const { container } = renderWithProvider(, mockStore); + const { container } = renderWithProvider( + , + mockStore, + ); expect(container).toMatchSnapshot(); }); }); diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx index 04eaba8f9be4..f6a116b0842e 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx @@ -21,9 +21,12 @@ import { import { SignatureRequestType } from '../../../../../types/confirm'; import useTokenExchangeRate from '../../../../../../../components/app/currency-input/hooks/useTokenExchangeRate'; import { IndividualFiatDisplay } from '../../../../simulation-details/fiat-display'; +import { formatNumber } from '../../../utils'; import { ConfirmInfoSection } from '../../../../../../../components/app/confirm/info/row/section'; -const PermitSimulation: React.FC = () => { +const PermitSimulation: React.FC<{ + tokenDecimals: number; +}> = ({ tokenDecimals }) => { const t = useI18nContext(); const currentConfirmation = useSelector( currentConfirmationSelector, @@ -61,7 +64,10 @@ const PermitSimulation: React.FC = () => { paddingInline={2} textAlign={TextAlign.Center} > - {value} + {formatNumber( + value / Math.pow(10, tokenDecimals), + tokenDecimals, + )} diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx index 332b3f79a6ff..1ac473dd40f8 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx @@ -10,6 +10,12 @@ import { } from '../../../../../../../test/data/confirmations/typed_sign'; import TypedSignInfo from './typed-sign'; +jest.mock('../../../../../../store/actions', () => { + return { + getTokenStandardAndDetails: jest.fn().mockResolvedValue({ decimals: 2 }), + }; +}); + describe('TypedSignInfo', () => { it('renders origin for typed sign data request', () => { const state = { diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.tsx index 019dd138f401..88ade5cc2a6f 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { isValidAddress } from 'ethereumjs-util'; @@ -11,6 +11,7 @@ import { } from '../../../../../../components/app/confirm/info/row'; import { useI18nContext } from '../../../../../../hooks/useI18nContext'; import { currentConfirmationSelector } from '../../../../../../selectors'; +import { getTokenStandardAndDetails } from '../../../../../../store/actions'; import { SignatureRequestType } from '../../../../types/confirm'; import { isPermitSignatureRequest } from '../../../../utils'; import { selectUseTransactionSimulations } from '../../../../selectors/preferences'; @@ -26,6 +27,7 @@ const TypedSignInfo: React.FC = () => { const useTransactionSimulations = useSelector( selectUseTransactionSimulations, ); + const [decimals, setDecimals] = useState(0); if (!currentConfirmation?.msgParams) { return null; @@ -38,9 +40,23 @@ const TypedSignInfo: React.FC = () => { const isPermit = isPermitSignatureRequest(currentConfirmation); + useEffect(() => { + (async () => { + if (!isPermit) { + return; + } + const { decimals: tokenDecimals } = await getTokenStandardAndDetails( + verifyingContract, + ); + setDecimals(parseInt(tokenDecimals ?? '0', 10)); + })(); + }, [verifyingContract]); + return ( <> - {isPermit && useTransactionSimulations && } + {isPermit && useTransactionSimulations && ( + + )} {isPermit && ( <> @@ -64,6 +80,7 @@ const TypedSignInfo: React.FC = () => { diff --git a/ui/pages/confirmations/components/confirm/row/dataTree.tsx b/ui/pages/confirmations/components/confirm/row/dataTree.tsx index 307b608488d9..9f5511e72446 100644 --- a/ui/pages/confirmations/components/confirm/row/dataTree.tsx +++ b/ui/pages/confirmations/components/confirm/row/dataTree.tsx @@ -11,6 +11,7 @@ import { ConfirmInfoRowDate, ConfirmInfoRowText, } from '../../../../../components/app/confirm/info/row'; +import { formatNumber } from '../utils'; type ValueType = string | Record | TreeData[]; @@ -22,9 +23,11 @@ export type TreeData = { export const DataTree = ({ data, isPermit = false, + tokenDecimals = 0, }: { data: Record | TreeData[]; isPermit?: boolean; + tokenDecimals?: number; }) => ( {Object.entries(data).map(([label, { value, type }], i) => ( @@ -42,6 +45,7 @@ export const DataTree = ({ isPermit={isPermit} value={value} type={type} + tokenDecimals={tokenDecimals} /> } @@ -54,14 +58,32 @@ const DataField = ({ isPermit, type, value, + tokenDecimals, }: { label: string; isPermit: boolean; type: string; value: ValueType; + tokenDecimals: number; }) => { if (typeof value === 'object' && value !== null) { - return ; + return ( + + ); + } + if (isPermit && label === 'value') { + return ( + + ); } if (isPermit && label === 'deadline') { return ; diff --git a/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.tsx b/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.tsx index 9b3934cef651..0b60f45b6a38 100644 --- a/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.tsx +++ b/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.tsx @@ -7,16 +7,17 @@ import { ConfirmInfoRow, ConfirmInfoRowText, } from '../../../../../../components/app/confirm/info/row'; - -import { DataTree } from '../dataTree'; import { parseSanitizeTypedDataMessage } from '../../../../utils'; +import { DataTree } from '../dataTree'; export const ConfirmInfoRowTypedSignData = ({ data, isPermit, + tokenDecimals, }: { data: string; isPermit?: boolean; + tokenDecimals?: number; }) => { const t = useI18nContext(); @@ -35,7 +36,11 @@ export const ConfirmInfoRowTypedSignData = ({ - + ); diff --git a/ui/pages/confirmations/components/confirm/utils.test.ts b/ui/pages/confirmations/components/confirm/utils.test.ts index 87e6307de7be..b967a81e8f77 100644 --- a/ui/pages/confirmations/components/confirm/utils.test.ts +++ b/ui/pages/confirmations/components/confirm/utils.test.ts @@ -8,29 +8,39 @@ import { unapprovedPersonalSignMsg, } from '../../../../../test/data/confirmations/personal_sign'; import { SignatureRequestType } from '../../types/confirm'; -import { getConfirmationSender } from './utils'; +import { formatNumber, getConfirmationSender } from './utils'; -describe('getConfirmationSender()', () => { - test("returns the sender address from a signature if it's passed", () => { - const testCurrentConfirmation = - genUnapprovedContractInteractionConfirmation() as TransactionMeta; - const { from } = getConfirmationSender(testCurrentConfirmation); +describe('confirm - utils', () => { + describe('getConfirmationSender()', () => { + test("returns the sender address from a signature if it's passed", () => { + const testCurrentConfirmation = + genUnapprovedContractInteractionConfirmation() as TransactionMeta; + const { from } = getConfirmationSender(testCurrentConfirmation); - expect(from).toEqual(CONTRACT_INTERACTION_SENDER_ADDRESS); - }); + expect(from).toEqual(CONTRACT_INTERACTION_SENDER_ADDRESS); + }); - test("returns the sender address from a transaction if it's passed", () => { - const { from } = getConfirmationSender( - unapprovedPersonalSignMsg as SignatureRequestType, - ); + test("returns the sender address from a transaction if it's passed", () => { + const { from } = getConfirmationSender( + unapprovedPersonalSignMsg as SignatureRequestType, + ); - expect(from).toEqual(PERSONAL_SIGN_SENDER_ADDRESS); - }); + expect(from).toEqual(PERSONAL_SIGN_SENDER_ADDRESS); + }); - test('returns no sender address if no confirmation is passed', () => { - const testCurrentConfirmation = undefined; - const { from } = getConfirmationSender(testCurrentConfirmation); + test('returns no sender address if no confirmation is passed', () => { + const testCurrentConfirmation = undefined; + const { from } = getConfirmationSender(testCurrentConfirmation); + + expect(from).toEqual(undefined); + }); + }); - expect(from).toEqual(undefined); + describe('formatNumber()', () => { + test('formats number according to decimal places passed', () => { + expect(formatNumber(123456, 2)).toEqual('123,456.00'); + expect(formatNumber(123456, 0)).toEqual('123,456'); + expect(formatNumber(123456, 7)).toEqual('123,456.0000000'); + }); }); }); diff --git a/ui/pages/confirmations/components/confirm/utils.ts b/ui/pages/confirmations/components/confirm/utils.ts index 21b4aac78998..581bf1a5ccee 100644 --- a/ui/pages/confirmations/components/confirm/utils.ts +++ b/ui/pages/confirmations/components/confirm/utils.ts @@ -17,3 +17,14 @@ export const getConfirmationSender = ( return { from }; }; + +export const formatNumber = (value: number, decimals: number) => { + if (value === undefined) { + return value; + } + const formatter = new Intl.NumberFormat('en-US', { + minimumFractionDigits: decimals, + maximumFractionDigits: decimals, + }); + return formatter.format(value); +}; From c9fcc1439a52bf3cee55b484f1833c55d77b44ef Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:09:28 +0200 Subject: [PATCH 27/78] feat: update Permit Signature Redesign "Spending cap" copy (#25618) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Updates Permit signature redesign copy "Approve spend limit" → "Spending cap" [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25618?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/2719 ## **Manual testing steps** 1. Go to test dapp 2. Click on Malicious Permit within the ppom section ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] 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. ## **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. --- .../__snapshots__/permit-simulation.test.tsx.snap | 2 +- .../info/typed-sign/permit-simulation/permit-simulation.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap index 9b67671cbb06..6ff73fd88fed 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap @@ -54,7 +54,7 @@ exports[`PermitSimulation renders component correctly 1`] = `

- Approve spend limit + Spending cap

- + From dfad34b22ab1ba7197faa655fb95fcad3da8dae2 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 2 Jul 2024 23:27:12 +0800 Subject: [PATCH 28/78] fix: support multichain in blockexplorer and qr code (#25526) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR updates the block explorer to support non-EVM explorers and fixes the QR code to also display non-EVM accounts. ## **Related issues** Fixes https://github.com/MetaMask/accounts-planning/issues/489 ## **Manual testing steps** EVM 1. Click on global menu and click on `View on Explorer` 2. See that the user goes to etherscan 3. Click on the account details 4. Click on copy address to clipboard and verify that the address is a 0x prefixed. 5. Verify QR code is also `ethereum:` prefixed Non-EVM 1. Create a BTC account 2. Click on global menu and click on `View on Explorer` 3. See that the user goes to blockstream.io 4. Click on the account details 6. Click on copy address to clipboard and verify that the address is not 0x prefixed. 7. Verify QR code is only the btc address ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-06-26 at 23 14 46](https://github.com/MetaMask/metamask-extension/assets/96463427/b358be0b-5586-42b6-850e-64e5d7f91279) ![Screenshot 2024-06-26 at 23 14 18](https://github.com/MetaMask/metamask-extension/assets/96463427/5f5b8d4c-993c-4de9-ad99-0cb28ab46617) ![Screenshot 2024-06-26 at 23 14 28](https://github.com/MetaMask/metamask-extension/assets/96463427/415f40ad-06eb-4936-a71f-c71db5a3f5ca) ### **After** ![Screenshot 2024-06-26 at 23 14 40](https://github.com/MetaMask/metamask-extension/assets/96463427/3d8467db-eacc-48a9-90b5-c29ee121fe7b) ![Screenshot 2024-06-26 at 23 14 37](https://github.com/MetaMask/metamask-extension/assets/96463427/00b914a5-82de-4420-b85c-f26989b79740) ![Screenshot 2024-06-26 at 23 14 32](https://github.com/MetaMask/metamask-extension/assets/96463427/f5445c78-90c6-434c-9fb0-9315db289908) ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [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. ## **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: Charly Chevalier --- .../lib/accounts/BalancesController.test.ts | 1 + shared/constants/multichain/networks.ts | 12 ++ .../dapp-interactions/block-explorer.spec.js | 30 +++++ test/jest/mocks.js | 2 +- .../confirm-remove-account.test.js.snap | 17 +-- .../confirm-remove-account.component.js | 42 +++---- .../confirm-remove-account.container.js | 10 +- .../confirm-remove-account.test.js | 118 +++++++++++++----- .../nickname-popovers.component.test.tsx.snap | 9 ++ .../nickname-popovers.component.js | 25 ++-- .../nickname-popovers.component.test.tsx | 112 +++++++++++++++++ .../account-list-item-menu.js | 2 +- .../address-copy-button.js | 4 +- .../multichain/global-menu/global-menu.js | 2 +- .../view-explorer-menu-item.test.js | 30 ----- .../view-explorer-menu-item.test.tsx | 72 +++++++++++ ...nu-item.js => view-explorer-menu-item.tsx} | 80 ++++++------ .../ui/qr-code-view/qr-code-view.test.tsx | 97 ++++++++++++++ .../{qr-code-view.js => qr-code-view.tsx} | 16 ++- .../utils/multichain/blockExplorer.test.ts | 82 ++++++++++++ ui/helpers/utils/multichain/blockExplorer.ts | 27 ++++ ui/helpers/utils/util.js | 3 +- ...MultichainAccountTotalFiatBalance.test.tsx | 1 - ui/selectors/multichain.ts | 54 +++++++- ui/store/store.ts | 10 ++ 25 files changed, 698 insertions(+), 160 deletions(-) create mode 100644 ui/components/app/modals/nickname-popovers/__snapshots__/nickname-popovers.component.test.tsx.snap create mode 100644 ui/components/app/modals/nickname-popovers/nickname-popovers.component.test.tsx delete mode 100644 ui/components/multichain/menu-items/view-explorer-menu-item.test.js create mode 100644 ui/components/multichain/menu-items/view-explorer-menu-item.test.tsx rename ui/components/multichain/menu-items/{view-explorer-menu-item.js => view-explorer-menu-item.tsx} (67%) create mode 100644 ui/components/ui/qr-code-view/qr-code-view.test.tsx rename ui/components/ui/qr-code-view/{qr-code-view.js => qr-code-view.tsx} (84%) create mode 100644 ui/helpers/utils/multichain/blockExplorer.test.ts create mode 100644 ui/helpers/utils/multichain/blockExplorer.ts diff --git a/app/scripts/lib/accounts/BalancesController.test.ts b/app/scripts/lib/accounts/BalancesController.test.ts index 02627c6aa201..6bfe0bfabfc6 100644 --- a/app/scripts/lib/accounts/BalancesController.test.ts +++ b/app/scripts/lib/accounts/BalancesController.test.ts @@ -21,6 +21,7 @@ const mockBtcAccount = createMockInternalAccount({ name: 'Btc Account', // @ts-expect-error - account type may be btc or eth, mock file is not typed type: BtcAccountType.P2wpkh, + // @ts-expect-error - snap options is not typed and defaults to undefined snapOptions: { id: 'mock-btc-snap', name: 'mock-btc-snap', diff --git a/shared/constants/multichain/networks.ts b/shared/constants/multichain/networks.ts index 7f629ca49c6b..366a161a27a2 100644 --- a/shared/constants/multichain/networks.ts +++ b/shared/constants/multichain/networks.ts @@ -21,6 +21,12 @@ export enum MultichainNetworks { export const BITCOIN_TOKEN_IMAGE_URL = './images/bitcoin-logo.svg'; +export const MULTICHAIN_NETWORK_BLOCK_EXPLORER_URL_MAP = { + [MultichainNetworks.BITCOIN]: 'https://blockstream.info/address', + [MultichainNetworks.BITCOIN_TESTNET]: + 'https://blockstream.info/testnet/address', +} as const; + export const MULTICHAIN_TOKEN_IMAGE_MAP = { [MultichainNetworks.BITCOIN]: BITCOIN_TOKEN_IMAGE_URL, } as const; @@ -38,6 +44,8 @@ export const MULTICHAIN_PROVIDER_CONFIGS: Record< type: 'rpc', rpcPrefs: { imageUrl: MULTICHAIN_TOKEN_IMAGE_MAP[MultichainNetworks.BITCOIN], + blockExplorerUrl: + MULTICHAIN_NETWORK_BLOCK_EXPLORER_URL_MAP[MultichainNetworks.BITCOIN], }, isAddressCompatible: isBtcMainnetAddress, }, @@ -50,6 +58,10 @@ export const MULTICHAIN_PROVIDER_CONFIGS: Record< type: 'rpc', rpcPrefs: { imageUrl: MULTICHAIN_TOKEN_IMAGE_MAP[MultichainNetworks.BITCOIN], + blockExplorerUrl: + MULTICHAIN_NETWORK_BLOCK_EXPLORER_URL_MAP[ + MultichainNetworks.BITCOIN_TESTNET + ], }, isAddressCompatible: isBtcTestnetAddress, }, diff --git a/test/e2e/tests/dapp-interactions/block-explorer.spec.js b/test/e2e/tests/dapp-interactions/block-explorer.spec.js index a94e0518c6d4..50c8d09efc6c 100644 --- a/test/e2e/tests/dapp-interactions/block-explorer.spec.js +++ b/test/e2e/tests/dapp-interactions/block-explorer.spec.js @@ -16,6 +16,16 @@ describe('Block Explorer', function () { providerConfig: { rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, }, + networkConfigurations: { + networkConfigurationId: { + chainId: '0x539', + nickname: 'Localhost 8545', + rpcUrl: 'http://localhost:8545', + ticker: 'ETH', + rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, + }, + }, + selectedNetworkClientId: 'networkConfigurationId', }) .build(), ganacheOptions: defaultGanacheOptions, @@ -58,6 +68,16 @@ describe('Block Explorer', function () { providerConfig: { rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, }, + networkConfigurations: { + networkConfigurationId: { + chainId: '0x539', + nickname: 'Localhost 8545', + rpcUrl: 'http://localhost:8545', + ticker: 'ETH', + rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, + }, + }, + selectedNetworkClientId: 'networkConfigurationId', }) .withTokensControllerERC20() .build(), @@ -108,6 +128,16 @@ describe('Block Explorer', function () { providerConfig: { rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, }, + networkConfigurations: { + networkConfigurationId: { + chainId: '0x539', + nickname: 'Localhost 8545', + rpcUrl: 'http://localhost:8545', + ticker: 'ETH', + rpcPrefs: { blockExplorerUrl: 'https://etherscan.io/' }, + }, + }, + selectedNetworkClientId: 'networkConfigurationId', }) .withTransactionControllerCompletedTransaction() .build(), diff --git a/test/jest/mocks.js b/test/jest/mocks.js index b52a0d984df3..49796dd30965 100644 --- a/test/jest/mocks.js +++ b/test/jest/mocks.js @@ -175,7 +175,7 @@ export function createMockInternalAccount({ name, type = EthAccountType.Eoa, keyringType = KeyringTypes.hd, - snapOptions, + snapOptions = undefined, } = {}) { let methods; diff --git a/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap b/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap index e62c58582592..08f3bd4364cd 100644 --- a/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap +++ b/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap @@ -36,7 +36,7 @@ exports[`Confirm Remove Account should match snapshot 1`] = ` style="height: 32px; width: 32px; border-radius: 16px;" >