From b38244b877cea0e1aace85dc45d1dab9c74b0258 Mon Sep 17 00:00:00 2001 From: Salim TOUBAL Date: Fri, 22 Nov 2024 19:22:11 +0100 Subject: [PATCH 01/40] fix: add unit test for assets polling loops (#28646) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** the goal of this PR is to add unit tests for all multichain assets polling loops [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28646?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask 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. --- ui/hooks/useAccountTrackerPolling.test.ts | 143 +++++++++++++++++++ ui/hooks/useCurrencyRatePolling.test.ts | 128 +++++++++++++++++ ui/hooks/useTokenDetectionPolling.test.ts | 157 +++++++++++++++++++++ ui/hooks/useTokenRatesPolling.test.ts | 160 ++++++++++++++++++++++ 4 files changed, 588 insertions(+) create mode 100644 ui/hooks/useAccountTrackerPolling.test.ts create mode 100644 ui/hooks/useCurrencyRatePolling.test.ts create mode 100644 ui/hooks/useTokenDetectionPolling.test.ts create mode 100644 ui/hooks/useTokenRatesPolling.test.ts diff --git a/ui/hooks/useAccountTrackerPolling.test.ts b/ui/hooks/useAccountTrackerPolling.test.ts new file mode 100644 index 000000000000..010085ddce61 --- /dev/null +++ b/ui/hooks/useAccountTrackerPolling.test.ts @@ -0,0 +1,143 @@ +import { renderHookWithProvider } from '../../test/lib/render-helpers'; +import { + accountTrackerStartPolling, + accountTrackerStopPollingByPollingToken, +} from '../store/actions'; +import useAccountTrackerPolling from './useAccountTrackerPolling'; + +let mockPromises: Promise[]; + +jest.mock('../store/actions', () => ({ + accountTrackerStartPolling: jest.fn().mockImplementation((input) => { + const promise = Promise.resolve(`${input}_tracking`); + mockPromises.push(promise); + return promise; + }), + accountTrackerStopPollingByPollingToken: jest.fn(), +})); + +let originalPortfolioView: string | undefined; + +describe('useAccountTrackerPolling', () => { + beforeEach(() => { + // Mock process.env.PORTFOLIO_VIEW + originalPortfolioView = process.env.PORTFOLIO_VIEW; + process.env.PORTFOLIO_VIEW = 'true'; // Set your desired mock value here + + mockPromises = []; + jest.clearAllMocks(); + }); + + afterEach(() => { + // Restore the original value + process.env.PORTFOLIO_VIEW = originalPortfolioView; + }); + + it('should poll account trackers for network client IDs when enabled and stop on dismount', async () => { + process.env.PORTFOLIO_VIEW = 'true'; + + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x1': { + chainId: '0x1', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + '0x89': { + chainId: '0x89', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId2', + }, + ], + }, + }, + }, + }; + + const { unmount } = renderHookWithProvider( + () => useAccountTrackerPolling(), + state, + ); + + // Should poll each client ID + await Promise.all(mockPromises); + expect(accountTrackerStartPolling).toHaveBeenCalledTimes(2); + expect(accountTrackerStartPolling).toHaveBeenCalledWith( + 'selectedNetworkClientId', + ); + expect(accountTrackerStartPolling).toHaveBeenCalledWith( + 'selectedNetworkClientId2', + ); + + // Stop polling on dismount + unmount(); + expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledTimes(2); + expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledWith( + 'selectedNetworkClientId_tracking', + ); + }); + + it('should not poll if onboarding is not completed', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: false, + networkConfigurationsByChainId: { + '0x1': {}, + }, + }, + }; + + renderHookWithProvider(() => useAccountTrackerPolling(), state); + + await Promise.all(mockPromises); + expect(accountTrackerStartPolling).toHaveBeenCalledTimes(0); + expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when locked', async () => { + const state = { + metamask: { + isUnlocked: false, + completedOnboarding: true, + networkConfigurationsByChainId: { + '0x1': {}, + }, + }, + }; + + renderHookWithProvider(() => useAccountTrackerPolling(), state); + + await Promise.all(mockPromises); + expect(accountTrackerStartPolling).toHaveBeenCalledTimes(0); + expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when no network client IDs are provided', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + networkConfigurationsByChainId: { + '0x1': {}, + }, + }, + }; + + renderHookWithProvider(() => useAccountTrackerPolling(), state); + + await Promise.all(mockPromises); + expect(accountTrackerStartPolling).toHaveBeenCalledTimes(0); + expect(accountTrackerStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); +}); diff --git a/ui/hooks/useCurrencyRatePolling.test.ts b/ui/hooks/useCurrencyRatePolling.test.ts new file mode 100644 index 000000000000..9ad405a31c46 --- /dev/null +++ b/ui/hooks/useCurrencyRatePolling.test.ts @@ -0,0 +1,128 @@ +import { renderHookWithProvider } from '../../test/lib/render-helpers'; +import { + currencyRateStartPolling, + currencyRateStopPollingByPollingToken, +} from '../store/actions'; +import useCurrencyRatePolling from './useCurrencyRatePolling'; + +let mockPromises: Promise[]; + +jest.mock('../store/actions', () => ({ + currencyRateStartPolling: jest.fn().mockImplementation((input) => { + const promise = Promise.resolve(`${input}_rates`); + mockPromises.push(promise); + return promise; + }), + currencyRateStopPollingByPollingToken: jest.fn(), +})); + +describe('useCurrencyRatePolling', () => { + beforeEach(() => { + mockPromises = []; + jest.clearAllMocks(); + }); + + it('should poll currency rates for native currencies when enabled and stop on dismount', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useCurrencyRateCheck: true, + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x1': { + nativeCurrency: 'ETH', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + '0x89': { + nativeCurrency: 'BNB', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId2', + }, + ], + }, + }, + }, + }; + + const { unmount } = renderHookWithProvider( + () => useCurrencyRatePolling(), + state, + ); + + await Promise.all(mockPromises); + expect(currencyRateStartPolling).toHaveBeenCalledTimes(1); + expect(currencyRateStartPolling).toHaveBeenCalledWith(['ETH', 'BNB']); + + // Stop polling on dismount + unmount(); + expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledTimes(1); + expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledWith( + 'ETH,BNB_rates', + ); + }); + + it('should not poll if onboarding is not completed', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: false, + useCurrencyRateCheck: true, + networkConfigurationsByChainId: { + '0x1': { nativeCurrency: 'ETH' }, + }, + }, + }; + + renderHookWithProvider(() => useCurrencyRatePolling(), state); + + await Promise.all(mockPromises); + expect(currencyRateStartPolling).toHaveBeenCalledTimes(0); + expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when locked', async () => { + const state = { + metamask: { + isUnlocked: false, + completedOnboarding: true, + useCurrencyRateCheck: true, + networkConfigurationsByChainId: { + '0x1': { nativeCurrency: 'ETH' }, + }, + }, + }; + + renderHookWithProvider(() => useCurrencyRatePolling(), state); + + await Promise.all(mockPromises); + expect(currencyRateStartPolling).toHaveBeenCalledTimes(0); + expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when currency rate checking is disabled', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useCurrencyRateCheck: false, + networkConfigurationsByChainId: { + '0x1': { nativeCurrency: 'ETH' }, + }, + }, + }; + + renderHookWithProvider(() => useCurrencyRatePolling(), state); + + await Promise.all(mockPromises); + expect(currencyRateStartPolling).toHaveBeenCalledTimes(0); + expect(currencyRateStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); +}); diff --git a/ui/hooks/useTokenDetectionPolling.test.ts b/ui/hooks/useTokenDetectionPolling.test.ts new file mode 100644 index 000000000000..bae369ffd525 --- /dev/null +++ b/ui/hooks/useTokenDetectionPolling.test.ts @@ -0,0 +1,157 @@ +import { renderHookWithProvider } from '../../test/lib/render-helpers'; +import { + tokenDetectionStartPolling, + tokenDetectionStopPollingByPollingToken, +} from '../store/actions'; +import useTokenDetectionPolling from './useTokenDetectionPolling'; + +let mockPromises: Promise[]; + +jest.mock('../store/actions', () => ({ + tokenDetectionStartPolling: jest.fn().mockImplementation((input) => { + const promise = Promise.resolve(`${input}_detection`); + mockPromises.push(promise); + return promise; + }), + tokenDetectionStopPollingByPollingToken: jest.fn(), +})); +let originalPortfolioView: string | undefined; + +describe('useTokenDetectionPolling', () => { + beforeEach(() => { + // Mock process.env.PORTFOLIO_VIEW + originalPortfolioView = process.env.PORTFOLIO_VIEW; + process.env.PORTFOLIO_VIEW = 'true'; // Set your desired mock value here + + mockPromises = []; + jest.clearAllMocks(); + }); + + afterEach(() => { + // Restore the original value + process.env.PORTFOLIO_VIEW = originalPortfolioView; + }); + + it('should poll token detection for chain IDs when enabled and stop on dismount', async () => { + process.env.PORTFOLIO_VIEW = 'true'; + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useTokenDetection: true, + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x1': { + chainId: '0x1', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + '0x89': { + chainId: '0x89', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId2', + }, + ], + }, + }, + }, + }; + + const { unmount } = renderHookWithProvider( + () => useTokenDetectionPolling(), + state, + ); + + // Should poll each chain + await Promise.all(mockPromises); + expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(1); + expect(tokenDetectionStartPolling).toHaveBeenCalledWith(['0x1', '0x89']); + + // Stop polling on dismount + unmount(); + expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(1); + expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledWith( + '0x1,0x89_detection', + ); + }); + + it('should not poll if onboarding is not completed', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: false, + useTokenDetection: true, + networkConfigurationsByChainId: { + '0x1': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenDetectionPolling(), state); + + await Promise.all(mockPromises); + expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(0); + expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when locked', async () => { + const state = { + metamask: { + isUnlocked: false, + completedOnboarding: true, + useTokenDetection: true, + networkConfigurationsByChainId: { + '0x1': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenDetectionPolling(), state); + + await Promise.all(mockPromises); + expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(0); + expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when token detection is disabled', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useTokenDetection: false, + networkConfigurationsByChainId: { + '0x1': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenDetectionPolling(), state); + + await Promise.all(mockPromises); + expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(0); + expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when no chains are provided', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useTokenDetection: true, + networkConfigurationsByChainId: { + '0x1': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenDetectionPolling(), state); + + await Promise.all(mockPromises); + expect(tokenDetectionStartPolling).toHaveBeenCalledTimes(0); + expect(tokenDetectionStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); +}); diff --git a/ui/hooks/useTokenRatesPolling.test.ts b/ui/hooks/useTokenRatesPolling.test.ts new file mode 100644 index 000000000000..9383527e6fbd --- /dev/null +++ b/ui/hooks/useTokenRatesPolling.test.ts @@ -0,0 +1,160 @@ +import { renderHookWithProvider } from '../../test/lib/render-helpers'; +import { + tokenRatesStartPolling, + tokenRatesStopPollingByPollingToken, +} from '../store/actions'; +import useTokenRatesPolling from './useTokenRatesPolling'; + +let mockPromises: Promise[]; + +jest.mock('../store/actions', () => ({ + tokenRatesStartPolling: jest.fn().mockImplementation((input) => { + const promise = Promise.resolve(`${input}_rates`); + mockPromises.push(promise); + return promise; + }), + tokenRatesStopPollingByPollingToken: jest.fn(), +})); + +let originalPortfolioView: string | undefined; +describe('useTokenRatesPolling', () => { + beforeEach(() => { + // Mock process.env.PORTFOLIO_VIEW + originalPortfolioView = process.env.PORTFOLIO_VIEW; + process.env.PORTFOLIO_VIEW = 'true'; // Set your desired mock value here + + mockPromises = []; + jest.clearAllMocks(); + }); + + afterEach(() => { + // Restore the original value + process.env.PORTFOLIO_VIEW = originalPortfolioView; + }); + + it('should poll token rates when enabled and stop on dismount', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useCurrencyRateCheck: true, + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x1': { + chainId: '0x1', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + '0x89': { + chainId: '0x89', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId2', + }, + ], + }, + }, + }, + }; + + const { unmount } = renderHookWithProvider( + () => useTokenRatesPolling(), + state, + ); + + // Should poll each chain + await Promise.all(mockPromises); + expect(tokenRatesStartPolling).toHaveBeenCalledTimes(2); + expect(tokenRatesStartPolling).toHaveBeenCalledWith('0x1'); + expect(tokenRatesStartPolling).toHaveBeenCalledWith('0x89'); + // Stop polling on dismount + unmount(); + expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(2); + expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledWith( + '0x1_rates', + ); + expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledWith( + '0x89_rates', + ); + }); + + it('should not poll if onboarding is not completed', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: false, + useCurrencyRateCheck: true, + networkConfigurationsByChainId: { + '0x1': {}, + '0x89': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenRatesPolling(), state); + + await Promise.all(mockPromises); + expect(tokenRatesStartPolling).toHaveBeenCalledTimes(0); + expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when locked', async () => { + const state = { + metamask: { + isUnlocked: false, + completedOnboarding: true, + useCurrencyRateCheck: true, + networkConfigurationsByChainId: { + '0x1': {}, + '0x89': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenRatesPolling(), state); + + await Promise.all(mockPromises); + expect(tokenRatesStartPolling).toHaveBeenCalledTimes(0); + expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when rate checking is disabled', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useCurrencyRateCheck: false, + networkConfigurationsByChainId: { + '0x1': {}, + '0x89': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenRatesPolling(), state); + + await Promise.all(mockPromises); + expect(tokenRatesStartPolling).toHaveBeenCalledTimes(0); + expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when no chains are provided', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useCurrencyRateCheck: true, + networkConfigurationsByChainId: {}, + }, + }; + + renderHookWithProvider(() => useTokenRatesPolling(), state); + + await Promise.all(mockPromises); + expect(tokenRatesStartPolling).toHaveBeenCalledTimes(0); + expect(tokenRatesStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); +}); From ad9a74841748e4353c2e1ee749208dc1cf099c28 Mon Sep 17 00:00:00 2001 From: jiexi Date: Fri, 22 Nov 2024 12:10:59 -0800 Subject: [PATCH 02/40] fix: Reset streams on BFCache events (#24950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Previously, Chrome's BFCache (Back Forward) strategy was to evict a page from cache if it received a message over any connected port streams. The port stream would remain open and the background script would NOT receive an onDisconnect. As long as the cached page did not receive a message, the port would still function when the page became active again. This was problematic because MetaMask was likely to send a message to the still connected cached page at some point due to the nature of notifications, which would evict the page from cache, neutralizing the performance benefit of the BFCache for the end user. Now, Chrome's BFCache strategy is to trigger an onDisconnect for the background script, but NOT the cached page. The port stream is invalid despite not being closed on the cached page side. This is problematic because we do not listen for events relevant to when a BFCached page becomes active and thus do not reset the invalid stream. To address both strategies, we now listen for the `pageshow` and `pagehide` events. When a page is entering a BFCached state, we preemptively end the port stream connection (even if the user is on an older version of chrome that would have kept it alive). When a BFCached page is restored to an active state, we establish a port stream connection. We know the port stream must be restored/reset because we were the ones responsible for preemptively ending it earlier in the lifecycle. Combining these two changes allows us to handle both the old and new BFCache strategies without having to target them by versions separately. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/24950?quickstart=1) ## **Related issues** See: https://developer.chrome.com/blog/bfcache-extension-messaging-changes?hl=en See: https://github.com/MetaMask/metamask-extension/issues/13373 See: https://web.dev/articles/bfcache (useful links at bottom) See: https://github.com/w3c/webextensions/issues/474 Fixes: https://github.com/MetaMask/MetaMask-planning/issues/2582 ## **Manual testing steps** Steps are for macOS. Using chrome 123 or newer **Testing with old BFCache strategy** 1. Close the entire chrome app 1. run `open /Applications/Google\ Chrome.app --args --disable-features=DisconnectExtensionMessagePortWhenPageEntersBFCache` 1. Visit `http://www.brainjar.com/java/host/test.html` 1. Open console 1. Enter `await window.ethereum.request({method: 'eth_chainId'})`, which should be responsive 1. Visit `chrome://terms/` 1. Use the back button to go back to the brainjar test page 1. Enter `await window.ethereum.request({method: 'eth_chainId'})`, which should be responsive **Testing with the new BFCache strategy** Repeat the steps above, but use `--enable-features` instead of `disable` MetaMask Behavior should look the same regardless of browser's BFCache strategy ## **Screenshots/Recordings** BFCache Behavior https://github.com/MetaMask/metamask-extension/assets/918701/efeb1591-5fde-44c8-b0a3-3573dfb97806 Prerender Behavior (to show affected chromium browsers still reset streams correctly) https://github.com/MetaMask/metamask-extension/assets/918701/7461bf64-b5b0-4e70-96d5-416cf5bf6b7c ## **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 - [ ] 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/scripts/contentscript.js | 16 +++++++ app/scripts/streams/provider-stream.ts | 32 +++++++++---- test/e2e/provider/bfcache.spec.js | 65 ++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 test/e2e/provider/bfcache.spec.js diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7c427a1821c5..a38ac9fe5833 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,8 +1,10 @@ import { getIsBrowserPrerenderBroken } from '../../shared/modules/browser-runtime.utils'; import shouldInjectProvider from '../../shared/modules/provider-injection'; import { + destroyStreams, initStreams, onDisconnectDestroyStreams, + setupExtensionStreams, } from './streams/provider-stream'; import { isDetectedPhishingSite, @@ -33,6 +35,20 @@ const start = () => { ); }); } + + window.addEventListener('pageshow', (event) => { + if (event.persisted) { + console.warn('BFCached page has become active. Restoring the streams.'); + setupExtensionStreams(); + } + }); + + window.addEventListener('pagehide', (event) => { + if (event.persisted) { + console.warn('Page may become BFCached. Destroying the streams.'); + destroyStreams(); + } + }); } }; diff --git a/app/scripts/streams/provider-stream.ts b/app/scripts/streams/provider-stream.ts index 9616253815e8..82b159130242 100644 --- a/app/scripts/streams/provider-stream.ts +++ b/app/scripts/streams/provider-stream.ts @@ -33,7 +33,7 @@ let legacyExtMux: ObjectMultiplex, let extensionMux: ObjectMultiplex, extensionChannel: Substream, - extensionPort: browser.Runtime.Port, + extensionPort: browser.Runtime.Port | null, extensionStream: PortStream | null, pageMux: ObjectMultiplex, pageChannel: Substream; @@ -65,7 +65,7 @@ const setupPageStreams = () => { // The field below is used to ensure that replay is done only once for each restart. let METAMASK_EXTENSION_CONNECT_SENT = false; -const setupExtensionStreams = () => { +export const setupExtensionStreams = () => { METAMASK_EXTENSION_CONNECT_SENT = true; extensionPort = browser.runtime.connect({ name: CONTENT_SCRIPT }); extensionStream = new PortStream(extensionPort); @@ -226,19 +226,35 @@ const onMessageSetUpExtensionStreams = (msg: MessageType) => { return undefined; }; +/** + * Ends two-way communication streams between browser extension and + * the local per-page browser context. + */ +export function destroyStreams() { + if (!extensionPort) { + return; + } + extensionPort.onDisconnect.removeListener(onDisconnectDestroyStreams); + + destroyExtensionStreams(); + destroyLegacyExtensionStreams(); + + extensionPort.disconnect(); + extensionPort = null; + + METAMASK_EXTENSION_CONNECT_SENT = false; +} + /** * This listener destroys the extension streams when the extension port is disconnected, * so that streams may be re-established later when the extension port is reconnected. * * @param [err] - Stream connection error */ -export const onDisconnectDestroyStreams = (err: unknown) => { +export function onDisconnectDestroyStreams(err: unknown) { const lastErr = err || checkForLastError(); - extensionPort.onDisconnect.removeListener(onDisconnectDestroyStreams); - - destroyExtensionStreams(); - destroyLegacyExtensionStreams(); + destroyStreams(); /** * If an error is found, reset the streams. When running two or more dapps, resetting the service @@ -251,7 +267,7 @@ export const onDisconnectDestroyStreams = (err: unknown) => { console.warn(`${lastErr} Resetting the streams.`); setTimeout(setupExtensionStreams, 1000); } -}; +} /** * Initializes two-way communication streams between the browser extension and diff --git a/test/e2e/provider/bfcache.spec.js b/test/e2e/provider/bfcache.spec.js new file mode 100644 index 000000000000..4d2c5d2bb1e7 --- /dev/null +++ b/test/e2e/provider/bfcache.spec.js @@ -0,0 +1,65 @@ +const { strict: assert } = require('assert'); +const { + withFixtures, + defaultGanacheOptions, + DAPP_URL, + openDapp, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); + +const triggerBFCache = async (driver) => { + await driver.executeScript(` + window.addEventListener('pageshow', (event) => { + if (event.persisted) { + window.restoredFromBFCache = true + } + }); + `); + + await driver.driver.get(`chrome://terms/`); + + await driver.driver.navigate().back(); + + const restoredFromBFCache = await driver.executeScript( + `return window.restoredFromBFCache`, + ); + + if (!restoredFromBFCache) { + assert.fail(new Error('Failed to trigger BFCache')); + } +}; + +describe('BFCache', function () { + it('has a working provider stream when a dapp is restored from BFCache', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await openDapp(driver, undefined, DAPP_URL); + + const request = JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_chainId', + params: [], + id: 0, + }); + + const initialResult = await driver.executeScript( + `return window.ethereum.request(${request})`, + ); + assert.equal(initialResult, '0x539'); + + await triggerBFCache(driver); + + const bfcacheResult = await driver.executeScript( + `return window.ethereum.request(${request})`, + ); + assert.equal(bfcacheResult, '0x539'); + }, + ); + }); +}); From 9bdfd03bfafb46f10c11ba104bcd60d37fc370eb Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Fri, 22 Nov 2024 13:27:38 -0800 Subject: [PATCH 03/40] fix: market data for native tokens with non zero addresses (#28584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Draft When querying the price API, the native token is usually represented by the zero address. But this is not the case on some chains like polygon, whose native token has a contract `0x0000000000000000000000000000000000001010`. Depends on: https://github.com/MetaMask/core/pull/4952 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28584?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** (pre-req - you may need to enable the `PORTFOLIO_VIEW=true` flag) 1. Onboard with an SRP that have POL tokens. 2. Connect and switch to Polygon 3. View the POL token on the tokens section on the home page. - Does it contain percentages and market data. 5. View the POL asset page. - Does it contain the market details view; and percentage sections? ## **Screenshots/Recordings** | Before | After | |--------|--------| | ![Screenshot 2024-11-22 at 16 42 25](https://github.com/user-attachments/assets/51e67809-b53f-4a29-a345-ddda516a08b2) | ![Screenshot 2024-11-22 at 16 29 38](https://github.com/user-attachments/assets/87245972-5a03-4acb-85d0-dfe01660b038) | | ![Screenshot 2024-11-22 at 16 42 41](https://github.com/user-attachments/assets/b87a9cc3-dcb5-4479-8406-3a832f9f926f) | ![Screenshot 2024-11-22 at 16 29 46](https://github.com/user-attachments/assets/20ab36e9-0d55-45d6-a57b-0c05e8c533b9) | ### **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** - [ ] 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: Prithpal Sooriya --- ...s-controllers-npm-45.1.0-d914c453f0.patch} | 40 +----------- ...-assets-controllers-patch-9e00573eb4.patch | 62 ------------------- package.json | 2 +- ...gated-percentage-overview-cross-chains.tsx | 9 ++- .../aggregated-percentage-overview.test.tsx | 19 +++--- .../aggregated-percentage-overview.tsx | 8 ++- .../app/wallet-overview/coin-overview.tsx | 7 ++- .../percentage-and-amount-change.test.tsx | 38 +++++++++--- .../percentage-and-amount-change.tsx | 8 ++- .../token-list-item/token-list-item.tsx | 10 +-- ui/pages/asset/components/asset-page.tsx | 4 +- yarn.lock | 18 +++--- 12 files changed, 83 insertions(+), 142 deletions(-) rename .yarn/patches/{@metamask-assets-controllers-npm-45.0.0-31810ece32.patch => @metamask-assets-controllers-npm-45.1.0-d914c453f0.patch} (51%) delete mode 100644 .yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch diff --git a/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch b/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch similarity index 51% rename from .yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch rename to .yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch index 77a2e7f21cfb..ca412ba89489 100644 --- a/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch +++ b/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch @@ -1,39 +1,3 @@ -diff --git a/dist/TokenDetectionController.cjs b/dist/TokenDetectionController.cjs -index 8fd5efde7a3c24080f8a43f79d10300e8c271245..66f656d9a55f1154024a8c18a9fe27b4ed39a21d 100644 ---- a/dist/TokenDetectionController.cjs -+++ b/dist/TokenDetectionController.cjs -@@ -250,17 +250,20 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_ - } - }); - this.messagingSystem.subscribe('AccountsController:selectedEvmAccountChange', -- // TODO: Either fix this lint violation or explain why it's necessary to ignore. -- // eslint-disable-next-line @typescript-eslint/no-misused-promises -- async (selectedAccount) => { -- const isSelectedAccountIdChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f") !== selectedAccount.id; -- if (isSelectedAccountIdChanged) { -- __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, selectedAccount.id, "f"); -- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { -- selectedAddress: selectedAccount.address, -- }); -- } -- }); -+ // TODO: Either fix this lint violation or explain why it's necessary to ignore. -+ // eslint-disable-next-line @typescript-eslint/no-misused-promises -+ async (selectedAccount) => { -+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState'); -+ const chainIds = Object.keys(networkConfigurationsByChainId); -+ const isSelectedAccountIdChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f") !== selectedAccount.id; -+ if (isSelectedAccountIdChanged) { -+ __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, selectedAccount.id, "f"); -+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { -+ selectedAddress: selectedAccount.address, -+ chainIds, -+ }); -+ } -+ }); - }, _TokenDetectionController_stopPolling = function _TokenDetectionController_stopPolling() { - if (__classPrivateFieldGet(this, _TokenDetectionController_intervalId, "f")) { - clearInterval(__classPrivateFieldGet(this, _TokenDetectionController_intervalId, "f")); diff --git a/dist/assetsUtil.cjs b/dist/assetsUtil.cjs index 48571b8c1b78e94d88e1837e986b5f8735ac651b..61246f51500c8cab48f18296a73629fb73454caa 100644 --- a/dist/assetsUtil.cjs @@ -56,7 +20,7 @@ index 48571b8c1b78e94d88e1837e986b5f8735ac651b..61246f51500c8cab48f18296a73629fb // because most cid v0s appear to be incompatible with IPFS subdomains return { diff --git a/dist/token-prices-service/codefi-v2.mjs b/dist/token-prices-service/codefi-v2.mjs -index e7eaad2cfa8b233c4fd42a51f745233a1cc5c387..bf8ec7819f678c2f185d6a85d7e3ea81f055a309 100644 +index a13403446a2376d4d905a9ef733941798da89c88..3c8229f9ea40f4c1ee760a22884e1066dac82ec7 100644 --- a/dist/token-prices-service/codefi-v2.mjs +++ b/dist/token-prices-service/codefi-v2.mjs @@ -12,8 +12,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function ( @@ -65,7 +29,7 @@ index e7eaad2cfa8b233c4fd42a51f745233a1cc5c387..bf8ec7819f678c2f185d6a85d7e3ea81 import { hexToNumber } from "@metamask/utils"; -import $cockatiel from "cockatiel"; -const { circuitBreaker, ConsecutiveBreaker, ExponentialBackoff, handleAll, retry, wrap, CircuitState } = $cockatiel; -+import { circuitBreaker, ConsecutiveBreaker, ExponentialBackoff, handleAll, retry, wrap, CircuitState } from "cockatiel" ++import { circuitBreaker, ConsecutiveBreaker, ExponentialBackoff, handleAll, retry, wrap, CircuitState } from "cockatiel"; /** * The list of currencies that can be supplied as the `vsCurrency` parameter to * the `/spot-prices` endpoint, in lowercase form. diff --git a/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch b/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch deleted file mode 100644 index 1b9e5a4ba848..000000000000 --- a/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/dist/TokenDetectionController.cjs b/dist/TokenDetectionController.cjs -index ab23c95d667357db365f925c4c4acce4736797f8..8fd5efde7a3c24080f8a43f79d10300e8c271245 100644 ---- a/dist/TokenDetectionController.cjs -+++ b/dist/TokenDetectionController.cjs -@@ -204,13 +204,10 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo - // Try detecting tokens via Account API first if conditions allow - if (supportedNetworks && chainsToDetectUsingAccountAPI.length > 0) { - const apiResult = await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_attemptAccountAPIDetection).call(this, chainsToDetectUsingAccountAPI, addressToDetect, supportedNetworks); -- // If API succeeds and no chains are left for RPC detection, we can return early -- if (apiResult?.result === 'success' && -- chainsToDetectUsingRpc.length === 0) { -- return; -+ // If the account API call failed, have those chains fall back to RPC detection -+ if (apiResult?.result === 'failed') { -+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks); - } -- // If API fails or chainsToDetectUsingRpc still has items, add chains to RPC detection -- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks); - } - // Proceed with RPC detection if there are chains remaining in chainsToDetectUsingRpc - if (chainsToDetectUsingRpc.length > 0) { -@@ -446,8 +443,7 @@ async function _TokenDetectionController_addDetectedTokensViaAPI({ selectedAddre - const tokenBalancesByChain = await __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f") - .getMultiNetworksBalances(selectedAddress, chainIds, supportedNetworks) - .catch(() => null); -- if (!tokenBalancesByChain || -- Object.keys(tokenBalancesByChain).length === 0) { -+ if (tokenBalancesByChain === null) { - return { result: 'failed' }; - } - // Process each chain ID individually -diff --git a/dist/TokenDetectionController.mjs b/dist/TokenDetectionController.mjs -index f75eb5c2c74f2a9d15a79760985111171dc938e1..ebc30bb915cc39dabf49f9e0da84a7948ae1ed48 100644 ---- a/dist/TokenDetectionController.mjs -+++ b/dist/TokenDetectionController.mjs -@@ -205,13 +205,10 @@ export class TokenDetectionController extends StaticIntervalPollingController() - // Try detecting tokens via Account API first if conditions allow - if (supportedNetworks && chainsToDetectUsingAccountAPI.length > 0) { - const apiResult = await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_attemptAccountAPIDetection).call(this, chainsToDetectUsingAccountAPI, addressToDetect, supportedNetworks); -- // If API succeeds and no chains are left for RPC detection, we can return early -- if (apiResult?.result === 'success' && -- chainsToDetectUsingRpc.length === 0) { -- return; -+ // If the account API call failed, have those chains fall back to RPC detection -+ if (apiResult?.result === 'failed') { -+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks); - } -- // If API fails or chainsToDetectUsingRpc still has items, add chains to RPC detection -- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks); - } - // Proceed with RPC detection if there are chains remaining in chainsToDetectUsingRpc - if (chainsToDetectUsingRpc.length > 0) { -@@ -446,8 +443,7 @@ async function _TokenDetectionController_addDetectedTokensViaAPI({ selectedAddre - const tokenBalancesByChain = await __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f") - .getMultiNetworksBalances(selectedAddress, chainIds, supportedNetworks) - .catch(() => null); -- if (!tokenBalancesByChain || -- Object.keys(tokenBalancesByChain).length === 0) { -+ if (tokenBalancesByChain === null) { - return { result: 'failed' }; - } - // Process each chain ID individually diff --git a/package.json b/package.json index 0f963c3aab2b..45896fdabd1f 100644 --- a/package.json +++ b/package.json @@ -294,7 +294,7 @@ "@metamask/address-book-controller": "^6.0.0", "@metamask/announcement-controller": "^7.0.0", "@metamask/approval-controller": "^7.0.0", - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch", "@metamask/base-controller": "^7.0.0", "@metamask/bitcoin-wallet-snap": "^0.8.2", "@metamask/browser-passworder": "^4.3.0", diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx index fe3698e2fc2f..6f7de0c95c7c 100644 --- a/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx +++ b/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx @@ -1,7 +1,9 @@ import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { zeroAddress, toChecksumAddress } from 'ethereumjs-util'; +import { toChecksumAddress } from 'ethereumjs-util'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; +import { Hex } from '@metamask/utils'; import { getCurrentCurrency, getSelectedAccount, @@ -89,8 +91,9 @@ export const AggregatedPercentageOverviewCrossChains = () => { item.tokensWithBalances, ); const nativePricePercentChange1d = - crossChainMarketData?.[item.chainId]?.[zeroAddress()] - ?.pricePercentChange1d; + crossChainMarketData?.[item.chainId]?.[ + getNativeTokenAddress(item.chainId as Hex) + ]?.pricePercentChange1d; const nativeFiat1dAgo = getCalculatedTokenAmount1dAgo( item.nativeFiatValue, diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx index 8da096151908..7610890d48da 100644 --- a/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx +++ b/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx @@ -8,6 +8,7 @@ import { getShouldHideZeroBalanceTokens, getTokensMarketData, getPreferences, + getCurrentChainId, } from '../../../selectors'; import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance'; import { AggregatedPercentageOverview } from './aggregated-percentage-overview'; @@ -26,20 +27,22 @@ jest.mock('../../../selectors', () => ({ getPreferences: jest.fn(), getShouldHideZeroBalanceTokens: jest.fn(), getTokensMarketData: jest.fn(), + getCurrentChainId: jest.fn(), })); jest.mock('../../../hooks/useAccountTotalFiatBalance', () => ({ useAccountTotalFiatBalance: jest.fn(), })); -const mockGetIntlLocale = getIntlLocale as unknown as jest.Mock; -const mockGetCurrentCurrency = getCurrentCurrency as jest.Mock; -const mockGetPreferences = getPreferences as jest.Mock; -const mockGetSelectedAccount = getSelectedAccount as unknown as jest.Mock; -const mockGetShouldHideZeroBalanceTokens = - getShouldHideZeroBalanceTokens as jest.Mock; - +const mockGetIntlLocale = jest.mocked(getIntlLocale); +const mockGetCurrentCurrency = jest.mocked(getCurrentCurrency); +const mockGetPreferences = jest.mocked(getPreferences); +const mockGetSelectedAccount = jest.mocked(getSelectedAccount); +const mockGetShouldHideZeroBalanceTokens = jest.mocked( + getShouldHideZeroBalanceTokens, +); const mockGetTokensMarketData = getTokensMarketData as jest.Mock; +const mockGetCurrentChainId = jest.mocked(getCurrentChainId); const selectedAccountMock = { id: 'd51c0116-de36-4e77-b35b-408d4ea82d01', @@ -166,7 +169,7 @@ describe('AggregatedPercentageOverview', () => { mockGetSelectedAccount.mockReturnValue(selectedAccountMock); mockGetShouldHideZeroBalanceTokens.mockReturnValue(false); mockGetTokensMarketData.mockReturnValue(marketDataMock); - + mockGetCurrentChainId.mockReturnValue('0x1'); jest.clearAllMocks(); }); diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx index 8c609610daa1..89bc94dab774 100644 --- a/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx +++ b/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx @@ -1,13 +1,15 @@ import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { zeroAddress, toChecksumAddress } from 'ethereumjs-util'; +import { toChecksumAddress } from 'ethereumjs-util'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; import { getCurrentCurrency, getSelectedAccount, getShouldHideZeroBalanceTokens, getTokensMarketData, getPreferences, + getCurrentChainId, } from '../../../selectors'; import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance'; @@ -37,6 +39,7 @@ export const AggregatedPercentageOverview = () => { const fiatCurrency = useSelector(getCurrentCurrency); const { privacyMode } = useSelector(getPreferences); const selectedAccount = useSelector(getSelectedAccount); + const currentChainId = useSelector(getCurrentChainId); const shouldHideZeroBalanceTokens = useSelector( getShouldHideZeroBalanceTokens, ); @@ -63,7 +66,8 @@ export const AggregatedPercentageOverview = () => { } // native token const nativePricePercentChange1d = - tokensMarketData?.[zeroAddress()]?.pricePercentChange1d; + tokensMarketData?.[getNativeTokenAddress(currentChainId)] + ?.pricePercentChange1d; const nativeFiat1dAgo = getCalculatedTokenAmount1dAgo( item.fiatBalance, nativePricePercentChange1d, diff --git a/ui/components/app/wallet-overview/coin-overview.tsx b/ui/components/app/wallet-overview/coin-overview.tsx index bf054d993e74..3be53776581d 100644 --- a/ui/components/app/wallet-overview/coin-overview.tsx +++ b/ui/components/app/wallet-overview/coin-overview.tsx @@ -7,11 +7,11 @@ import React, { } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import classnames from 'classnames'; -import { zeroAddress } from 'ethereumjs-util'; import { CaipChainId } from '@metamask/utils'; import type { Hex } from '@metamask/utils'; import { InternalAccount } from '@metamask/keyring-api'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; import { Box, ButtonIcon, @@ -231,7 +231,10 @@ export const CoinOverview = ({ return ( { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) diff --git a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx index abff9f40da8d..439c030a59cd 100644 --- a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx +++ b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx @@ -2,11 +2,13 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import { zeroAddress } from 'ethereumjs-util'; +import { MarketDataDetails } from '@metamask/assets-controllers'; import { getIntlLocale } from '../../../../../ducks/locale/locale'; import { getCurrentCurrency, getSelectedAccountCachedBalance, getTokensMarketData, + getCurrentChainId, } from '../../../../../selectors'; import { getConversionRate, @@ -26,6 +28,7 @@ jest.mock('../../../../../selectors', () => ({ getCurrentCurrency: jest.fn(), getSelectedAccountCachedBalance: jest.fn(), getTokensMarketData: jest.fn(), + getCurrentChainId: jest.fn(), })); jest.mock('../../../../../ducks/metamask/metamask', () => ({ @@ -33,13 +36,15 @@ jest.mock('../../../../../ducks/metamask/metamask', () => ({ getNativeCurrency: jest.fn(), })); -const mockGetIntlLocale = getIntlLocale as unknown as jest.Mock; -const mockGetCurrentCurrency = getCurrentCurrency as jest.Mock; -const mockGetSelectedAccountCachedBalance = - getSelectedAccountCachedBalance as jest.Mock; -const mockGetConversionRate = getConversionRate as jest.Mock; -const mockGetNativeCurrency = getNativeCurrency as jest.Mock; -const mockGetTokensMarketData = getTokensMarketData as jest.Mock; +const mockGetIntlLocale = jest.mocked(getIntlLocale); +const mockGetCurrentCurrency = jest.mocked(getCurrentCurrency); +const mockGetSelectedAccountCachedBalance = jest.mocked( + getSelectedAccountCachedBalance, +); +const mockGetConversionRate = jest.mocked(getConversionRate); +const mockGetNativeCurrency = jest.mocked(getNativeCurrency); +const mockGetTokensMarketData = jest.mocked(getTokensMarketData); +const mockGetCurrentChainId = jest.mocked(getCurrentChainId); describe('PercentageChange Component', () => { beforeEach(() => { @@ -51,9 +56,9 @@ describe('PercentageChange Component', () => { mockGetTokensMarketData.mockReturnValue({ [zeroAddress()]: { pricePercentChange1d: 2, - }, + } as MarketDataDetails, }); - + mockGetCurrentChainId.mockReturnValue('0x1'); jest.clearAllMocks(); }); @@ -108,4 +113,19 @@ describe('PercentageChange Component', () => { expect(percentageElement).toBeInTheDocument(); expect(numberElement).toBeInTheDocument(); }); + + it('should display percentage for non-zero native tokens (MATIC)', () => { + mockGetTokensMarketData.mockReturnValue({ + '0x0000000000000000000000000000000000001010': { + pricePercentChange1d: 2, + } as MarketDataDetails, + }); + mockGetCurrentCurrency.mockReturnValue('POL'); + mockGetCurrentChainId.mockReturnValue('0x89'); + render(); + const percentageElement = screen.getByText('(+1.00%)'); + const numberElement = screen.getByText('+POL 12.21'); + expect(percentageElement).toBeInTheDocument(); + expect(numberElement).toBeInTheDocument(); + }); }); diff --git a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx index be9921e88793..f1ba436ef47f 100644 --- a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx +++ b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx @@ -1,7 +1,8 @@ import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { BigNumber } from 'bignumber.js'; -import { isHexString, zeroAddress } from 'ethereumjs-util'; +import { isHexString } from 'ethereumjs-util'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; import { Text, Box } from '../../../../component-library'; import { Display, @@ -9,6 +10,7 @@ import { TextVariant, } from '../../../../../helpers/constants/design-system'; import { + getCurrentChainId, getCurrentCurrency, getSelectedAccountCachedBalance, getTokensMarketData, @@ -66,10 +68,12 @@ export const PercentageAndAmountChange = ({ const conversionRate = useSelector(getConversionRate); const nativeCurrency = useSelector(getNativeCurrency); const marketData = useSelector(getTokensMarketData); + const currentChainId = useSelector(getCurrentChainId); const balanceChange = useMemo(() => { // Extracts the 1-day percentage change in price from marketData using the zero address as a key. - const percentage1d = marketData?.[zeroAddress()]?.pricePercentChange1d; + const percentage1d = + marketData?.[getNativeTokenAddress(currentChainId)]?.pricePercentChange1d; // Checks if the balanceValue is in hex format. This is important for cryptocurrency balances which are often represented in hex. if (isHexString(balanceValue)) { diff --git a/ui/components/multichain/token-list-item/token-list-item.tsx b/ui/components/multichain/token-list-item/token-list-item.tsx index 540d2d8be98a..ef49ec3126cb 100644 --- a/ui/components/multichain/token-list-item/token-list-item.tsx +++ b/ui/components/multichain/token-list-item/token-list-item.tsx @@ -2,7 +2,8 @@ import React, { useContext, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import classnames from 'classnames'; -import { zeroAddress } from 'ethereumjs-util'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; +import { Hex } from '@metamask/utils'; import { AlignItems, BackgroundColor, @@ -336,13 +337,14 @@ export const TokenListItem = ({ diff --git a/ui/pages/asset/components/asset-page.tsx b/ui/pages/asset/components/asset-page.tsx index 19ce592f0071..0f4529861dbc 100644 --- a/ui/pages/asset/components/asset-page.tsx +++ b/ui/pages/asset/components/asset-page.tsx @@ -3,8 +3,8 @@ import { useHistory } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { EthMethod } from '@metamask/keyring-api'; import { isEqual } from 'lodash'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; import { Hex } from '@metamask/utils'; -import { zeroAddress } from 'ethereumjs-util'; import { getCurrentCurrency, getDataCollectionForMarketing, @@ -140,7 +140,7 @@ const AssetPage = ({ const address = type === AssetType.token ? toChecksumHexAddress(asset.address) - : zeroAddress(); + : getNativeTokenAddress(chainId); const balance = calculateTokenBalance({ isNative: type === AssetType.native, diff --git a/yarn.lock b/yarn.lock index bc208e1a8f92..db952a4b1e70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4934,9 +4934,9 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@npm:45.0.0": - version: 45.0.0 - resolution: "@metamask/assets-controllers@npm:45.0.0" +"@metamask/assets-controllers@npm:45.1.0": + version: 45.1.0 + resolution: "@metamask/assets-controllers@npm:45.1.0" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@ethersproject/abi": "npm:^5.7.0" @@ -4969,13 +4969,13 @@ __metadata: "@metamask/keyring-controller": ^19.0.0 "@metamask/network-controller": ^22.0.0 "@metamask/preferences-controller": ^15.0.0 - checksum: 10/0ad51464cf060f1c2cab56c2c8d9daa5f29987e8ead69c0e029fb8357fa5c629434116de5663dc38a57c11b3736b6c7d9b1db9b6892a453fbc3f9c6965d42295 + checksum: 10/7e366739c2b3fc8000aaa8cd302d3e2c3958e29e7c88f3e7e188c4ec46454cf9e894c1e230a84092bba8e6c5274b301dfdb4e55a0ba4322bdcb9e7325ad5a5e5 languageName: node linkType: hard -"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch": - version: 45.0.0 - resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch::version=45.0.0&hash=8e5354" +"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch": + version: 45.1.0 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch::version=45.1.0&hash=86167d" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@ethersproject/abi": "npm:^5.7.0" @@ -5008,7 +5008,7 @@ __metadata: "@metamask/keyring-controller": ^19.0.0 "@metamask/network-controller": ^22.0.0 "@metamask/preferences-controller": ^15.0.0 - checksum: 10/823627b5bd23829d81a54291f74c4ddf52d0732a840c121c4ae7f1fc468dd98f3fc1e64b7f8a9bbaaa76cd6670082f2976e5e6ecf872e04c212a5c8ec5fe4916 + checksum: 10/985ec7dffb75aaff8eea00f556157e42cd5db063cbfa94dfd4f070c5b9d98b1315f3680fa7370f4c734a1688598bbda9c44a7c33c342e1d123d6ee2edd6120fc languageName: node linkType: hard @@ -26816,7 +26816,7 @@ __metadata: "@metamask/announcement-controller": "npm:^7.0.0" "@metamask/api-specs": "npm:^0.9.3" "@metamask/approval-controller": "npm:^7.0.0" - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.0.0-31810ece32.patch" + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch" "@metamask/auto-changelog": "npm:^2.1.0" "@metamask/base-controller": "npm:^7.0.0" "@metamask/bitcoin-wallet-snap": "npm:^0.8.2" From 6eb7ccf4f4e4f080cb7d7550b3742202d22a5bea Mon Sep 17 00:00:00 2001 From: micaelae <100321200+micaelae@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:50:35 -0800 Subject: [PATCH 04/40] chore: sort and display all bridge quotes (#27731) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Changes - Fetch exchange rates on src/dest token selection - Calculate quote metadata and implement sorting - Create and style modal for displaying all bridge quotes - Autofill src token if navigating from asset page [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27731?quickstart=1) ## **Related issues** Fixes: N/A ## **Manual testing steps** 1. Request quotes 2. View all quotes 3. Toggle sorting and inspect output 4. Verify that modal matches mocks 5. Try selecting alternate quote ## **Screenshots/Recordings** ### **Before** Mocks: https://www.figma.com/design/IuOIRmU3wI0IdJIfol0ESu/Cross-Chain-Swaps?node-id=1374-7239&m=dev ### **After** ![Screenshot 2024-11-12 at 5 08 49 PM](https://github.com/user-attachments/assets/f9db89b7-2753-4259-a28d-b5eb7e908323) ## **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/_locales/en/messages.json | 13 +- .../bridge/bridge-controller.test.ts | 183 +++- .../controllers/bridge/bridge-controller.ts | 80 +- app/scripts/controllers/bridge/types.ts | 12 +- app/scripts/metamask-controller.js | 4 + shared/constants/bridge.ts | 4 + .../data/bridge/mock-quotes-erc20-native.json | 894 ++++++++++++++++++ .../bridge/mock-quotes-native-erc20-eth.json | 258 +++++ .../data/bridge/mock-quotes-native-erc20.json | 2 +- test/jest/mock-store.js | 12 + ui/ducks/bridge/actions.ts | 12 +- ui/ducks/bridge/bridge.test.ts | 103 +- ui/ducks/bridge/bridge.ts | 41 +- ui/ducks/bridge/selectors.test.ts | 379 +++++++- ui/ducks/bridge/selectors.ts | 262 ++++- ui/ducks/bridge/utils.ts | 19 + ui/hooks/bridge/useBridging.test.ts | 8 +- ui/hooks/bridge/useBridging.ts | 9 +- ui/hooks/bridge/useCountdownTimer.test.ts | 5 +- ui/hooks/useTokensWithFiltering.ts | 16 + ui/pages/bridge/index.tsx | 32 +- ui/pages/bridge/layout/column.tsx | 23 + ui/pages/bridge/layout/index.tsx | 5 + ui/pages/bridge/layout/row.tsx | 27 + ui/pages/bridge/layout/tooltip.tsx | 83 ++ ui/pages/bridge/prepare/bridge-cta-button.tsx | 12 +- .../bridge/prepare/bridge-input-group.tsx | 12 +- .../prepare/prepare-bridge-page.test.tsx | 7 + .../bridge/prepare/prepare-bridge-page.tsx | 107 ++- .../bridge-quote-card.test.tsx.snap | 16 +- .../bridge-quotes-modal.test.tsx.snap | 135 ++- .../bridge/quotes/bridge-quote-card.test.tsx | 4 +- ui/pages/bridge/quotes/bridge-quote-card.tsx | 71 +- .../quotes/bridge-quotes-modal.stories.tsx | 106 +++ .../bridge/quotes/bridge-quotes-modal.tsx | 201 +++- ui/pages/bridge/quotes/index.scss | 20 +- ui/pages/bridge/types.ts | 24 + ui/pages/bridge/utils/quote.test.ts | 309 ++++++ ui/pages/bridge/utils/quote.ts | 175 +++- 39 files changed, 3412 insertions(+), 273 deletions(-) create mode 100644 test/data/bridge/mock-quotes-erc20-native.json create mode 100644 test/data/bridge/mock-quotes-native-erc20-eth.json create mode 100644 ui/pages/bridge/layout/column.tsx create mode 100644 ui/pages/bridge/layout/index.tsx create mode 100644 ui/pages/bridge/layout/row.tsx create mode 100644 ui/pages/bridge/layout/tooltip.tsx create mode 100644 ui/pages/bridge/quotes/bridge-quotes-modal.stories.tsx create mode 100644 ui/pages/bridge/utils/quote.test.ts diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f51c9708fc20..2d603ddc5156 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -863,8 +863,8 @@ "bridgeFrom": { "message": "Bridge from" }, - "bridgeOverallCost": { - "message": "Overall cost" + "bridgeNetCost": { + "message": "Net cost" }, "bridgeSelectNetwork": { "message": "Select network" @@ -873,7 +873,7 @@ "message": "Select token and amount" }, "bridgeTimingMinutes": { - "message": "$1 minutes", + "message": "$1 min", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" }, "bridgeTimingTooltipText": { @@ -4373,6 +4373,13 @@ "quoteRate": { "message": "Quote rate" }, + "quotedNetworkFee": { "message": "$1 network fee" }, + "quotedReceiveAmount": { + "message": "$1 receive amount" + }, + "quotedReceivingAmount": { + "message": "$1 receiving" + }, "rank": { "message": "Rank" }, diff --git a/app/scripts/controllers/bridge/bridge-controller.test.ts b/app/scripts/controllers/bridge/bridge-controller.test.ts index 8369d910f78b..5cadcb1bd375 100644 --- a/app/scripts/controllers/bridge/bridge-controller.test.ts +++ b/app/scripts/controllers/bridge/bridge-controller.test.ts @@ -1,4 +1,6 @@ import nock from 'nock'; +import { BigNumber } from 'bignumber.js'; +import { add0x } from '@metamask/utils'; import { BRIDGE_API_BASE_URL } from '../../../../shared/constants/bridge'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import { SWAPS_API_V2_BASE_URL } from '../../../../shared/constants/swaps'; @@ -7,6 +9,13 @@ import { flushPromises } from '../../../../test/lib/timer-helpers'; // eslint-disable-next-line import/no-restricted-paths import * as bridgeUtil from '../../../../ui/pages/bridge/bridge.util'; import * as balanceUtils from '../../../../shared/modules/bridge-utils/balance'; +import mockBridgeQuotesErc20Native from '../../../../test/data/bridge/mock-quotes-erc20-native.json'; +import mockBridgeQuotesNativeErc20 from '../../../../test/data/bridge/mock-quotes-native-erc20.json'; +import mockBridgeQuotesNativeErc20Eth from '../../../../test/data/bridge/mock-quotes-native-erc20-eth.json'; +// TODO: Remove restricted import +// eslint-disable-next-line import/no-restricted-paths +import { QuoteResponse } from '../../../../ui/pages/bridge/types'; +import { decimalToHex } from '../../../../shared/modules/conversion.utils'; import BridgeController from './bridge-controller'; import { BridgeControllerMessenger } from './types'; import { DEFAULT_BRIDGE_CONTROLLER_STATE } from './constants'; @@ -35,6 +44,7 @@ jest.mock('@ethersproject/providers', () => { Web3Provider: jest.fn(), }; }); +const getLayer1GasFeeMock = jest.fn(); describe('BridgeController', function () { let bridgeController: BridgeController; @@ -42,6 +52,7 @@ describe('BridgeController', function () { beforeAll(function () { bridgeController = new BridgeController({ messenger: messengerMock, + getLayer1GasFee: getLayer1GasFeeMock, }); }); @@ -278,7 +289,7 @@ describe('BridgeController', function () { .mockImplementationOnce(async () => { return await new Promise((resolve) => { return setTimeout(() => { - resolve([1, 2, 3] as never); + resolve(mockBridgeQuotesNativeErc20Eth as never); }, 5000); }); }); @@ -286,7 +297,10 @@ describe('BridgeController', function () { fetchBridgeQuotesSpy.mockImplementationOnce(async () => { return await new Promise((resolve) => { return setTimeout(() => { - resolve([5, 6, 7] as never); + resolve([ + ...mockBridgeQuotesNativeErc20Eth, + ...mockBridgeQuotesNativeErc20Eth, + ] as never); }, 10000); }); }); @@ -363,7 +377,7 @@ describe('BridgeController', function () { expect(bridgeController.state.bridgeState).toEqual( expect.objectContaining({ quoteRequest: { ...quoteRequest, insufficientBal: false }, - quotes: [1, 2, 3], + quotes: mockBridgeQuotesNativeErc20Eth, quotesLoadingStatus: 1, }), ); @@ -377,7 +391,10 @@ describe('BridgeController', function () { expect(bridgeController.state.bridgeState).toEqual( expect.objectContaining({ quoteRequest: { ...quoteRequest, insufficientBal: false }, - quotes: [5, 6, 7], + quotes: [ + ...mockBridgeQuotesNativeErc20Eth, + ...mockBridgeQuotesNativeErc20Eth, + ], quotesLoadingStatus: 1, quotesRefreshCount: 2, }), @@ -394,7 +411,10 @@ describe('BridgeController', function () { expect(bridgeController.state.bridgeState).toEqual( expect.objectContaining({ quoteRequest: { ...quoteRequest, insufficientBal: false }, - quotes: [5, 6, 7], + quotes: [ + ...mockBridgeQuotesNativeErc20Eth, + ...mockBridgeQuotesNativeErc20Eth, + ], quotesLoadingStatus: 2, quotesRefreshCount: 3, }), @@ -404,6 +424,7 @@ describe('BridgeController', function () { ); expect(hasSufficientBalanceSpy).toHaveBeenCalledTimes(1); + expect(getLayer1GasFeeMock).not.toHaveBeenCalled(); }); it('updateBridgeQuoteRequestParams should only poll once if insufficientBal=true', async function () { @@ -426,7 +447,7 @@ describe('BridgeController', function () { .mockImplementationOnce(async () => { return await new Promise((resolve) => { return setTimeout(() => { - resolve([1, 2, 3] as never); + resolve(mockBridgeQuotesNativeErc20Eth as never); }, 5000); }); }); @@ -434,7 +455,10 @@ describe('BridgeController', function () { fetchBridgeQuotesSpy.mockImplementation(async () => { return await new Promise((resolve) => { return setTimeout(() => { - resolve([5, 6, 7] as never); + resolve([ + ...mockBridgeQuotesNativeErc20Eth, + ...mockBridgeQuotesNativeErc20Eth, + ] as never); }, 10000); }); }); @@ -503,7 +527,7 @@ describe('BridgeController', function () { expect(bridgeController.state.bridgeState).toEqual( expect.objectContaining({ quoteRequest: { ...quoteRequest, insufficientBal: true }, - quotes: [1, 2, 3], + quotes: mockBridgeQuotesNativeErc20Eth, quotesLoadingStatus: 1, quotesRefreshCount: 1, }), @@ -519,7 +543,7 @@ describe('BridgeController', function () { expect(bridgeController.state.bridgeState).toEqual( expect.objectContaining({ quoteRequest: { ...quoteRequest, insufficientBal: true }, - quotes: [1, 2, 3], + quotes: mockBridgeQuotesNativeErc20Eth, quotesLoadingStatus: 1, quotesRefreshCount: 1, }), @@ -527,6 +551,7 @@ describe('BridgeController', function () { const secondFetchTime = bridgeController.state.bridgeState.quotesLastFetched; expect(secondFetchTime).toStrictEqual(firstFetchTime); + expect(getLayer1GasFeeMock).not.toHaveBeenCalled(); }); it('updateBridgeQuoteRequestParams should not trigger quote polling if request is invalid', function () { @@ -574,6 +599,7 @@ describe('BridgeController', function () { address: '0x123', provider: jest.fn(), } as never); + const allowance = await bridgeController.getBridgeERC20Allowance( '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', '0xa', @@ -581,4 +607,143 @@ describe('BridgeController', function () { expect(allowance).toBe('100000000000000000000'); }); }); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + [ + 'should append l1GasFees if srcChain is 10 and srcToken is erc20', + mockBridgeQuotesErc20Native, + add0x(decimalToHex(new BigNumber('2608710388388').mul(2).toFixed())), + 12, + ], + [ + 'should append l1GasFees if srcChain is 10 and srcToken is native', + mockBridgeQuotesNativeErc20, + add0x(decimalToHex(new BigNumber('2608710388388').toFixed())), + 2, + ], + [ + 'should not append l1GasFees if srcChain is not 10', + mockBridgeQuotesNativeErc20Eth, + undefined, + 0, + ], + ])( + 'updateBridgeQuoteRequestParams: %s', + async ( + _: string, + quoteResponse: QuoteResponse[], + l1GasFeesInHexWei: string, + getLayer1GasFeeMockCallCount: number, + ) => { + jest.useFakeTimers(); + const stopAllPollingSpy = jest.spyOn(bridgeController, 'stopAllPolling'); + const startPollingByNetworkClientIdSpy = jest.spyOn( + bridgeController, + 'startPollingByNetworkClientId', + ); + const hasSufficientBalanceSpy = jest + .spyOn(balanceUtils, 'hasSufficientBalance') + .mockResolvedValue(false); + messengerMock.call.mockReturnValue({ + address: '0x123', + provider: jest.fn(), + } as never); + getLayer1GasFeeMock.mockResolvedValue('0x25F63418AA4'); + + const fetchBridgeQuotesSpy = jest + .spyOn(bridgeUtil, 'fetchBridgeQuotes') + .mockImplementationOnce(async () => { + return await new Promise((resolve) => { + return setTimeout(() => { + resolve(quoteResponse as never); + }, 1000); + }); + }); + + const quoteParams = { + srcChainId: 10, + destChainId: 1, + srcTokenAddress: '0x4200000000000000000000000000000000000006', + destTokenAddress: '0x0000000000000000000000000000000000000000', + srcTokenAmount: '991250000000000000', + }; + const quoteRequest = { + ...quoteParams, + slippage: 0.5, + walletAddress: '0x123', + }; + await bridgeController.updateBridgeQuoteRequestParams(quoteParams); + + expect(stopAllPollingSpy).toHaveBeenCalledTimes(1); + expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(hasSufficientBalanceSpy).toHaveBeenCalledTimes(1); + expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledWith( + expect.anything(), + { + ...quoteRequest, + insufficientBal: true, + }, + ); + + expect(bridgeController.state.bridgeState).toStrictEqual( + expect.objectContaining({ + quoteRequest: { ...quoteRequest, walletAddress: undefined }, + quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes, + quotesLastFetched: DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched, + quotesLoadingStatus: + DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus, + }), + ); + + // // Loading state + jest.advanceTimersByTime(500); + await flushPromises(); + expect(fetchBridgeQuotesSpy).toHaveBeenCalledTimes(1); + expect(fetchBridgeQuotesSpy).toHaveBeenCalledWith( + { + ...quoteRequest, + insufficientBal: true, + }, + expect.any(AbortSignal), + ); + expect( + bridgeController.state.bridgeState.quotesLastFetched, + ).toStrictEqual(undefined); + + expect(bridgeController.state.bridgeState).toEqual( + expect.objectContaining({ + quoteRequest: { ...quoteRequest, insufficientBal: true }, + quotes: [], + quotesLoadingStatus: 0, + }), + ); + + // After first fetch + jest.advanceTimersByTime(1500); + await flushPromises(); + const { quotes } = bridgeController.state.bridgeState; + expect(bridgeController.state.bridgeState).toEqual( + expect.objectContaining({ + quoteRequest: { ...quoteRequest, insufficientBal: true }, + quotesLoadingStatus: 1, + quotesRefreshCount: 1, + }), + ); + quotes.forEach((quote) => { + const expectedQuote = l1GasFeesInHexWei + ? { ...quote, l1GasFeesInHexWei } + : quote; + expect(quote).toStrictEqual(expectedQuote); + }); + + const firstFetchTime = + bridgeController.state.bridgeState.quotesLastFetched ?? 0; + expect(firstFetchTime).toBeGreaterThan(0); + + expect(getLayer1GasFeeMock).toHaveBeenCalledTimes( + getLayer1GasFeeMockCallCount, + ); + }, + ); }); diff --git a/app/scripts/controllers/bridge/bridge-controller.ts b/app/scripts/controllers/bridge/bridge-controller.ts index 2518e9caa9bd..bbe016ac7aea 100644 --- a/app/scripts/controllers/bridge/bridge-controller.ts +++ b/app/scripts/controllers/bridge/bridge-controller.ts @@ -6,6 +6,8 @@ import { Contract } from '@ethersproject/contracts'; import { abiERC20 } from '@metamask/metamask-eth-abis'; import { Web3Provider } from '@ethersproject/providers'; import { BigNumber } from '@ethersproject/bignumber'; +import { TransactionParams } from '@metamask/transaction-controller'; +import type { ChainId } from '@metamask/controller-utils'; import { fetchBridgeFeatureFlags, fetchBridgeQuotes, @@ -16,14 +18,23 @@ import { // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { fetchTopAssetsList } from '../../../../ui/pages/swaps/swaps.util'; -import { decimalToHex } from '../../../../shared/modules/conversion.utils'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { QuoteRequest } from '../../../../ui/pages/bridge/types'; +import { + decimalToHex, + sumHexes, +} from '../../../../shared/modules/conversion.utils'; +import { + L1GasFees, + QuoteRequest, + QuoteResponse, + TxData, + // TODO: Remove restricted import + // eslint-disable-next-line import/no-restricted-paths +} from '../../../../ui/pages/bridge/types'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { isValidQuoteRequest } from '../../../../ui/pages/bridge/utils/quote'; import { hasSufficientBalance } from '../../../../shared/modules/bridge-utils/balance'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; import { BRIDGE_CONTROLLER_NAME, DEFAULT_BRIDGE_CONTROLLER_STATE, @@ -53,7 +64,21 @@ export default class BridgeController extends StaticIntervalPollingController< > { #abortController: AbortController | undefined; - constructor({ messenger }: { messenger: BridgeControllerMessenger }) { + #getLayer1GasFee: (params: { + transactionParams: TransactionParams; + chainId: ChainId; + }) => Promise; + + constructor({ + messenger, + getLayer1GasFee, + }: { + messenger: BridgeControllerMessenger; + getLayer1GasFee: (params: { + transactionParams: TransactionParams; + chainId: ChainId; + }) => Promise; + }) { super({ name: BRIDGE_CONTROLLER_NAME, metadata, @@ -91,6 +116,8 @@ export default class BridgeController extends StaticIntervalPollingController< `${BRIDGE_CONTROLLER_NAME}:getBridgeERC20Allowance`, this.getBridgeERC20Allowance.bind(this), ); + + this.#getLayer1GasFee = getLayer1GasFee; } _executePoll = async ( @@ -226,10 +253,12 @@ export default class BridgeController extends StaticIntervalPollingController< this.stopAllPolling(); } + const quotesWithL1GasFees = await this.#appendL1GasFees(quotes); + this.update((_state) => { _state.bridgeState = { ..._state.bridgeState, - quotes, + quotes: quotesWithL1GasFees, quotesLastFetched: Date.now(), quotesLoadingStatus: RequestStatus.FETCHED, quotesRefreshCount: newQuotesRefreshCount, @@ -253,6 +282,45 @@ export default class BridgeController extends StaticIntervalPollingController< } }; + #appendL1GasFees = async ( + quotes: QuoteResponse[], + ): Promise<(QuoteResponse & L1GasFees)[]> => { + return await Promise.all( + quotes.map(async (quoteResponse) => { + const { quote, trade, approval } = quoteResponse; + const chainId = add0x(decimalToHex(quote.srcChainId)) as ChainId; + if ( + [CHAIN_IDS.OPTIMISM.toString(), CHAIN_IDS.BASE.toString()].includes( + chainId, + ) + ) { + const getTxParams = (txData: TxData) => ({ + from: txData.from, + to: txData.to, + value: txData.value, + data: txData.data, + gasLimit: txData.gasLimit?.toString(), + }); + const approvalL1GasFees = approval + ? await this.#getLayer1GasFee({ + transactionParams: getTxParams(approval), + chainId, + }) + : '0'; + const tradeL1GasFees = await this.#getLayer1GasFee({ + transactionParams: getTxParams(trade), + chainId, + }); + return { + ...quoteResponse, + l1GasFeesInHexWei: sumHexes(approvalL1GasFees, tradeL1GasFees), + }; + } + return quoteResponse; + }), + ); + }; + #setTopAssets = async ( chainId: Hex, stateKey: 'srcTopAssets' | 'destTopAssets', diff --git a/app/scripts/controllers/bridge/types.ts b/app/scripts/controllers/bridge/types.ts index 577a9fa99836..c9e221b82418 100644 --- a/app/scripts/controllers/bridge/types.ts +++ b/app/scripts/controllers/bridge/types.ts @@ -9,9 +9,13 @@ import { NetworkControllerGetSelectedNetworkClientAction, } from '@metamask/network-controller'; import { SwapsTokenObject } from '../../../../shared/constants/swaps'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { QuoteRequest, QuoteResponse } from '../../../../ui/pages/bridge/types'; +import { + L1GasFees, + QuoteRequest, + QuoteResponse, + // TODO: Remove restricted import + // eslint-disable-next-line import/no-restricted-paths +} from '../../../../ui/pages/bridge/types'; import BridgeController from './bridge-controller'; import { BRIDGE_CONTROLLER_NAME, RequestStatus } from './constants'; @@ -39,7 +43,7 @@ export type BridgeControllerState = { destTokens: Record; destTopAssets: { address: string }[]; quoteRequest: Partial; - quotes: QuoteResponse[]; + quotes: (QuoteResponse & L1GasFees)[]; quotesLastFetched?: number; quotesLoadingStatus?: RequestStatus; quotesRefreshCount: number; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 548b2f9d940c..29e6f09a3aa9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2178,6 +2178,10 @@ export default class MetamaskController extends EventEmitter { }); this.bridgeController = new BridgeController({ messenger: bridgeControllerMessenger, + // TODO: Remove once TransactionController exports this action type + getLayer1GasFee: this.txController.getLayer1GasFee.bind( + this.txController, + ), }); const smartTransactionsControllerMessenger = diff --git a/shared/constants/bridge.ts b/shared/constants/bridge.ts index 10f2587d3fbd..8ad27dce4944 100644 --- a/shared/constants/bridge.ts +++ b/shared/constants/bridge.ts @@ -26,3 +26,7 @@ export const BRIDGE_CLIENT_ID = 'extension'; export const ETH_USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7'; export const METABRIDGE_ETHEREUM_ADDRESS = '0x0439e60F02a8900a951603950d8D4527f400C3f1'; +export const BRIDGE_QUOTE_MAX_ETA_SECONDS = 60 * 60; // 1 hour +export const BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE = 0.8; // if a quote returns in x times less return than the best quote, ignore it + +export const BRIDGE_PREFERRED_GAS_ESTIMATE = 'medium'; diff --git a/test/data/bridge/mock-quotes-erc20-native.json b/test/data/bridge/mock-quotes-erc20-native.json new file mode 100644 index 000000000000..cd4a1963c6fc --- /dev/null +++ b/test/data/bridge/mock-quotes-erc20-native.json @@ -0,0 +1,894 @@ +[ + { + "quote": { + "requestId": "a63df72a-75ae-4416-a8ab-aff02596c75c", + "srcChainId": 10, + "srcTokenAmount": "991250000000000000", + "srcAsset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + }, + "destChainId": 42161, + "destTokenAmount": "991225000000000000", + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3135.46", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "feeData": { + "metabridge": { + "amount": "8750000000000000", + "asset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + } + } + }, + "bridgeId": "lifi", + "bridges": ["stargate"], + "steps": [ + { + "action": "bridge", + "srcChainId": 10, + "destChainId": 42161, + "protocol": { + "name": "stargate", + "displayName": "StargateV2 (Fast mode)", + "icon": "https://raw.githubusercontent.com/lifinance/types/5685c638772f533edad80fcb210b4bb89e30a50f/src/assets/icons/bridges/stargate.png" + }, + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3136", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3135.46", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "srcAmount": "991250000000000000", + "destAmount": "991225000000000000" + } + ] + }, + "approval": { + "chainId": 10, + "to": "0x4200000000000000000000000000000000000006", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "gasLimit": 29122 + }, + "trade": { + "chainId": 10, + "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x1c8598b5db2e", + "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000000564a6010a660000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003804bdedbea3f94faf8c8fac5ec841251d96cf5e64e8706ada4688877885e5249520000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000000000000000000000000000000dc1a09f859b2000000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7374617267617465563200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d6574616d61736b2d6272696467650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000005215e9fd223bc909083fbdb2860213873046e45d0000000000000000000000005215e9fd223bc909083fbdb2860213873046e45d000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000043ccfd60b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000001c8598b5db2e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838000000000000000000000000000000000000000000000000000000000000759e000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000000000000000000000000000000dc1a09f859b2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c83dc7c11df600d7293f778cb365d3dfcc1ffa2221cf5447a8f2ea407a97792135d9f585ecb68916479dfa1f071f169cbe1cfec831b5ad01f4e4caa09204e5181c", + "gasLimit": 641446 + }, + "estimatedProcessingTimeInSeconds": 64 + }, + { + "quote": { + "requestId": "aad73198-a64d-4310-b12d-9dcc81c412e2", + "srcChainId": 10, + "srcTokenAmount": "991250000000000000", + "srcAsset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + }, + "destChainId": 42161, + "destTokenAmount": "991147696728676903", + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3135.46", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "feeData": { + "metabridge": { + "amount": "8750000000000000", + "asset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + } + } + }, + "bridgeId": "lifi", + "bridges": ["celer"], + "steps": [ + { + "action": "bridge", + "srcChainId": 10, + "destChainId": 42161, + "protocol": { + "name": "celer", + "displayName": "Celer cBridge", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/cbridge.svg" + }, + "srcAsset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + }, + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3135.46", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "srcAmount": "991250000000000000", + "destAmount": "991147696728676903" + } + ] + }, + "approval": { + "chainId": 10, + "to": "0x4200000000000000000000000000000000000006", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "gasLimit": 29122 + }, + "trade": { + "chainId": 10, + "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000e7bf43c55551b1036e796e7fd3b125d1f9903e2e000000000000000000000000e7bf43c55551b1036e796e7fd3b125d1f9903e2e000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000000050f68486970f93a855b27794b8141d32a89a1e0a5ef360034a2f60a4b917c188380000a4b1420000000000000000000000000000000000000600000000000000000dc1a09f859b20002c03873900002777000000000000000000000000000000002d68122053030bf8df41a8bb8c6f0a9de411c7d94eed376b7d91234e1585fd9f77dcf974dd25160d0c2c16c8382d8aa85b0edd429edff19b4d4cdcf50d0a9d4d1c", + "gasLimit": 203352 + }, + "estimatedProcessingTimeInSeconds": 53 + }, + { + "quote": { + "requestId": "6cfd4952-c9b2-4aec-9349-af39c212f84b", + "srcChainId": 10, + "srcTokenAmount": "991250000000000000", + "srcAsset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + }, + "destChainId": 42161, + "destTokenAmount": "991112862890876485", + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3135.46", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "feeData": { + "metabridge": { + "amount": "8750000000000000", + "asset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + } + } + }, + "bridgeId": "lifi", + "bridges": ["across"], + "steps": [ + { + "action": "bridge", + "srcChainId": 10, + "destChainId": 42161, + "protocol": { + "name": "across", + "displayName": "Across", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png" + }, + "srcAsset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + }, + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3135.46", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "srcAmount": "991250000000000000", + "destAmount": "991112862890876485" + } + ] + }, + "approval": { + "chainId": 10, + "to": "0x4200000000000000000000000000000000000006", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "gasLimit": 29122 + }, + "trade": { + "chainId": 10, + "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a518700000000000000000000000000000000000000000000000000000000000000902340ab8f6a57ef0c43231b98141d32a89a1e0a5ef360034a2f60a4b917c18838420000000000000000000000000000000000000600000000000000000dc1a09f859b20000000a4b100007dd39298f9ad673645ebffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b710000000000000000000000000000000088d06e7971021eee573a0ab6bc3e22039fc1c5ded5d12c4cf2b6311f47f909e06197aa8b2f647ae78ae33a6ea5d23f7c951c0e1686abecd01d7c796990d56f391c", + "gasLimit": 177423 + }, + "estimatedProcessingTimeInSeconds": 15 + }, + { + "quote": { + "requestId": "2c2ba7d8-3922-4081-9f27-63b7d5cc1986", + "srcChainId": 10, + "srcTokenAmount": "991250000000000000", + "srcAsset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + }, + "destChainId": 42161, + "destTokenAmount": "990221346602370184", + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3135.46", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "feeData": { + "metabridge": { + "amount": "8750000000000000", + "asset": { + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "symbol": "WETH", + "decimals": 18, + "name": "Wrapped ETH", + "coinKey": "WETH", + "logoURI": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png", + "priceUSD": "3136", + "icon": "https://static.debank.com/image/op_token/logo_url/0x4200000000000000000000000000000000000006/61844453e63cf81301f845d7864236f6.png" + } + } + }, + "bridgeId": "lifi", + "bridges": ["hop"], + "steps": [ + { + "action": "bridge", + "srcChainId": 10, + "destChainId": 42161, + "protocol": { + "name": "hop", + "displayName": "Hop", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/hop.png" + }, + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3136", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3135.46", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "srcAmount": "991250000000000000", + "destAmount": "990221346602370184" + } + ] + }, + "approval": { + "chainId": 10, + "to": "0x4200000000000000000000000000000000000006", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "gasLimit": 29122 + }, + "trade": { + "chainId": 10, + "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000000484ca360ae0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000001168a464edd170000000000000000000000000000000000000000000000000dac6213fc70c84400000000000000000000000000000000000000000000000000000000673a3b080000000000000000000000000000000000000000000000000dac6213fc70c84400000000000000000000000000000000000000000000000000000000673a3b0800000000000000000000000086ca30bef97fb651b8d866d45503684b90cb3312000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d5640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067997b63db4b9059d22e50750707b46a6d48dfbb32e50d85fc3bff1170ed9ca30000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000000000000000000000000000000dc1a09f859b2000000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003686f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d6574616d61736b2d6272696467650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000005215e9fd223bc909083fbdb2860213873046e45d0000000000000000000000005215e9fd223bc909083fbdb2860213873046e45d000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000043ccfd60b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099d00cde1f22e8afd37d7f103ec3c6c1eb835ace46e502ec8c5ab51413e539461b89c0e26892efd1de1cbfe4222b5589e76231080252197507cce4fb72a30b031b", + "gasLimit": 547501 + }, + "estimatedProcessingTimeInSeconds": 24.159 + }, + { + "quote": { + "requestId": "a77bc7b2-e8c8-4463-89db-5dd239d6aacc", + "srcChainId": 10, + "srcAsset": { + "chainId": 10, + "address": "0x4200000000000000000000000000000000000006", + "symbol": "WETH", + "name": "Wrapped Ether", + "decimals": 18, + "icon": "https://media.socket.tech/tokens/all/WETH", + "logoURI": "https://media.socket.tech/tokens/all/WETH", + "chainAgnosticId": "ETH" + }, + "srcTokenAmount": "991250000000000000", + "destChainId": 42161, + "destAsset": { + "chainId": 42161, + "address": "0x0000000000000000000000000000000000000000", + "symbol": "ETH", + "name": "Ethereum", + "decimals": 18, + "icon": "https://media.socket.tech/tokens/all/ETH", + "logoURI": "https://media.socket.tech/tokens/all/ETH", + "chainAgnosticId": null + }, + "destTokenAmount": "991147696728676903", + "feeData": { + "metabridge": { + "amount": "8750000000000000", + "asset": { + "chainId": 10, + "address": "0x4200000000000000000000000000000000000006", + "symbol": "WETH", + "name": "Wrapped Ether", + "decimals": 18, + "icon": "https://media.socket.tech/tokens/all/WETH", + "logoURI": "https://media.socket.tech/tokens/all/WETH", + "chainAgnosticId": "ETH" + } + } + }, + "bridgeId": "socket", + "bridges": ["celer"], + "steps": [ + { + "action": "bridge", + "srcChainId": 10, + "destChainId": 42161, + "protocol": { + "name": "celer", + "displayName": "Celer", + "icon": "https://socketicons.s3.amazonaws.com/Celer+Light.png" + }, + "srcAsset": { + "chainId": 10, + "address": "0x4200000000000000000000000000000000000006", + "symbol": "WETH", + "name": "Wrapped Ether", + "decimals": 18, + "icon": "https://media.socket.tech/tokens/all/WETH", + "logoURI": "https://media.socket.tech/tokens/all/WETH", + "chainAgnosticId": "ETH" + }, + "destAsset": { + "chainId": 42161, + "address": "0x0000000000000000000000000000000000000000", + "symbol": "ETH", + "name": "Ethereum", + "decimals": 18, + "icon": "https://media.socket.tech/tokens/all/ETH", + "logoURI": "https://media.socket.tech/tokens/all/ETH", + "chainAgnosticId": null + }, + "srcAmount": "991250000000000000", + "destAmount": "991147696728676903" + } + ] + }, + "approval": { + "chainId": 10, + "to": "0x4200000000000000000000000000000000000006", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "gasLimit": 29122 + }, + "trade": { + "chainId": 10, + "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000f736f636b6574416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000003a23f943181408eac424116af7b7790c94cb97a5000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a5187000000000000000000000000000000000000000000000000000000000000004c0000001252106ce9141d32a89a1e0a5ef360034a2f60a4b917c18838420000000000000000000000000000000000000600000000000000000dc1a09f859b20000000a4b1245fa5dd00002777000000000000000000000000000000000000000022be703a074ef6089a301c364c2bbf391d51067ea5cd91515c9ec5421cdaabb23451cd2086f3ebe3e19ff138f3a9be154dcae6033838cc5fabeeb0d260b075cb1c", + "gasLimit": 182048 + }, + "estimatedProcessingTimeInSeconds": 360 + }, + { + "quote": { + "requestId": "4f2154d9b330221b2ad461adf63acc2c", + "srcChainId": 10, + "srcTokenAmount": "991250000000000000", + "srcAsset": { + "id": "10_0x4200000000000000000000000000000000000006", + "symbol": "WETH", + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "name": "Wrapped ETH", + "decimals": 18, + "usdPrice": 3135.9632118339764, + "coingeckoId": "weth", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg", + "volatility": 2, + "axelarNetworkSymbol": "WETH", + "subGraphIds": [], + "enabled": true, + "subGraphOnly": false, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg" + }, + "destChainId": 42161, + "destTokenAmount": "989989428114299041", + "destAsset": { + "id": "42161_0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "symbol": "ETH", + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "name": "ETH", + "decimals": 18, + "usdPrice": 3133.259355489038, + "coingeckoId": "ethereum", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/eth.svg", + "volatility": 2, + "axelarNetworkSymbol": "ETH", + "subGraphIds": ["chainflip-bridge"], + "enabled": true, + "subGraphOnly": false, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/eth.svg" + }, + "feeData": { + "metabridge": { + "amount": "8750000000000000", + "asset": { + "id": "10_0x4200000000000000000000000000000000000006", + "symbol": "WETH", + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "name": "Wrapped ETH", + "decimals": 18, + "usdPrice": 3135.9632118339764, + "coingeckoId": "weth", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg", + "volatility": 2, + "axelarNetworkSymbol": "WETH", + "subGraphIds": [], + "enabled": true, + "subGraphOnly": false, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg" + } + } + }, + "bridgeId": "squid", + "bridges": ["axelar"], + "steps": [ + { + "action": "swap", + "srcChainId": 10, + "destChainId": 10, + "protocol": { + "name": "Uniswap V3", + "displayName": "Uniswap V3" + }, + "srcAsset": { + "id": "10_0x4200000000000000000000000000000000000006", + "symbol": "WETH", + "address": "0x4200000000000000000000000000000000000006", + "chainId": 10, + "name": "Wrapped ETH", + "decimals": 18, + "usdPrice": 3135.9632118339764, + "coingeckoId": "weth", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg", + "axelarNetworkSymbol": "WETH", + "subGraphIds": [], + "enabled": true, + "subGraphOnly": false, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/weth.svg" + }, + "destAsset": { + "id": "10_0x0b2c639c533813f4aa9d7837caf62653d097ff85", + "symbol": "USDC", + "address": "0x0b2c639c533813f4aa9d7837caf62653d097ff85", + "chainId": 10, + "name": "USDC", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "USDC", + "subGraphOnly": false, + "subGraphIds": ["uusdc", "cctp-uusdc-optimism-to-noble"], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg" + }, + "srcAmount": "991250000000000000", + "destAmount": "3100880215" + }, + { + "action": "swap", + "srcChainId": 10, + "destChainId": 10, + "protocol": { + "name": "Uniswap V3", + "displayName": "Uniswap V3" + }, + "srcAsset": { + "id": "10_0x0b2c639c533813f4aa9d7837caf62653d097ff85", + "symbol": "USDC", + "address": "0x0b2c639c533813f4aa9d7837caf62653d097ff85", + "chainId": 10, + "name": "USDC", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "USDC", + "subGraphOnly": false, + "subGraphIds": ["uusdc", "cctp-uusdc-optimism-to-noble"], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg" + }, + "destAsset": { + "id": "10_0x7f5c764cbc14f9669b88837ca1490cca17c31607", + "symbol": "USDC.e", + "address": "0x7f5c764cbc14f9669b88837ca1490cca17c31607", + "chainId": 10, + "name": "USDC.e", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "USDC.e", + "subGraphIds": [], + "enabled": true, + "subGraphOnly": false, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg" + }, + "srcAmount": "3100880215", + "destAmount": "3101045779" + }, + { + "action": "swap", + "srcChainId": 10, + "destChainId": 10, + "protocol": { + "name": "Uniswap V3", + "displayName": "Uniswap V3" + }, + "srcAsset": { + "id": "10_0x7f5c764cbc14f9669b88837ca1490cca17c31607", + "symbol": "USDC.e", + "address": "0x7f5c764cbc14f9669b88837ca1490cca17c31607", + "chainId": 10, + "name": "USDC.e", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "USDC.e", + "subGraphIds": [], + "enabled": true, + "subGraphOnly": false, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg" + }, + "destAsset": { + "id": "10_0xeb466342c4d449bc9f53a865d5cb90586f405215", + "symbol": "USDC.axl", + "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215", + "chainId": 10, + "name": " USDC (Axelar)", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "interchainTokenId": null, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "axlUSDC", + "subGraphOnly": false, + "subGraphIds": ["uusdc"], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg" + }, + "srcAmount": "3101045779", + "destAmount": "3101521947" + }, + { + "action": "bridge", + "srcChainId": 10, + "destChainId": 42161, + "protocol": { + "name": "axelar", + "displayName": "Axelar" + }, + "srcAsset": { + "id": "10_0xeb466342c4d449bc9f53a865d5cb90586f405215", + "symbol": "USDC.axl", + "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215", + "chainId": 10, + "name": " USDC (Axelar)", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "interchainTokenId": null, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "axlUSDC", + "subGraphOnly": false, + "subGraphIds": ["uusdc"], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg" + }, + "destAsset": { + "id": "42161_0xeb466342c4d449bc9f53a865d5cb90586f405215", + "symbol": "USDC.axl", + "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215", + "chainId": 42161, + "name": " USDC (Axelar)", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "interchainTokenId": null, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "axlUSDC", + "subGraphOnly": false, + "subGraphIds": ["uusdc"], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg" + }, + "srcAmount": "3101521947", + "destAmount": "3101521947" + }, + { + "action": "swap", + "srcChainId": 42161, + "destChainId": 42161, + "protocol": { + "name": "Pancakeswap V3", + "displayName": "Pancakeswap V3" + }, + "srcAsset": { + "id": "42161_0xeb466342c4d449bc9f53a865d5cb90586f405215", + "symbol": "USDC.axl", + "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215", + "chainId": 42161, + "name": " USDC (Axelar)", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "interchainTokenId": null, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "axlUSDC", + "subGraphOnly": false, + "subGraphIds": ["uusdc"], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/usdc.svg" + }, + "destAsset": { + "id": "42161_0xaf88d065e77c8cc2239327c5edb3a432268e5831", + "symbol": "USDC", + "address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", + "chainId": 42161, + "name": "USDC", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "USDC", + "subGraphOnly": false, + "subGraphIds": [ + "uusdc", + "cctp-uusdc-arbitrum-to-noble", + "chainflip-bridge" + ], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg" + }, + "srcAmount": "3101521947", + "destAmount": "3100543869" + }, + { + "action": "swap", + "srcChainId": 42161, + "destChainId": 42161, + "protocol": { + "name": "Uniswap V3", + "displayName": "Uniswap V3" + }, + "srcAsset": { + "id": "42161_0xaf88d065e77c8cc2239327c5edb3a432268e5831", + "symbol": "USDC", + "address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", + "chainId": 42161, + "name": "USDC", + "decimals": 6, + "usdPrice": 1.0003003590332982, + "coingeckoId": "usd-coin", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg", + "axelarNetworkSymbol": "USDC", + "subGraphOnly": false, + "subGraphIds": [ + "uusdc", + "cctp-uusdc-arbitrum-to-noble", + "chainflip-bridge" + ], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg" + }, + "destAsset": { + "id": "42161_0x82af49447d8a07e3bd95bd0d56f35241523fbab1", + "symbol": "WETH", + "address": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", + "chainId": 42161, + "name": "Wrapped ETH", + "decimals": 18, + "usdPrice": 3135.9632118339764, + "interchainTokenId": null, + "coingeckoId": "weth", + "type": "evm", + "logoURI": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/weth.svg", + "axelarNetworkSymbol": "WETH", + "subGraphOnly": false, + "subGraphIds": ["arbitrum-weth-wei"], + "enabled": true, + "active": true, + "icon": "https://raw.githubusercontent.com/axelarnetwork/axelar-configs/main/images/tokens/weth.svg" + }, + "srcAmount": "3100543869", + "destAmount": "989989428114299041" + } + ] + }, + "approval": { + "chainId": 10, + "to": "0x4200000000000000000000000000000000000006", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x00", + "data": "0x095ea7b3000000000000000000000000b90357f2b86dbfd59c3502215d4060f71df8ca0e0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "gasLimit": 29122 + }, + "trade": { + "chainId": 10, + "to": "0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x4653ce53e6b1", + "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000e73717569644164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b60000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000001a14846a1bc600000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000ce00000000000000000000000000000000000000000000000000000000000000d200000000000000000000000000000000000000000000000000000000000000d600000000000000000000000000000000000000000000000000000000000000dc0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000098000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf00000000000000000000000042000000000000000000000000000000000000060000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff8500000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000ea749fd6ba492dbc14c24fe8a3d08769229b896c0000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000b8833d8e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf0000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c316070000000000000000000000000000000000000000000000000000000000000064000000000000000000000000ea749fd6ba492dbc14c24fe8a3d08769229b896c00000000000000000000000000000000000000000000000000000000b8d3ad5700000000000000000000000000000000000000000000000000000000b8c346b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c316070000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf0000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f4052150000000000000000000000000000000000000000000000000000000000000064000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d66600000000000000000000000000000000000000000000000000000000b8d6341300000000000000000000000000000000000000000000000000000000b8ca89fa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c316070000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000761786c55534443000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008417262697472756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307863653136463639333735353230616230313337376365374238386635424138433438463844363636000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c100000000000000000000000000000000000000000000000000000000000000040000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009200000000000000000000000000000000000000000000000000000000000000a8000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f4052150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f405215000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000032226588378236fd0c7c4053999f88ac0e5cac77ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f4052150000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000032226588378236fd0c7c4053999f88ac0e5cac77000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f405215000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000000000064000000000000000000000000ea749fd6ba492dbc14c24fe8a3d08769229b896c00000000000000000000000000000000000000000000000000000000b8dd781b00000000000000000000000000000000000000000000000000000000b8bb9ee30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f40521500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000ea749fd6ba492dbc14c24fe8a3d08769229b896c00000000000000000000000000000000000000000000000000000000b8ce8b7d0000000000000000000000000000000000000000000000000db72b79f837011c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000004f2154d9b330221b2ad461adf63acc2c000000000000000000000000000000004f2154d9b330221b2ad461adf63acc2c0000000000000000000000003c17c95cdb5887c334bfae85750ce00e1a720a76eff35e60db6c9f3b8384a6d63db3c56f1ce6545b50ba2f250429055ca77e7e6203ddd65a7a4d89ae1af3d61b1c", + "gasLimit": 710342 + }, + "estimatedProcessingTimeInSeconds": 20 + } +] diff --git a/test/data/bridge/mock-quotes-native-erc20-eth.json b/test/data/bridge/mock-quotes-native-erc20-eth.json new file mode 100644 index 000000000000..0afd77760e75 --- /dev/null +++ b/test/data/bridge/mock-quotes-native-erc20-eth.json @@ -0,0 +1,258 @@ +[ + { + "quote": { + "requestId": "34c4136d-8558-4d87-bdea-eef8d2d30d6d", + "srcChainId": 1, + "srcTokenAmount": "991250000000000000", + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 1, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3145.41", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "destChainId": 42161, + "destTokenAmount": "3104367033", + "destAsset": { + "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + "chainId": 42161, + "symbol": "USDC", + "decimals": 6, + "name": "USD Coin", + "coinKey": "USDC", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + "priceUSD": "0.9998000399920016", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" + }, + "feeData": { + "metabridge": { + "amount": "8750000000000000", + "asset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 1, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3145.41", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + } + } + }, + "bridgeId": "lifi", + "bridges": ["across"], + "steps": [ + { + "action": "swap", + "srcChainId": 1, + "destChainId": 1, + "protocol": { + "name": "0x", + "displayName": "0x", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/exchanges/zerox.png" + }, + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 1, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3145.41", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "destAsset": { + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "chainId": 1, + "symbol": "USDC", + "decimals": 6, + "name": "USD Coin", + "coinKey": "USDC", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + "priceUSD": "0.9997000899730081", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" + }, + "srcAmount": "991250000000000000", + "destAmount": "3104701473" + }, + { + "action": "bridge", + "srcChainId": 1, + "destChainId": 42161, + "protocol": { + "name": "across", + "displayName": "Across", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png" + }, + "srcAsset": { + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "chainId": 1, + "symbol": "USDC", + "decimals": 6, + "name": "USD Coin", + "coinKey": "USDC", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + "priceUSD": "0.9997000899730081", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" + }, + "destAsset": { + "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + "chainId": 42161, + "symbol": "USDC", + "decimals": 6, + "name": "USD Coin", + "coinKey": "USDC", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + "priceUSD": "0.9998000399920016", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" + }, + "srcAmount": "3104701473", + "destAmount": "3104367033" + } + ] + }, + "trade": { + "chainId": 1, + "to": "0x0439e60F02a8900a951603950d8D4527f400C3f1", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x0de0b6b3a7640000", + "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c696669416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de51520000000000000000000000000000000000000000000000000000000000000a003a3f733200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000094027363a1fac5600d1f7e8a4c50087ff1f32a09359512d2379d46b331c6033cc7b000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000b8211d6e000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066163726f73730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d6574616d61736b2d6272696467650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000001ff3684f28c67538d4d072c227340000000000000000000000000000000000001ff3684f28c67538d4d072c227340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000005c42213bc0b00000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f471000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f471000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004e41fff991f0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b909399a00000000000000000000000000000000000000000000000000000000000000a094cc69295a8f2a3016ede239627ab300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e48d68a15600000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f4710000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2010001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c147000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000005000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000ad01c20d5886137e056775af56915de824c8fce50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000620541d325b000000000000000000000000000000000000000000000000000000000673656d70000000000000000000000000000000000000000000000000000000000000080ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000d00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b71dcbfe555f9a744b18195d9b52032871d6f3c5a558275c08a71c2b6214801f5161be976f49181b854a3ebcbe1f2b896133b03314a5ff2746e6494c43e59d0c9ee1c", + "gasLimit": 540076 + }, + "estimatedProcessingTimeInSeconds": 45 + }, + { + "quote": { + "requestId": "5bf0f2f0-655c-4e13-a545-1ebad6f9d2bc", + "srcChainId": 1, + "srcTokenAmount": "991250000000000000", + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 1, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3145.41", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "destChainId": 42161, + "destTokenAmount": "3104601473", + "destAsset": { + "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + "chainId": 42161, + "symbol": "USDC", + "decimals": 6, + "name": "USD Coin", + "coinKey": "USDC", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + "priceUSD": "0.9998000399920016", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" + }, + "feeData": { + "metabridge": { + "amount": "8750000000000000", + "asset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 1, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3145.41", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + } + } + }, + "bridgeId": "lifi", + "bridges": ["celercircle"], + "steps": [ + { + "action": "swap", + "srcChainId": 1, + "destChainId": 1, + "protocol": { + "name": "0x", + "displayName": "0x", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/exchanges/zerox.png" + }, + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 1, + "symbol": "ETH", + "decimals": 18, + "name": "ETH", + "coinKey": "ETH", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "priceUSD": "3145.41", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" + }, + "destAsset": { + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "chainId": 1, + "symbol": "USDC", + "decimals": 6, + "name": "USD Coin", + "coinKey": "USDC", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + "priceUSD": "0.9997000899730081", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" + }, + "srcAmount": "991250000000000000", + "destAmount": "3104701473" + }, + { + "action": "bridge", + "srcChainId": 1, + "destChainId": 42161, + "protocol": { + "name": "celercircle", + "displayName": "Circle CCTP", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/circle.png" + }, + "srcAsset": { + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "chainId": 1, + "symbol": "USDC", + "decimals": 6, + "name": "USD Coin", + "coinKey": "USDC", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + "priceUSD": "0.9997000899730081", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" + }, + "destAsset": { + "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + "chainId": 42161, + "symbol": "USDC", + "decimals": 6, + "name": "USD Coin", + "coinKey": "USDC", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + "priceUSD": "0.9998000399920016", + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" + }, + "srcAmount": "3104701473", + "destAmount": "3104601473" + } + ] + }, + "trade": { + "chainId": 1, + "to": "0x0439e60F02a8900a951603950d8D4527f400C3f1", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "value": "0x0de0b6b3a7640000", + "data": "0x3ce33bff000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c696669416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de515200000000000000000000000000000000000000000000000000000000000009248fab066300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200b431adcab44c6fe13ade53dbd3b714f57922ab5b776924a913685ad0fe680f6c000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000b8211d6e000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b63656c6572636972636c65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d6574616d61736b2d6272696467650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000001ff3684f28c67538d4d072c227340000000000000000000000000000000000001ff3684f28c67538d4d072c227340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000005c42213bc0b00000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f471000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f471000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004e41fff991f0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b909399a00000000000000000000000000000000000000000000000000000000000000a0c0452b52ecb7cf70409b16cd627ab300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e48d68a15600000000000000000000000070bf6634ee8cb27d04478f184b9b8bb13e5f4710000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2010001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c147000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000005000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000ad01c20d5886137e056775af56915de824c8fce50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047896dca097909ba9db4c9631bce0e53090bce14a9b7d203e21fa80cee7a16fa049aa1ef7d663c2ec3148e698e01774b62ddedc9c2dcd21994e549cd6f318f971b", + "gasLimit": 682910 + }, + "estimatedProcessingTimeInSeconds": 1029.717 + } +] diff --git a/test/data/bridge/mock-quotes-native-erc20.json b/test/data/bridge/mock-quotes-native-erc20.json index fb6ecfcc0b73..f7efe7950ba0 100644 --- a/test/data/bridge/mock-quotes-native-erc20.json +++ b/test/data/bridge/mock-quotes-native-erc20.json @@ -289,6 +289,6 @@ "data": "0x3ce33bff00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002714711487800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000f736f636b657441646170746572563200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dc00000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000003a23f943181408eac424116af7b7790c94cb97a5000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c33590000000000000000000000000000000000000000000000000023375dc1560800000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000004f94ae6af800000000000000000000000000716a8b9dd056055c84b7a2ba0a016099465a51870000000000000000000000000000000000000000000000000000000000000c6437c6145a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000bc4123506490000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001960000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000018c0000000000000000000000000000000000000000000000000000000000000ac00000000000000000000000000000000000000000000000000000000000000084ad69fa4f00000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000000000890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000904ee8f0b86000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000000000000000000000000000000023375dc156080000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000828415565b0000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000000000000000000000000000000023375dc15608000000000000000000000000000000000000000000000000000000000001734d0800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000005e0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000023375dc15608000000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000060000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff8500000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000023375dc1560800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000012556e69737761705633000000000000000000000000000000000000000000000000000000000000000023375dc1560800000000000000000000000000000000000000000000000000000000000173dbd3000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000e592427a0aece92de3edee1f18e0157c0586156400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b42000000000000000000000000000000000000060001f40b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff850000000000000000000000000000000000000000000000000000000000008ecb000000000000000000000000ad01c20d5886137e056775af56915de824c8fce5000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000004200000000000000000000000000000000000006000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000000869584cd00000000000000000000000010000000000000000000000000000000000000110000000000000000000000000000000000000000974132b87a5cb75e32f034280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000890000000000000000000000000000000000000000000000000000000000030d4000000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f9e43204a24f476db20f2518722627a122d31a1bc7c63fc15412e6a327295a9460b76bea5bb53b1f73fa6a15811055f6bada592d2e9e6c8cf48a855ce6968951c", "gasLimit": 664389 }, - "estimatedProcessingTimeInSeconds": 1560 + "estimatedProcessingTimeInSeconds": 15 } ] diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index 4720bf427372..a3543e485bb7 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -4,6 +4,7 @@ import { KeyringType } from '../../shared/constants/keyring'; import { ETH_EOA_METHODS } from '../../shared/constants/eth-methods'; import { mockNetworkState } from '../stub/networks'; import { DEFAULT_BRIDGE_CONTROLLER_STATE } from '../../app/scripts/controllers/bridge/constants'; +import { BRIDGE_PREFERRED_GAS_ESTIMATE } from '../../shared/constants/bridge'; export const createGetSmartTransactionFeesApiResponse = () => { return { @@ -711,6 +712,7 @@ export const createBridgeMockStore = ( ...swapsStore, bridge: { toChainId: null, + sortOrder: 0, ...bridgeSliceOverrides, }, metamask: { @@ -719,6 +721,16 @@ export const createBridgeMockStore = ( { chainId: CHAIN_IDS.MAINNET }, { chainId: CHAIN_IDS.LINEA_MAINNET }, ), + gasFeeEstimates: { + estimatedBaseFee: '0.00010456', + [BRIDGE_PREFERRED_GAS_ESTIMATE]: { + suggestedMaxFeePerGas: '0.00018456', + suggestedMaxPriorityFeePerGas: '0.0001', + }, + }, + currencyRates: { + ETH: { conversionRate: 2524.25 }, + }, ...metamaskStateOverrides, bridgeState: { ...(swapsStore.metamask.bridgeState ?? {}), diff --git a/ui/ducks/bridge/actions.ts b/ui/ducks/bridge/actions.ts index a61d2fdcd8fd..766689cb8cda 100644 --- a/ui/ducks/bridge/actions.ts +++ b/ui/ducks/bridge/actions.ts @@ -11,7 +11,11 @@ import { forceUpdateMetamaskState } from '../../store/actions'; import { submitRequestToBackground } from '../../store/background-connection'; import { QuoteRequest } from '../../pages/bridge/types'; import { MetaMaskReduxDispatch } from '../../store/store'; -import { bridgeSlice } from './bridge'; +import { + bridgeSlice, + setDestTokenExchangeRates, + setSrcTokenExchangeRates, +} from './bridge'; const { setToChainId, @@ -19,6 +23,8 @@ const { setToToken, setFromTokenInputValue, resetInputFields, + setSortOrder, + setSelectedQuote, } = bridgeSlice.actions; export { @@ -27,6 +33,10 @@ export { setToToken, setFromToken, setFromTokenInputValue, + setDestTokenExchangeRates, + setSrcTokenExchangeRates, + setSortOrder, + setSelectedQuote, }; const callBridgeControllerMethod = ( diff --git a/ui/ducks/bridge/bridge.test.ts b/ui/ducks/bridge/bridge.test.ts index dc9596fcafba..5a395fa23036 100644 --- a/ui/ducks/bridge/bridge.test.ts +++ b/ui/ducks/bridge/bridge.test.ts @@ -10,6 +10,7 @@ import { // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths } from '../../../app/scripts/controllers/bridge/types'; +import * as util from '../../helpers/utils/util'; import bridgeReducer from './bridge'; import { setBridgeFeatureFlags, @@ -22,6 +23,7 @@ import { setToChainId, updateQuoteRequestParams, resetBridgeState, + setDestTokenExchangeRates, } from './actions'; const middleware = [thunk]; @@ -143,10 +145,14 @@ describe('Ducks - Bridge', () => { expect(actions[0].type).toStrictEqual('bridge/resetInputFields'); const newState = bridgeReducer(state, actions[0]); expect(newState).toStrictEqual({ + selectedQuote: null, toChainId: null, fromToken: null, toToken: null, fromTokenInputValue: null, + sortOrder: 0, + toTokenExchangeRate: null, + fromTokenExchangeRate: null, }); }); }); @@ -201,10 +207,103 @@ describe('Ducks - Bridge', () => { expect(actions[0].type).toStrictEqual('bridge/resetInputFields'); const newState = bridgeReducer(state, actions[0]); expect(newState).toStrictEqual({ - toChainId: null, fromToken: null, - toToken: null, + fromTokenExchangeRate: null, fromTokenInputValue: null, + selectedQuote: null, + sortOrder: 0, + toChainId: null, + toToken: null, + toTokenExchangeRate: null, + }); + }); + }); + describe('setDestTokenExchangeRates', () => { + it('fetches token prices and updates dest exchange rates in state, native dest token', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const mockStore = configureMockStore(middleware)( + createBridgeMockStore(), + ); + const state = mockStore.getState().bridge; + const fetchTokenExchangeRatesSpy = jest + .spyOn(util, 'fetchTokenExchangeRates') + .mockResolvedValue({ + '0x0000000000000000000000000000000000000000': 0.356628, + }); + + await mockStore.dispatch( + setDestTokenExchangeRates({ + chainId: CHAIN_IDS.LINEA_MAINNET, + tokenAddress: zeroAddress(), + currency: 'usd', + }) as never, + ); + + expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledTimes(1); + expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledWith( + 'usd', + ['0x0000000000000000000000000000000000000000'], + CHAIN_IDS.LINEA_MAINNET, + ); + + const actions = mockStore.getActions(); + expect(actions).toHaveLength(2); + expect(actions[0].type).toStrictEqual( + 'bridge/setDestTokenExchangeRates/pending', + ); + expect(actions[1].type).toStrictEqual( + 'bridge/setDestTokenExchangeRates/fulfilled', + ); + const newState = bridgeReducer(state, actions[1]); + expect(newState).toStrictEqual({ + toChainId: null, + toTokenExchangeRate: 0.356628, + sortOrder: 0, + }); + }); + + it('fetches token prices and updates dest exchange rates in state, erc20 dest token', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const mockStore = configureMockStore(middleware)( + createBridgeMockStore(), + ); + const state = mockStore.getState().bridge; + const fetchTokenExchangeRatesSpy = jest + .spyOn(util, 'fetchTokenExchangeRates') + .mockResolvedValue({ + '0x0000000000000000000000000000000000000000': 0.356628, + '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359': 0.999881, + }); + + await mockStore.dispatch( + setDestTokenExchangeRates({ + chainId: CHAIN_IDS.LINEA_MAINNET, + tokenAddress: + '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359'.toLowerCase(), + currency: 'usd', + }) as never, + ); + + expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledTimes(1); + expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledWith( + 'usd', + ['0x3c499c542cef5e3811e1192ce70d8cc03d5c3359'], + CHAIN_IDS.LINEA_MAINNET, + ); + + const actions = mockStore.getActions(); + expect(actions).toHaveLength(2); + expect(actions[0].type).toStrictEqual( + 'bridge/setDestTokenExchangeRates/pending', + ); + expect(actions[1].type).toStrictEqual( + 'bridge/setDestTokenExchangeRates/fulfilled', + ); + const newState = bridgeReducer(state, actions[1]); + expect(newState).toStrictEqual({ + toChainId: null, + toTokenExchangeRate: 0.999881, + sortOrder: 0, }); }); }); diff --git a/ui/ducks/bridge/bridge.ts b/ui/ducks/bridge/bridge.ts index c75030c7591d..edb0c9ca0d13 100644 --- a/ui/ducks/bridge/bridge.ts +++ b/ui/ducks/bridge/bridge.ts @@ -1,15 +1,24 @@ -import { createSlice } from '@reduxjs/toolkit'; - +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { Hex } from '@metamask/utils'; import { swapsSlice } from '../swaps/swaps'; import { SwapsTokenObject } from '../../../shared/constants/swaps'; import { SwapsEthToken } from '../../selectors'; +import { + QuoteMetadata, + QuoteResponse, + SortOrder, +} from '../../pages/bridge/types'; +import { getTokenExchangeRate } from './utils'; export type BridgeState = { toChainId: Hex | null; fromToken: SwapsTokenObject | SwapsEthToken | null; toToken: SwapsTokenObject | SwapsEthToken | null; fromTokenInputValue: string | null; + fromTokenExchangeRate: number | null; + toTokenExchangeRate: number | null; + sortOrder: SortOrder; + selectedQuote: (QuoteResponse & QuoteMetadata) | null; // Alternate quote selected by user. When quotes refresh, the best match will be activated. }; const initialState: BridgeState = { @@ -17,8 +26,22 @@ const initialState: BridgeState = { fromToken: null, toToken: null, fromTokenInputValue: null, + fromTokenExchangeRate: null, + toTokenExchangeRate: null, + sortOrder: SortOrder.COST_ASC, + selectedQuote: null, }; +export const setSrcTokenExchangeRates = createAsyncThunk( + 'bridge/setSrcTokenExchangeRates', + getTokenExchangeRate, +); + +export const setDestTokenExchangeRates = createAsyncThunk( + 'bridge/setDestTokenExchangeRates', + getTokenExchangeRate, +); + const bridgeSlice = createSlice({ name: 'bridge', initialState: { ...initialState }, @@ -39,6 +62,20 @@ const bridgeSlice = createSlice({ resetInputFields: () => ({ ...initialState, }), + setSortOrder: (state, action) => { + state.sortOrder = action.payload; + }, + setSelectedQuote: (state, action) => { + state.selectedQuote = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addCase(setDestTokenExchangeRates.fulfilled, (state, action) => { + state.toTokenExchangeRate = action.payload ?? null; + }); + builder.addCase(setSrcTokenExchangeRates.fulfilled, (state, action) => { + state.fromTokenExchangeRate = action.payload ?? null; + }); }, }); diff --git a/ui/ducks/bridge/selectors.test.ts b/ui/ducks/bridge/selectors.test.ts index e39f73f2fa15..b92c8e60e4f0 100644 --- a/ui/ducks/bridge/selectors.test.ts +++ b/ui/ducks/bridge/selectors.test.ts @@ -1,12 +1,18 @@ +import { BigNumber } from 'bignumber.js'; import { createBridgeMockStore } from '../../../test/jest/mock-store'; import { BUILT_IN_NETWORKS, CHAIN_IDS, FEATURED_RPCS, } from '../../../shared/constants/network'; -import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../../shared/constants/bridge'; +import { + ALLOWED_BRIDGE_CHAIN_IDS, + BRIDGE_QUOTE_MAX_ETA_SECONDS, +} from '../../../shared/constants/bridge'; import { mockNetworkState } from '../../../test/stub/networks'; import mockErc20Erc20Quotes from '../../../test/data/bridge/mock-quotes-erc20-erc20.json'; +import mockBridgeQuotesNativeErc20 from '../../../test/data/bridge/mock-quotes-native-erc20.json'; +import { SortOrder } from '../../pages/bridge/types'; import { getAllBridgeableNetworks, getBridgeQuotes, @@ -17,7 +23,6 @@ import { getFromTokens, getFromTopAssets, getIsBridgeTx, - getToAmount, getToChain, getToChains, getToToken, @@ -392,15 +397,6 @@ describe('Bridge selectors', () => { }); }); - describe('getToAmount', () => { - it('returns hardcoded 0', () => { - const state = createBridgeMockStore(); - const result = getToAmount(state as never); - - expect(result).toStrictEqual(undefined); - }); - }); - describe('getToTokens', () => { it('returns dest tokens from controller state when toChainId is defined', () => { const state = createBridgeMockStore( @@ -498,7 +494,12 @@ describe('Bridge selectors', () => { it('returns quote list and fetch data, insufficientBal=false,quotesRefreshCount=5', () => { const state = createBridgeMockStore( { extensionConfig: { maxRefreshCount: 5 } }, - { toChainId: '0x1' }, + { + toChainId: '0x1', + fromTokenExchangeRate: 1, + toTokenExchangeRate: 0.99, + toNativeExchangeRate: 0.354073, + }, { quoteRequest: { insufficientBal: false }, quotes: mockErc20Erc20Quotes, @@ -508,11 +509,51 @@ describe('Bridge selectors', () => { srcTokens: { '0x00': { address: '0x00', symbol: 'TEST' } }, srcTopAssets: [{ address: '0x00', symbol: 'TEST' }], }, + { + currencyRates: { + ETH: { + conversionRate: 1, + }, + }, + }, ); - const result = getBridgeQuotes(state as never); + const recommendedQuoteMetadata = { + adjustedReturn: { + fiat: expect.any(Object), + }, + cost: { fiat: new BigNumber('0.15656287141025952') }, + sentAmount: { + fiat: new BigNumber('14'), + amount: new BigNumber('14'), + }, + swapRate: new BigNumber('0.998877142857142857142857142857142857'), + toTokenAmount: { + fiat: new BigNumber('13.8444372'), + amount: new BigNumber('13.98428'), + }, + gasFee: { + amount: new BigNumber('7.141025952e-8'), + fiat: new BigNumber('7.141025952e-8'), + }, + totalNetworkFee: { + fiat: new BigNumber('0.00100007141025952'), + amount: new BigNumber('0.00100007141025952'), + }, + }; + + const result = getBridgeQuotes(state as never); + expect(result.sortedQuotes).toHaveLength(2); expect(result).toStrictEqual({ - quotes: mockErc20Erc20Quotes, + sortedQuotes: expect.any(Array), + recommendedQuote: { + ...mockErc20Erc20Quotes[0], + ...recommendedQuoteMetadata, + }, + activeQuote: { + ...mockErc20Erc20Quotes[0], + ...recommendedQuoteMetadata, + }, quotesLastFetchedMs: 100, isLoading: false, quotesRefreshCount: 5, @@ -523,7 +564,12 @@ describe('Bridge selectors', () => { it('returns quote list and fetch data, insufficientBal=false,quotesRefreshCount=2', () => { const state = createBridgeMockStore( { extensionConfig: { maxRefreshCount: 5 } }, - { toChainId: '0x1' }, + { + toChainId: '0x1', + fromTokenExchangeRate: 1, + toTokenExchangeRate: 0.99, + toNativeExchangeRate: 0.354073, + }, { quoteRequest: { insufficientBal: false }, quotes: mockErc20Erc20Quotes, @@ -533,11 +579,57 @@ describe('Bridge selectors', () => { srcTokens: { '0x00': { address: '0x00', symbol: 'TEST' } }, srcTopAssets: [{ address: '0x00', symbol: 'TEST' }], }, + { + currencyRates: { + ETH: { + conversionRate: 1, + }, + }, + }, ); const result = getBridgeQuotes(state as never); + const recommendedQuoteMetadata = { + adjustedReturn: { + fiat: new BigNumber('13.84343712858974048'), + }, + cost: { fiat: new BigNumber('0.15656287141025952') }, + sentAmount: { + fiat: new BigNumber('14'), + amount: new BigNumber('14'), + }, + swapRate: new BigNumber('0.998877142857142857142857142857142857'), + toTokenAmount: { + fiat: new BigNumber('13.8444372'), + amount: new BigNumber('13.98428'), + }, + gasFee: { + amount: new BigNumber('7.141025952e-8'), + fiat: new BigNumber('7.141025952e-8'), + }, + totalNetworkFee: { + fiat: new BigNumber('0.00100007141025952'), + amount: new BigNumber('0.00100007141025952'), + }, + }; + expect(result.sortedQuotes).toHaveLength(2); + const EXPECTED_SORTED_COSTS = [ + { fiat: new BigNumber('0.15656287141025952') }, + { fiat: new BigNumber('0.33900008283534464') }, + ]; + result.sortedQuotes.forEach((quote, idx) => { + expect(quote.cost).toStrictEqual(EXPECTED_SORTED_COSTS[idx]); + }); expect(result).toStrictEqual({ - quotes: mockErc20Erc20Quotes, + sortedQuotes: expect.any(Array), + recommendedQuote: { + ...mockErc20Erc20Quotes[0], + ...recommendedQuoteMetadata, + }, + activeQuote: { + ...mockErc20Erc20Quotes[0], + ...recommendedQuoteMetadata, + }, quotesLastFetchedMs: 100, isLoading: false, quotesRefreshCount: 2, @@ -548,7 +640,12 @@ describe('Bridge selectors', () => { it('returns quote list and fetch data, insufficientBal=true', () => { const state = createBridgeMockStore( { extensionConfig: { maxRefreshCount: 5 } }, - { toChainId: '0x1' }, + { + toChainId: '0x1', + fromTokenExchangeRate: 1, + toTokenExchangeRate: 0.99, + toNativeExchangeRate: 0.354073, + }, { quoteRequest: { insufficientBal: true }, quotes: mockErc20Erc20Quotes, @@ -558,11 +655,58 @@ describe('Bridge selectors', () => { srcTokens: { '0x00': { address: '0x00', symbol: 'TEST' } }, srcTopAssets: [{ address: '0x00', symbol: 'TEST' }], }, + { + currencyRates: { + ETH: { + conversionRate: 1, + }, + }, + }, ); const result = getBridgeQuotes(state as never); + const recommendedQuoteMetadata = { + adjustedReturn: { + fiat: new BigNumber('13.84343712858974048'), + }, + cost: { fiat: new BigNumber('0.15656287141025952') }, + sentAmount: { + fiat: new BigNumber('14'), + amount: new BigNumber('14'), + }, + swapRate: new BigNumber('0.998877142857142857142857142857142857'), + toTokenAmount: { + fiat: new BigNumber('13.8444372'), + amount: new BigNumber('13.98428'), + }, + gasFee: { + amount: new BigNumber('7.141025952e-8'), + fiat: new BigNumber('7.141025952e-8'), + }, + totalNetworkFee: { + fiat: new BigNumber('0.00100007141025952'), + amount: new BigNumber('0.00100007141025952'), + }, + }; + expect(result.sortedQuotes).toHaveLength(2); + const EXPECTED_SORTED_COSTS = [ + { fiat: new BigNumber('0.15656287141025952') }, + { fiat: new BigNumber('0.33900008283534464') }, + ]; + result.sortedQuotes.forEach((quote, idx) => { + expect(quote.cost).toStrictEqual(EXPECTED_SORTED_COSTS[idx]); + }); + expect(result).toStrictEqual({ - quotes: mockErc20Erc20Quotes, + sortedQuotes: expect.any(Array), + recommendedQuote: { + ...mockErc20Erc20Quotes[0], + ...recommendedQuoteMetadata, + }, + activeQuote: { + ...mockErc20Erc20Quotes[0], + ...recommendedQuoteMetadata, + }, quotesLastFetchedMs: 100, isLoading: false, quotesRefreshCount: 1, @@ -570,4 +714,203 @@ describe('Bridge selectors', () => { }); }); }); + + describe('getBridgeQuotes', () => { + it('should return empty values when quotes are not present', () => { + const state = createBridgeMockStore(); + + const result = getBridgeQuotes(state as never); + + expect(result).toStrictEqual({ + activeQuote: undefined, + isLoading: false, + isQuoteGoingToRefresh: false, + quotesLastFetchedMs: undefined, + quotesRefreshCount: undefined, + recommendedQuote: undefined, + sortedQuotes: [], + }); + }); + + it('should sort quotes by adjustedReturn', () => { + const state = createBridgeMockStore( + {}, + {}, + { quotes: mockBridgeQuotesNativeErc20 }, + ); + + const { activeQuote, recommendedQuote, sortedQuotes } = getBridgeQuotes( + state as never, + ); + + const quoteMetadataKeys = [ + 'adjustedReturn', + 'toTokenAmount', + 'sentAmount', + 'totalNetworkFee', + 'swapRate', + ]; + expect( + quoteMetadataKeys.every((k) => + Object.keys(activeQuote ?? {}).includes(k), + ), + ).toBe(true); + expect(activeQuote?.quote.requestId).toStrictEqual( + '381c23bc-e3e4-48fe-bc53-257471e388ad', + ); + expect(recommendedQuote?.quote.requestId).toStrictEqual( + '381c23bc-e3e4-48fe-bc53-257471e388ad', + ); + expect(sortedQuotes).toHaveLength(2); + sortedQuotes.forEach((quote, idx) => { + expect( + quoteMetadataKeys.every((k) => Object.keys(quote ?? {}).includes(k)), + ).toBe(true); + expect(quote?.quote.requestId).toStrictEqual( + mockBridgeQuotesNativeErc20[idx]?.quote.requestId, + ); + }); + }); + + it('should sort quotes by ETA', () => { + const state = createBridgeMockStore( + {}, + { sortOrder: SortOrder.ETA_ASC }, + { + quotes: [ + ...mockBridgeQuotesNativeErc20, + { + ...mockBridgeQuotesNativeErc20[0], + estimatedProcessingTimeInSeconds: 1, + quote: { + ...mockBridgeQuotesNativeErc20[0].quote, + requestId: 'fastestQuote', + }, + }, + ], + }, + ); + + const { activeQuote, recommendedQuote, sortedQuotes } = getBridgeQuotes( + state as never, + ); + + expect(activeQuote?.quote.requestId).toStrictEqual('fastestQuote'); + expect(recommendedQuote?.quote.requestId).toStrictEqual('fastestQuote'); + expect(sortedQuotes).toHaveLength(3); + expect(sortedQuotes[0]?.quote.requestId).toStrictEqual('fastestQuote'); + expect(sortedQuotes[1]?.quote.requestId).toStrictEqual( + mockBridgeQuotesNativeErc20[1]?.quote.requestId, + ); + expect(sortedQuotes[2]?.quote.requestId).toStrictEqual( + mockBridgeQuotesNativeErc20[0]?.quote.requestId, + ); + }); + + it('should recommend 2nd cheapest quote if ETA exceeds 1 hour', () => { + const state = createBridgeMockStore( + {}, + { sortOrder: SortOrder.COST_ASC }, + { + quotes: [ + mockBridgeQuotesNativeErc20[1], + { + ...mockBridgeQuotesNativeErc20[0], + estimatedProcessingTimeInSeconds: + BRIDGE_QUOTE_MAX_ETA_SECONDS + 1, + quote: { + ...mockBridgeQuotesNativeErc20[0].quote, + requestId: 'cheapestQuoteWithLongETA', + }, + }, + ], + }, + ); + + const { activeQuote, recommendedQuote, sortedQuotes } = getBridgeQuotes( + state as never, + ); + + expect(activeQuote?.quote.requestId).toStrictEqual( + '4277a368-40d7-4e82-aa67-74f29dc5f98a', + ); + expect(recommendedQuote?.quote.requestId).toStrictEqual( + '4277a368-40d7-4e82-aa67-74f29dc5f98a', + ); + expect(sortedQuotes).toHaveLength(2); + expect(sortedQuotes[0]?.quote.requestId).toStrictEqual( + '4277a368-40d7-4e82-aa67-74f29dc5f98a', + ); + expect(sortedQuotes[1]?.quote.requestId).toStrictEqual( + 'cheapestQuoteWithLongETA', + ); + }); + + it('should recommend 2nd fastest quote if adjustedReturn is less than 80% of cheapest quote', () => { + const state = createBridgeMockStore( + {}, + { + sortOrder: SortOrder.ETA_ASC, + toTokenExchangeRate: 0.998781, + toNativeExchangeRate: 0.354073, + }, + { + quotes: [ + ...mockBridgeQuotesNativeErc20, + { + ...mockBridgeQuotesNativeErc20[0], + estimatedProcessingTimeInSeconds: 1, + quote: { + ...mockBridgeQuotesNativeErc20[0].quote, + requestId: 'fastestQuote', + destTokenAmount: '1', + }, + }, + ], + }, + { + currencyRates: { + ETH: { + conversionRate: 2524.25, + }, + }, + }, + ); + + const { activeQuote, recommendedQuote, sortedQuotes } = getBridgeQuotes( + state as never, + ); + const { + sentAmount, + totalNetworkFee, + toTokenAmount, + adjustedReturn, + cost, + } = activeQuote ?? {}; + + expect(activeQuote?.quote.requestId).toStrictEqual( + '4277a368-40d7-4e82-aa67-74f29dc5f98a', + ); + expect(recommendedQuote?.quote.requestId).toStrictEqual( + '4277a368-40d7-4e82-aa67-74f29dc5f98a', + ); + expect(sentAmount?.fiat?.toString()).toStrictEqual('25.2425'); + expect(totalNetworkFee?.fiat?.toString()).toStrictEqual( + '2.52459306428938562', + ); + expect(toTokenAmount?.fiat?.toString()).toStrictEqual('24.226654664163'); + expect(adjustedReturn?.fiat?.toString()).toStrictEqual( + '21.70206159987361438', + ); + expect(cost?.fiat?.toString()).toStrictEqual('3.54043840012638562'); + expect(sortedQuotes).toHaveLength(3); + expect(sortedQuotes[0]?.quote.requestId).toStrictEqual('fastestQuote'); + expect(sortedQuotes[1]?.quote.requestId).toStrictEqual( + '4277a368-40d7-4e82-aa67-74f29dc5f98a', + ); + expect(sortedQuotes[2]?.quote.requestId).toStrictEqual( + '381c23bc-e3e4-48fe-bc53-257471e388ad', + ); + }); + }); }); diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 86f4c8155b17..b78a0d09de51 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -1,12 +1,22 @@ -import { NetworkConfiguration } from '@metamask/network-controller'; -import { uniqBy } from 'lodash'; +import { + NetworkConfiguration, + NetworkState, +} from '@metamask/network-controller'; +import { orderBy, uniqBy } from 'lodash'; import { createSelector } from 'reselect'; +import { GasFeeEstimates } from '@metamask/gas-fee-controller'; +import { BigNumber } from 'bignumber.js'; import { getIsBridgeEnabled, getSwapsDefaultToken, SwapsEthToken, } from '../../selectors/selectors'; -import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../../shared/constants/bridge'; +import { + ALLOWED_BRIDGE_CHAIN_IDS, + BRIDGE_PREFERRED_GAS_ESTIMATE, + BRIDGE_QUOTE_MAX_ETA_SECONDS, + BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE, +} from '../../../shared/constants/bridge'; import { BridgeControllerState, BridgeFeatureFlagsKey, @@ -15,21 +25,38 @@ import { } from '../../../app/scripts/controllers/bridge/types'; import { createDeepEqualSelector } from '../../../shared/modules/selectors/util'; import { - NetworkState, getProviderConfig, getNetworkConfigurationsByChainId, } from '../../../shared/modules/selectors/networks'; import { SwapsTokenObject } from '../../../shared/constants/swaps'; -import { calcTokenAmount } from '../../../shared/lib/transactions-controller-utils'; +import { getConversionRate, getGasFeeEstimates } from '../metamask/metamask'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { RequestStatus } from '../../../app/scripts/controllers/bridge/constants'; +import { + L1GasFees, + QuoteMetadata, + QuoteResponse, + SortOrder, +} from '../../pages/bridge/types'; +import { + calcAdjustedReturn, + calcCost, + calcRelayerFee, + calcSentAmount, + calcSwapRate, + calcToAmount, + calcTotalGasFee, + isNativeAddress, +} from '../../pages/bridge/utils/quote'; +import { decGWEIToHexWEI } from '../../../shared/modules/conversion.utils'; import { BridgeState } from './bridge'; -type BridgeAppState = NetworkState & { - metamask: { bridgeState: BridgeControllerState } & { - useExternalServices: boolean; - }; +type BridgeAppState = { + metamask: { bridgeState: BridgeControllerState } & NetworkState & { + useExternalServices: boolean; + currencyRates: { [currency: string]: { conversionRate: number } }; + }; bridge: BridgeState; }; @@ -140,48 +167,203 @@ export const getBridgeQuotesConfig = (state: BridgeAppState) => BridgeFeatureFlagsKey.EXTENSION_CONFIG ] ?? {}; +const _getBridgeFeesPerGas = createSelector( + getGasFeeEstimates, + (gasFeeEstimates) => ({ + estimatedBaseFeeInDecGwei: (gasFeeEstimates as GasFeeEstimates) + ?.estimatedBaseFee, + maxPriorityFeePerGasInDecGwei: (gasFeeEstimates as GasFeeEstimates)?.[ + BRIDGE_PREFERRED_GAS_ESTIMATE + ]?.suggestedMaxPriorityFeePerGas, + maxFeePerGas: decGWEIToHexWEI( + (gasFeeEstimates as GasFeeEstimates)?.high?.suggestedMaxFeePerGas, + ), + maxPriorityFeePerGas: decGWEIToHexWEI( + (gasFeeEstimates as GasFeeEstimates)?.high?.suggestedMaxPriorityFeePerGas, + ), + }), +); + +export const getBridgeSortOrder = (state: BridgeAppState) => + state.bridge.sortOrder; + +// A dest network can be selected before it's imported +// The cached exchange rate won't be available so the rate from the bridge state is used +const _getToTokenExchangeRate = createSelector( + (state) => state.metamask.currencyRates, + (state: BridgeAppState) => state.bridge.toTokenExchangeRate, + getToChain, + getToToken, + (cachedCurrencyRates, toTokenExchangeRate, toChain, toToken) => { + return ( + toTokenExchangeRate ?? + (isNativeAddress(toToken?.address) && toChain?.nativeCurrency + ? cachedCurrencyRates[toChain.nativeCurrency]?.conversionRate + : null) + ); + }, +); + +const _getQuotesWithMetadata = createDeepEqualSelector( + (state) => state.metamask.bridgeState.quotes, + _getToTokenExchangeRate, + (state: BridgeAppState) => state.bridge.fromTokenExchangeRate, + getConversionRate, + _getBridgeFeesPerGas, + ( + quotes, + toTokenExchangeRate, + fromTokenExchangeRate, + nativeExchangeRate, + { estimatedBaseFeeInDecGwei, maxPriorityFeePerGasInDecGwei }, + ): (QuoteResponse & QuoteMetadata)[] => { + const newQuotes = quotes.map((quote: QuoteResponse) => { + const toTokenAmount = calcToAmount(quote.quote, toTokenExchangeRate); + const gasFee = calcTotalGasFee( + quote, + estimatedBaseFeeInDecGwei, + maxPriorityFeePerGasInDecGwei, + nativeExchangeRate, + ); + const relayerFee = calcRelayerFee(quote, nativeExchangeRate); + const totalNetworkFee = { + amount: gasFee.amount.plus(relayerFee.amount), + fiat: gasFee.fiat?.plus(relayerFee.fiat || '0') ?? null, + }; + const sentAmount = calcSentAmount( + quote.quote, + isNativeAddress(quote.quote.srcAsset.address) + ? nativeExchangeRate + : fromTokenExchangeRate, + ); + const adjustedReturn = calcAdjustedReturn( + toTokenAmount.fiat, + totalNetworkFee.fiat, + ); + + return { + ...quote, + toTokenAmount, + sentAmount, + totalNetworkFee, + adjustedReturn, + gasFee, + swapRate: calcSwapRate(sentAmount.amount, toTokenAmount.amount), + cost: calcCost(adjustedReturn.fiat, sentAmount.fiat), + }; + }); + + return newQuotes; + }, +); + +const _getSortedQuotesWithMetadata = createDeepEqualSelector( + _getQuotesWithMetadata, + getBridgeSortOrder, + (quotesWithMetadata, sortOrder) => { + switch (sortOrder) { + case SortOrder.ETA_ASC: + return orderBy( + quotesWithMetadata, + (quote) => quote.estimatedProcessingTimeInSeconds, + 'asc', + ); + case SortOrder.COST_ASC: + default: + return orderBy(quotesWithMetadata, ({ cost }) => cost.fiat, 'asc'); + } + }, +); + +const _getRecommendedQuote = createDeepEqualSelector( + _getSortedQuotesWithMetadata, + getBridgeSortOrder, + (sortedQuotesWithMetadata, sortOrder) => { + if (!sortedQuotesWithMetadata.length) { + return undefined; + } + + const bestReturnValue = BigNumber.max( + sortedQuotesWithMetadata.map( + ({ adjustedReturn }) => adjustedReturn.fiat ?? 0, + ), + ); + + const isFastestQuoteValueReasonable = ( + adjustedReturnInFiat: BigNumber | null, + ) => + adjustedReturnInFiat + ? adjustedReturnInFiat + .div(bestReturnValue) + .gte(BRIDGE_QUOTE_MAX_RETURN_DIFFERENCE_PERCENTAGE) + : true; + + const isBestPricedQuoteETAReasonable = ( + estimatedProcessingTimeInSeconds: number, + ) => estimatedProcessingTimeInSeconds < BRIDGE_QUOTE_MAX_ETA_SECONDS; + + return ( + sortedQuotesWithMetadata.find((quote) => { + return sortOrder === SortOrder.ETA_ASC + ? isFastestQuoteValueReasonable(quote.adjustedReturn.fiat) + : isBestPricedQuoteETAReasonable( + quote.estimatedProcessingTimeInSeconds, + ); + }) ?? sortedQuotesWithMetadata[0] + ); + }, +); + +// Generates a pseudo-unique string that identifies each quote +// by aggregator, bridge, steps and value +const _getQuoteIdentifier = ({ quote }: QuoteResponse & L1GasFees) => + `${quote.bridgeId}-${quote.bridges[0]}-${quote.steps.length}`; + +const _getSelectedQuote = createSelector( + (state: BridgeAppState) => state.metamask.bridgeState.quotesRefreshCount, + (state: BridgeAppState) => state.bridge.selectedQuote, + _getSortedQuotesWithMetadata, + (quotesRefreshCount, selectedQuote, sortedQuotesWithMetadata) => + quotesRefreshCount <= 1 + ? selectedQuote + : // Find match for selectedQuote in new quotes + sortedQuotesWithMetadata.find((quote) => + selectedQuote + ? _getQuoteIdentifier(quote) === _getQuoteIdentifier(selectedQuote) + : false, + ), +); + export const getBridgeQuotes = createSelector( - (state: BridgeAppState) => state.metamask.bridgeState.quotes, - (state: BridgeAppState) => state.metamask.bridgeState.quotesLastFetched, - (state: BridgeAppState) => + _getSortedQuotesWithMetadata, + _getRecommendedQuote, + _getSelectedQuote, + (state) => state.metamask.bridgeState.quotesLastFetched, + (state) => state.metamask.bridgeState.quotesLoadingStatus === RequestStatus.LOADING, (state: BridgeAppState) => state.metamask.bridgeState.quotesRefreshCount, getBridgeQuotesConfig, getQuoteRequest, ( - quotes, + sortedQuotesWithMetadata, + recommendedQuote, + selectedQuote, quotesLastFetchedMs, isLoading, quotesRefreshCount, { maxRefreshCount }, { insufficientBal }, - ) => { - return { - quotes, - quotesLastFetchedMs, - isLoading, - quotesRefreshCount, - isQuoteGoingToRefresh: insufficientBal - ? false - : quotesRefreshCount < maxRefreshCount, - }; - }, -); - -export const getRecommendedQuote = createSelector( - getBridgeQuotes, - ({ quotes }) => { - return quotes[0]; - }, -); - -export const getToAmount = createSelector(getRecommendedQuote, (quote) => - quote - ? calcTokenAmount( - quote.quote.destTokenAmount, - quote.quote.destAsset.decimals, - ) - : undefined, + ) => ({ + sortedQuotes: sortedQuotesWithMetadata, + recommendedQuote, + activeQuote: selectedQuote ?? recommendedQuote, + quotesLastFetchedMs, + isLoading, + quotesRefreshCount, + isQuoteGoingToRefresh: insufficientBal + ? false + : quotesRefreshCount < maxRefreshCount, + }), ); export const getIsBridgeTx = createDeepEqualSelector( diff --git a/ui/ducks/bridge/utils.ts b/ui/ducks/bridge/utils.ts index 853c344310fe..de45111cc10b 100644 --- a/ui/ducks/bridge/utils.ts +++ b/ui/ducks/bridge/utils.ts @@ -1,9 +1,11 @@ import { Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; +import { getAddress } from 'ethers/lib/utils'; import { decGWEIToHexWEI } from '../../../shared/modules/conversion.utils'; import { Numeric } from '../../../shared/modules/Numeric'; import { TxData } from '../../pages/bridge/types'; import { getTransaction1559GasFeeEstimates } from '../../pages/swaps/swaps.util'; +import { fetchTokenExchangeRates } from '../../helpers/utils/util'; // We don't need to use gas multipliers here because the gasLimit from Bridge API already included it export const getHexMaxGasLimit = (gasLimit: number) => { @@ -45,3 +47,20 @@ export const getTxGasEstimates = async ({ maxPriorityFeePerGas: undefined, }; }; + +export const getTokenExchangeRate = async (request: { + chainId: Hex; + tokenAddress: string; + currency: string; +}) => { + const { chainId, tokenAddress, currency } = request; + const exchangeRates = await fetchTokenExchangeRates( + currency, + [tokenAddress], + chainId, + ); + return ( + exchangeRates?.[tokenAddress.toLowerCase()] ?? + exchangeRates?.[getAddress(tokenAddress)] + ); +}; diff --git a/ui/hooks/bridge/useBridging.test.ts b/ui/hooks/bridge/useBridging.test.ts index 6e3f3b534e35..9fe02c439048 100644 --- a/ui/hooks/bridge/useBridging.test.ts +++ b/ui/hooks/bridge/useBridging.test.ts @@ -123,19 +123,19 @@ describe('useBridging', () => { // @ts-expect-error This is missing from the Mocha type definitions it.each([ [ - '/cross-chain/swaps/prepare-swap-page', + '/cross-chain/swaps/prepare-swap-page?token=0x0000000000000000000000000000000000000000', ETH_SWAPS_TOKEN_OBJECT, 'Home', undefined, ], [ - '/cross-chain/swaps/prepare-swap-page', + '/cross-chain/swaps/prepare-swap-page?token=0x0000000000000000000000000000000000000000', ETH_SWAPS_TOKEN_OBJECT, MetaMetricsSwapsEventSource.TokenView, '&token=native', ], [ - '/cross-chain/swaps/prepare-swap-page', + '/cross-chain/swaps/prepare-swap-page?token=0x00232f2jksdauo', { iconUrl: 'https://icon.url', symbol: 'TEST', @@ -174,7 +174,7 @@ describe('useBridging', () => { result.current.openBridgeExperience(location, token, urlSuffix); - expect(mockDispatch.mock.calls).toHaveLength(2); + expect(mockDispatch.mock.calls).toHaveLength(1); expect(mockHistoryPush.mock.calls).toHaveLength(1); expect(mockHistoryPush).toHaveBeenCalledWith(expectedUrl); expect(openTabSpy).not.toHaveBeenCalled(); diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts index c4ae1cca57a3..62945e3e7a92 100644 --- a/ui/hooks/bridge/useBridging.ts +++ b/ui/hooks/bridge/useBridging.ts @@ -28,7 +28,6 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) import { isHardwareKeyring } from '../../helpers/utils/hardware'; import { getPortfolioUrl } from '../../helpers/utils/portfolio'; -import { setSwapsFromToken } from '../../ducks/swaps/swaps'; import { SwapsTokenObject } from '../../../shared/constants/swaps'; import { getProviderConfig } from '../../../shared/modules/selectors/networks'; ///: END:ONLY_INCLUDE_IF @@ -74,9 +73,6 @@ const useBridging = () => { chain_id: providerConfig.chainId, }, }); - dispatch( - setSwapsFromToken({ ...token, address: token.address.toLowerCase() }), - ); if (usingHardwareWallet && global.platform.openExtensionInBrowser) { global.platform.openExtensionInBrowser( PREPARE_SWAP_ROUTE, @@ -84,7 +80,9 @@ const useBridging = () => { false, ); } else { - history.push(CROSS_CHAIN_SWAP_ROUTE + PREPARE_SWAP_ROUTE); + history.push( + `${CROSS_CHAIN_SWAP_ROUTE}${PREPARE_SWAP_ROUTE}?token=${token.address.toLowerCase()}`, + ); } } else { const portfolioUrl = getPortfolioUrl( @@ -115,7 +113,6 @@ const useBridging = () => { [ isBridgeSupported, isBridgeChain, - setSwapsFromToken, dispatch, usingHardwareWallet, history, diff --git a/ui/hooks/bridge/useCountdownTimer.test.ts b/ui/hooks/bridge/useCountdownTimer.test.ts index f2cd1190b1ba..293fe1ac679b 100644 --- a/ui/hooks/bridge/useCountdownTimer.test.ts +++ b/ui/hooks/bridge/useCountdownTimer.test.ts @@ -17,14 +17,11 @@ describe('useCountdownTimer', () => { const quotesLastFetched = Date.now(); const { result } = renderUseCountdownTimer( createBridgeMockStore( - {}, + { extensionConfig: { maxRefreshCount: 5, refreshRate: 40000 } }, {}, { quotesLastFetched, quotesRefreshCount: 0, - bridgeFeatureFlags: { - extensionConfig: { maxRefreshCount: 5, refreshRate: 40000 }, - }, }, ), ); diff --git a/ui/hooks/useTokensWithFiltering.ts b/ui/hooks/useTokensWithFiltering.ts index a7ff3f2513ac..d729ce3c1fdc 100644 --- a/ui/hooks/useTokensWithFiltering.ts +++ b/ui/hooks/useTokensWithFiltering.ts @@ -3,6 +3,7 @@ import { useSelector } from 'react-redux'; import { isEqual } from 'lodash'; import { ChainId, hexToBN } from '@metamask/controller-utils'; import { Hex } from '@metamask/utils'; +import { useParams } from 'react-router-dom'; import { getAllTokens, getCurrentCurrency, @@ -39,6 +40,8 @@ export const useTokensWithFiltering = ( sortOrder: TokenBucketPriority = TokenBucketPriority.owned, chainId?: ChainId | Hex, ) => { + const { token: tokenAddressFromUrl } = useParams(); + // Only includes non-native tokens const allDetectedTokens = useSelector(getAllTokens); const { address: selectedAddress, balance: balanceOnActiveChain } = @@ -123,6 +126,18 @@ export const useTokensWithFiltering = ( yield nativeToken; } + if (tokenAddressFromUrl) { + const tokenListItem = + tokenList?.[tokenAddressFromUrl] ?? + tokenList?.[tokenAddressFromUrl.toLowerCase()]; + if (tokenListItem) { + const tokenWithTokenListData = buildTokenData(tokenListItem); + if (tokenWithTokenListData) { + yield tokenWithTokenListData; + } + } + } + if (sortOrder === TokenBucketPriority.owned) { for (const tokenWithBalance of sortedErc20TokensWithBalances) { const cachedTokenData = @@ -171,6 +186,7 @@ export const useTokensWithFiltering = ( currentCurrency, chainId, tokenList, + tokenAddressFromUrl, ], ); diff --git a/ui/pages/bridge/index.tsx b/ui/pages/bridge/index.tsx index 687057094005..6dd54b424d06 100644 --- a/ui/pages/bridge/index.tsx +++ b/ui/pages/bridge/index.tsx @@ -1,6 +1,7 @@ import React, { useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Switch, useHistory } from 'react-router-dom'; +import { zeroAddress } from 'ethereumjs-util'; import { I18nContext } from '../../contexts/i18n'; import { clearSwapsState } from '../../ducks/swaps/swaps'; import { @@ -16,16 +17,25 @@ import { ButtonIconSize, IconName, } from '../../components/component-library'; -import { getIsBridgeChain, getIsBridgeEnabled } from '../../selectors'; import { getProviderConfig } from '../../../shared/modules/selectors/networks'; +import { + getCurrentCurrency, + getIsBridgeChain, + getIsBridgeEnabled, +} from '../../selectors'; import useBridging from '../../hooks/bridge/useBridging'; import { Content, Footer, Header, } from '../../components/multichain/pages/page'; -import { resetBridgeState, setFromChain } from '../../ducks/bridge/actions'; import { useSwapsFeatureFlags } from '../swaps/hooks/useSwapsFeatureFlags'; +import { + resetBridgeState, + setFromChain, + setSrcTokenExchangeRates, +} from '../../ducks/bridge/actions'; +import { useGasFeeEstimates } from '../../hooks/useGasFeeEstimates'; import PrepareBridgePage from './prepare/prepare-bridge-page'; import { BridgeCTAButton } from './prepare/bridge-cta-button'; @@ -42,13 +52,20 @@ const CrossChainSwap = () => { const isBridgeEnabled = useSelector(getIsBridgeEnabled); const providerConfig = useSelector(getProviderConfig); const isBridgeChain = useSelector(getIsBridgeChain); + const currency = useSelector(getCurrentCurrency); useEffect(() => { - isBridgeChain && - isBridgeEnabled && - providerConfig && + if (isBridgeChain && isBridgeEnabled && providerConfig && currency) { dispatch(setFromChain(providerConfig.chainId)); - }, [isBridgeChain, isBridgeEnabled, providerConfig]); + dispatch( + setSrcTokenExchangeRates({ + chainId: providerConfig.chainId, + tokenAddress: zeroAddress(), + currency, + }), + ); + } + }, [isBridgeChain, isBridgeEnabled, providerConfig, currency]); const resetControllerAndInputStates = async () => { await dispatch(resetBridgeState()); @@ -66,6 +83,9 @@ const CrossChainSwap = () => { }; }, []); + // Needed for refreshing gas estimates + useGasFeeEstimates(providerConfig?.id); + const redirectToDefaultRoute = async () => { history.push({ pathname: DEFAULT_ROUTE, diff --git a/ui/pages/bridge/layout/column.tsx b/ui/pages/bridge/layout/column.tsx new file mode 100644 index 000000000000..6f5b2847b5e5 --- /dev/null +++ b/ui/pages/bridge/layout/column.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { + Container, + ContainerProps, +} from '../../../components/component-library'; +import { + BlockSize, + Display, + FlexDirection, +} from '../../../helpers/constants/design-system'; + +const Column = (props: ContainerProps<'div'>) => { + return ( + + ); +}; + +export default Column; diff --git a/ui/pages/bridge/layout/index.tsx b/ui/pages/bridge/layout/index.tsx new file mode 100644 index 000000000000..d519d211f500 --- /dev/null +++ b/ui/pages/bridge/layout/index.tsx @@ -0,0 +1,5 @@ +import Column from './column'; +import Row from './row'; +import Tooltip from './tooltip'; + +export { Column, Row, Tooltip }; diff --git a/ui/pages/bridge/layout/row.tsx b/ui/pages/bridge/layout/row.tsx new file mode 100644 index 000000000000..eeb94a7e06f7 --- /dev/null +++ b/ui/pages/bridge/layout/row.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { + Container, + ContainerProps, +} from '../../../components/component-library'; +import { + AlignItems, + Display, + FlexDirection, + FlexWrap, + JustifyContent, +} from '../../../helpers/constants/design-system'; + +const Row = (props: ContainerProps<'div'>) => { + return ( + + ); +}; + +export default Row; diff --git a/ui/pages/bridge/layout/tooltip.tsx b/ui/pages/bridge/layout/tooltip.tsx new file mode 100644 index 000000000000..b6781c9bf480 --- /dev/null +++ b/ui/pages/bridge/layout/tooltip.tsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; +import { + Box, + Popover, + PopoverHeader, + PopoverPosition, + PopoverProps, + Text, +} from '../../../components/component-library'; +import { + JustifyContent, + TextAlign, + TextColor, +} from '../../../helpers/constants/design-system'; + +const Tooltip = React.forwardRef( + ({ + children, + title, + triggerElement, + disabled = false, + ...props + }: PopoverProps<'div'> & { + triggerElement: React.ReactElement; + disabled?: boolean; + }) => { + const [isOpen, setIsOpen] = useState(false); + const [referenceElement, setReferenceElement] = + useState(null); + + const handleMouseEnter = () => setIsOpen(true); + const handleMouseLeave = () => setIsOpen(false); + const setBoxRef = (ref: HTMLSpanElement | null) => setReferenceElement(ref); + + return ( + <> + + {triggerElement} + + {!disabled && ( + + + {title} + + + {children} + + + )} + + ); + }, +); + +export default Tooltip; diff --git a/ui/pages/bridge/prepare/bridge-cta-button.tsx b/ui/pages/bridge/prepare/bridge-cta-button.tsx index 06d784f2e0ea..7355e6579dfa 100644 --- a/ui/pages/bridge/prepare/bridge-cta-button.tsx +++ b/ui/pages/bridge/prepare/bridge-cta-button.tsx @@ -2,14 +2,12 @@ import React, { useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Button } from '../../../components/component-library'; import { - getBridgeQuotes, getFromAmount, getFromChain, getFromToken, - getRecommendedQuote, - getToAmount, getToChain, getToToken, + getBridgeQuotes, } from '../../../ducks/bridge/selectors'; import { useI18nContext } from '../../../hooks/useI18nContext'; import useSubmitBridgeTransaction from '../hooks/useSubmitBridgeTransaction'; @@ -25,15 +23,13 @@ export const BridgeCTAButton = () => { const toChain = useSelector(getToChain); const fromAmount = useSelector(getFromAmount); - const toAmount = useSelector(getToAmount); - const { isLoading } = useSelector(getBridgeQuotes); - const quoteResponse = useSelector(getRecommendedQuote); + const { isLoading, activeQuote } = useSelector(getBridgeQuotes); const { submitBridgeTransaction } = useSubmitBridgeTransaction(); const isTxSubmittable = - fromToken && toToken && fromChain && toChain && fromAmount && toAmount; + fromToken && toToken && fromChain && toChain && fromAmount && activeQuote; const label = useMemo(() => { if (isLoading && !isTxSubmittable) { @@ -59,7 +55,7 @@ export const BridgeCTAButton = () => { data-testid="bridge-cta-button" onClick={() => { if (isTxSubmittable) { - dispatch(submitBridgeTransaction(quoteResponse)); + dispatch(submitBridgeTransaction(activeQuote)); } }} disabled={!isTxSubmittable} diff --git a/ui/pages/bridge/prepare/bridge-input-group.tsx b/ui/pages/bridge/prepare/bridge-input-group.tsx index 266af5b9a3cc..0dbecf6cffdd 100644 --- a/ui/pages/bridge/prepare/bridge-input-group.tsx +++ b/ui/pages/bridge/prepare/bridge-input-group.tsx @@ -28,10 +28,7 @@ import { CHAIN_ID_TOKEN_IMAGE_MAP, } from '../../../../shared/constants/network'; import useLatestBalance from '../../../hooks/bridge/useLatestBalance'; -import { - getBridgeQuotes, - getRecommendedQuote, -} from '../../../ducks/bridge/selectors'; +import { getBridgeQuotes } from '../../../ducks/bridge/selectors'; const generateAssetFromToken = ( chainId: Hex, @@ -82,8 +79,7 @@ export const BridgeInputGroup = ({ >) => { const t = useI18nContext(); - const { isLoading } = useSelector(getBridgeQuotes); - const recommendedQuote = useSelector(getRecommendedQuote); + const { isLoading, activeQuote } = useSelector(getBridgeQuotes); const tokenFiatValue = useTokenFiatAmount( token?.address || undefined, @@ -134,9 +130,7 @@ export const BridgeInputGroup = ({ type={TextFieldType.Number} className="amount-input" placeholder={ - isLoading && !recommendedQuote - ? t('bridgeCalculatingAmount') - : '0' + isLoading && !activeQuote ? t('bridgeCalculatingAmount') : '0' } onChange={(e) => { onAmountChange?.(e.target.value); diff --git a/ui/pages/bridge/prepare/prepare-bridge-page.test.tsx b/ui/pages/bridge/prepare/prepare-bridge-page.test.tsx index aba35d5b89be..95248bdbb0bc 100644 --- a/ui/pages/bridge/prepare/prepare-bridge-page.test.tsx +++ b/ui/pages/bridge/prepare/prepare-bridge-page.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { act } from '@testing-library/react'; +import * as reactRouterUtils from 'react-router-dom-v5-compat'; import { fireEvent, renderWithProvider } from '../../../../test/jest'; import configureStore from '../../../store/store'; import { createBridgeMockStore } from '../../../../test/jest/mock-store'; @@ -23,6 +24,9 @@ describe('PrepareBridgePage', () => { }); it('should render the component, with initial state', async () => { + jest + .spyOn(reactRouterUtils, 'useSearchParams') + .mockReturnValue([{ get: () => null }] as never); const mockStore = createBridgeMockStore( { srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM], @@ -54,6 +58,9 @@ describe('PrepareBridgePage', () => { }); it('should render the component, with inputs set', async () => { + jest + .spyOn(reactRouterUtils, 'useSearchParams') + .mockReturnValue([{ get: () => '0x3103910' }, jest.fn()] as never); const mockStore = createBridgeMockStore( { srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.LINEA_MAINNET], diff --git a/ui/pages/bridge/prepare/prepare-bridge-page.tsx b/ui/pages/bridge/prepare/prepare-bridge-page.tsx index b0553407686d..aea037c71f13 100644 --- a/ui/pages/bridge/prepare/prepare-bridge-page.tsx +++ b/ui/pages/bridge/prepare/prepare-bridge-page.tsx @@ -2,16 +2,23 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import classnames from 'classnames'; import { debounce } from 'lodash'; +import { Hex } from '@metamask/utils'; +import { zeroAddress } from 'ethereumjs-util'; +import { useHistory, useLocation } from 'react-router-dom'; import { + setDestTokenExchangeRates, setFromChain, setFromToken, setFromTokenInputValue, + setSrcTokenExchangeRates, + setSelectedQuote, setToChain, setToChainId, setToToken, updateQuoteRequestParams, } from '../../../ducks/bridge/actions'; import { + getBridgeQuotes, getFromAmount, getFromChain, getFromChains, @@ -19,7 +26,6 @@ import { getFromTokens, getFromTopAssets, getQuoteRequest, - getToAmount, getToChain, getToChains, getToToken, @@ -42,6 +48,8 @@ import { calcTokenValue } from '../../../../shared/lib/swaps-utils'; import { BridgeQuoteCard } from '../quotes/bridge-quote-card'; import { isValidQuoteRequest } from '../utils/quote'; import { getProviderConfig } from '../../../../shared/modules/selectors/networks'; +import { getCurrentCurrency } from '../../../selectors'; +import { SECOND } from '../../../../shared/constants/time'; import { BridgeInputGroup } from './bridge-input-group'; const PrepareBridgePage = () => { @@ -49,6 +57,8 @@ const PrepareBridgePage = () => { const t = useI18nContext(); + const currency = useSelector(getCurrentCurrency); + const fromToken = useSelector(getFromToken); const fromTokens = useSelector(getFromTokens); const fromTopAssets = useSelector(getFromTopAssets); @@ -63,11 +73,11 @@ const PrepareBridgePage = () => { const toChain = useSelector(getToChain); const fromAmount = useSelector(getFromAmount); - const toAmount = useSelector(getToAmount); const providerConfig = useSelector(getProviderConfig); const quoteRequest = useSelector(getQuoteRequest); + const { activeQuote } = useSelector(getBridgeQuotes); const fromTokenListGenerator = useTokensWithFiltering( fromTokens, @@ -114,10 +124,10 @@ const PrepareBridgePage = () => { ); const debouncedUpdateQuoteRequestInController = useCallback( - debounce( - (p: Partial) => dispatch(updateQuoteRequestParams(p)), - 300, - ), + debounce((p: Partial) => { + dispatch(updateQuoteRequestParams(p)); + dispatch(setSelectedQuote(null)); + }, 300), [], ); @@ -125,6 +135,62 @@ const PrepareBridgePage = () => { debouncedUpdateQuoteRequestInController(quoteParams); }, Object.values(quoteParams)); + const debouncedFetchFromExchangeRate = debounce( + (chainId: Hex, tokenAddress: string) => { + dispatch(setSrcTokenExchangeRates({ chainId, tokenAddress, currency })); + }, + SECOND, + ); + + const debouncedFetchToExchangeRate = debounce( + (chainId: Hex, tokenAddress: string) => { + dispatch(setDestTokenExchangeRates({ chainId, tokenAddress, currency })); + }, + SECOND, + ); + + const { search } = useLocation(); + const history = useHistory(); + + useEffect(() => { + if (!fromChain?.chainId || Object.keys(fromTokens).length === 0) { + return; + } + + const searchParams = new URLSearchParams(search); + const tokenAddressFromUrl = searchParams.get('token'); + if (!tokenAddressFromUrl) { + return; + } + + const removeTokenFromUrl = () => { + const newParams = new URLSearchParams(searchParams); + newParams.delete('token'); + history.replace({ + search: newParams.toString(), + }); + }; + + switch (tokenAddressFromUrl) { + case fromToken?.address?.toLowerCase(): + // If the token is already set, remove the query param + removeTokenFromUrl(); + break; + case fromTokens[tokenAddressFromUrl]?.address?.toLowerCase(): { + // If there is a matching fromToken, set it as the fromToken + const matchedToken = fromTokens[tokenAddressFromUrl]; + dispatch(setFromToken(matchedToken)); + debouncedFetchFromExchangeRate(fromChain.chainId, matchedToken.address); + removeTokenFromUrl(); + break; + } + default: + // Otherwise remove query param + removeTokenFromUrl(); + break; + } + }, [fromChain, fromToken, fromTokens, search]); + return (
@@ -138,6 +204,9 @@ const PrepareBridgePage = () => { onAssetChange={(token) => { dispatch(setFromToken(token)); dispatch(setFromTokenInputValue(null)); + fromChain?.chainId && + token?.address && + debouncedFetchFromExchangeRate(fromChain.chainId, token.address); }} networkProps={{ network: fromChain, @@ -192,6 +261,19 @@ const PrepareBridgePage = () => { fromChain?.chainId && dispatch(setToChain(fromChain.chainId)); fromChain?.chainId && dispatch(setToChainId(fromChain.chainId)); dispatch(setToToken(fromToken)); + fromChain?.chainId && + fromToken?.address && + debouncedFetchToExchangeRate( + fromChain.chainId, + fromToken.address, + ); + toChain?.chainId && + toToken?.address && + toToken.address !== zeroAddress() && + debouncedFetchFromExchangeRate( + toChain.chainId, + toToken.address, + ); }} /> @@ -200,7 +282,12 @@ const PrepareBridgePage = () => { className="bridge-box" header={t('bridgeTo')} token={toToken} - onAssetChange={(token) => dispatch(setToToken(token))} + onAssetChange={(token) => { + dispatch(setToToken(token)); + toChain?.chainId && + token?.address && + debouncedFetchToExchangeRate(toChain.chainId, token.address); + }} networkProps={{ network: toChain, networks: toChains, @@ -218,8 +305,10 @@ const PrepareBridgePage = () => { testId: 'to-amount', readOnly: true, disabled: true, - value: toAmount?.toString() ?? '0', - className: toAmount ? 'amount-input defined' : 'amount-input', + value: activeQuote?.toTokenAmount?.amount.toFixed() ?? '0', + className: activeQuote?.toTokenAmount.amount + ? 'amount-input defined' + : 'amount-input', }} /> diff --git a/ui/pages/bridge/quotes/__snapshots__/bridge-quote-card.test.tsx.snap b/ui/pages/bridge/quotes/__snapshots__/bridge-quote-card.test.tsx.snap index cb7b5afb4c77..6b69b8ec9a6c 100644 --- a/ui/pages/bridge/quotes/__snapshots__/bridge-quote-card.test.tsx.snap +++ b/ui/pages/bridge/quotes/__snapshots__/bridge-quote-card.test.tsx.snap @@ -61,7 +61,7 @@ exports[`BridgeQuoteCard should render the recommended quote 1`] = `

- 1 minutes + 1 min

@@ -90,7 +90,7 @@ exports[`BridgeQuoteCard should render the recommended quote 1`] = `

- 1 USDC = 0.9989 USDC + 1 USDC = 1.00 USDC

@@ -131,13 +131,13 @@ Fees are based on network traffic and transaction complexity. MetaMask does not

- 0.01 ETH + 0.001000 ETH

- $0.01 + $2.52

@@ -234,7 +234,7 @@ exports[`BridgeQuoteCard should render the recommended quote while loading new q

- 1 minutes + 1 min

@@ -263,7 +263,7 @@ exports[`BridgeQuoteCard should render the recommended quote while loading new q

- 1 ETH = 2465.4630 USDC + 1 ETH = 2443.89 USDC

@@ -304,13 +304,13 @@ Fees are based on network traffic and transaction complexity. MetaMask does not

- 0.01 ETH + 0.001000 ETH

- $0.01 + $2.52

diff --git a/ui/pages/bridge/quotes/__snapshots__/bridge-quotes-modal.test.tsx.snap b/ui/pages/bridge/quotes/__snapshots__/bridge-quotes-modal.test.tsx.snap index 41d8a03d1ac1..137dc246864e 100644 --- a/ui/pages/bridge/quotes/__snapshots__/bridge-quotes-modal.test.tsx.snap +++ b/ui/pages/bridge/quotes/__snapshots__/bridge-quotes-modal.test.tsx.snap @@ -33,31 +33,31 @@ exports[`BridgeQuotesModal should render the modal 1`] = ` class="mm-box mm-header-base mm-modal-header mm-box--padding-right-4 mm-box--padding-bottom-4 mm-box--padding-left-4 mm-box--display-flex mm-box--justify-content-space-between" >
-

- Select a quote -

-
-
+
+

+ Select a quote +

+
-

+

- $0.01 -

-

+

+ $3 network fee +

+

+ 14 USDC receive amount +

+
+
- 1 minutes -

+

+ 1 min +

+

+ Across +

+
-

- $0.01 -

-

+

+ $3 network fee +

+

+ 14 USDC receive amount +

+
+
- 26 minutes -

+

+ 26 min +

+

+ Celercircle +

+
diff --git a/ui/pages/bridge/quotes/bridge-quote-card.test.tsx b/ui/pages/bridge/quotes/bridge-quote-card.test.tsx index 274ade65a4d1..7de52fef1d58 100644 --- a/ui/pages/bridge/quotes/bridge-quote-card.test.tsx +++ b/ui/pages/bridge/quotes/bridge-quote-card.test.tsx @@ -20,6 +20,7 @@ describe('BridgeQuoteCard', () => { { srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM], destNetworkAllowlist: [CHAIN_IDS.OPTIMISM], + extensionConfig: { maxRefreshCount: 5, refreshRate: 30000 }, }, { fromTokenInputValue: 1 }, { @@ -28,9 +29,6 @@ describe('BridgeQuoteCard', () => { quotes: mockBridgeQuotesErc20Erc20, getQuotesLastFetched: Date.now(), quotesLoadingStatus: RequestStatus.FETCHED, - bridgeFeatureFlags: { - extensionConfig: { maxRefreshCount: 5, refreshRate: 30000 }, - }, }, ); const { container } = renderWithProvider( diff --git a/ui/pages/bridge/quotes/bridge-quote-card.tsx b/ui/pages/bridge/quotes/bridge-quote-card.tsx index fc1176c8c3f9..8adf675afbb3 100644 --- a/ui/pages/bridge/quotes/bridge-quote-card.tsx +++ b/ui/pages/bridge/quotes/bridge-quote-card.tsx @@ -6,30 +6,32 @@ import { ButtonVariant, Text, } from '../../../components/component-library'; -import { - getBridgeQuotes, - getRecommendedQuote, -} from '../../../ducks/bridge/selectors'; +import { getBridgeQuotes } from '../../../ducks/bridge/selectors'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { getQuoteDisplayData } from '../utils/quote'; +import { + formatFiatAmount, + formatTokenAmount, + formatEtaInMinutes, +} from '../utils/quote'; import { useCountdownTimer } from '../../../hooks/bridge/useCountdownTimer'; import MascotBackgroundAnimation from '../../swaps/mascot-background-animation/mascot-background-animation'; +import { getCurrentCurrency } from '../../../selectors'; +import { getNativeCurrency } from '../../../ducks/metamask/metamask'; import { QuoteInfoRow } from './quote-info-row'; import { BridgeQuotesModal } from './bridge-quotes-modal'; export const BridgeQuoteCard = () => { const t = useI18nContext(); - const recommendedQuote = useSelector(getRecommendedQuote); - const { isLoading, isQuoteGoingToRefresh } = useSelector(getBridgeQuotes); - - const { etaInMinutes, totalFees, quoteRate } = - getQuoteDisplayData(recommendedQuote); + const { isLoading, isQuoteGoingToRefresh, activeQuote } = + useSelector(getBridgeQuotes); + const currency = useSelector(getCurrentCurrency); + const ticker = useSelector(getNativeCurrency); const secondsUntilNextRefresh = useCountdownTimer(); const [showAllQuotes, setShowAllQuotes] = useState(false); - if (isLoading && !recommendedQuote) { + if (isLoading && !activeQuote) { return ( @@ -37,7 +39,7 @@ export const BridgeQuoteCard = () => { ); } - return etaInMinutes && totalFees && quoteRate ? ( + return activeQuote ? ( { - - + {activeQuote.swapRate && ( + + )} + {activeQuote.totalNetworkFee && ( + + )} diff --git a/ui/pages/bridge/quotes/bridge-quotes-modal.stories.tsx b/ui/pages/bridge/quotes/bridge-quotes-modal.stories.tsx new file mode 100644 index 000000000000..bbdf9b47fa47 --- /dev/null +++ b/ui/pages/bridge/quotes/bridge-quotes-modal.stories.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from '../../../store/store'; +import { BridgeQuotesModal } from './bridge-quotes-modal'; +import { createBridgeMockStore } from '../../../../test/jest/mock-store'; +import mockBridgeQuotesErc20Erc20 from '../../../../test/data/bridge/mock-quotes-erc20-erc20.json'; +import { SortOrder } from '../types'; + +const storybook = { + title: 'Pages/Bridge/BridgeQuotesModal', + component: BridgeQuotesModal, +}; + +export const NoTokenPricesAvailableStory = () => { + return {}} isOpen={true} />; +}; +NoTokenPricesAvailableStory.storyName = 'Token Prices Not Available'; +NoTokenPricesAvailableStory.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export const DefaultStory = () => { + return {}} isOpen={true} />; +}; +DefaultStory.storyName = 'Default'; +DefaultStory.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export const PositiveArbitrage = () => { + return {}} isOpen={true} />; +}; +PositiveArbitrage.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export default storybook; diff --git a/ui/pages/bridge/quotes/bridge-quotes-modal.tsx b/ui/pages/bridge/quotes/bridge-quotes-modal.tsx index 7e78e515af6e..0f4986aa18fc 100644 --- a/ui/pages/bridge/quotes/bridge-quotes-modal.tsx +++ b/ui/pages/bridge/quotes/bridge-quotes-modal.tsx @@ -1,11 +1,9 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { IconName } from '@metamask/snaps-sdk/jsx'; +import { useDispatch, useSelector } from 'react-redux'; +import { startCase } from 'lodash'; import { - Box, - Button, - ButtonVariant, - Icon, + ButtonLink, IconSize, Modal, ModalContent, @@ -14,51 +12,198 @@ import { Text, } from '../../../components/component-library'; import { + AlignItems, + BackgroundColor, TextAlign, + TextColor, TextVariant, } from '../../../helpers/constants/design-system'; -import { getBridgeQuotes } from '../../../ducks/bridge/selectors'; -import { getQuoteDisplayData } from '../utils/quote'; +import { + formatEtaInMinutes, + formatFiatAmount, + formatTokenAmount, +} from '../utils/quote'; import { useI18nContext } from '../../../hooks/useI18nContext'; +import { getCurrentCurrency } from '../../../selectors'; +import { setSelectedQuote, setSortOrder } from '../../../ducks/bridge/actions'; +import { SortOrder } from '../types'; +import { + getBridgeQuotes, + getBridgeSortOrder, +} from '../../../ducks/bridge/selectors'; +import { Column, Row } from '../layout'; +import { getNativeCurrency } from '../../../ducks/metamask/metamask'; export const BridgeQuotesModal = ({ onClose, ...modalProps }: Omit, 'children'>) => { - const { quotes } = useSelector(getBridgeQuotes); const t = useI18nContext(); + const dispatch = useDispatch(); + + const { sortedQuotes, activeQuote } = useSelector(getBridgeQuotes); + const sortOrder = useSelector(getBridgeSortOrder); + const currency = useSelector(getCurrentCurrency); + const nativeCurrency = useSelector(getNativeCurrency); return ( - - + + + {t('swapSelectAQuote')} - - {[t('bridgeOverallCost'), t('time')].map((label) => { - return ( - - ); - })} - - - {quotes.map((quote, index) => { - const { totalFees, etaInMinutes } = getQuoteDisplayData(quote); + {/* HEADERS */} + + {[ + [SortOrder.COST_ASC, t('bridgeNetCost'), IconName.Arrow2Up], + [SortOrder.ETA_ASC, t('time'), IconName.Arrow2Down], + ].map(([sortOrderOption, label, icon]) => ( + dispatch(setSortOrder(sortOrderOption))} + startIconName={ + sortOrder === sortOrderOption && sortOrder === SortOrder.ETA_ASC + ? icon + : undefined + } + startIconProps={{ + size: IconSize.Xs, + }} + endIconName={ + sortOrder === sortOrderOption && + sortOrder === SortOrder.COST_ASC + ? icon + : undefined + } + endIconProps={{ + size: IconSize.Xs, + }} + color={ + sortOrder === sortOrderOption + ? TextColor.primaryDefault + : TextColor.textAlternative + } + > + + {label} + + + ))} + + {/* QUOTE LIST */} + + {sortedQuotes.map((quote, index) => { + const { + totalNetworkFee, + estimatedProcessingTimeInSeconds, + toTokenAmount, + cost, + quote: { destAsset, bridges, requestId }, + } = quote; + const isQuoteActive = requestId === activeQuote?.quote.requestId; + return ( - - {totalFees?.fiat} - {t('bridgeTimingMinutes', [etaInMinutes])} - + { + dispatch(setSelectedQuote(quote)); + onClose(); + }} + paddingInline={4} + paddingTop={3} + paddingBottom={3} + style={{ position: 'relative', height: 78 }} + > + {isQuoteActive && ( + + )} + + + {cost.fiat && formatFiatAmount(cost.fiat, currency, 0)} + + {[ + totalNetworkFee?.fiat + ? t('quotedNetworkFee', [ + formatFiatAmount(totalNetworkFee.fiat, currency, 0), + ]) + : t('quotedNetworkFee', [ + formatTokenAmount( + totalNetworkFee.amount, + nativeCurrency, + ), + ]), + t( + sortOrder === SortOrder.ETA_ASC + ? 'quotedReceivingAmount' + : 'quotedReceiveAmount', + [ + formatFiatAmount(toTokenAmount.fiat, currency, 0) ?? + formatTokenAmount( + toTokenAmount.amount, + destAsset.symbol, + 0, + ), + ], + ), + ] + [sortOrder === SortOrder.ETA_ASC ? 'reverse' : 'slice']() + .map((content) => ( + + {content} + + ))} + + + + {t('bridgeTimingMinutes', [ + formatEtaInMinutes(estimatedProcessingTimeInSeconds), + ])} + + + {startCase(bridges[0])} + + + ); })} - + ); diff --git a/ui/pages/bridge/quotes/index.scss b/ui/pages/bridge/quotes/index.scss index 6d52c9e1e753..6407309220c2 100644 --- a/ui/pages/bridge/quotes/index.scss +++ b/ui/pages/bridge/quotes/index.scss @@ -63,21 +63,7 @@ } } -.quotes-modal { - &__column-header { - display: flex; - flex-direction: row; - justify-content: space-between; - } - - &__quotes { - display: flex; - flex-direction: column; - - &__row { - display: flex; - flex-direction: row; - justify-content: space-between; - } - } +.mm-modal-content__dialog { + display: flex; + height: 100%; } diff --git a/ui/pages/bridge/types.ts b/ui/pages/bridge/types.ts index a1ee163eca48..61143bb9de68 100644 --- a/ui/pages/bridge/types.ts +++ b/ui/pages/bridge/types.ts @@ -1,3 +1,27 @@ +import { BigNumber } from 'bignumber.js'; + +export type L1GasFees = { + l1GasFeesInHexWei?: string; // l1 fees for approval and trade in hex wei, appended by controller +}; + +// Values derived from the quote response +// fiat values are calculated based on the user's selected currency +export type QuoteMetadata = { + gasFee: { amount: BigNumber; fiat: BigNumber | null }; + totalNetworkFee: { amount: BigNumber; fiat: BigNumber | null }; // gasFees + relayerFees + toTokenAmount: { amount: BigNumber; fiat: BigNumber | null }; + adjustedReturn: { fiat: BigNumber | null }; // destTokenAmount - totalNetworkFee + sentAmount: { amount: BigNumber; fiat: BigNumber | null }; // srcTokenAmount + metabridgeFee + swapRate: BigNumber; // destTokenAmount / sentAmount + cost: { fiat: BigNumber | null }; // sentAmount - adjustedReturn +}; + +// Sort order set by the user +export enum SortOrder { + COST_ASC, + ETA_ASC, +} + // Types copied from Metabridge API export enum BridgeFlag { EXTENSION_CONFIG = 'extension-config', diff --git a/ui/pages/bridge/utils/quote.test.ts b/ui/pages/bridge/utils/quote.test.ts new file mode 100644 index 000000000000..eec342517f83 --- /dev/null +++ b/ui/pages/bridge/utils/quote.test.ts @@ -0,0 +1,309 @@ +import { BigNumber } from 'bignumber.js'; +import { zeroAddress } from 'ethereumjs-util'; +import { + calcAdjustedReturn, + calcSentAmount, + calcSwapRate, + calcToAmount, + calcTotalGasFee, + calcRelayerFee, + formatEtaInMinutes, +} from './quote'; + +const ERC20_TOKEN = { + decimals: 6, + address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85'.toLowerCase(), +}; +const NATIVE_TOKEN = { decimals: 18, address: zeroAddress() }; + +describe('Bridge quote utils', () => { + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + [ + 'native', + NATIVE_TOKEN, + '1009000000000000000', + 2521.73, + { amount: '1.009', fiat: '2544.42557' }, + ], + [ + 'erc20', + ERC20_TOKEN, + '2543140000', + 0.999781, + { amount: '2543.14', fiat: '2542.58305234' }, + ], + [ + 'erc20 with null exchange rates', + ERC20_TOKEN, + '2543140000', + null, + { amount: '2543.14', fiat: undefined }, + ], + ])( + 'calcToAmount: toToken is %s', + ( + _: string, + destAsset: { decimals: number; address: string }, + destTokenAmount: string, + toTokenExchangeRate: number, + { amount, fiat }: { amount: string; fiat: string }, + ) => { + const result = calcToAmount( + { + destAsset, + destTokenAmount, + } as never, + toTokenExchangeRate, + ); + expect(result.amount?.toString()).toStrictEqual(amount); + expect(result.fiat?.toString()).toStrictEqual(fiat); + }, + ); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + [ + 'native', + NATIVE_TOKEN, + '1009000000000000000', + 2515.02, + { + amount: '1.143217728', + fiat: '2875.21545027456', + }, + ], + [ + 'erc20', + ERC20_TOKEN, + '100000000', + 0.999781, + { amount: '100.512', fiat: '100.489987872' }, + ], + [ + 'erc20 with null exchange rates', + ERC20_TOKEN, + '2543140000', + null, + { amount: '2543.652', fiat: undefined }, + ], + ])( + 'calcSentAmount: fromToken is %s', + ( + _: string, + srcAsset: { decimals: number; address: string }, + srcTokenAmount: string, + fromTokenExchangeRate: number, + { amount, fiat }: { amount: string; fiat: string }, + ) => { + const result = calcSentAmount( + { + srcAsset, + srcTokenAmount, + feeData: { + metabridge: { + amount: Math.pow(8 * 10, srcAsset.decimals / 2), + }, + }, + } as never, + fromTokenExchangeRate, + ); + expect(result.amount?.toString()).toStrictEqual(amount); + expect(result.fiat?.toString()).toStrictEqual(fiat); + }, + ); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + [ + 'native', + NATIVE_TOKEN, + '1000000000000000000', + '0x0de0b6b3a7640000', + { amount: '2.2351800712e-7', fiat: '0.0005626887014840304' }, + undefined, + ], + [ + 'erc20', + ERC20_TOKEN, + '100000000', + '0x00', + { amount: '2.2351800712e-7', fiat: '0.0005626887014840304' }, + undefined, + ], + [ + 'erc20 with approval', + ERC20_TOKEN, + '100000000', + '0x00', + { amount: '4.4703601424e-7', fiat: '0.0011253774029680608' }, + 1092677, + ], + [ + 'erc20 with relayer fee', + ERC20_TOKEN, + '100000000', + '0x0de0b6b3a7640000', + { amount: '1.00000022351800712', fiat: '2517.4205626887014840304' }, + undefined, + ], + [ + 'native with relayer fee', + NATIVE_TOKEN, + '1000000000000000000', + '0x0de1b6b3a7640000', + { amount: '0.000281698494717776', fiat: '0.70915342457242365792' }, + undefined, + ], + ])( + 'calcTotalGasFee and calcRelayerFee: fromToken is %s', + ( + _: string, + srcAsset: { decimals: number; address: string }, + srcTokenAmount: string, + value: string, + { amount, fiat }: { amount: string; fiat: string }, + approvalGasLimit?: number, + ) => { + const feeData = { metabridge: { amount: 0 } }; + const quote = { + trade: { value, gasLimit: 1092677 }, + approval: approvalGasLimit ? { gasLimit: approvalGasLimit } : undefined, + quote: { srcAsset, srcTokenAmount, feeData }, + } as never; + const gasFee = calcTotalGasFee(quote, '0.00010456', '0.0001', 2517.42); + const relayerFee = calcRelayerFee(quote, 2517.42); + const result = { + amount: gasFee.amount.plus(relayerFee.amount), + fiat: gasFee.fiat?.plus(relayerFee.fiat || '0') ?? null, + }; + expect(result.amount?.toString()).toStrictEqual(amount); + expect(result.fiat?.toString()).toStrictEqual(fiat); + }, + ); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + [ + 'native', + NATIVE_TOKEN, + '1000000000000000000', + '0x0de0b6b3a7640000', + { amount: '0.000002832228395508', fiat: '0.00712990840741974936' }, + undefined, + ], + [ + 'erc20', + ERC20_TOKEN, + '100000000', + '0x00', + { amount: '0.000002832228395508', fiat: '0.00712990840741974936' }, + undefined, + ], + [ + 'erc20 with approval', + ERC20_TOKEN, + '100000000', + '0x00', + { amount: '0.000003055746402628', fiat: '0.00769259710890377976' }, + 1092677, + ], + [ + 'erc20 with relayer fee', + ERC20_TOKEN, + '100000000', + '0x0de0b6b3a7640000', + { amount: '1.000002832228395508', fiat: '2517.42712990840741974936' }, + undefined, + ], + [ + 'native with relayer fee', + NATIVE_TOKEN, + '1000000000000000000', + '0x0de1b6b3a7640000', + { amount: '0.000284307205106164', fiat: '0.71572064427835937688' }, + undefined, + ], + ])( + 'calcTotalGasFee and calcRelayerFee: fromToken is %s with l1GasFee', + ( + _: string, + srcAsset: { decimals: number; address: string }, + srcTokenAmount: string, + value: string, + { amount, fiat }: { amount: string; fiat: string }, + approvalGasLimit?: number, + ) => { + const feeData = { metabridge: { amount: 0 } }; + const quote = { + trade: { value, gasLimit: 1092677 }, + approval: approvalGasLimit ? { gasLimit: approvalGasLimit } : undefined, + quote: { srcAsset, srcTokenAmount, feeData }, + l1GasFeesInHexWei: '0x25F63418AA4', + } as never; + const gasFee = calcTotalGasFee(quote, '0.00010456', '0.0001', 2517.42); + const relayerFee = calcRelayerFee(quote, 2517.42); + const result = { + amount: gasFee.amount.plus(relayerFee.amount), + fiat: gasFee.fiat?.plus(relayerFee.fiat || '0') ?? null, + }; + expect(result.amount?.toString()).toStrictEqual(amount); + expect(result.fiat?.toString()).toStrictEqual(fiat); + }, + ); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + [ + 'available', + new BigNumber('100'), + new BigNumber('5'), + new BigNumber('95'), + ], + ['unavailable', null, null, null], + ])( + 'calcAdjustedReturn: fiat amounts are %s', + ( + _: string, + destTokenAmountInFiat: BigNumber, + totalNetworkFeeInFiat: BigNumber, + fiat: string, + ) => { + const result = calcAdjustedReturn( + destTokenAmountInFiat, + totalNetworkFeeInFiat, + ); + expect(result.fiat).toStrictEqual(fiat); + }, + ); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + ['< 1', new BigNumber('100'), new BigNumber('5'), new BigNumber('0.05')], + ['>= 1', new BigNumber('1'), new BigNumber('2000'), new BigNumber('2000')], + ['0', new BigNumber('1'), new BigNumber('0'), new BigNumber('0')], + ])( + 'calcSwapRate: %s rate', + ( + _: string, + sentAmount: BigNumber, + destTokenAmount: BigNumber, + rate: string, + ) => { + const result = calcSwapRate(sentAmount, destTokenAmount); + expect(result).toStrictEqual(rate); + }, + ); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + ['exact', 120, '2'], + ['rounded down', 2000, '33'], + ])( + 'formatEtaInMinutes: %s conversion', + (_: string, estimatedProcessingTimeInSeconds: number, minutes: string) => { + const result = formatEtaInMinutes(estimatedProcessingTimeInSeconds); + expect(result).toStrictEqual(minutes); + }, + ); +}); diff --git a/ui/pages/bridge/utils/quote.ts b/ui/pages/bridge/utils/quote.ts index b5945f64a9df..2fff7e9c1b18 100644 --- a/ui/pages/bridge/utils/quote.ts +++ b/ui/pages/bridge/utils/quote.ts @@ -1,5 +1,17 @@ +import { zeroAddress } from 'ethereumjs-util'; +import { BigNumber } from 'bignumber.js'; import { calcTokenAmount } from '../../../../shared/lib/transactions-controller-utils'; -import { QuoteResponse, QuoteRequest } from '../types'; +import { QuoteResponse, QuoteRequest, Quote, L1GasFees } from '../types'; +import { + hexToDecimal, + sumDecimals, +} from '../../../../shared/modules/conversion.utils'; +import { formatCurrency } from '../../../helpers/utils/confirm-tx.util'; +import { Numeric } from '../../../../shared/modules/Numeric'; +import { EtherDenomination } from '../../../../shared/constants/common'; +import { DEFAULT_PRECISION } from '../../../hooks/useCurrencyDisplay'; + +export const isNativeAddress = (address?: string) => address === zeroAddress(); export const isValidQuoteRequest = ( partialRequest: Partial, @@ -33,27 +45,150 @@ export const isValidQuoteRequest = ( ); }; -export const getQuoteDisplayData = (quoteResponse?: QuoteResponse) => { - const { quote, estimatedProcessingTimeInSeconds } = quoteResponse ?? {}; - if (!quoteResponse || !quote || !estimatedProcessingTimeInSeconds) { - return {}; - } +export const calcToAmount = ( + { destTokenAmount, destAsset }: Quote, + exchangeRate: number | null, +) => { + const normalizedDestAmount = calcTokenAmount( + destTokenAmount, + destAsset.decimals, + ); + return { + amount: normalizedDestAmount, + fiat: exchangeRate + ? normalizedDestAmount.mul(exchangeRate.toString()) + : null, + }; +}; - const etaInMinutes = (estimatedProcessingTimeInSeconds / 60).toFixed(); - const quoteRate = `1 ${quote.srcAsset.symbol} = ${calcTokenAmount( - quote.destTokenAmount, - quote.destAsset.decimals, - ) - .div(calcTokenAmount(quote.srcTokenAmount, quote.srcAsset.decimals)) - .toFixed(4) - .toString()} ${quote.destAsset.symbol}`; +export const calcSentAmount = ( + { srcTokenAmount, srcAsset, feeData }: Quote, + exchangeRate: number | null, +) => { + const normalizedSentAmount = calcTokenAmount( + new BigNumber(srcTokenAmount).plus(feeData.metabridge.amount), + srcAsset.decimals, + ); + return { + amount: normalizedSentAmount, + fiat: exchangeRate + ? normalizedSentAmount.mul(exchangeRate.toString()) + : null, + }; +}; +export const calcRelayerFee = ( + bridgeQuote: QuoteResponse, + nativeExchangeRate?: number, +) => { + const { + quote: { srcAsset, srcTokenAmount, feeData }, + trade, + } = bridgeQuote; + const relayerFeeInNative = calcTokenAmount( + new BigNumber(hexToDecimal(trade.value)).minus( + isNativeAddress(srcAsset.address) + ? new BigNumber(srcTokenAmount).plus(feeData.metabridge.amount) + : 0, + ), + 18, + ); return { - etaInMinutes, - totalFees: { - amount: '0.01 ETH', // TODO implement gas + relayer fee - fiat: '$0.01', - }, - quoteRate, + amount: relayerFeeInNative, + fiat: nativeExchangeRate + ? relayerFeeInNative.mul(nativeExchangeRate.toString()) + : null, }; }; + +export const calcTotalGasFee = ( + bridgeQuote: QuoteResponse & L1GasFees, + estimatedBaseFeeInDecGwei: string, + maxPriorityFeePerGasInDecGwei: string, + nativeExchangeRate?: number, +) => { + const { approval, trade, l1GasFeesInHexWei } = bridgeQuote; + const totalGasLimitInDec = sumDecimals( + trade.gasLimit?.toString() ?? '0', + approval?.gasLimit?.toString() ?? '0', + ); + const feePerGasInDecGwei = sumDecimals( + estimatedBaseFeeInDecGwei, + maxPriorityFeePerGasInDecGwei, + ); + + const l1GasFeesInDecGWei = Numeric.from( + l1GasFeesInHexWei ?? '0', + 16, + EtherDenomination.WEI, + ).toDenomination(EtherDenomination.GWEI); + + const gasFeesInDecGwei = totalGasLimitInDec + .times(feePerGasInDecGwei) + .add(l1GasFeesInDecGWei); + + const gasFeesInDecEth = new BigNumber( + gasFeesInDecGwei.shiftedBy(9).toString(), + ); + const gasFeesInUSD = nativeExchangeRate + ? gasFeesInDecEth.times(nativeExchangeRate.toString()) + : null; + + return { + amount: gasFeesInDecEth, + fiat: gasFeesInUSD, + }; +}; + +export const calcAdjustedReturn = ( + destTokenAmountInFiat: BigNumber | null, + totalNetworkFeeInFiat: BigNumber | null, +) => ({ + fiat: + destTokenAmountInFiat && totalNetworkFeeInFiat + ? destTokenAmountInFiat.minus(totalNetworkFeeInFiat) + : null, +}); + +export const calcSwapRate = ( + sentAmount: BigNumber, + destTokenAmount: BigNumber, +) => destTokenAmount.div(sentAmount); + +export const calcCost = ( + adjustedReturnInFiat: BigNumber | null, + sentAmountInFiat: BigNumber | null, +) => ({ + fiat: + adjustedReturnInFiat && sentAmountInFiat + ? sentAmountInFiat.minus(adjustedReturnInFiat) + : null, +}); + +export const formatEtaInMinutes = (estimatedProcessingTimeInSeconds: number) => + (estimatedProcessingTimeInSeconds / 60).toFixed(); + +export const formatTokenAmount = ( + amount: BigNumber, + symbol: string, + precision: number = 2, +) => `${amount.toFixed(precision)} ${symbol}`; + +export const formatFiatAmount = ( + amount: BigNumber | null, + currency: string, + precision: number = DEFAULT_PRECISION, +) => { + if (!amount) { + return undefined; + } + if (precision === 0) { + if (amount.lt(0.01)) { + return `<${formatCurrency('0', currency, precision)}`; + } + if (amount.lt(1)) { + return formatCurrency(amount.toString(), currency, 2); + } + } + return formatCurrency(amount.toString(), currency, precision); +}; From 23ec9475e46a29710c6969cc4a2c9f8d101d3fbd Mon Sep 17 00:00:00 2001 From: David Walsh Date: Fri, 22 Nov 2024 15:59:51 -0600 Subject: [PATCH 05/40] =?UTF-8?q?chore:=20PortfolioView=E2=84=A2:=20Design?= =?UTF-8?q?=20Review=20Cleanup:=20Networks,=20sort,=20&=20Menu=20(#28663)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Improves various design review aspects pointed out by @amandaye0h [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28663?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask 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. --- .../asset-list-control-bar.tsx | 23 +++++++++++----- .../asset-list-control-bar/index.scss | 7 +---- .../import-control/import-control.tsx | 1 + .../asset-list/network-filter/index.scss | 27 ------------------- .../network-filter/network-filter.tsx | 5 ++-- .../assets/asset-list/sort-control/index.scss | 7 ++++- .../asset-list/sort-control/sort-control.tsx | 16 ++++++++--- 7 files changed, 40 insertions(+), 46 deletions(-) delete mode 100644 ui/components/app/assets/asset-list/network-filter/index.scss diff --git a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx index dede9004d29e..8e6abb940d1c 100644 --- a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx +++ b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx @@ -6,7 +6,9 @@ import { Box, ButtonBase, ButtonBaseSize, + Icon, IconName, + IconSize, Popover, PopoverPosition, } from '../../../../component-library'; @@ -198,7 +200,8 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { className="asset-list-control-bar__button" onClick={toggleTokenSortPopover} size={ButtonBaseSize.Sm} - endIconName={IconName.SwapVertical} + startIconName={IconName.Filter} + startIconProps={{ marginInlineEnd: 0 }} backgroundColor={ isTokenSortPopoverOpen ? BackgroundColor.backgroundPressed @@ -221,13 +224,13 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { isOpen={isNetworkFilterPopoverOpen} position={PopoverPosition.BottomStart} referenceElement={popoverRef.current} - matchWidth={!isFullScreen} + matchWidth={false} style={{ zIndex: 10, display: 'flex', flexDirection: 'column', padding: 0, - minWidth: isFullScreen ? '325px' : '', + minWidth: isFullScreen ? '250px' : '', }} > @@ -237,13 +240,13 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { isOpen={isTokenSortPopoverOpen} position={PopoverPosition.BottomEnd} referenceElement={popoverRef.current} - matchWidth={!isFullScreen} + matchWidth={false} style={{ zIndex: 10, display: 'flex', flexDirection: 'column', padding: 0, - minWidth: isFullScreen ? '325px' : '', + minWidth: isFullScreen ? '250px' : '', }} > @@ -254,19 +257,25 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { isOpen={isImportTokensPopoverOpen} position={PopoverPosition.BottomEnd} referenceElement={popoverRef.current} - matchWidth={!isFullScreen} + matchWidth={false} style={{ zIndex: 10, display: 'flex', flexDirection: 'column', padding: 0, - minWidth: isFullScreen ? '325px' : '', + minWidth: isFullScreen ? '158px' : '', }} > + {t('importTokensCamelCase')} + {t('refreshList')} diff --git a/ui/components/app/assets/asset-list/asset-list-control-bar/index.scss b/ui/components/app/assets/asset-list/asset-list-control-bar/index.scss index b133586371c3..21cdbe2e83e1 100644 --- a/ui/components/app/assets/asset-list/asset-list-control-bar/index.scss +++ b/ui/components/app/assets/asset-list/asset-list-control-bar/index.scss @@ -8,12 +8,7 @@ min-width: auto; border-radius: 8px; padding: 0 8px !important; - gap: 5px; - text-transform: lowercase; - - span::first-letter { - text-transform: uppercase; - } + gap: 4px; } &__buttons { diff --git a/ui/components/app/assets/asset-list/import-control/import-control.tsx b/ui/components/app/assets/asset-list/import-control/import-control.tsx index d3a9bfd9ccb7..6ac4a6b1fce1 100644 --- a/ui/components/app/assets/asset-list/import-control/import-control.tsx +++ b/ui/components/app/assets/asset-list/import-control/import-control.tsx @@ -33,6 +33,7 @@ const AssetListControlBar = ({ disabled={!shouldShowTokensLinks} size={ButtonBaseSize.Sm} startIconName={IconName.MoreVertical} + startIconProps={{ marginInlineEnd: 0 }} backgroundColor={BackgroundColor.backgroundDefault} color={TextColor.textDefault} onClick={onClick} diff --git a/ui/components/app/assets/asset-list/network-filter/index.scss b/ui/components/app/assets/asset-list/network-filter/index.scss deleted file mode 100644 index 76e61c1025ae..000000000000 --- a/ui/components/app/assets/asset-list/network-filter/index.scss +++ /dev/null @@ -1,27 +0,0 @@ -.selectable-list-item-wrapper { - position: relative; -} - -.selectable-list-item { - cursor: pointer; - padding: 16px; - - &--selected { - background: var(--color-primary-muted); - } - - &:not(.selectable-list-item--selected) { - &:hover, - &:focus-within { - background: var(--color-background-default-hover); - } - } - - &__selected-indicator { - width: 4px; - height: calc(100% - 8px); - position: absolute; - top: 4px; - left: 4px; - } -} diff --git a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx index 4e9aa14eea25..d032712be9c1 100644 --- a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx +++ b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx @@ -15,6 +15,7 @@ import { SelectableListItem } from '../sort-control/sort-control'; import { Text } from '../../../../component-library/text/text'; import { AlignItems, + BlockSize, Display, JustifyContent, TextColor, @@ -108,6 +109,7 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => { { color={TextColor.textAlternative} data-testid="network-filter-all__total" > - {/* TODO: Should query cross chain account balance */} - { display={Display.Flex} justifyContent={JustifyContent.spaceBetween} alignItems={AlignItems.center} + width={BlockSize.Full} > { return ( - {children} - + {isSelected && ( Date: Sun, 24 Nov 2024 11:47:23 -0500 Subject: [PATCH 06/40] test: Adding unit test for setupPhishingCommunication and setUpCookieHandlerCommunication (#27736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding unit test for setupPhishingCommunication and setUpCookieHandlerCommunication. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27736?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/27119 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **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/scripts/metamask-controller.test.js | 124 ++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 0cd4fba34589..880df69aa00f 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -45,6 +45,7 @@ import { } from './lib/accounts/BalancesController'; import { BalancesTracker as MultichainBalancesTracker } from './lib/accounts/BalancesTracker'; import { deferredPromise } from './lib/util'; +import { METAMASK_COOKIE_HANDLER } from './constants/stream'; import MetaMaskController, { ONE_KEY_VIA_TREZOR_MINOR_VERSION, } from './metamask-controller'; @@ -1273,6 +1274,129 @@ describe('MetaMaskController', () => { expect(mockKeyring.destroy).toHaveBeenCalledTimes(1); }); }); + describe('#setupPhishingCommunication', () => { + beforeEach(() => { + jest.spyOn(metamaskController, 'safelistPhishingDomain'); + jest.spyOn(metamaskController, 'backToSafetyPhishingWarning'); + metamaskController.preferencesController.setUsePhishDetect(true); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('creates a phishing stream with safelistPhishingDomain and backToSafetyPhishingWarning handler', async () => { + const safelistPhishingDomainRequest = { + name: 'metamask-phishing-safelist', + data: { + id: 1, + method: 'safelistPhishingDomain', + params: ['mockHostname'], + }, + }; + const backToSafetyPhishingWarningRequest = { + name: 'metamask-phishing-safelist', + data: { id: 2, method: 'backToSafetyPhishingWarning', params: [] }, + }; + + const { promise, resolve } = deferredPromise(); + const { promise: promiseStream, resolve: resolveStream } = + deferredPromise(); + const streamTest = createThroughStream((chunk, _, cb) => { + if (chunk.name !== 'metamask-phishing-safelist') { + cb(); + return; + } + resolve(); + cb(null, chunk); + }); + + metamaskController.setupPhishingCommunication({ + connectionStream: streamTest, + }); + + streamTest.write(safelistPhishingDomainRequest, null, () => { + expect( + metamaskController.safelistPhishingDomain, + ).toHaveBeenCalledWith('mockHostname'); + }); + streamTest.write(backToSafetyPhishingWarningRequest, null, () => { + expect( + metamaskController.backToSafetyPhishingWarning, + ).toHaveBeenCalled(); + resolveStream(); + }); + + await promise; + streamTest.end(); + await promiseStream; + }); + }); + + describe('#setUpCookieHandlerCommunication', () => { + let localMetaMaskController; + beforeEach(() => { + localMetaMaskController = new MetaMaskController({ + showUserConfirmation: noop, + encryptor: mockEncryptor, + initState: { + ...cloneDeep(firstTimeState), + MetaMetricsController: { + metaMetricsId: 'MOCK_METRICS_ID', + participateInMetaMetrics: true, + dataCollectionForMarketing: true, + }, + }, + initLangCode: 'en_US', + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + isFirstMetaMaskControllerSetup: true, + }); + jest.spyOn(localMetaMaskController, 'getCookieFromMarketingPage'); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('creates a cookie handler communication stream with getCookieFromMarketingPage handler', async () => { + const attributionRequest = { + name: METAMASK_COOKIE_HANDLER, + data: { + id: 1, + method: 'getCookieFromMarketingPage', + params: [{ ga_client_id: 'XYZ.ABC' }], + }, + }; + + const { promise, resolve } = deferredPromise(); + const { promise: promiseStream, resolve: resolveStream } = + deferredPromise(); + const streamTest = createThroughStream((chunk, _, cb) => { + if (chunk.name !== METAMASK_COOKIE_HANDLER) { + cb(); + return; + } + resolve(); + cb(null, chunk); + }); + + localMetaMaskController.setUpCookieHandlerCommunication({ + connectionStream: streamTest, + }); + + streamTest.write(attributionRequest, null, () => { + expect( + localMetaMaskController.getCookieFromMarketingPage, + ).toHaveBeenCalledWith({ ga_client_id: 'XYZ.ABC' }); + resolveStream(); + }); + + await promise; + streamTest.end(); + await promiseStream; + }); + }); describe('#setupUntrustedCommunicationEip1193', () => { const mockTxParams = { from: TEST_ADDRESS }; From ebb492665bf4b2d09a102ce20a448952ff601df6 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 25 Nov 2024 16:43:30 +0530 Subject: [PATCH 07/40] fix: add alert when selected account is different from signing account in confirmation (#28562) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Show warning when signing account in confirmation is different from currently selected account in MM. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28015 ## **Manual testing steps** 1. Go to test dapp and connect with an account 2. Switch to a different account 3. Submit a confirmation and check warning next to signing account ## **Screenshots/Recordings** Signature Request: Screenshot 2024-11-20 at 6 12 02 PM Contract Interaction: Screenshot 2024-11-20 at 6 12 16 PM Send token: Screenshot 2024-11-20 at 6 47 29 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/_locales/en/messages.json | 6 + .../info/__snapshots__/info.test.tsx.snap | 375 +++++++++++++++ .../__snapshots__/approve.test.tsx.snap | 75 +++ .../approve-details/approve-details.tsx | 2 + .../approve/revoke-details/revoke-details.tsx | 2 + .../base-transaction-info.test.tsx.snap | 75 +++ .../__snapshots__/personal-sign.test.tsx.snap | 150 ++++++ .../info/personal-sign/personal-sign.tsx | 14 +- .../set-approval-for-all-info.test.tsx.snap | 75 +++ .../sign-in-with-row.test.tsx | 30 ++ .../sign-in-with-row/sign-in-with-row.tsx | 34 ++ .../transaction-details.test.tsx.snap | 75 +++ .../transaction-details.tsx | 2 + .../transaction-flow-section.test.tsx.snap | 166 ++++++- .../transaction-flow-section.test.tsx | 11 + .../transaction-flow-section.tsx | 48 +- .../__snapshots__/typed-sign-v1.test.tsx.snap | 75 +++ .../info/typed-sign-v1/typed-sign-v1.tsx | 2 + .../__snapshots__/typed-sign.test.tsx.snap | 375 +++++++++++++++ .../confirm/info/typed-sign/typed-sign.tsx | 2 + .../__snapshots__/confirm.test.tsx.snap | 450 ++++++++++++++++++ .../alerts/useSelectedAccountAlerts.test.ts | 79 +++ .../hooks/alerts/useSelectedAccountAlerts.ts | 41 ++ .../hooks/useConfirmationAlerts.ts | 5 + 24 files changed, 2123 insertions(+), 46 deletions(-) create mode 100644 ui/pages/confirmations/components/confirm/info/shared/sign-in-with-row/sign-in-with-row.test.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/shared/sign-in-with-row/sign-in-with-row.tsx create mode 100644 ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.test.ts create mode 100644 ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.ts diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2d603ddc5156..62c7b4542cf5 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -488,6 +488,9 @@ "alertReasonWrongAccount": { "message": "Wrong account" }, + "alertSelectedAccountWarning": { + "message": "This request is for a different account than the one selected in your wallet. To use another account, connect it to the site." + }, "alerts": { "message": "Alerts" }, @@ -4863,6 +4866,9 @@ "selectType": { "message": "Select Type" }, + "selectedAccountMismatch": { + "message": "Different account selected" + }, "selectingAllWillAllow": { "message": "Selecting all will allow this site to view all of your current accounts. Make sure you trust this site." }, diff --git a/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap index 86779c10cad6..6d48461f1c89 100644 --- a/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap @@ -50,6 +50,81 @@ exports[`Info renders info section for approve request 1`] = `

+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ Test Account +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ Test Account +

+
+
+
renders component for approve request 1`] = `

+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x2e0D7...5d09B +

+
+
+
+ {showAdvancedDetails && ( <> diff --git a/ui/pages/confirmations/components/confirm/info/approve/revoke-details/revoke-details.tsx b/ui/pages/confirmations/components/confirm/info/approve/revoke-details/revoke-details.tsx index 49bf5e7724e1..09b2e6a809f3 100644 --- a/ui/pages/confirmations/components/confirm/info/approve/revoke-details/revoke-details.tsx +++ b/ui/pages/confirmations/components/confirm/info/approve/revoke-details/revoke-details.tsx @@ -1,11 +1,13 @@ import React from 'react'; import { ConfirmInfoSection } from '../../../../../../../components/app/confirm/info/row/section'; import { OriginRow } from '../../shared/transaction-details/transaction-details'; +import { SigningInWithRow } from '../../shared/sign-in-with-row/sign-in-with-row'; export const RevokeDetails = () => { return ( + ); }; diff --git a/ui/pages/confirmations/components/confirm/info/base-transaction-info/__snapshots__/base-transaction-info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/base-transaction-info/__snapshots__/base-transaction-info.test.tsx.snap index d7054050f710..10506183561d 100644 --- a/ui/pages/confirmations/components/confirm/info/base-transaction-info/__snapshots__/base-transaction-info.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/base-transaction-info/__snapshots__/base-transaction-info.test.tsx.snap @@ -233,6 +233,81 @@ exports[` renders component for contract interaction requ
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x2e0D7...5d09B +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ Test Account +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ Test Account +

+
+
+
{ @@ -51,9 +51,7 @@ const PersonalSignInfo: React.FC = () => { return null; } - const { from } = currentConfirmation.msgParams; const isSIWE = isSIWESignatureRequest(currentConfirmation); - const chainId = currentConfirmation.chainId as string; const messageText = sanitizeString( hexToText(currentConfirmation.msgParams?.data), ); @@ -138,15 +136,7 @@ const PersonalSignInfo: React.FC = () => { > - {isSIWE && ( - - - - )} + {isSIWE ? ( diff --git a/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap index 42efa15a2a5b..5da63fd56680 100644 --- a/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/set-approval-for-all-info/__snapshots__/set-approval-for-all-info.test.tsx.snap @@ -154,6 +154,81 @@ exports[` renders component for approve request 1`] = `

+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x2e0D7...5d09B +

+
+
+
({ + useAlertMetrics: jest.fn(() => ({ + trackAlertMetrics: jest.fn(), + })), + }), +); + +describe('', () => { + const middleware = [thunk]; + + it('renders component for transaction details', () => { + const state = getMockContractInteractionConfirmState(); + const mockStore = configureMockStore(middleware)(state); + const { getByText } = renderWithConfirmContextProvider( + , + mockStore, + ); + expect(getByText('Signing in with')).toBeInTheDocument(); + expect(getByText('0x2e0D7...5d09B')).toBeInTheDocument(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/info/shared/sign-in-with-row/sign-in-with-row.tsx b/ui/pages/confirmations/components/confirm/info/shared/sign-in-with-row/sign-in-with-row.tsx new file mode 100644 index 000000000000..7b20cbc08062 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/shared/sign-in-with-row/sign-in-with-row.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { TransactionMeta } from '@metamask/transaction-controller'; + +import { ConfirmInfoRowAddress } from '../../../../../../../components/app/confirm/info/row'; +import { ConfirmInfoAlertRow } from '../../../../../../../components/app/confirm/info/row/alert-row/alert-row'; +import { RowAlertKey } from '../../../../../../../components/app/confirm/info/row/constants'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { useConfirmContext } from '../../../../../context/confirm'; +import { SignatureRequestType } from '../../../../../types/confirm'; + +export const SigningInWithRow = () => { + const t = useI18nContext(); + + const { currentConfirmation } = useConfirmContext(); + + const chainId = currentConfirmation?.chainId as string; + const from = + (currentConfirmation as TransactionMeta)?.txParams?.from ?? + (currentConfirmation as SignatureRequestType)?.msgParams?.from; + + if (!from) { + return null; + } + + return ( + + + + ); +}; diff --git a/ui/pages/confirmations/components/confirm/info/shared/transaction-details/__snapshots__/transaction-details.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/shared/transaction-details/__snapshots__/transaction-details.test.tsx.snap index f5183e21206c..9e9b9b23eca5 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/transaction-details/__snapshots__/transaction-details.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/shared/transaction-details/__snapshots__/transaction-details.test.tsx.snap @@ -151,6 +151,81 @@ exports[` renders component for transaction details 1`] =
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x2e0D7...5d09B +

+
+
+
`; diff --git a/ui/pages/confirmations/components/confirm/info/shared/transaction-details/transaction-details.tsx b/ui/pages/confirmations/components/confirm/info/shared/transaction-details/transaction-details.tsx index 89c2da783a4f..79cea5963c45 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/transaction-details/transaction-details.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/transaction-details/transaction-details.tsx @@ -20,6 +20,7 @@ import { ConfirmInfoRowCurrency } from '../../../../../../../components/app/conf import { PRIMARY } from '../../../../../../../helpers/constants/common'; import { useUserPreferencedCurrency } from '../../../../../../../hooks/useUserPreferencedCurrency'; import { HEX_ZERO } from '../constants'; +import { SigningInWithRow } from '../sign-in-with-row/sign-in-with-row'; export const OriginRow = () => { const t = useI18nContext(); @@ -156,6 +157,7 @@ export const TransactionDetails = () => { {showAdvancedDetails && } + diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap index 23cddb2b59b2..01614eec26cd 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap @@ -7,23 +7,85 @@ exports[` renders correctly 1`] = ` data-testid="confirmation__transaction-flow" >
- -

- 0x2e0D7...5d09B -

+

+ From +

+
+
+
+
+
+ +

+ 0x2e0D7...5d09B +

+
+
renders correctly 1`] = ` style="mask-image: url('./images/icons/arrow-right.svg');" />
+
+

+ To +

+
+
+
- -

- 0x6B175...71d0F -

+
+ +

+ 0x6B175...71d0F +

+
+
diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx index c23d3645abd3..bdc6ed30678a 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.test.tsx @@ -11,6 +11,17 @@ jest.mock('../hooks/useDecodedTransactionData', () => ({ useDecodedTransactionData: jest.fn(), })); +jest.mock( + '../../../../../../components/app/alert-system/contexts/alertMetricsContext.tsx', + () => ({ + useAlertMetrics: jest.fn(() => ({ + trackInlineAlertClicked: jest.fn(), + trackAlertRender: jest.fn(), + trackAlertActionClicked: jest.fn(), + })), + }), +); + describe('', () => { const useDecodedTransactionDataMock = jest.fn().mockImplementation(() => ({ pending: false, diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx index f5a9a46acbb2..d868611be4cc 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx @@ -1,11 +1,9 @@ -import { NameType } from '@metamask/name-controller'; import { TransactionMeta, TransactionType, } from '@metamask/transaction-controller'; import React from 'react'; import { ConfirmInfoSection } from '../../../../../../components/app/confirm/info/row/section'; -import Name from '../../../../../../components/app/name'; import { Box, Icon, @@ -19,10 +17,18 @@ import { IconColor, JustifyContent, } from '../../../../../../helpers/constants/design-system'; +import { + ConfirmInfoRow, + ConfirmInfoRowAddress, +} from '../../../../../../components/app/confirm/info/row'; +import { RowAlertKey } from '../../../../../../components/app/confirm/info/row/constants'; +import { ConfirmInfoAlertRow } from '../../../../../../components/app/confirm/info/row/alert-row/alert-row'; +import { useI18nContext } from '../../../../../../hooks/useI18nContext'; import { useConfirmContext } from '../../../../context/confirm'; import { useDecodedTransactionData } from '../hooks/useDecodedTransactionData'; export const TransactionFlowSection = () => { + const t = useI18nContext(); const { currentConfirmation: transactionMeta } = useConfirmContext(); @@ -50,24 +56,40 @@ export const TransactionFlowSection = () => { flexDirection={FlexDirection.Row} justifyContent={JustifyContent.spaceBetween} alignItems={AlignItems.center} - padding={3} > - + + + + + + {recipientAddress && ( - + + + + + )}
diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/__snapshots__/typed-sign-v1.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/__snapshots__/typed-sign-v1.test.tsx.snap index e80d317f574b..8713c5d303ca 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/__snapshots__/typed-sign-v1.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/__snapshots__/typed-sign-v1.test.tsx.snap @@ -47,6 +47,81 @@ exports[`TypedSignInfo correctly renders typed sign data request 1`] = `

+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
{ const t = useI18nContext(); @@ -42,6 +43,7 @@ const TypedSignV1Info: React.FC = () => { url={currentConfirmation.msgParams?.origin ?? ''} /> +
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ Test Account +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ Test Account +

+
+
+
{ @@ -81,6 +82,7 @@ const TypedSignInfo: React.FC = () => { /> )} +
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ Test Account +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ Test Account +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
+
+
+
+

+ Signing in with +

+
+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
{ + it('returns an empty array when there is no current confirmation', () => { + const { result } = renderHookWithConfirmContextProvider( + () => useSelectedAccountAlerts(), + mockState, + ); + expect(result.current).toEqual([]); + }); + + it('returns an alert for signature if signing account is different from selected account', () => { + const { result } = renderHookWithConfirmContextProvider( + () => useSelectedAccountAlerts(), + getMockPersonalSignConfirmStateForRequest({ + ...unapprovedPersonalSignMsg, + msgParams: { + ...unapprovedPersonalSignMsg.msgParams, + from: '0x0', + }, + } as SignatureRequestType), + ); + expect(result.current).toEqual(expectedAlert); + }); + + it('does not returns an alert for signature if signing account is same as selected account', () => { + const { result } = renderHookWithConfirmContextProvider( + () => useSelectedAccountAlerts(), + getMockPersonalSignConfirmStateForRequest( + unapprovedPersonalSignMsg as SignatureRequestType, + ), + ); + expect(result.current).toEqual([]); + }); + + it('returns an alert for transaction if signing account is different from selected account', () => { + const contractInteraction = genUnapprovedContractInteractionConfirmation({ + address: '0x0', + }); + const { result } = renderHookWithConfirmContextProvider( + () => useSelectedAccountAlerts(), + getMockConfirmStateForTransaction(contractInteraction as TransactionMeta), + ); + expect(result.current).toEqual(expectedAlert); + }); + + it('does not returns an alert for transaction if signing account is same as selected account', () => { + const contractInteraction = genUnapprovedContractInteractionConfirmation({ + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + }); + const { result } = renderHookWithConfirmContextProvider( + () => useSelectedAccountAlerts(), + getMockConfirmStateForTransaction(contractInteraction as TransactionMeta), + ); + expect(result.current).toEqual([]); + }); +}); diff --git a/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.ts b/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.ts new file mode 100644 index 000000000000..6e4be13b1ae5 --- /dev/null +++ b/ui/pages/confirmations/hooks/alerts/useSelectedAccountAlerts.ts @@ -0,0 +1,41 @@ +import { TransactionMeta } from '@metamask/transaction-controller'; +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; + +import { Alert } from '../../../../ducks/confirm-alerts/confirm-alerts'; +import { RowAlertKey } from '../../../../components/app/confirm/info/row/constants'; +import { Severity } from '../../../../helpers/constants/design-system'; +import { getSelectedAccount } from '../../../../selectors'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { SignatureRequestType } from '../../types/confirm'; +import { useConfirmContext } from '../../context/confirm'; + +export const useSelectedAccountAlerts = (): Alert[] => { + const t = useI18nContext(); + + const { currentConfirmation } = useConfirmContext(); + const selectedAccount = useSelector(getSelectedAccount); + + const fromAccount = + (currentConfirmation as SignatureRequestType)?.msgParams?.from ?? + (currentConfirmation as TransactionMeta)?.txParams?.from; + const confirmationAccountSameAsSelectedAccount = + !fromAccount || + fromAccount.toLowerCase() === selectedAccount?.address?.toLowerCase(); + + return useMemo((): Alert[] => { + if (confirmationAccountSameAsSelectedAccount) { + return []; + } + + return [ + { + key: 'selectedAccountWarning', + reason: t('selectedAccountMismatch'), + field: RowAlertKey.SigningInWith, + severity: Severity.Warning, + message: t('alertSelectedAccountWarning'), + }, + ]; + }, [confirmationAccountSameAsSelectedAccount, t]); +}; diff --git a/ui/pages/confirmations/hooks/useConfirmationAlerts.ts b/ui/pages/confirmations/hooks/useConfirmationAlerts.ts index c5f77f143cb6..efcb0beacf9e 100644 --- a/ui/pages/confirmations/hooks/useConfirmationAlerts.ts +++ b/ui/pages/confirmations/hooks/useConfirmationAlerts.ts @@ -16,6 +16,7 @@ import { useSigningOrSubmittingAlerts } from './alerts/transactions/useSigningOr ///: END:ONLY_INCLUDE_IF import useConfirmationOriginAlerts from './alerts/useConfirmationOriginAlerts'; import useBlockaidAlerts from './alerts/useBlockaidAlerts'; +import { useSelectedAccountAlerts } from './alerts/useSelectedAccountAlerts'; function useSignatureAlerts(): Alert[] { const accountMismatchAlerts = useAccountMismatchAlerts(); @@ -40,6 +41,7 @@ function useTransactionAlerts(): Alert[] { const signingOrSubmittingAlerts = useSigningOrSubmittingAlerts(); ///: END:ONLY_INCLUDE_IF const queuedConfirmationsAlerts = useQueuedConfirmationsAlerts(); + return useMemo( () => [ ...gasEstimateFailedAlerts, @@ -77,6 +79,7 @@ export default function useConfirmationAlerts(): Alert[] { const confirmationOriginAlerts = useConfirmationOriginAlerts(); const signatureAlerts = useSignatureAlerts(); const transactionAlerts = useTransactionAlerts(); + const selectedAccountAlerts = useSelectedAccountAlerts(); return useMemo( () => [ @@ -84,12 +87,14 @@ export default function useConfirmationAlerts(): Alert[] { ...confirmationOriginAlerts, ...signatureAlerts, ...transactionAlerts, + ...selectedAccountAlerts, ], [ blockaidAlerts, confirmationOriginAlerts, signatureAlerts, transactionAlerts, + selectedAccountAlerts, ], ); } From 67b2f5a1f3df8096ed10aa59fc61e3db3122d040 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 25 Nov 2024 16:45:08 +0530 Subject: [PATCH 08/40] feat: adding tooltip to signature decoding state changes (#28430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Add tooltip to state change labels. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3628 ## **Manual testing steps** 1. Enable signature decoding locally. 2. Check NFT bidding or listing permit 3. It should show appropriate tooltip ## **Screenshots/Recordings** Screenshot 2024-11-13 at 10 55 39 AM ## **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: MetaMask Bot --- app/_locales/en/messages.json | 6 ++ .../decoded-simulation.test.tsx | 59 ++++++++++++++++++- .../decoded-simulation/decoded-simulation.tsx | 44 +++++++++++++- .../permit-simulation/permit-simulation.tsx | 2 +- .../value-display/value-display.tsx | 18 ++---- 5 files changed, 110 insertions(+), 19 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 62c7b4542cf5..060ba2a43dca 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -5020,6 +5020,12 @@ "signatureRequestGuidance": { "message": "Only sign this message if you fully understand the content and trust the requesting site." }, + "signature_decoding_bid_nft_tooltip": { + "message": "The NFT will be reflected in your wallet, when the bid is accepted." + }, + "signature_decoding_list_nft_tooltip": { + "message": "Expect changes only if someone buys your NFTs." + }, "signed": { "message": "Signed" }, diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.test.tsx index 690cfb5b5195..86f30472b0e5 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.test.tsx @@ -3,12 +3,13 @@ import configureMockStore from 'redux-mock-store'; import { DecodingData, DecodingDataChangeType, + DecodingDataStateChanges, } from '@metamask/signature-controller'; import { getMockTypedSignConfirmStateForRequest } from '../../../../../../../../../test/data/confirmations/helper'; import { renderWithConfirmContextProvider } from '../../../../../../../../../test/lib/confirmations/render-helpers'; import { permitSignatureMsg } from '../../../../../../../../../test/data/confirmations/typed_sign'; -import PermitSimulation from './decoded-simulation'; +import PermitSimulation, { getStateChangeToolip } from './decoded-simulation'; const decodingData: DecodingData = { stateChanges: [ @@ -22,6 +23,42 @@ const decodingData: DecodingData = { ], }; +const decodingDataListing: DecodingDataStateChanges = [ + { + assetType: 'NATIVE', + changeType: DecodingDataChangeType.Receive, + address: '', + amount: '900000000000000000', + contractAddress: '', + }, + { + assetType: 'ERC721', + changeType: DecodingDataChangeType.Listing, + address: '', + amount: '', + contractAddress: '0xafd4896984CA60d2feF66136e57f958dCe9482d5', + tokenID: '2101', + }, +]; + +const decodingDataBidding: DecodingDataStateChanges = [ + { + assetType: 'ERC721', + changeType: DecodingDataChangeType.Receive, + address: '', + amount: '900000000000000000', + contractAddress: '', + }, + { + assetType: 'Native', + changeType: DecodingDataChangeType.Bidding, + address: '', + amount: '', + contractAddress: '0xafd4896984CA60d2feF66136e57f958dCe9482d5', + tokenID: '2101', + }, +]; + describe('DecodedSimulation', () => { it('renders component correctly', async () => { const state = getMockTypedSignConfirmStateForRequest({ @@ -38,4 +75,24 @@ describe('DecodedSimulation', () => { expect(container).toMatchSnapshot(); }); + + describe('getStateChangeToolip', () => { + it('return correct tooltip when permit is for listing NFT', async () => { + const tooltip = getStateChangeToolip( + decodingDataListing, + decodingDataListing?.[0], + (str: string) => str, + ); + expect(tooltip).toBe('signature_decoding_list_nft_tooltip'); + }); + }); + + it('return correct tooltip when permit is for bidding NFT', async () => { + const tooltip = getStateChangeToolip( + decodingDataBidding, + decodingDataBidding?.[0], + (str: string) => str, + ); + expect(tooltip).toBe('signature_decoding_bid_nft_tooltip'); + }); }); diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx index 3798776ca85d..cf774483ee6c 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/decoded-simulation/decoded-simulation.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { DecodingDataChangeType, DecodingDataStateChange, + DecodingDataStateChanges, } from '@metamask/signature-controller'; import { Hex } from '@metamask/utils'; @@ -11,8 +12,35 @@ import { useI18nContext } from '../../../../../../../../hooks/useI18nContext'; import { SignatureRequestType } from '../../../../../../types/confirm'; import { useConfirmContext } from '../../../../../../context/confirm'; import StaticSimulation from '../../../shared/static-simulation/static-simulation'; -import NativeValueDisplay from '../native-value-display/native-value-display'; import TokenValueDisplay from '../value-display/value-display'; +import NativeValueDisplay from '../native-value-display/native-value-display'; + +export const getStateChangeToolip = ( + stateChangeList: DecodingDataStateChanges | null, + stateChange: DecodingDataStateChange, + t: ReturnType, +): string | undefined => { + if (stateChange.changeType === DecodingDataChangeType.Receive) { + if ( + stateChangeList?.some( + (change) => + change.changeType === DecodingDataChangeType.Listing && + change.assetType === TokenStandard.ERC721, + ) + ) { + return t('signature_decoding_list_nft_tooltip'); + } + if ( + stateChange.assetType === TokenStandard.ERC721 && + stateChangeList?.some( + (change) => change.changeType === DecodingDataChangeType.Bidding, + ) + ) { + return t('signature_decoding_bid_nft_tooltip'); + } + } + return undefined; +}; const getStateChangeLabelMap = ( t: ReturnType, @@ -28,17 +56,23 @@ const getStateChangeLabelMap = ( }[changeType]); const StateChangeRow = ({ + stateChangeList, stateChange, chainId, }: { + stateChangeList: DecodingDataStateChanges | null; stateChange: DecodingDataStateChange; chainId: Hex; }) => { const t = useI18nContext(); const { assetType, changeType, amount, contractAddress, tokenID } = stateChange; + const tooltip = getStateChangeToolip(stateChangeList, stateChange, t); return ( - + {(assetType === TokenStandard.ERC20 || assetType === TokenStandard.ERC721) && ( = () => { const stateChangeFragment = (decodingData?.stateChanges ?? []).map( (change: DecodingDataStateChange) => ( - + ), ); 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 0b4d9eed22d4..86055425fa46 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 @@ -11,7 +11,7 @@ const PermitSimulation: React.FC = () => { if ( decodingData?.error || - (decodingData === undefined && decodingLoading !== true) + (decodingData?.stateChanges === undefined && decodingLoading !== true) ) { return ; } diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.tsx index 01f97625d404..2867522b42ab 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.tsx @@ -21,16 +21,15 @@ import { } from '../../../../../../../../components/component-library'; import Tooltip from '../../../../../../../../components/ui/tooltip'; import { - BackgroundColor, BlockSize, BorderRadius, Display, JustifyContent, TextAlign, - TextColor, } from '../../../../../../../../helpers/constants/design-system'; import Name from '../../../../../../../../components/app/name/name'; import { TokenDetailsERC20 } from '../../../../../../utils/token'; +import { getAmountColors } from '../../../utils'; type PermitSimulationValueDisplayParams = { /** ID of the associated chain. */ @@ -112,16 +111,7 @@ const PermitSimulationValueDisplay: React.FC< return null; } - let valueColor = TextColor.textDefault; - let valueBackgroundColor = BackgroundColor.backgroundAlternative; - - if (credit) { - valueColor = TextColor.successDefault; - valueBackgroundColor = BackgroundColor.successMuted; - } else if (debit) { - valueColor = TextColor.errorDefault; - valueBackgroundColor = BackgroundColor.errorMuted; - } + const { color, backgroundColor } = getAmountColors(credit, debit); return ( @@ -139,9 +129,9 @@ const PermitSimulationValueDisplay: React.FC< > Date: Mon, 25 Nov 2024 19:16:45 +0800 Subject: [PATCH 09/40] fix: display new network popup only for accounts that are compatible. (#28535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR updates the new network popup to only show up for accounts that supports it. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28535?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/accounts-planning/issues/660 ## **Manual testing steps** 1. On flask, enable BTC experimental feature and create an account. 2. Switch to an EVM account 3. Add a different RPC such as base 4. See 'You're now using` alert for the added network 5. tap `Got it` 6. Tap the account picker and select BTC account 7. See that the new network popup does not show. 8. tap BTC under tokens 9. See that the new network popup does not show. ## **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** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- shared/lib/multichain.ts | 26 +++++- ui/pages/routes/routes.component.js | 7 +- ui/pages/routes/routes.component.test.js | 109 ++++++++++++++++++++++- ui/pages/routes/routes.container.js | 4 +- 4 files changed, 141 insertions(+), 5 deletions(-) diff --git a/shared/lib/multichain.ts b/shared/lib/multichain.ts index 815d9d9e6763..26111a0970a2 100644 --- a/shared/lib/multichain.ts +++ b/shared/lib/multichain.ts @@ -1,6 +1,12 @@ -import { CaipNamespace, KnownCaipNamespace } from '@metamask/utils'; +import { + CaipNamespace, + isCaipChainId, + KnownCaipNamespace, + parseCaipChainId, +} from '@metamask/utils'; import { validate, Network } from 'bitcoin-address-validation'; import { isAddress } from '@solana/addresses'; +import { InternalAccount, isEvmAccountType } from '@metamask/keyring-api'; /** * Returns whether an address is on the Bitcoin mainnet. @@ -59,3 +65,21 @@ export function getCaipNamespaceFromAddress(address: string): CaipNamespace { // Defaults to "Ethereum" for all other cases for now. return KnownCaipNamespace.Eip155; } + +export function isCurrentChainCompatibleWithAccount( + chainId: string, + account: InternalAccount, +): boolean { + if (!chainId) { + return false; + } + + if (isCaipChainId(chainId)) { + const { namespace } = parseCaipChainId(chainId); + return namespace === getCaipNamespaceFromAddress(account.address); + } + + // For EVM accounts, we do not check the chain ID format, but we just expect it + // to be defined. + return isEvmAccountType(account.type); +} diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index edf1ff8bbe22..bce88a9f9236 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -114,6 +114,8 @@ import NetworkConfirmationPopover from '../../components/multichain/network-list import NftFullImage from '../../components/app/assets/nfts/nft-details/nft-full-image'; import CrossChainSwap from '../bridge'; import { ToastMaster } from '../../components/app/toast-master/toast-master'; +import { InternalAccountPropType } from '../../selectors/multichain'; +import { isCurrentChainCompatibleWithAccount } from '../../../shared/lib/multichain'; import { isCorrectDeveloperTransactionType, isCorrectSignatureApprovalType, @@ -130,6 +132,7 @@ export default class Routes extends Component { static propTypes = { currentCurrency: PropTypes.string, activeTabOrigin: PropTypes.string, + account: InternalAccountPropType, setCurrentCurrencyToUSD: PropTypes.func, isLoading: PropTypes.bool, loadingMessage: PropTypes.string, @@ -410,6 +413,7 @@ export default class Routes extends Component { isNetworkUsed, allAccountsOnNetworkAreEmpty, isTestNet, + account, currentChainId, shouldShowSeedPhraseReminder, isCurrentProviderCustom, @@ -455,7 +459,8 @@ export default class Routes extends Component { }); const shouldShowNetworkInfo = isUnlocked && - currentChainId && + account && + isCurrentChainCompatibleWithAccount(currentChainId, account) && !isTestNet && !isSendRoute && !isNetworkUsed && diff --git a/ui/pages/routes/routes.component.test.js b/ui/pages/routes/routes.component.test.js index 1b1823728a2f..4318310c6ef5 100644 --- a/ui/pages/routes/routes.component.test.js +++ b/ui/pages/routes/routes.component.test.js @@ -1,6 +1,6 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; -import { act } from '@testing-library/react'; +import { act, waitFor } from '@testing-library/react'; import thunk from 'redux-thunk'; import { BtcAccountType } from '@metamask/keyring-api'; import { SEND_STAGES } from '../../ducks/send'; @@ -15,6 +15,10 @@ import { useIsOriginalNativeTokenSymbol } from '../../hooks/useIsOriginalNativeT import { createMockInternalAccount } from '../../../test/jest/mocks'; import { CHAIN_IDS } from '../../../shared/constants/network'; import { mockNetworkState } from '../../../test/stub/networks'; +import { + MOCK_ACCOUNT_BIP122_P2WPKH, + MOCK_ACCOUNT_EOA, +} from '../../../test/data/mock-accounts'; import useMultiPolling from '../../hooks/useMultiPolling'; import Routes from '.'; @@ -22,6 +26,7 @@ const middlewares = [thunk]; const mockShowNetworkDropdown = jest.fn(); const mockHideNetworkDropdown = jest.fn(); +const mockFetchWithCache = jest.fn(); jest.mock('webextension-polyfill', () => ({ runtime: { @@ -34,6 +39,7 @@ jest.mock('webextension-polyfill', () => ({ })); jest.mock('../../store/actions', () => ({ + ...jest.requireActual('../../store/actions'), getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()), gasFeeStartPollingByNetworkClientId: jest .fn() @@ -92,6 +98,11 @@ jest.mock( '../../components/app/metamask-template-renderer/safe-component-list', ); +jest.mock( + '../../../shared/lib/fetch-with-cache', + () => () => mockFetchWithCache, +); + jest.mock('../../hooks/useMultiPolling', () => ({ __esModule: true, default: jest.fn(), @@ -180,6 +191,102 @@ describe('Routes Component', () => { expect(getByTestId('account-menu-icon')).not.toBeDisabled(); }); }); + + describe('new network popup', () => { + const mockBtcAccount = MOCK_ACCOUNT_BIP122_P2WPKH; + const mockEvmAccount = MOCK_ACCOUNT_EOA; + + const mockNewlyAddedNetwork = { + chainId: CHAIN_IDS.BASE, + name: 'Base', + nativeCurrency: 'ETH', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + type: 'custom', + url: 'https://base.com', + networkClientId: CHAIN_IDS.BASE, + }, + ], + }; + + const renderPopup = async (account) => { + // This popup does not show up for tests, so we have to disable this: + process.env.IN_TEST = ''; + const state = { + ...mockSendState, + metamask: { + ...mockState.metamask, + completedOnboarding: true, + selectedNetworkClientId: mockNewlyAddedNetwork.chainId, + internalAccounts: { + accounts: { + [account.id]: account, + }, + selectedAccount: account.id, + }, + usedNetworks: { + '0x1': true, + '0x5': true, + '0x539': true, + [mockNewlyAddedNetwork.chainId]: false, + }, + networkConfigurationsByChainId: { + ...mockState.metamask.networkConfigurationsByChainId, + [mockNewlyAddedNetwork.chainId]: mockNewlyAddedNetwork, + }, + networksMetadata: { + ...mockState.metamask.networksMetadata, + [mockNewlyAddedNetwork.chainId]: { + EIPS: { + 1559: true, + }, + status: 'available', + }, + }, + tokens: [], + swapsState: { swapsFeatureIsLive: false }, + announcements: {}, + pendingApprovals: {}, + termsOfUseLastAgreed: new Date('2999-03-25'), + shouldShowSeedPhraseReminder: false, + useExternalServices: true, + }, + send: { + ...mockSendState.send, + stage: SEND_STAGES.INACTIVE, + currentTransactionUUID: null, + draftTransactions: {}, + }, + appState: { + ...mockSendState.appState, + showWhatsNewPopup: false, + onboardedInThisUISession: false, + }, + }; + return await render(['/'], state); + }; + + it('displays new EVM network popup for EVM accounts', async () => { + const { getAllByText, getByTestId } = await renderPopup(mockEvmAccount); + + const networkInfo = getByTestId('new-network-info__bullet-paragraph'); + + await waitFor(() => { + expect(getAllByText(mockNewlyAddedNetwork.name).length).toBeGreaterThan( + 0, + ); + expect(networkInfo).toBeInTheDocument(); + }); + }); + + it('does not display new EVM network popup for non-EVM accounts', async () => { + const { queryByTestId } = await renderPopup(mockBtcAccount); + + const networkInfo = queryByTestId('new-network-info__bullet-paragraph'); + expect(networkInfo).not.toBeInTheDocument(); + }); + }); }); describe('toast display', () => { diff --git a/ui/pages/routes/routes.container.js b/ui/pages/routes/routes.container.js index bb02e0ebaaa9..c155be4ba488 100644 --- a/ui/pages/routes/routes.container.js +++ b/ui/pages/routes/routes.container.js @@ -15,12 +15,12 @@ import { getUnapprovedConfirmations, ///: END:ONLY_INCLUDE_IF getShowExtensionInFullSizeView, - getSelectedAccount, getSwitchedNetworkDetails, getNetworkToAutomaticallySwitchTo, getNumberOfAllUnapprovedTransactionsAndMessages, getUseRequestQueue, getCurrentNetwork, + getSelectedInternalAccount, oldestPendingConfirmationSelector, getUnapprovedTransactions, getPendingApprovals, @@ -64,7 +64,7 @@ function mapStateToProps(state) { // If there is more than one connected account to activeTabOrigin, // *BUT* the current account is not one of them, show the banner - const account = getSelectedAccount(state); + const account = getSelectedInternalAccount(state); const activeTabOrigin = activeTab?.origin; const currentNetwork = getCurrentNetwork(state); From 9ae485349f7a652d60b9e2cae4c92c8906f95f4b Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:18:28 +0100 Subject: [PATCH 10/40] test: rename balance functions to cover both Ganache and Anvil in preparation for ganache migration (#28676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The `loginWithBalanceValidation` and `check_ganacheBalance` functions will be updated when the Anvil migration happens, so those will accept both a ganacheServer param or an anvilServer param. In order to make the main migration PR smaller, in this PR those functions/params are renamed to be more generic. In the main PR, those functions will accept the Anvil class too, but this PR is solely to update names and specs using those functions, so those specs won't need to be updated in the main PR, making it smaller. See how these functions will be used in anvil [here](https://github.com/MetaMask/metamask-extension/pull/27246/files#diff-af94879a75170f2f9e28710b48669b2ddfa353d7220fd725df3d44574f0eaff9L32) and [here](https://github.com/MetaMask/metamask-extension/pull/27246/files#diff-907f87ff9f4852f2c65855968d6f7a5028f728eaeb370239cdabc6a57c139342R288). Note: the e2e quality gate can be skipped here, as there is no functional change, just a rename of functions. This will help to spare some ci credits. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28676?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3680 ## **Manual testing steps** 1. Check ci. All tests should continue to pass normally ## **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** - [ ] 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/flows/login.flow.ts | 10 ++++++---- test/e2e/page-objects/pages/homepage.ts | 10 ++++++---- test/e2e/tests/account/add-account.spec.ts | 6 +++--- test/e2e/tests/network/multi-rpc.spec.ts | 2 +- test/e2e/tests/network/switch-network.spec.ts | 8 ++++---- test/e2e/tests/onboarding/onboarding.spec.ts | 2 +- test/e2e/tests/tokens/nft/import-nft.spec.ts | 2 +- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/test/e2e/page-objects/flows/login.flow.ts b/test/e2e/page-objects/flows/login.flow.ts index fcd0bcb22d8a..f5ed61946ce8 100644 --- a/test/e2e/page-objects/flows/login.flow.ts +++ b/test/e2e/page-objects/flows/login.flow.ts @@ -24,12 +24,12 @@ export const loginWithoutBalanceValidation = async ( * 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 ganacheServer - The ganache server instance + * @param localBlockchainServer - The local blockchain server instance * @param password - The password used to unlock the wallet. */ export const loginWithBalanceValidation = async ( driver: Driver, - ganacheServer?: Ganache, + localBlockchainServer?: Ganache, password?: string, ) => { await loginWithoutBalanceValidation(driver, password); @@ -38,8 +38,10 @@ export const loginWithBalanceValidation = async ( await homePage.check_pageIsLoaded(); // Verify the expected balance on the homepage - if (ganacheServer) { - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + if (localBlockchainServer) { + await homePage.check_localBlockchainBalanceIsDisplayed( + localBlockchainServer, + ); } else { await homePage.check_expectedBalanceIsDisplayed(); } diff --git a/test/e2e/page-objects/pages/homepage.ts b/test/e2e/page-objects/pages/homepage.ts index 6a8a916d4349..c5c4d5369d4e 100644 --- a/test/e2e/page-objects/pages/homepage.ts +++ b/test/e2e/page-objects/pages/homepage.ts @@ -283,13 +283,15 @@ class HomePage { ); } - async check_ganacheBalanceIsDisplayed( - ganacheServer?: Ganache, + async check_localBlockchainBalanceIsDisplayed( + localBlockchainServer?: Ganache, address = null, ): Promise { let expectedBalance: string; - if (ganacheServer) { - expectedBalance = (await ganacheServer.getBalance(address)).toString(); + if (localBlockchainServer) { + expectedBalance = ( + await localBlockchainServer.getBalance(address) + ).toString(); } else { expectedBalance = '0'; } diff --git a/test/e2e/tests/account/add-account.spec.ts b/test/e2e/tests/account/add-account.spec.ts index 3fa65b14e87d..76060611198b 100644 --- a/test/e2e/tests/account/add-account.spec.ts +++ b/test/e2e/tests/account/add-account.spec.ts @@ -26,7 +26,7 @@ describe('Add account', function () { await completeImportSRPOnboardingFlow({ driver }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); const headerNavbar = new HeaderNavbar(driver); await headerNavbar.openAccountMenu(); @@ -43,7 +43,7 @@ describe('Add account', function () { await accountListPage.check_accountDisplayedInAccountList('Account 1'); await accountListPage.switchToAccount('Account 1'); await headerNavbar.check_accountLabel('Account 1'); - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); await sendTransactionToAccount({ driver, recipientAccount: 'Account 2', @@ -64,7 +64,7 @@ describe('Add account', function () { // Check wallet balance for both accounts await homePage.check_pageIsLoaded(); - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); await accountListPage.check_accountDisplayedInAccountList('Account 2'); diff --git a/test/e2e/tests/network/multi-rpc.spec.ts b/test/e2e/tests/network/multi-rpc.spec.ts index ac693361435d..f5c40259a33b 100644 --- a/test/e2e/tests/network/multi-rpc.spec.ts +++ b/test/e2e/tests/network/multi-rpc.spec.ts @@ -83,7 +83,7 @@ describe('MultiRpc:', function (this: Suite) { await completeImportSRPOnboardingFlow({ driver }); const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); await new HeaderNavbar(driver).clickSwitchNetworkDropDown(); const selectNetworkDialog = new SelectNetwork(driver); diff --git a/test/e2e/tests/network/switch-network.spec.ts b/test/e2e/tests/network/switch-network.spec.ts index a45e634dbbec..b320e095cc69 100644 --- a/test/e2e/tests/network/switch-network.spec.ts +++ b/test/e2e/tests/network/switch-network.spec.ts @@ -36,19 +36,19 @@ describe('Switch network - ', function (this: Suite) { // Validate the switch network functionality to Ethereum Mainnet await switchToNetworkFlow(driver, 'Ethereum Mainnet'); - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); // Validate the switch network functionality to test network await switchToNetworkFlow(driver, 'Localhost 8545', true); - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); // Add Arbitrum network and perform the switch network functionality await searchAndSwitchToNetworkFlow(driver, 'Arbitrum One'); - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); // Validate the switch network functionality back to Ethereum Mainnet await switchToNetworkFlow(driver, 'Ethereum Mainnet'); - await homePage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); }, ); }); diff --git a/test/e2e/tests/onboarding/onboarding.spec.ts b/test/e2e/tests/onboarding/onboarding.spec.ts index 9409ef7e351c..979e416f5090 100644 --- a/test/e2e/tests/onboarding/onboarding.spec.ts +++ b/test/e2e/tests/onboarding/onboarding.spec.ts @@ -198,7 +198,7 @@ describe('MetaMask onboarding @no-mmi', function () { // Check the correct balance for the custom network is displayed if (secondaryGanacheServer && Array.isArray(secondaryGanacheServer)) { - await homePage.check_ganacheBalanceIsDisplayed( + await homePage.check_localBlockchainBalanceIsDisplayed( secondaryGanacheServer[0], ); } else { diff --git a/test/e2e/tests/tokens/nft/import-nft.spec.ts b/test/e2e/tests/tokens/nft/import-nft.spec.ts index 73049a8c9d0d..808bc26ac6e6 100644 --- a/test/e2e/tests/tokens/nft/import-nft.spec.ts +++ b/test/e2e/tests/tokens/nft/import-nft.spec.ts @@ -75,7 +75,7 @@ describe('Import NFT', function () { await accountListPage.check_accountDisplayedInAccountList('Account 1'); await accountListPage.switchToAccount('Account 1'); await headerNavbar.check_accountLabel('Account 1'); - await homepage.check_ganacheBalanceIsDisplayed(ganacheServer); + await homepage.check_localBlockchainBalanceIsDisplayed(ganacheServer); await homepage.check_nftNameIsDisplayed('TestDappNFTs'); await homepage.check_nftImageIsDisplayed(); }, From 2c9f8c2b6c9fccf1557ab684b7301d6bc4c69403 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Mon, 25 Nov 2024 13:24:45 +0000 Subject: [PATCH 11/40] fix: Reduce max pet name length (#28660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Following up on https://github.com/MetaMask/metamask-extension/pull/28560, reduce truncation length to 12 characters instead of 15 (see screenshots). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28660?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3630 Related: https://github.com/MetaMask/metamask-extension/pull/28560 ## **Manual testing steps** 1. Trigger a new confirmation 2. Add a long petname, by clicking an address and writing it in the input field 3. The name should be truncated with an ellipsis. ## **Screenshots/Recordings** ### **Before** Max 15 chars limit 15 ### **After** Max 12 chars limit 12 ## **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. --- ui/components/app/name/__snapshots__/name.test.tsx.snap | 2 +- .../name-details/__snapshots__/name-details.test.tsx.snap | 2 +- ui/components/app/name/name.tsx | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/components/app/name/__snapshots__/name.test.tsx.snap b/ui/components/app/name/__snapshots__/name.test.tsx.snap index 7e3f98c8576e..286429760c1e 100644 --- a/ui/components/app/name/__snapshots__/name.test.tsx.snap +++ b/ui/components/app/name/__snapshots__/name.test.tsx.snap @@ -79,7 +79,7 @@ exports[`Name renders address with long saved name 1`] = `

- Very long and l... + Very long an...

diff --git a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap index da6f598dfa5e..3520a1b64a13 100644 --- a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap +++ b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap @@ -753,7 +753,7 @@ exports[`NameDetails renders with recognized name 1`] = `

- iZUMi Bond USD + iZUMi Bond U...

diff --git a/ui/components/app/name/name.tsx b/ui/components/app/name/name.tsx index 75f2a2f79c15..4871cc481f0c 100644 --- a/ui/components/app/name/name.tsx +++ b/ui/components/app/name/name.tsx @@ -103,9 +103,10 @@ const Name = memo( }, [setModalOpen]); const formattedValue = formatValue(value, type); + const MAX_PET_NAME_LENGTH = 12; const formattedName = shortenString(name || undefined, { - truncatedCharLimit: 15, - truncatedStartChars: 15, + truncatedCharLimit: MAX_PET_NAME_LENGTH, + truncatedStartChars: MAX_PET_NAME_LENGTH, truncatedEndChars: 0, skipCharacterInEnd: true, }); From b7fa3fa7539c43409a34f209d9347e553748ed16 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Mon, 25 Nov 2024 13:25:01 +0000 Subject: [PATCH 12/40] fix: Add default value to custom nonce modal (#28659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** See original bug ticket. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28659?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28033 ## **Manual testing steps** See original bug ticket. ## **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__/customize-nonce.test.js.snap | 2 +- .../modals/customize-nonce/customize-nonce.component.js | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap b/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap index 020adaa0c952..4698963a26ec 100644 --- a/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap +++ b/ui/components/app/modals/customize-nonce/__snapshots__/customize-nonce.test.js.snap @@ -86,7 +86,7 @@ exports[`Customize Nonce should match snapshot 1`] = ` min="0" placeholder="1" type="number" - value="" + value="1" /> diff --git a/ui/components/app/modals/customize-nonce/customize-nonce.component.js b/ui/components/app/modals/customize-nonce/customize-nonce.component.js index 1d87cbd549b1..b6b2f82a096e 100644 --- a/ui/components/app/modals/customize-nonce/customize-nonce.component.js +++ b/ui/components/app/modals/customize-nonce/customize-nonce.component.js @@ -27,7 +27,9 @@ const CustomizeNonce = ({ updateCustomNonce, getNextNonce, }) => { - const [customNonce, setCustomNonce] = useState(''); + const defaultNonce = + customNonceValue || (typeof nextNonce === 'number' && nextNonce.toString()); + const [customNonce, setCustomNonce] = useState(defaultNonce); const t = useI18nContext(); return ( @@ -107,10 +109,7 @@ const CustomizeNonce = ({ type="number" data-testid="custom-nonce-input" min="0" - placeholder={ - customNonceValue || - (typeof nextNonce === 'number' && nextNonce.toString()) - } + placeholder={defaultNonce} onChange={(e) => { setCustomNonce(e.target.value); }} From 592a628b2bd99558d6f86b7f4d80af3701deec41 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:43:53 +0100 Subject: [PATCH 13/40] fix: add missing filter for scheduled job rerun-from-failed (#28644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** After [the rerun from failed PR ](https://github.com/MetaMask/metamask-extension/pull/28143) was merged there was one remaining filter to tweak, related to the scheduled trigger that will be done in the UI: - In the Circle ci UI panel, there is no way to tell to trigger a specific workflow, rather all the config file will be checked, which means both the test_and_release and the rerun_from_failed workflows would run. For that, we need to add a filter in the `test_and_release` flow, so that is not run when we schedule the automatic runs using the name rerun-from-failed. Unfortunately there is no way to do this from the UI, so we need this filter in the config file - I saw, that the rerun-failed workflow was run once the PR was merged, which shouldn't be the case. I've tweaked that following this example in circle [ci docs](https://circleci.com/docs/schedule-pipelines-with-multiple-workflows/#schedule-using-built-in-pipeline-values)) ![Screenshot from 2024-11-22 11-06-07](https://github.com/user-attachments/assets/ef82e9e0-4da9-42e6-bb24-6fb97b55034e) Why this was missed: on the testing for the previous PR, I removed ALL the filters, to make the rerun-from-failed workflow run and check that it works (it worked). However, I didn't test leaving the filter of the `[rerun-from-failed, << pipeline.schedule.name >>]` and checking that it is not run [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28644?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** How to test this: instead of removing ALL the filters (like it was done for testing the previous PR), just remove the filter for develop. This way, we can see if the job rerun-from-failed is run --> it shouldn't be, as it's not a scheduled run with that name. It can be checked [here](https://app.circleci.com/pipelines/github/MetaMask/metamask-extension?branch=rerun-failed-missing-filter): see only the test and release is run ![Screenshot from 2024-11-22 11-12-15](https://github.com/user-attachments/assets/d33761b2-beec-4d01-914a-c559815578d5) For testing the scheduled run, it needs to be done in the UI once this PR is merged, but if the filter has been proven to work here, it should then also work there, for when it needs to be run ## **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** - [ ] 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 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1b4242570daf..0817f18c36a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -103,9 +103,11 @@ workflows: test_and_release: when: not: - matches: - pattern: /^l10n_crowdin_action$/ - value: << pipeline.git.branch >> + or: + - matches: + pattern: /^l10n_crowdin_action$/ + value: << pipeline.git.branch >> + - equal: [rerun-from-failed, << pipeline.schedule.name >>] jobs: - create_release_pull_request: <<: *rc_branch_only @@ -358,8 +360,7 @@ workflows: rerun-from-failed: when: - condition: - equal: ["<< pipeline.schedule.name >>", "rerun-from-failed"] + equal: [rerun-from-failed, << pipeline.schedule.name >>] jobs: - prep-deps - rerun-workflows-from-failed: From bf1455bec13d6a910f9528a000c026d375e5b13e Mon Sep 17 00:00:00 2001 From: Howard Braham Date: Mon, 25 Nov 2024 21:03:18 +0700 Subject: [PATCH 14/40] perf: add React.lazy to the Routes (#28172) ## **Description** > **Note:** I would really appreciate quick reviews here mostly based on "did I break anything." It's not really worth our time to nit-pick about the style or the details because every line of this is going to be changed by me again in the near future. Still to do in later PRs that are going to completely re-write what I did here: > - Convert routes.component.js to TypeScript > - Convert routes.component.js to Hooks > - Convert routes.component.js to react-router v6 > - Eliminate routes.container.js > > All this has to be done in sequential PRs based on top of this one, so while I wait for reviews I can only work on a non-Routes topic. This is the first PR to add `React.lazy` to the codebase. The meat of the changes are in `ui/pages/routes/routes.component.js`. Every other file changed is just a reaction to those changes. Much of this is caused by the fact that `React.lazy` only works on default exports ([see the documentation here](https://react.dev/reference/react/lazy)), so I had to change a few named exports to default exports. I don't think this PR has much of an impact on loading times, but it sets up further work that should have an impact. There was one component (`ConfirmTransaction`) that needed some extra attention to work with `React.lazy`. I had to remove the `history.replace(mostRecentOverviewPage);`, which I'm pretty sure was an unnecessary statement, and remove the Unit Test related to it. **UPDATE:** I changed all of the Integration Tests to convert `getBy...` to `await findBy...` to wait for the `React.lazy` component to load. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28172?quickstart=1) ## **Related issues** Progresses: MetaMask/MetaMask-planning#3302 --- app/scripts/lib/manifestFlags.ts | 9 + builds.yml | 3 + jest.integration.config.js | 3 +- shared/lib/trace.ts | 1 + .../confirmations/signatures/permit.test.tsx | 36 +- .../signatures/personalSign.test.tsx | 33 +- .../transactions/alerts.test.tsx | 14 +- .../transactions/contract-deployment.test.tsx | 79 +++-- .../contract-interaction.test.tsx | 112 ++++--- .../transactions/erc20-approve.test.tsx | 50 +-- .../transactions/erc721-approve.test.tsx | 46 ++- .../transactions/increase-allowance.test.tsx | 50 +-- .../set-approval-for-all.test.tsx | 48 ++- .../notifications-activation.test.tsx | 6 +- .../notifications-list.test.tsx | 60 ++-- .../notifications-toggle.test.tsx | 12 +- .../onboarding/wallet-created.test.tsx | 17 +- ui/components/multichain/pages/index.js | 3 - .../pages/review-permissions-page/index.js | 2 - .../review-permissions-page.stories.tsx | 2 +- .../review-permissions-page.test.tsx | 2 +- .../review-permissions-page.tsx | 2 +- ui/helpers/utils/mm-lazy.ts | 86 +++++ .../confirm-transaction.component.js | 4 +- .../confirm-transaction.test.js | 21 -- .../connect-page/connect-page.tsx | 2 +- ui/pages/routes/routes.component.js | 315 ++++++++++-------- .../add-contact/add-contact.test.js | 15 +- 28 files changed, 617 insertions(+), 416 deletions(-) delete mode 100644 ui/components/multichain/pages/index.js delete mode 100644 ui/components/multichain/pages/review-permissions-page/index.js create mode 100644 ui/helpers/utils/mm-lazy.ts diff --git a/app/scripts/lib/manifestFlags.ts b/app/scripts/lib/manifestFlags.ts index 93925bf63a0c..5804c7391973 100644 --- a/app/scripts/lib/manifestFlags.ts +++ b/app/scripts/lib/manifestFlags.ts @@ -11,6 +11,7 @@ export type ManifestFlags = { }; sentry?: { tracesSampleRate?: number; + lazyLoadSubSampleRate?: number; // multiply by tracesSampleRate to get the actual probability forceEnable?: boolean; }; }; @@ -27,6 +28,14 @@ interface WebExtensionManifestWithFlags * @returns flags if they exist, otherwise an empty object */ export function getManifestFlags(): ManifestFlags { + // If this is running in a unit test, there's no manifest, so just return an empty object + if ( + process.env.JEST_WORKER_ID === undefined || + !browser.runtime.getManifest + ) { + return {}; + } + return ( (browser.runtime.getManifest() as WebExtensionManifestWithFlags)._flags || {} diff --git a/builds.yml b/builds.yml index fe33507c1e4a..75f6f4b462c6 100644 --- a/builds.yml +++ b/builds.yml @@ -298,6 +298,9 @@ env: # Enables the notifications feature within the build: - NOTIFICATIONS: '' + # This will be defined if running a unit test + - JEST_WORKER_ID: undefined + - METAMASK_RAMP_API_CONTENT_BASE_URL: https://on-ramp-content.api.cx.metamask.io ### diff --git a/jest.integration.config.js b/jest.integration.config.js index d7236b832aed..685080330fb3 100644 --- a/jest.integration.config.js +++ b/jest.integration.config.js @@ -25,7 +25,8 @@ module.exports = { setupFilesAfterEnv: ['/test/integration/config/setupAfter.js'], testMatch: ['/test/integration/**/*.test.(js|ts|tsx)'], testPathIgnorePatterns: ['/test/integration/config/*'], - testTimeout: 5500, + // This was increased from 5500 to 10000 to when lazy loading was introduced + testTimeout: 10000, // We have to specify the environment we are running in, which is jsdom. The // default is 'node'. This can be modified *per file* using a comment at the // head of the file. So it may be worthwhile to switch to 'node' in any diff --git a/shared/lib/trace.ts b/shared/lib/trace.ts index 0d58ddcdcfcc..4c05b098f120 100644 --- a/shared/lib/trace.ts +++ b/shared/lib/trace.ts @@ -18,6 +18,7 @@ export enum TraceName { FirstRender = 'First Render', GetState = 'Get State', InitialActions = 'Initial Actions', + LazyLoadComponent = 'Lazy Load Component', LoadScripts = 'Load Scripts', Middleware = 'Middleware', NestedTest1 = 'Nested Test 1', diff --git a/test/integration/confirmations/signatures/permit.test.tsx b/test/integration/confirmations/signatures/permit.test.tsx index 5ff87bf7c533..7af3be743f5f 100644 --- a/test/integration/confirmations/signatures/permit.test.tsx +++ b/test/integration/confirmations/signatures/permit.test.tsx @@ -103,25 +103,29 @@ describe('Permit Confirmation', () => { }); }); - expect(screen.getByTestId('header-account-name')).toHaveTextContent( + expect(await screen.findByTestId('header-account-name')).toHaveTextContent( accountName, ); - expect(screen.getByTestId('header-network-display-name')).toHaveTextContent( - 'Sepolia', - ); + expect( + await screen.findByTestId('header-network-display-name'), + ).toHaveTextContent('Sepolia'); - fireEvent.click(screen.getByTestId('header-info__account-details-button')); + fireEvent.click( + await screen.findByTestId('header-info__account-details-button'), + ); expect( await screen.findByTestId( 'confirmation-account-details-modal__account-name', ), ).toHaveTextContent(accountName); - expect(screen.getByTestId('address-copy-button-text')).toHaveTextContent( - '0x0DCD5...3E7bc', - ); expect( - screen.getByTestId('confirmation-account-details-modal__account-balance'), + await screen.findByTestId('address-copy-button-text'), + ).toHaveTextContent('0x0DCD5...3E7bc'); + expect( + await screen.findByTestId( + 'confirmation-account-details-modal__account-balance', + ), ).toHaveTextContent('1.582717SepoliaETH'); let confirmAccountDetailsModalMetricsEvent; @@ -154,7 +158,9 @@ describe('Permit Confirmation', () => { ); fireEvent.click( - screen.getByTestId('confirmation-account-details-modal__close-button'), + await screen.findByTestId( + 'confirmation-account-details-modal__close-button', + ), ); await waitFor(() => { @@ -252,7 +258,7 @@ describe('Permit Confirmation', () => { }); }); - const simulationSection = screen.getByTestId( + const simulationSection = await screen.findByTestId( 'confirmation__simulation_section', ); expect(simulationSection).toBeInTheDocument(); @@ -262,9 +268,9 @@ describe('Permit Confirmation', () => { ); expect(simulationSection).toHaveTextContent('Spending cap'); expect(simulationSection).toHaveTextContent('0xCcCCc...ccccC'); - expect(screen.getByTestId('simulation-token-value')).toHaveTextContent( - '30', - ); + expect( + await screen.findByTestId('simulation-token-value'), + ).toHaveTextContent('30'); const individualFiatDisplay = await screen.findByTestId( 'individual-fiat-display', @@ -303,6 +309,6 @@ describe('Permit Confirmation', () => { account.address, )})`; - expect(screen.getByText(mismatchAccountText)).toBeInTheDocument(); + expect(await screen.findByText(mismatchAccountText)).toBeInTheDocument(); }); }); diff --git a/test/integration/confirmations/signatures/personalSign.test.tsx b/test/integration/confirmations/signatures/personalSign.test.tsx index 5a9c311c9abd..03685f46ab7b 100644 --- a/test/integration/confirmations/signatures/personalSign.test.tsx +++ b/test/integration/confirmations/signatures/personalSign.test.tsx @@ -82,17 +82,20 @@ describe('PersonalSign Confirmation', () => { account.address, ); - const { getByTestId, queryByTestId } = await integrationTestRender({ - preloadedState: mockedMetaMaskState, - backgroundConnection: backgroundConnectionMocked, - }); + const { findByTestId, getByTestId, queryByTestId } = + await integrationTestRender({ + preloadedState: mockedMetaMaskState, + backgroundConnection: backgroundConnectionMocked, + }); - expect(getByTestId('header-account-name')).toHaveTextContent(accountName); - expect(getByTestId('header-network-display-name')).toHaveTextContent( + expect(await findByTestId('header-account-name')).toHaveTextContent( + accountName, + ); + expect(await findByTestId('header-network-display-name')).toHaveTextContent( 'Sepolia', ); - fireEvent.click(getByTestId('header-info__account-details-button')); + fireEvent.click(await findByTestId('header-info__account-details-button')); await waitFor(() => { expect( @@ -101,13 +104,13 @@ describe('PersonalSign Confirmation', () => { }); expect( - getByTestId('confirmation-account-details-modal__account-name'), + await findByTestId('confirmation-account-details-modal__account-name'), ).toHaveTextContent(accountName); - expect(getByTestId('address-copy-button-text')).toHaveTextContent( + expect(await findByTestId('address-copy-button-text')).toHaveTextContent( '0x0DCD5...3E7bc', ); expect( - getByTestId('confirmation-account-details-modal__account-balance'), + await findByTestId('confirmation-account-details-modal__account-balance'), ).toHaveTextContent('1.582717SepoliaETH'); let confirmAccountDetailsModalMetricsEvent; @@ -137,7 +140,7 @@ describe('PersonalSign Confirmation', () => { ); fireEvent.click( - getByTestId('confirmation-account-details-modal__close-button'), + await findByTestId('confirmation-account-details-modal__close-button'), ); await waitFor(() => { @@ -165,9 +168,9 @@ describe('PersonalSign Confirmation', () => { }); }); - expect(screen.getByText('Signature request')).toBeInTheDocument(); + expect(await screen.findByText('Signature request')).toBeInTheDocument(); expect( - screen.getByText('Review request details before you confirm.'), + await screen.findByText('Review request details before you confirm.'), ).toBeInTheDocument(); }); @@ -186,7 +189,7 @@ describe('PersonalSign Confirmation', () => { account.address, ); - const { getByText } = await integrationTestRender({ + const { findByText } = await integrationTestRender({ preloadedState: mockedMetaMaskState, backgroundConnection: backgroundConnectionMocked, }); @@ -197,6 +200,6 @@ describe('PersonalSign Confirmation', () => { account.address, )})`; - expect(getByText(mismatchAccountText)).toBeInTheDocument(); + expect(await findByText(mismatchAccountText)).toBeInTheDocument(); }); }); diff --git a/test/integration/confirmations/transactions/alerts.test.tsx b/test/integration/confirmations/transactions/alerts.test.tsx index 74d37c858b0f..1bbf9d1fd2c5 100644 --- a/test/integration/confirmations/transactions/alerts.test.tsx +++ b/test/integration/confirmations/transactions/alerts.test.tsx @@ -129,7 +129,7 @@ describe('Contract Interaction Confirmation Alerts', () => { }); }); - fireEvent.click(screen.getByTestId('inline-alert')); + fireEvent.click(await screen.findByTestId('inline-alert')); expect(await screen.findByTestId('alert-modal')).toBeInTheDocument(); @@ -182,7 +182,7 @@ describe('Contract Interaction Confirmation Alerts', () => { }); }); - fireEvent.click(screen.getByTestId('inline-alert')); + fireEvent.click(await screen.findByTestId('inline-alert')); expect(await screen.findByTestId('alert-modal')).toBeInTheDocument(); @@ -228,7 +228,7 @@ describe('Contract Interaction Confirmation Alerts', () => { }); }); - fireEvent.click(screen.getByTestId('inline-alert')); + fireEvent.click(await screen.findByTestId('inline-alert')); expect(await screen.findByTestId('alert-modal')).toBeInTheDocument(); @@ -274,7 +274,7 @@ describe('Contract Interaction Confirmation Alerts', () => { }); }); - fireEvent.click(screen.getByTestId('inline-alert')); + fireEvent.click(await screen.findByTestId('inline-alert')); expect(await screen.findByTestId('alert-modal')).toBeInTheDocument(); @@ -349,9 +349,9 @@ describe('Contract Interaction Confirmation Alerts', () => { }); }); - expect(await screen.getByTestId('inline-alert')).toBeInTheDocument(); + expect(await screen.findByTestId('inline-alert')).toBeInTheDocument(); - fireEvent.click(screen.getByTestId('inline-alert')); + fireEvent.click(await screen.findByTestId('inline-alert')); expect(await screen.findByTestId('alert-modal')).toBeInTheDocument(); @@ -390,7 +390,7 @@ describe('Contract Interaction Confirmation Alerts', () => { }); }); - fireEvent.click(screen.getByTestId('inline-alert')); + fireEvent.click(await screen.findByTestId('inline-alert')); expect(await screen.findByTestId('alert-modal')).toBeInTheDocument(); diff --git a/test/integration/confirmations/transactions/contract-deployment.test.tsx b/test/integration/confirmations/transactions/contract-deployment.test.tsx index 67862e6b9550..7698ce3ef8b2 100644 --- a/test/integration/confirmations/transactions/contract-deployment.test.tsx +++ b/test/integration/confirmations/transactions/contract-deployment.test.tsx @@ -162,25 +162,29 @@ describe('Contract Deployment Confirmation', () => { }); }); - expect(screen.getByTestId('header-account-name')).toHaveTextContent( + expect(await screen.findByTestId('header-account-name')).toHaveTextContent( accountName, ); - expect(screen.getByTestId('header-network-display-name')).toHaveTextContent( - 'Sepolia', - ); + expect( + await screen.findByTestId('header-network-display-name'), + ).toHaveTextContent('Sepolia'); - fireEvent.click(screen.getByTestId('header-info__account-details-button')); + fireEvent.click( + await screen.findByTestId('header-info__account-details-button'), + ); expect( await screen.findByTestId( 'confirmation-account-details-modal__account-name', ), ).toHaveTextContent(accountName); - expect(screen.getByTestId('address-copy-button-text')).toHaveTextContent( - '0x0DCD5...3E7bc', - ); expect( - screen.getByTestId('confirmation-account-details-modal__account-balance'), + await screen.findByTestId('address-copy-button-text'), + ).toHaveTextContent('0x0DCD5...3E7bc'); + expect( + await screen.findByTestId( + 'confirmation-account-details-modal__account-balance', + ), ).toHaveTextContent('1.582717SepoliaETH'); let confirmAccountDetailsModalMetricsEvent; @@ -213,7 +217,9 @@ describe('Contract Deployment Confirmation', () => { ); fireEvent.click( - screen.getByTestId('confirmation-account-details-modal__close-button'), + await screen.findByTestId( + 'confirmation-account-details-modal__close-button', + ), ); await waitFor(() => { @@ -245,10 +251,12 @@ describe('Contract Deployment Confirmation', () => { }); expect( - screen.getByText(tEn('confirmTitleDeployContract') as string), + await screen.findByText(tEn('confirmTitleDeployContract') as string), ).toBeInTheDocument(); - const simulationSection = screen.getByTestId('simulation-details-layout'); + const simulationSection = await screen.findByTestId( + 'simulation-details-layout', + ); expect(simulationSection).toBeInTheDocument(); expect(simulationSection).toHaveTextContent( tEn('simulationDetailsTitle') as string, @@ -261,10 +269,10 @@ describe('Contract Deployment Confirmation', () => { tEn('simulationDetailsIncomingHeading') as string, ); expect(simulationDetailsRow).toContainElement( - screen.getByTestId('simulation-details-amount-pill'), + await screen.findByTestId('simulation-details-amount-pill'), ); - const transactionDetailsSection = screen.getByTestId( + const transactionDetailsSection = await screen.findByTestId( 'transaction-details-section', ); expect(transactionDetailsSection).toBeInTheDocument(); @@ -275,25 +283,30 @@ describe('Contract Deployment Confirmation', () => { tEn('interactingWith') as string, ); - const gasFeesSection = screen.getByTestId('gas-fee-section'); + const gasFeesSection = await screen.findByTestId('gas-fee-section'); expect(gasFeesSection).toBeInTheDocument(); - const editGasFeesRow = - within(gasFeesSection).getByTestId('edit-gas-fees-row'); + const editGasFeesRow = await within(gasFeesSection).findByTestId( + 'edit-gas-fees-row', + ); expect(editGasFeesRow).toHaveTextContent(tEn('networkFee') as string); - const firstGasField = within(editGasFeesRow).getByTestId('first-gas-field'); + const firstGasField = await within(editGasFeesRow).findByTestId( + 'first-gas-field', + ); expect(firstGasField).toHaveTextContent('0.0001 SepoliaETH'); expect(editGasFeesRow).toContainElement( - screen.getByTestId('edit-gas-fee-icon'), + await screen.findByTestId('edit-gas-fee-icon'), ); - const gasFeeSpeed = within(gasFeesSection).getByTestId( + const gasFeeSpeed = await within(gasFeesSection).findByTestId( 'gas-fee-details-speed', ); expect(gasFeeSpeed).toHaveTextContent(tEn('speed') as string); - const gasTimingTime = within(gasFeeSpeed).getByTestId('gas-timing-time'); + const gasTimingTime = await within(gasFeeSpeed).findByTestId( + 'gas-timing-time', + ); expect(gasTimingTime).toHaveTextContent('~0 sec'); }); @@ -321,7 +334,9 @@ describe('Contract Deployment Confirmation', () => { }); }); - fireEvent.click(screen.getByTestId('header-advanced-details-button')); + fireEvent.click( + await screen.findByTestId('header-advanced-details-button'), + ); await waitFor(() => { expect( @@ -364,28 +379,32 @@ describe('Contract Deployment Confirmation', () => { ).toHaveBeenCalledWith('getNextNonce', expect.anything()); }); - const gasFeesSection = screen.getByTestId('gas-fee-section'); - const maxFee = screen.getByTestId('gas-fee-details-max-fee'); + const gasFeesSection = await screen.findByTestId('gas-fee-section'); + const maxFee = await screen.findByTestId('gas-fee-details-max-fee'); expect(gasFeesSection).toContainElement(maxFee); expect(maxFee).toHaveTextContent(tEn('maxFee') as string); expect(maxFee).toHaveTextContent('0.0023 SepoliaETH'); - const nonceSection = screen.getByTestId('advanced-details-nonce-section'); + const nonceSection = await screen.findByTestId( + 'advanced-details-nonce-section', + ); expect(nonceSection).toBeInTheDocument(); expect(nonceSection).toHaveTextContent( tEn('advancedDetailsNonceDesc') as string, ); expect(nonceSection).toContainElement( - screen.getByTestId('advanced-details-displayed-nonce'), + await screen.findByTestId('advanced-details-displayed-nonce'), ); expect( - screen.getByTestId('advanced-details-displayed-nonce'), + await screen.findByTestId('advanced-details-displayed-nonce'), ).toHaveTextContent('9'); - const dataSection = screen.getByTestId('advanced-details-data-section'); + const dataSection = await screen.findByTestId( + 'advanced-details-data-section', + ); expect(dataSection).toBeInTheDocument(); - const dataSectionFunction = screen.getByTestId( + const dataSectionFunction = await screen.findByTestId( 'advanced-details-data-function', ); expect(dataSection).toContainElement(dataSectionFunction); @@ -394,7 +413,7 @@ describe('Contract Deployment Confirmation', () => { ); expect(dataSectionFunction).toHaveTextContent('Deposit'); - const transactionDataParams = screen.getByTestId( + const transactionDataParams = await screen.findByTestId( 'advanced-details-data-param-0', ); expect(dataSection).toContainElement(transactionDataParams); diff --git a/test/integration/confirmations/transactions/contract-interaction.test.tsx b/test/integration/confirmations/transactions/contract-interaction.test.tsx index 9a955e1a45fb..5db121bc9fda 100644 --- a/test/integration/confirmations/transactions/contract-interaction.test.tsx +++ b/test/integration/confirmations/transactions/contract-interaction.test.tsx @@ -23,6 +23,8 @@ import { getUnapprovedContractInteractionTransaction, } from './transactionDataHelpers'; +jest.setTimeout(20_000); + jest.mock('../../../../ui/store/background-connection', () => ({ ...jest.requireActual('../../../../ui/store/background-connection'), submitRequestToBackground: jest.fn(), @@ -180,20 +182,25 @@ describe('Contract Interaction Confirmation', () => { }); }); - expect(screen.getByTestId('header-account-name')).toHaveTextContent( + await screen.findByText(accountName); + expect(await screen.findByTestId('header-account-name')).toHaveTextContent( accountName, ); - expect(screen.getByTestId('header-network-display-name')).toHaveTextContent( - 'Sepolia', - ); + expect( + await screen.findByTestId('header-network-display-name'), + ).toHaveTextContent('Sepolia'); - fireEvent.click(screen.getByTestId('header-info__account-details-button')); + await act(async () => { + fireEvent.click( + await screen.findByTestId('header-info__account-details-button'), + ); + }); - expect( - await screen.findByTestId( - 'confirmation-account-details-modal__account-name', - ), - ).toHaveTextContent(accountName); + await waitFor(() => { + expect( + screen.getByTestId('confirmation-account-details-modal__account-name'), + ).toHaveTextContent(accountName); + }); expect(screen.getByTestId('address-copy-button-text')).toHaveTextContent( '0x0DCD5...3E7bc', ); @@ -214,21 +221,21 @@ describe('Contract Interaction Confirmation', () => { expect(confirmAccountDetailsModalMetricsEvent?.[0]).toBe( 'trackMetaMetricsEvent', ); - }); - expect(confirmAccountDetailsModalMetricsEvent?.[1]).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - category: MetaMetricsEventCategory.Confirmations, - event: MetaMetricsEventName.AccountDetailsOpened, - properties: { - action: 'Confirm Screen', - location: MetaMetricsEventLocation.Transaction, - transaction_type: TransactionType.contractInteraction, - }, - }), - ]), - ); + expect(confirmAccountDetailsModalMetricsEvent?.[1]).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + category: MetaMetricsEventCategory.Confirmations, + event: MetaMetricsEventName.AccountDetailsOpened, + properties: { + action: 'Confirm Screen', + location: MetaMetricsEventLocation.Transaction, + transaction_type: TransactionType.contractInteraction, + }, + }), + ]), + ); + }); fireEvent.click( screen.getByTestId('confirmation-account-details-modal__close-button'), @@ -263,10 +270,12 @@ describe('Contract Interaction Confirmation', () => { }); expect( - screen.getByText(tEn('confirmTitleTransaction') as string), + await screen.findByText(tEn('confirmTitleTransaction') as string), ).toBeInTheDocument(); - const simulationSection = screen.getByTestId('simulation-details-layout'); + const simulationSection = await screen.findByTestId( + 'simulation-details-layout', + ); expect(simulationSection).toBeInTheDocument(); expect(simulationSection).toHaveTextContent( tEn('simulationDetailsTitle') as string, @@ -279,10 +288,10 @@ describe('Contract Interaction Confirmation', () => { tEn('simulationDetailsIncomingHeading') as string, ); expect(simulationDetailsRow).toContainElement( - screen.getByTestId('simulation-details-amount-pill'), + await screen.findByTestId('simulation-details-amount-pill'), ); - const transactionDetailsSection = screen.getByTestId( + const transactionDetailsSection = await screen.findByTestId( 'transaction-details-section', ); expect(transactionDetailsSection).toBeInTheDocument(); @@ -293,25 +302,30 @@ describe('Contract Interaction Confirmation', () => { tEn('interactingWith') as string, ); - const gasFeesSection = screen.getByTestId('gas-fee-section'); + const gasFeesSection = await screen.findByTestId('gas-fee-section'); expect(gasFeesSection).toBeInTheDocument(); - const editGasFeesRow = - within(gasFeesSection).getByTestId('edit-gas-fees-row'); + const editGasFeesRow = await within(gasFeesSection).findByTestId( + 'edit-gas-fees-row', + ); expect(editGasFeesRow).toHaveTextContent(tEn('networkFee') as string); - const firstGasField = within(editGasFeesRow).getByTestId('first-gas-field'); + const firstGasField = await within(editGasFeesRow).findByTestId( + 'first-gas-field', + ); expect(firstGasField).toHaveTextContent('0.0001 SepoliaETH'); expect(editGasFeesRow).toContainElement( - screen.getByTestId('edit-gas-fee-icon'), + await screen.findByTestId('edit-gas-fee-icon'), ); - const gasFeeSpeed = within(gasFeesSection).getByTestId( + const gasFeeSpeed = await within(gasFeesSection).findByTestId( 'gas-fee-details-speed', ); expect(gasFeeSpeed).toHaveTextContent(tEn('speed') as string); - const gasTimingTime = within(gasFeeSpeed).getByTestId('gas-timing-time'); + const gasTimingTime = await within(gasFeeSpeed).findByTestId( + 'gas-timing-time', + ); expect(gasTimingTime).toHaveTextContent('~0 sec'); }); @@ -339,7 +353,9 @@ describe('Contract Interaction Confirmation', () => { }); }); - fireEvent.click(screen.getByTestId('header-advanced-details-button')); + fireEvent.click( + await screen.findByTestId('header-advanced-details-button'), + ); await waitFor(() => { expect( @@ -395,28 +411,32 @@ describe('Contract Interaction Confirmation', () => { ]); }); - const gasFeesSection = screen.getByTestId('gas-fee-section'); - const maxFee = screen.getByTestId('gas-fee-details-max-fee'); + const gasFeesSection = await screen.findByTestId('gas-fee-section'); + const maxFee = await screen.findByTestId('gas-fee-details-max-fee'); expect(gasFeesSection).toContainElement(maxFee); expect(maxFee).toHaveTextContent(tEn('maxFee') as string); expect(maxFee).toHaveTextContent('0.0023 SepoliaETH'); - const nonceSection = screen.getByTestId('advanced-details-nonce-section'); + const nonceSection = await screen.findByTestId( + 'advanced-details-nonce-section', + ); expect(nonceSection).toBeInTheDocument(); expect(nonceSection).toHaveTextContent( tEn('advancedDetailsNonceDesc') as string, ); expect(nonceSection).toContainElement( - screen.getByTestId('advanced-details-displayed-nonce'), + await screen.findByTestId('advanced-details-displayed-nonce'), ); expect( - screen.getByTestId('advanced-details-displayed-nonce'), + await screen.findByTestId('advanced-details-displayed-nonce'), ).toHaveTextContent('9'); - const dataSection = screen.getByTestId('advanced-details-data-section'); + const dataSection = await screen.findByTestId( + 'advanced-details-data-section', + ); expect(dataSection).toBeInTheDocument(); - const dataSectionFunction = screen.getByTestId( + const dataSectionFunction = await screen.findByTestId( 'advanced-details-data-function', ); expect(dataSection).toContainElement(dataSectionFunction); @@ -425,7 +445,7 @@ describe('Contract Interaction Confirmation', () => { ); expect(dataSectionFunction).toHaveTextContent('mintNFTs'); - const transactionDataParams = screen.getByTestId( + const transactionDataParams = await screen.findByTestId( 'advanced-details-data-param-0', ); expect(dataSection).toContainElement(transactionDataParams); @@ -454,7 +474,7 @@ describe('Contract Interaction Confirmation', () => { const headingText = tEn('blockaidTitleDeceptive') as string; const bodyText = tEn('blockaidDescriptionTransferFarming') as string; - expect(screen.getByText(headingText)).toBeInTheDocument(); - expect(screen.getByText(bodyText)).toBeInTheDocument(); + expect(await screen.findByText(headingText)).toBeInTheDocument(); + expect(await screen.findByText(bodyText)).toBeInTheDocument(); }); }); diff --git a/test/integration/confirmations/transactions/erc20-approve.test.tsx b/test/integration/confirmations/transactions/erc20-approve.test.tsx index a2404ba75b09..c25b2ee3627d 100644 --- a/test/integration/confirmations/transactions/erc20-approve.test.tsx +++ b/test/integration/confirmations/transactions/erc20-approve.test.tsx @@ -163,10 +163,10 @@ describe('ERC20 Approve Confirmation', () => { }); expect( - screen.getByText(tEn('confirmTitlePermitTokens') as string), + await screen.findByText(tEn('confirmTitlePermitTokens') as string), ).toBeInTheDocument(); expect( - screen.getByText( + await screen.findByText( tEn('confirmTitleDescERC20ApproveTransaction') as string, ), ).toBeInTheDocument(); @@ -183,7 +183,7 @@ describe('ERC20 Approve Confirmation', () => { }); }); - const simulationSection = screen.getByTestId( + const simulationSection = await screen.findByTestId( 'confirmation__simulation_section', ); expect(simulationSection).toBeInTheDocument(); @@ -192,7 +192,9 @@ describe('ERC20 Approve Confirmation', () => { tEn('simulationDetailsERC20ApproveDesc') as string, ); expect(simulationSection).toHaveTextContent(tEn('spendingCap') as string); - const spendingCapValue = screen.getByTestId('simulation-token-value'); + const spendingCapValue = await screen.findByTestId( + 'simulation-token-value', + ); expect(simulationSection).toContainElement(spendingCapValue); expect(spendingCapValue).toHaveTextContent('1'); expect(simulationSection).toHaveTextContent('0x07614...3ad68'); @@ -211,16 +213,18 @@ describe('ERC20 Approve Confirmation', () => { }); }); - const approveDetails = screen.getByTestId('confirmation__approve-details'); + const approveDetails = await screen.findByTestId( + 'confirmation__approve-details', + ); expect(approveDetails).toBeInTheDocument(); - const approveDetailsSpender = screen.getByTestId( + const approveDetailsSpender = await screen.findByTestId( 'confirmation__approve-spender', ); expect(approveDetails).toContainElement(approveDetailsSpender); expect(approveDetailsSpender).toHaveTextContent(tEn('spender') as string); expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B'); - const spenderTooltip = screen.getByTestId( + const spenderTooltip = await screen.findByTestId( 'confirmation__approve-spender-tooltip', ); expect(approveDetailsSpender).toContainElement(spenderTooltip); @@ -231,7 +235,7 @@ describe('ERC20 Approve Confirmation', () => { ); expect(spenderTooltipContent).toBeInTheDocument(); - const approveDetailsRequestFrom = screen.getByTestId( + const approveDetailsRequestFrom = await screen.findByTestId( 'transaction-details-origin-row', ); expect(approveDetails).toContainElement(approveDetailsRequestFrom); @@ -240,7 +244,7 @@ describe('ERC20 Approve Confirmation', () => { 'http://localhost:8086/', ); - const approveDetailsRequestFromTooltip = screen.getByTestId( + const approveDetailsRequestFromTooltip = await screen.findByTestId( 'transaction-details-origin-row-tooltip', ); expect(approveDetailsRequestFrom).toContainElement( @@ -266,7 +270,7 @@ describe('ERC20 Approve Confirmation', () => { }); }); - const spendingCapSection = screen.getByTestId( + const spendingCapSection = await screen.findByTestId( 'confirmation__approve-spending-cap-section', ); expect(spendingCapSection).toBeInTheDocument(); @@ -275,14 +279,14 @@ describe('ERC20 Approve Confirmation', () => { tEn('accountBalance') as string, ); expect(spendingCapSection).toHaveTextContent('0'); - const spendingCapGroup = screen.getByTestId( + const spendingCapGroup = await screen.findByTestId( 'confirmation__approve-spending-cap-group', ); expect(spendingCapSection).toContainElement(spendingCapGroup); expect(spendingCapGroup).toHaveTextContent(tEn('spendingCap') as string); expect(spendingCapGroup).toHaveTextContent('1'); - const spendingCapGroupTooltip = screen.getByTestId( + const spendingCapGroupTooltip = await screen.findByTestId( 'confirmation__approve-spending-cap-group-tooltip', ); expect(spendingCapGroup).toContainElement(spendingCapGroupTooltip); @@ -308,10 +312,12 @@ describe('ERC20 Approve Confirmation', () => { }); }); - const approveDetails = screen.getByTestId('confirmation__approve-details'); + const approveDetails = await screen.findByTestId( + 'confirmation__approve-details', + ); expect(approveDetails).toBeInTheDocument(); - const approveDetailsRecipient = screen.getByTestId( + const approveDetailsRecipient = await screen.findByTestId( 'transaction-details-recipient-row', ); expect(approveDetails).toContainElement(approveDetailsRecipient); @@ -320,7 +326,7 @@ describe('ERC20 Approve Confirmation', () => { ); expect(approveDetailsRecipient).toHaveTextContent('0x07614...3ad68'); - const approveDetailsRecipientTooltip = screen.getByTestId( + const approveDetailsRecipientTooltip = await screen.findByTestId( 'transaction-details-recipient-row-tooltip', ); expect(approveDetailsRecipient).toContainElement( @@ -338,7 +344,7 @@ describe('ERC20 Approve Confirmation', () => { expect(approveDetails).toContainElement(approveMethodData); expect(approveMethodData).toHaveTextContent(tEn('methodData') as string); expect(approveMethodData).toHaveTextContent('Approve'); - const approveMethodDataTooltip = screen.getByTestId( + const approveMethodDataTooltip = await screen.findByTestId( 'transaction-details-method-data-row-tooltip', ); expect(approveMethodData).toContainElement(approveMethodDataTooltip); @@ -348,15 +354,17 @@ describe('ERC20 Approve Confirmation', () => { ); expect(approveMethodDataTooltipContent).toBeInTheDocument(); - const approveDetailsNonce = screen.getByTestId( + const approveDetailsNonce = await screen.findByTestId( 'advanced-details-nonce-section', ); expect(approveDetailsNonce).toBeInTheDocument(); - const dataSection = screen.getByTestId('advanced-details-data-section'); + const dataSection = await screen.findByTestId( + 'advanced-details-data-section', + ); expect(dataSection).toBeInTheDocument(); - const dataSectionFunction = screen.getByTestId( + const dataSectionFunction = await screen.findByTestId( 'advanced-details-data-function', ); expect(dataSection).toContainElement(dataSectionFunction); @@ -365,14 +373,14 @@ describe('ERC20 Approve Confirmation', () => { ); expect(dataSectionFunction).toHaveTextContent('Approve'); - const approveDataParams1 = screen.getByTestId( + const approveDataParams1 = await screen.findByTestId( 'advanced-details-data-param-0', ); expect(dataSection).toContainElement(approveDataParams1); expect(approveDataParams1).toHaveTextContent('Param #1'); expect(approveDataParams1).toHaveTextContent('0x2e0D7...5d09B'); - const approveDataParams2 = screen.getByTestId( + const approveDataParams2 = await screen.findByTestId( 'advanced-details-data-param-1', ); expect(dataSection).toContainElement(approveDataParams2); diff --git a/test/integration/confirmations/transactions/erc721-approve.test.tsx b/test/integration/confirmations/transactions/erc721-approve.test.tsx index c3948d150b1d..c158717cc9c9 100644 --- a/test/integration/confirmations/transactions/erc721-approve.test.tsx +++ b/test/integration/confirmations/transactions/erc721-approve.test.tsx @@ -163,10 +163,12 @@ describe('ERC721 Approve Confirmation', () => { }); expect( - screen.getByText(tEn('confirmTitleApproveTransaction') as string), + await screen.findByText(tEn('confirmTitleApproveTransaction') as string), ).toBeInTheDocument(); expect( - screen.getByText(tEn('confirmTitleDescApproveTransaction') as string), + await screen.findByText( + tEn('confirmTitleDescApproveTransaction') as string, + ), ).toBeInTheDocument(); }); @@ -181,7 +183,7 @@ describe('ERC721 Approve Confirmation', () => { }); }); - const simulationSection = screen.getByTestId( + const simulationSection = await screen.findByTestId( 'confirmation__simulation_section', ); expect(simulationSection).toBeInTheDocument(); @@ -192,7 +194,9 @@ describe('ERC721 Approve Confirmation', () => { expect(simulationSection).toHaveTextContent( tEn('simulationApproveHeading') as string, ); - const spendingCapValue = screen.getByTestId('simulation-token-value'); + const spendingCapValue = await screen.findByTestId( + 'simulation-token-value', + ); expect(simulationSection).toContainElement(spendingCapValue); expect(spendingCapValue).toHaveTextContent('1'); expect(simulationSection).toHaveTextContent('0x07614...3ad68'); @@ -211,16 +215,18 @@ describe('ERC721 Approve Confirmation', () => { }); }); - const approveDetails = screen.getByTestId('confirmation__approve-details'); + const approveDetails = await screen.findByTestId( + 'confirmation__approve-details', + ); expect(approveDetails).toBeInTheDocument(); - const approveDetailsSpender = screen.getByTestId( + const approveDetailsSpender = await screen.findByTestId( 'confirmation__approve-spender', ); expect(approveDetails).toContainElement(approveDetailsSpender); expect(approveDetailsSpender).toHaveTextContent(tEn('spender') as string); expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B'); - const spenderTooltip = screen.getByTestId( + const spenderTooltip = await screen.findByTestId( 'confirmation__approve-spender-tooltip', ); expect(approveDetailsSpender).toContainElement(spenderTooltip); @@ -230,7 +236,7 @@ describe('ERC721 Approve Confirmation', () => { ); expect(spenderTooltipContent).toBeInTheDocument(); - const approveDetailsRequestFrom = screen.getByTestId( + const approveDetailsRequestFrom = await screen.findByTestId( 'transaction-details-origin-row', ); expect(approveDetails).toContainElement(approveDetailsRequestFrom); @@ -241,7 +247,7 @@ describe('ERC721 Approve Confirmation', () => { 'http://localhost:8086/', ); - const approveDetailsRequestFromTooltip = screen.getByTestId( + const approveDetailsRequestFromTooltip = await screen.findByTestId( 'transaction-details-origin-row-tooltip', ); expect(approveDetailsRequestFrom).toContainElement( @@ -269,10 +275,12 @@ describe('ERC721 Approve Confirmation', () => { }); }); - const approveDetails = screen.getByTestId('confirmation__approve-details'); + const approveDetails = await screen.findByTestId( + 'confirmation__approve-details', + ); expect(approveDetails).toBeInTheDocument(); - const approveDetailsRecipient = screen.getByTestId( + const approveDetailsRecipient = await screen.findByTestId( 'transaction-details-recipient-row', ); expect(approveDetails).toContainElement(approveDetailsRecipient); @@ -281,7 +289,7 @@ describe('ERC721 Approve Confirmation', () => { ); expect(approveDetailsRecipient).toHaveTextContent('0x07614...3ad68'); - const approveDetailsRecipientTooltip = screen.getByTestId( + const approveDetailsRecipientTooltip = await screen.findByTestId( 'transaction-details-recipient-row-tooltip', ); expect(approveDetailsRecipient).toContainElement( @@ -299,7 +307,7 @@ describe('ERC721 Approve Confirmation', () => { expect(approveDetails).toContainElement(approveMethodData); expect(approveMethodData).toHaveTextContent(tEn('methodData') as string); expect(approveMethodData).toHaveTextContent('Approve'); - const approveMethodDataTooltip = screen.getByTestId( + const approveMethodDataTooltip = await screen.findByTestId( 'transaction-details-method-data-row-tooltip', ); expect(approveMethodData).toContainElement(approveMethodDataTooltip); @@ -309,15 +317,17 @@ describe('ERC721 Approve Confirmation', () => { ); expect(approveMethodDataTooltipContent).toBeInTheDocument(); - const approveDetailsNonce = screen.getByTestId( + const approveDetailsNonce = await screen.findByTestId( 'advanced-details-nonce-section', ); expect(approveDetailsNonce).toBeInTheDocument(); - const dataSection = screen.getByTestId('advanced-details-data-section'); + const dataSection = await screen.findByTestId( + 'advanced-details-data-section', + ); expect(dataSection).toBeInTheDocument(); - const dataSectionFunction = screen.getByTestId( + const dataSectionFunction = await screen.findByTestId( 'advanced-details-data-function', ); expect(dataSection).toContainElement(dataSectionFunction); @@ -326,14 +336,14 @@ describe('ERC721 Approve Confirmation', () => { ); expect(dataSectionFunction).toHaveTextContent('Approve'); - const approveDataParams1 = screen.getByTestId( + const approveDataParams1 = await screen.findByTestId( 'advanced-details-data-param-0', ); expect(dataSection).toContainElement(approveDataParams1); expect(approveDataParams1).toHaveTextContent('Param #1'); expect(approveDataParams1).toHaveTextContent('0x2e0D7...5d09B'); - const approveDataParams2 = screen.getByTestId( + const approveDataParams2 = await screen.findByTestId( 'advanced-details-data-param-1', ); expect(dataSection).toContainElement(approveDataParams2); diff --git a/test/integration/confirmations/transactions/increase-allowance.test.tsx b/test/integration/confirmations/transactions/increase-allowance.test.tsx index c288a5cc4e6d..810477d3a3a5 100644 --- a/test/integration/confirmations/transactions/increase-allowance.test.tsx +++ b/test/integration/confirmations/transactions/increase-allowance.test.tsx @@ -167,10 +167,10 @@ describe('ERC20 increaseAllowance Confirmation', () => { }); expect( - screen.getByText(tEn('confirmTitlePermitTokens') as string), + await screen.findByText(tEn('confirmTitlePermitTokens') as string), ).toBeInTheDocument(); expect( - screen.getByText(tEn('confirmTitleDescPermitSignature') as string), + await screen.findByText(tEn('confirmTitleDescPermitSignature') as string), ).toBeInTheDocument(); }); @@ -185,7 +185,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { }); }); - const simulationSection = screen.getByTestId( + const simulationSection = await screen.findByTestId( 'confirmation__simulation_section', ); expect(simulationSection).toBeInTheDocument(); @@ -194,7 +194,9 @@ describe('ERC20 increaseAllowance Confirmation', () => { tEn('simulationDetailsERC20ApproveDesc') as string, ); expect(simulationSection).toHaveTextContent(tEn('spendingCap') as string); - const spendingCapValue = screen.getByTestId('simulation-token-value'); + const spendingCapValue = await screen.findByTestId( + 'simulation-token-value', + ); expect(simulationSection).toContainElement(spendingCapValue); expect(spendingCapValue).toHaveTextContent('1'); expect(simulationSection).toHaveTextContent('0x07614...3ad68'); @@ -213,16 +215,18 @@ describe('ERC20 increaseAllowance Confirmation', () => { }); }); - const approveDetails = screen.getByTestId('confirmation__approve-details'); + const approveDetails = await screen.findByTestId( + 'confirmation__approve-details', + ); expect(approveDetails).toBeInTheDocument(); - const approveDetailsSpender = screen.getByTestId( + const approveDetailsSpender = await screen.findByTestId( 'confirmation__approve-spender', ); expect(approveDetails).toContainElement(approveDetailsSpender); expect(approveDetailsSpender).toHaveTextContent(tEn('spender') as string); expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B'); - const spenderTooltip = screen.getByTestId( + const spenderTooltip = await screen.findByTestId( 'confirmation__approve-spender-tooltip', ); expect(approveDetailsSpender).toContainElement(spenderTooltip); @@ -233,7 +237,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { ); expect(spenderTooltipContent).toBeInTheDocument(); - const approveDetailsRequestFrom = screen.getByTestId( + const approveDetailsRequestFrom = await screen.findByTestId( 'transaction-details-origin-row', ); expect(approveDetails).toContainElement(approveDetailsRequestFrom); @@ -242,7 +246,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { 'http://localhost:8086/', ); - const approveDetailsRequestFromTooltip = screen.getByTestId( + const approveDetailsRequestFromTooltip = await screen.findByTestId( 'transaction-details-origin-row-tooltip', ); expect(approveDetailsRequestFrom).toContainElement( @@ -268,7 +272,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { }); }); - const spendingCapSection = screen.getByTestId( + const spendingCapSection = await screen.findByTestId( 'confirmation__approve-spending-cap-section', ); expect(spendingCapSection).toBeInTheDocument(); @@ -277,14 +281,14 @@ describe('ERC20 increaseAllowance Confirmation', () => { tEn('accountBalance') as string, ); expect(spendingCapSection).toHaveTextContent('0'); - const spendingCapGroup = screen.getByTestId( + const spendingCapGroup = await screen.findByTestId( 'confirmation__approve-spending-cap-group', ); expect(spendingCapSection).toContainElement(spendingCapGroup); expect(spendingCapGroup).toHaveTextContent(tEn('spendingCap') as string); expect(spendingCapGroup).toHaveTextContent('1'); - const spendingCapGroupTooltip = screen.getByTestId( + const spendingCapGroupTooltip = await screen.findByTestId( 'confirmation__approve-spending-cap-group-tooltip', ); expect(spendingCapGroup).toContainElement(spendingCapGroupTooltip); @@ -310,10 +314,12 @@ describe('ERC20 increaseAllowance Confirmation', () => { }); }); - const approveDetails = screen.getByTestId('confirmation__approve-details'); + const approveDetails = await screen.findByTestId( + 'confirmation__approve-details', + ); expect(approveDetails).toBeInTheDocument(); - const approveDetailsRecipient = screen.getByTestId( + const approveDetailsRecipient = await screen.findByTestId( 'transaction-details-recipient-row', ); expect(approveDetails).toContainElement(approveDetailsRecipient); @@ -322,7 +328,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { ); expect(approveDetailsRecipient).toHaveTextContent('0x07614...3ad68'); - const approveDetailsRecipientTooltip = screen.getByTestId( + const approveDetailsRecipientTooltip = await screen.findByTestId( 'transaction-details-recipient-row-tooltip', ); expect(approveDetailsRecipient).toContainElement( @@ -340,7 +346,7 @@ describe('ERC20 increaseAllowance Confirmation', () => { expect(approveDetails).toContainElement(approveMethodData); expect(approveMethodData).toHaveTextContent(tEn('methodData') as string); expect(approveMethodData).toHaveTextContent('increaseAllowance'); - const approveMethodDataTooltip = screen.getByTestId( + const approveMethodDataTooltip = await screen.findByTestId( 'transaction-details-method-data-row-tooltip', ); expect(approveMethodData).toContainElement(approveMethodDataTooltip); @@ -350,15 +356,17 @@ describe('ERC20 increaseAllowance Confirmation', () => { ); expect(approveMethodDataTooltipContent).toBeInTheDocument(); - const approveDetailsNonce = screen.getByTestId( + const approveDetailsNonce = await screen.findByTestId( 'advanced-details-nonce-section', ); expect(approveDetailsNonce).toBeInTheDocument(); - const dataSection = screen.getByTestId('advanced-details-data-section'); + const dataSection = await screen.findByTestId( + 'advanced-details-data-section', + ); expect(dataSection).toBeInTheDocument(); - const dataSectionFunction = screen.getByTestId( + const dataSectionFunction = await screen.findByTestId( 'advanced-details-data-function', ); expect(dataSection).toContainElement(dataSectionFunction); @@ -367,14 +375,14 @@ describe('ERC20 increaseAllowance Confirmation', () => { ); expect(dataSectionFunction).toHaveTextContent('increaseAllowance'); - const approveDataParams1 = screen.getByTestId( + const approveDataParams1 = await screen.findByTestId( 'advanced-details-data-param-0', ); expect(dataSection).toContainElement(approveDataParams1); expect(approveDataParams1).toHaveTextContent('Param #1'); expect(approveDataParams1).toHaveTextContent('0x2e0D7...5d09B'); - const approveDataParams2 = screen.getByTestId( + const approveDataParams2 = await screen.findByTestId( 'advanced-details-data-param-1', ); expect(dataSection).toContainElement(approveDataParams2); diff --git a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx index a65688030e90..ebe680983a6c 100644 --- a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx +++ b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx @@ -167,10 +167,14 @@ describe('ERC721 setApprovalForAll Confirmation', () => { }); expect( - screen.getByText(tEn('setApprovalForAllRedesignedTitle') as string), + await screen.findByText( + tEn('setApprovalForAllRedesignedTitle') as string, + ), ).toBeInTheDocument(); expect( - screen.getByText(tEn('confirmTitleDescApproveTransaction') as string), + await screen.findByText( + tEn('confirmTitleDescApproveTransaction') as string, + ), ).toBeInTheDocument(); }); @@ -185,7 +189,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => { }); }); - const simulationSection = screen.getByTestId( + const simulationSection = await screen.findByTestId( 'confirmation__simulation_section', ); expect(simulationSection).toBeInTheDocument(); @@ -194,7 +198,9 @@ describe('ERC721 setApprovalForAll Confirmation', () => { tEn('simulationDetailsSetApprovalForAllDesc') as string, ); expect(simulationSection).toHaveTextContent(tEn('withdrawing') as string); - const spendingCapValue = screen.getByTestId('simulation-token-value'); + const spendingCapValue = await screen.findByTestId( + 'simulation-token-value', + ); expect(simulationSection).toContainElement(spendingCapValue); expect(spendingCapValue).toHaveTextContent(tEn('all') as string); expect(simulationSection).toHaveTextContent('0x07614...3ad68'); @@ -213,9 +219,11 @@ describe('ERC721 setApprovalForAll Confirmation', () => { }); }); - const approveDetails = screen.getByTestId('confirmation__approve-details'); + const approveDetails = await screen.findByTestId( + 'confirmation__approve-details', + ); expect(approveDetails).toBeInTheDocument(); - const approveDetailsSpender = screen.getByTestId( + const approveDetailsSpender = await screen.findByTestId( 'confirmation__approve-spender', ); @@ -224,7 +232,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => { tEn('permissionFor') as string, ); expect(approveDetailsSpender).toHaveTextContent('0x2e0D7...5d09B'); - const spenderTooltip = screen.getByTestId( + const spenderTooltip = await screen.findByTestId( 'confirmation__approve-spender-tooltip', ); expect(approveDetailsSpender).toContainElement(spenderTooltip); @@ -235,7 +243,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => { ); expect(spenderTooltipContent).toBeInTheDocument(); - const approveDetailsRequestFrom = screen.getByTestId( + const approveDetailsRequestFrom = await screen.findByTestId( 'transaction-details-origin-row', ); expect(approveDetails).toContainElement(approveDetailsRequestFrom); @@ -246,7 +254,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => { 'http://localhost:8086/', ); - const approveDetailsRequestFromTooltip = screen.getByTestId( + const approveDetailsRequestFromTooltip = await screen.findByTestId( 'transaction-details-origin-row-tooltip', ); expect(approveDetailsRequestFrom).toContainElement( @@ -274,10 +282,12 @@ describe('ERC721 setApprovalForAll Confirmation', () => { }); }); - const approveDetails = screen.getByTestId('confirmation__approve-details'); + const approveDetails = await screen.findByTestId( + 'confirmation__approve-details', + ); expect(approveDetails).toBeInTheDocument(); - const approveDetailsRecipient = screen.getByTestId( + const approveDetailsRecipient = await screen.findByTestId( 'transaction-details-recipient-row', ); expect(approveDetails).toContainElement(approveDetailsRecipient); @@ -286,7 +296,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => { ); expect(approveDetailsRecipient).toHaveTextContent('0x07614...3ad68'); - const approveDetailsRecipientTooltip = screen.getByTestId( + const approveDetailsRecipientTooltip = await screen.findByTestId( 'transaction-details-recipient-row-tooltip', ); expect(approveDetailsRecipient).toContainElement( @@ -304,7 +314,7 @@ describe('ERC721 setApprovalForAll Confirmation', () => { expect(approveDetails).toContainElement(approveMethodData); expect(approveMethodData).toHaveTextContent(tEn('methodData') as string); expect(approveMethodData).toHaveTextContent('setApprovalForAll'); - const approveMethodDataTooltip = screen.getByTestId( + const approveMethodDataTooltip = await screen.findByTestId( 'transaction-details-method-data-row-tooltip', ); expect(approveMethodData).toContainElement(approveMethodDataTooltip); @@ -314,15 +324,17 @@ describe('ERC721 setApprovalForAll Confirmation', () => { ); expect(approveMethodDataTooltipContent).toBeInTheDocument(); - const approveDetailsNonce = screen.getByTestId( + const approveDetailsNonce = await screen.findByTestId( 'advanced-details-nonce-section', ); expect(approveDetailsNonce).toBeInTheDocument(); - const dataSection = screen.getByTestId('advanced-details-data-section'); + const dataSection = await screen.findByTestId( + 'advanced-details-data-section', + ); expect(dataSection).toBeInTheDocument(); - const dataSectionFunction = screen.getByTestId( + const dataSectionFunction = await screen.findByTestId( 'advanced-details-data-function', ); expect(dataSection).toContainElement(dataSectionFunction); @@ -331,14 +343,14 @@ describe('ERC721 setApprovalForAll Confirmation', () => { ); expect(dataSectionFunction).toHaveTextContent('setApprovalForAll'); - const approveDataParams1 = screen.getByTestId( + const approveDataParams1 = await screen.findByTestId( 'advanced-details-data-param-0', ); expect(dataSection).toContainElement(approveDataParams1); expect(approveDataParams1).toHaveTextContent('Param #1'); expect(approveDataParams1).toHaveTextContent('0x2e0D7...5d09B'); - const approveDataParams2 = screen.getByTestId( + const approveDataParams2 = await screen.findByTestId( 'advanced-details-data-param-1', ); expect(dataSection).toContainElement(approveDataParams2); diff --git a/test/integration/notifications&auth/notifications-activation.test.tsx b/test/integration/notifications&auth/notifications-activation.test.tsx index e11e58dad320..d52921c386e8 100644 --- a/test/integration/notifications&auth/notifications-activation.test.tsx +++ b/test/integration/notifications&auth/notifications-activation.test.tsx @@ -70,7 +70,7 @@ describe('Notifications Activation', () => { const clickElement = async (testId: string) => { await act(async () => { - fireEvent.click(screen.getByTestId(testId)); + fireEvent.click(await screen.findByTestId(testId)); }); }; @@ -105,7 +105,7 @@ describe('Notifications Activation', () => { }); await act(async () => { - fireEvent.click(screen.getByText('Turn on')); + fireEvent.click(await screen.findByText('Turn on')); }); await waitFor(() => { @@ -148,7 +148,7 @@ describe('Notifications Activation', () => { await act(async () => { fireEvent.click( - within(screen.getByRole('dialog')).getByRole('button', { + await within(screen.getByRole('dialog')).findByRole('button', { name: 'Close', }), ); diff --git a/test/integration/notifications&auth/notifications-list.test.tsx b/test/integration/notifications&auth/notifications-list.test.tsx index 4e17a53db107..e4c1d6f20107 100644 --- a/test/integration/notifications&auth/notifications-list.test.tsx +++ b/test/integration/notifications&auth/notifications-list.test.tsx @@ -77,8 +77,8 @@ describe('Notifications List', () => { }); }); - await waitFor(() => { - const unreadCount = screen.getByTestId( + await waitFor(async () => { + const unreadCount = await screen.findByTestId( 'notifications-tag-counter__unread-dot', ); expect(unreadCount).toBeInTheDocument(); @@ -96,30 +96,36 @@ describe('Notifications List', () => { }); }); - fireEvent.click(screen.getByTestId('account-options-menu-button')); + fireEvent.click(await screen.findByTestId('account-options-menu-button')); - await waitFor(() => { - expect(screen.getByTestId('notifications-menu-item')).toBeInTheDocument(); - fireEvent.click(screen.getByTestId('notifications-menu-item')); + await waitFor(async () => { + expect( + await screen.findByTestId('notifications-menu-item'), + ).toBeInTheDocument(); + fireEvent.click(await screen.findByTestId('notifications-menu-item')); }); - await waitFor(() => { - const notificationsList = screen.getByTestId('notifications-list'); + await waitFor(async () => { + const notificationsList = await screen.findByTestId('notifications-list'); expect(notificationsList).toBeInTheDocument(); expect(notificationsList.childElementCount).toBe(3); // Feature notification details expect( - within(notificationsList).getByText(featureNotification.data.title), + await within(notificationsList).findByText( + featureNotification.data.title, + ), ).toBeInTheDocument(); expect( - within(notificationsList).getByText( + await within(notificationsList).findByText( featureNotification.data.shortDescription, ), ).toBeInTheDocument(); // Eth sent notification details - const sentToElement = within(notificationsList).getByText('Sent to'); + const sentToElement = await within(notificationsList).findByText( + 'Sent to', + ); expect(sentToElement).toBeInTheDocument(); const addressElement = sentToElement.nextElementSibling; @@ -127,12 +133,12 @@ describe('Notifications List', () => { // Read all button expect( - within(notificationsList).getByTestId( + await within(notificationsList).findByTestId( 'notifications-list-read-all-button', ), ).toBeInTheDocument(); - const unreadDot = screen.getAllByTestId('unread-dot'); + const unreadDot = await screen.findAllByTestId('unread-dot'); expect(unreadDot).toHaveLength(2); }); @@ -178,17 +184,19 @@ describe('Notifications List', () => { backgroundConnection: backgroundConnectionMocked, }); - fireEvent.click(screen.getByTestId('account-options-menu-button')); + fireEvent.click(await screen.findByTestId('account-options-menu-button')); - await waitFor(() => { + await waitFor(async () => { expect( - screen.getByTestId('notifications-menu-item'), + await screen.findByTestId('notifications-menu-item'), ).toBeInTheDocument(); - fireEvent.click(screen.getByTestId('notifications-menu-item')); + fireEvent.click(await screen.findByTestId('notifications-menu-item')); }); - await waitFor(() => { - const notificationsList = screen.getByTestId('notifications-list'); + await waitFor(async () => { + const notificationsList = await screen.findByTestId( + 'notifications-list', + ); expect(notificationsList).toBeInTheDocument(); expect(notificationsList.childElementCount).toBe(2); @@ -211,14 +219,18 @@ describe('Notifications List', () => { }); }); - fireEvent.click(screen.getByTestId('account-options-menu-button')); + fireEvent.click(await screen.findByTestId('account-options-menu-button')); - await waitFor(() => { - expect(screen.getByTestId('notifications-menu-item')).toBeInTheDocument(); - fireEvent.click(screen.getByTestId('notifications-menu-item')); + await waitFor(async () => { + expect( + await screen.findByTestId('notifications-menu-item'), + ).toBeInTheDocument(); + fireEvent.click(await screen.findByTestId('notifications-menu-item')); }); - fireEvent.click(screen.getByTestId('notifications-list-read-all-button')); + fireEvent.click( + await screen.findByTestId('notifications-list-read-all-button'), + ); await waitFor(() => { const markAllAsReadEvent = diff --git a/test/integration/notifications&auth/notifications-toggle.test.tsx b/test/integration/notifications&auth/notifications-toggle.test.tsx index 8133e4c4bc3d..fd4c11ec4494 100644 --- a/test/integration/notifications&auth/notifications-toggle.test.tsx +++ b/test/integration/notifications&auth/notifications-toggle.test.tsx @@ -48,13 +48,13 @@ describe('Notifications Toggle', () => { const clickElement = async (testId: string) => { await act(async () => { - fireEvent.click(screen.getByTestId(testId)); + fireEvent.click(await screen.findByTestId(testId)); }); }; const waitForElement = async (testId: string) => { - await waitFor(() => { - expect(screen.getByTestId(testId)).toBeInTheDocument(); + await waitFor(async () => { + expect(await screen.findByTestId(testId)).toBeInTheDocument(); }); }; @@ -73,12 +73,12 @@ describe('Notifications Toggle', () => { await clickElement('notifications-settings-button'); await waitForElement('notifications-settings-allow-notifications'); - const toggleSection = screen.getByTestId( + const toggleSection = await screen.findByTestId( 'notifications-settings-allow-notifications', ); await act(async () => { - fireEvent.click(within(toggleSection).getByRole('checkbox')); + fireEvent.click(await within(toggleSection).findByRole('checkbox')); }); await waitFor(() => { @@ -159,7 +159,7 @@ describe('Notifications Toggle', () => { await clickElement('notifications-settings-button'); await waitForElement('notifications-settings-allow-notifications'); - const allToggles = screen.getAllByTestId('test-toggle'); + const allToggles = await screen.findAllByTestId('test-toggle'); await act(async () => { fireEvent.click(allToggles[1]); diff --git a/test/integration/onboarding/wallet-created.test.tsx b/test/integration/onboarding/wallet-created.test.tsx index 55be476839fe..c1ddb1f1886a 100644 --- a/test/integration/onboarding/wallet-created.test.tsx +++ b/test/integration/onboarding/wallet-created.test.tsx @@ -31,14 +31,15 @@ describe('Wallet Created Events', () => { }); it('are sent when onboarding user who chooses to opt in metrics', async () => { - const { getByTestId, getByText } = await integrationTestRender({ - preloadedState: mockMetaMaskState, - backgroundConnection: backgroundConnectionMocked, - }); + const { getByTestId, findByTestId, getByText, findByText } = + await integrationTestRender({ + preloadedState: mockMetaMaskState, + backgroundConnection: backgroundConnectionMocked, + }); - expect(getByText('Congratulations!')).toBeInTheDocument(); + expect(await findByText('Congratulations!')).toBeInTheDocument(); - fireEvent.click(getByTestId('onboarding-complete-done')); + fireEvent.click(await findByTestId('onboarding-complete-done')); await waitFor(() => { expect(getByTestId('onboarding-pin-extension')).toBeInTheDocument(); @@ -69,7 +70,7 @@ describe('Wallet Created Events', () => { ]), ); - fireEvent.click(getByTestId('pin-extension-next')); + fireEvent.click(await findByTestId('pin-extension-next')); let onboardingPinExtensionMetricsEvent; @@ -91,7 +92,7 @@ describe('Wallet Created Events', () => { ).toBeInTheDocument(); }); - fireEvent.click(getByTestId('pin-extension-done')); + fireEvent.click(await findByTestId('pin-extension-done')); await waitFor(() => { const completeOnboardingBackgroundRequest = diff --git a/ui/components/multichain/pages/index.js b/ui/components/multichain/pages/index.js deleted file mode 100644 index a19f23039138..000000000000 --- a/ui/components/multichain/pages/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { Connections } from './connections'; -export { PermissionsPage } from './permissions-page/permissions-page'; -export { ReviewPermissions, SiteCell } from './review-permissions-page'; diff --git a/ui/components/multichain/pages/review-permissions-page/index.js b/ui/components/multichain/pages/review-permissions-page/index.js deleted file mode 100644 index e2da178368f1..000000000000 --- a/ui/components/multichain/pages/review-permissions-page/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { ReviewPermissions } from './review-permissions-page'; -export { SiteCell } from './site-cell/site-cell'; diff --git a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.stories.tsx b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.stories.tsx index b2da4553ce50..a886d26e77e6 100644 --- a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.stories.tsx +++ b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.stories.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ReviewPermissions } from '.'; +import { ReviewPermissions } from './review-permissions-page'; export default { title: 'Components/Multichain/ReviewPermissions', diff --git a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.test.tsx b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.test.tsx index b644c16b6440..55f7ab9bf332 100644 --- a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.test.tsx +++ b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { renderWithProvider } from '../../../../../test/jest/rendering'; import mockState from '../../../../../test/data/mock-state.json'; import configureStore from '../../../../store/store'; -import { ReviewPermissions } from '.'; +import { ReviewPermissions } from './review-permissions-page'; const render = (state = {}) => { const store = configureStore({ diff --git a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.tsx b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.tsx index f65dd7a662cf..95a8ea394000 100644 --- a/ui/components/multichain/pages/review-permissions-page/review-permissions-page.tsx +++ b/ui/components/multichain/pages/review-permissions-page/review-permissions-page.tsx @@ -54,7 +54,7 @@ import { PermissionsHeader } from '../../permissions-header/permissions-header'; import { mergeAccounts } from '../../account-list-menu/account-list-menu'; import { MergedInternalAccount } from '../../../../selectors/selectors.types'; import { TEST_CHAINS } from '../../../../../shared/constants/network'; -import { SiteCell } from '.'; +import { SiteCell } from './site-cell/site-cell'; export const ReviewPermissions = () => { const t = useI18nContext(); diff --git a/ui/helpers/utils/mm-lazy.ts b/ui/helpers/utils/mm-lazy.ts new file mode 100644 index 000000000000..e31c22dfc99a --- /dev/null +++ b/ui/helpers/utils/mm-lazy.ts @@ -0,0 +1,86 @@ +import React from 'react'; +// eslint-disable-next-line import/no-restricted-paths +import { getManifestFlags } from '../../../app/scripts/lib/manifestFlags'; +import { endTrace, trace, TraceName } from '../../../shared/lib/trace'; + +type DynamicImportType = () => Promise<{ default: React.ComponentType }>; +type ModuleWithDefaultType = { + default: React.ComponentType; +}; + +// This only has to happen once per app load, so do it outside a function +const lazyLoadSubSampleRate = getManifestFlags().sentry?.lazyLoadSubSampleRate; + +/** + * A wrapper around React.lazy that adds two things: + * 1. Sentry tracing for how long it takes to load the component (not render, just load) + * 2. React.lazy can only deal with default exports, but the wrapper can handle named exports too + * + * @param fn - an import of the form `() => import('AAA')` + */ +export function mmLazy(fn: DynamicImportType) { + return React.lazy(async () => { + // We can't start the trace here because we don't have the componentName yet, so we just hold the startTime + const startTime = Date.now(); + + const importedModule = await fn(); + const { componentName, component } = parseImportedComponent(importedModule); + + // Only trace load time of lazy-loaded components if the manifestFlag is set, and then do it by Math.random probability + if (lazyLoadSubSampleRate && Math.random() < lazyLoadSubSampleRate) { + trace({ + name: TraceName.LazyLoadComponent, + data: { componentName }, + startTime, + }); + + endTrace({ name: TraceName.LazyLoadComponent }); + } + + return component; + }); +} + +// There can be a lot of different types here, and we're basically doing type-checking in the code, +// so I don't think TypeScript safety on `importedModule` is worth it in this function +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function parseImportedComponent(importedModule: any): { + componentName: string; // TODO: in many circumstances, the componentName gets minified + component: ModuleWithDefaultType; +} { + let componentName: string; + + // If there's no default export + if (!importedModule.default) { + const keys = Object.keys(importedModule); + + // If there's only one named export + if (keys.length === 1) { + componentName = keys[0]; + + return { + componentName, + // Force the component to be the default export + component: { default: importedModule[componentName] }, + }; + } + + // If there are multiple named exports, this isn't good for tree-shaking, so throw an error + throw new Error( + 'mmLazy: You cannot lazy-load a component when there are multiple exported components in one file', + ); + } + + if (importedModule.default.WrappedComponent) { + // If there's a wrapped component, we don't want to see the name reported as `withRouter(Connect(AAA))` we want just `AAA` + componentName = importedModule.default.WrappedComponent.name; + } else { + componentName = + importedModule.default.name || importedModule.default.displayName; + } + + return { + componentName, + component: importedModule, + }; +} diff --git a/ui/pages/confirmations/confirm-transaction/confirm-transaction.component.js b/ui/pages/confirmations/confirm-transaction/confirm-transaction.component.js index ef08fb6bbba1..f5858e853dd1 100644 --- a/ui/pages/confirmations/confirm-transaction/confirm-transaction.component.js +++ b/ui/pages/confirmations/confirm-transaction/confirm-transaction.component.js @@ -133,9 +133,7 @@ const ConfirmTransaction = () => { }); useEffect(() => { - if (!totalUnapproved && !sendTo) { - history.replace(mostRecentOverviewPage); - } else { + if (totalUnapproved || sendTo) { const { txParams: { data } = {}, origin } = transaction; if (origin !== ORIGIN_METAMASK) { diff --git a/ui/pages/confirmations/confirm-transaction/confirm-transaction.test.js b/ui/pages/confirmations/confirm-transaction/confirm-transaction.test.js index fbd8840de4ec..3c374fd7d0c0 100644 --- a/ui/pages/confirmations/confirm-transaction/confirm-transaction.test.js +++ b/ui/pages/confirmations/confirm-transaction/confirm-transaction.test.js @@ -270,26 +270,5 @@ describe('Confirmation Transaction Page', () => { expect(replaceSpy).not.toHaveBeenCalled(); }); }); - - describe('when no unapproved transactions and no sendTo recipient exist', () => { - it('should call history.replace(mostRecentOverviewPage)', () => { - const mockStore = configureMockStore(middleware)({ - ...mockState, - metamask: { - ...mockState.metamask, - transactions: [], - }, - }); - const replaceSpy = jest.fn(); - jest.spyOn(ReactRouterDOM, 'useHistory').mockImplementation(() => { - return { - replace: replaceSpy, - }; - }); - - renderWithProvider(, mockStore, '/asdfb'); - expect(replaceSpy).toHaveBeenCalled(); - }); - }); }); }); diff --git a/ui/pages/permissions-connect/connect-page/connect-page.tsx b/ui/pages/permissions-connect/connect-page/connect-page.tsx index ba9bcc6bf674..99791a8d5333 100644 --- a/ui/pages/permissions-connect/connect-page/connect-page.tsx +++ b/ui/pages/permissions-connect/connect-page/connect-page.tsx @@ -22,7 +22,7 @@ import { Header, Page, } from '../../../components/multichain/pages/page'; -import { SiteCell } from '../../../components/multichain/pages/review-permissions-page'; +import { SiteCell } from '../../../components/multichain/pages/review-permissions-page/site-cell/site-cell'; import { BackgroundColor, BlockSize, diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index bce88a9f9236..206998cf82ba 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -1,27 +1,12 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { Component, Suspense } from 'react'; import { matchPath, Route, Switch } from 'react-router-dom'; import IdleTimer from 'react-idle-timer'; -import Swaps from '../swaps'; -import ConfirmTransaction from '../confirmations/confirm-transaction'; -import Home from '../home'; -import { - PermissionsPage, - Connections, - ReviewPermissions, -} from '../../components/multichain/pages'; -import Settings from '../settings'; import Authenticated from '../../helpers/higher-order-components/authenticated'; import Initialized from '../../helpers/higher-order-components/initialized'; -import Lock from '../lock'; import PermissionsConnect from '../permissions-connect'; -import RestoreVaultPage from '../keychains/restore-vault'; -import RevealSeedConfirmation from '../keychains/reveal-seed'; -import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token'; -import CreateAccountPage from '../create-account/create-account.component'; -import ConfirmAddSuggestedNftPage from '../confirm-add-suggested-nft'; import Loading from '../../components/ui/loading-screen'; import LoadingNetwork from '../../components/app/loading-network-screen'; import { Modal } from '../../components/app/modals'; @@ -34,15 +19,8 @@ import { ImportNftsModal, ImportTokensModal, } from '../../components/multichain'; -import UnlockPage from '../unlock-page'; import Alerts from '../../components/app/alerts'; -import Asset from '../asset'; import OnboardingAppHeader from '../onboarding-flow/onboarding-app-header/onboarding-app-header'; -import Notifications from '../notifications'; -import NotificationsSettings from '../notifications-settings'; -import NotificationDetails from '../notification-details'; -import SnapList from '../snaps/snaps-list'; -import SnapView from '../snaps/snap-view'; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) import InstitutionalEntityDonePage from '../institutional/institutional-entity-done-page'; import InteractiveReplacementTokenNotification from '../../components/institutional/interactive-replacement-token-notification'; @@ -95,8 +73,6 @@ import { // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { getEnvironmentType } from '../../../app/scripts/lib/util'; -import ConfirmationPage from '../confirmations/confirmation'; -import OnboardingFlow from '../onboarding-flow/onboarding-flow'; import QRHardwarePopover from '../../components/app/qr-hardware-popover'; import DeprecatedNetworks from '../../components/ui/deprecated-networks/deprecated-networks'; import NewNetworkInfo from '../../components/ui/new-network-info/new-network-info'; @@ -107,13 +83,11 @@ import { BasicConfigurationModal } from '../../components/app/basic-configuratio import KeyringSnapRemovalResult from '../../components/app/modals/keyring-snap-removal-modal'; ///: END:ONLY_INCLUDE_IF -import { SendPage } from '../../components/multichain/pages/send'; import { DeprecatedNetworkModal } from '../settings/deprecated-network-modal/DeprecatedNetworkModal'; import { MultichainMetaFoxLogo } from '../../components/multichain/app-header/multichain-meta-fox-logo'; import NetworkConfirmationPopover from '../../components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover'; -import NftFullImage from '../../components/app/assets/nfts/nft-details/nft-full-image'; -import CrossChainSwap from '../bridge'; import { ToastMaster } from '../../components/app/toast-master/toast-master'; +import { mmLazy } from '../../helpers/utils/mm-lazy'; import { InternalAccountPropType } from '../../selectors/multichain'; import { isCurrentChainCompatibleWithAccount } from '../../../shared/lib/multichain'; import { @@ -128,6 +102,54 @@ import { showOnboardingHeader, } from './utils'; +// Begin Lazy Routes +const OnboardingFlow = mmLazy(() => + import('../onboarding-flow/onboarding-flow'), +); +const Lock = mmLazy(() => import('../lock')); +const UnlockPage = mmLazy(() => import('../unlock-page')); +const RestoreVaultPage = mmLazy(() => import('../keychains/restore-vault')); +const RevealSeedConfirmation = mmLazy(() => import('../keychains/reveal-seed')); +const Settings = mmLazy(() => import('../settings')); +const NotificationsSettings = mmLazy(() => import('../notifications-settings')); +const NotificationDetails = mmLazy(() => import('../notification-details')); +const Notifications = mmLazy(() => import('../notifications')); +const SnapList = mmLazy(() => import('../snaps/snaps-list')); +const SnapView = mmLazy(() => import('../snaps/snap-view')); +const ConfirmTransaction = mmLazy(() => + import('../confirmations/confirm-transaction'), +); +const SendPage = mmLazy(() => import('../../components/multichain/pages/send')); +const Swaps = mmLazy(() => import('../swaps')); +const CrossChainSwap = mmLazy(() => import('../bridge')); +const ConfirmAddSuggestedTokenPage = mmLazy(() => + import('../confirm-add-suggested-token'), +); +const ConfirmAddSuggestedNftPage = mmLazy(() => + import('../confirm-add-suggested-nft'), +); +const ConfirmationPage = mmLazy(() => import('../confirmations/confirmation')); +const CreateAccountPage = mmLazy(() => + import('../create-account/create-account.component'), +); +const NftFullImage = mmLazy(() => + import('../../components/app/assets/nfts/nft-details/nft-full-image'), +); +const Asset = mmLazy(() => import('../asset')); +const PermissionsPage = mmLazy(() => + import('../../components/multichain/pages/permissions-page/permissions-page'), +); +const Connections = mmLazy(() => + import('../../components/multichain/pages/connections'), +); +const ReviewPermissions = mmLazy(() => + import( + '../../components/multichain/pages/review-permissions-page/review-permissions-page' + ), +); +const Home = mmLazy(() => import('../home')); +// End Lazy Routes + export default class Routes extends Component { static propTypes = { currentCurrency: PropTypes.string, @@ -271,122 +293,127 @@ export default class Routes extends Component { const RestoreVaultComponent = forgottenPassword ? Route : Initialized; const routes = ( - - - - - - - - - - - - - - - - - - - - { - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - } - - - - - - - { - ///: END:ONLY_INCLUDE_IF - } - - - - - - - - - - - - + + {/* since the loading time is less than 200ms, we decided not to show a spinner fallback or anything */} + + + + + + + + + + + + + + + + + + + + { + ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) + } + + + + + + + { + ///: END:ONLY_INCLUDE_IF + } + + + + + + + + + + + + ); if (autoLockTimeLimit > 0) { diff --git a/ui/pages/settings/contact-list-tab/add-contact/add-contact.test.js b/ui/pages/settings/contact-list-tab/add-contact/add-contact.test.js index e162f84ba40f..1983a0143174 100644 --- a/ui/pages/settings/contact-list-tab/add-contact/add-contact.test.js +++ b/ui/pages/settings/contact-list-tab/add-contact/add-contact.test.js @@ -178,12 +178,15 @@ describe('AddContact component', () => { expect(saveButton).toBeDisabled(); }); - it('should display error message when name entered is an existing account name', () => { + it('should display error message when name entered is an existing account name', async () => { const duplicateName = 'Account 1'; const store = configureMockStore(middleware)(state); - const { getByText } = renderWithProvider(, store); + const { getByText, findByText } = renderWithProvider( + , + store, + ); const nameInput = document.getElementById('nickname'); @@ -191,7 +194,7 @@ describe('AddContact component', () => { const saveButton = getByText('Save'); - expect(getByText('Name is already in use')).toBeDefined(); + expect(await findByText('Name is already in use')).toBeDefined(); expect(saveButton).toBeDisabled(); }); @@ -212,10 +215,10 @@ describe('AddContact component', () => { expect(saveButton).toBeDisabled(); }); - it('should display error when ENS inserts a name that is already in use', () => { + it('should display error when ENS inserts a name that is already in use', async () => { const store = configureMockStore(middleware)(state); - const { getByTestId, getByText } = renderWithProvider( + const { getByTestId, getByText, findByText } = renderWithProvider( , store, ); @@ -231,7 +234,7 @@ describe('AddContact component', () => { const saveButton = getByText('Save'); - expect(getByText('Name is already in use')).toBeDefined(); + expect(await findByText('Name is already in use')).toBeDefined(); expect(saveButton).toBeDisabled(); }); }); From 652afc3961b7e038b2560af282bda395425208d5 Mon Sep 17 00:00:00 2001 From: Priya Date: Mon, 25 Nov 2024 22:06:52 +0700 Subject: [PATCH 15/40] test: blockaid e2e test for contract interaction (#28156) 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/28156?quickstart=1) ## **Related issues** Fixes: [#2802](https://github.com/MetaMask/MetaMask-planning/issues/2802) ## **Manual testing steps** 1. Go to this page... 2. 3. ## **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. --- .../mock-cdn/cdn-stale-diff-res-headers.json | 2 +- test/e2e/mock-cdn/cdn-stale-diff.txt | Bin 170935 -> 965977 bytes test/e2e/mock-cdn/ppom-version-headers.json | 2 +- test/e2e/mock-cdn/ppom-version.json | 306 +++++++++--------- .../tests/ppom/mocks/mock-server-json-rpc.ts | 15 +- ...lockaid-alert-contract-interaction.spec.js | 238 ++++++++++++++ 6 files changed, 406 insertions(+), 157 deletions(-) create mode 100644 test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js diff --git a/test/e2e/mock-cdn/cdn-stale-diff-res-headers.json b/test/e2e/mock-cdn/cdn-stale-diff-res-headers.json index 0fb0ec0f7d89..786436052a14 100644 --- a/test/e2e/mock-cdn/cdn-stale-diff-res-headers.json +++ b/test/e2e/mock-cdn/cdn-stale-diff-res-headers.json @@ -1,4 +1,4 @@ { "Content-Type": "text/plain", - "Etag": "W/\"5ae8a43f84ccd89e8ddc79b1dfed0035\"" + "Etag": "W/\"24396be8aa89668e00ba9f795d7753e6\"" } diff --git a/test/e2e/mock-cdn/cdn-stale-diff.txt b/test/e2e/mock-cdn/cdn-stale-diff.txt index 44eb67f85fa4afa1acd35bbdc3457e97dc8c30bc..9d9b4e83e97bc56900c292790947c415c00dd25a 100644 GIT binary patch literal 965977 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

mPa_z6R4`XE(Ztvnn`r|h+mb7TgM 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:q#0hSCdf8By%E$wNZ-^ZLHc86Jy1 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%0A-H!JjBkf%8zI1#RKe8s6hG>F8I9=D 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

4Y`>UySxPQCL#*}Oh(+SV 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 z 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

MYqhi>6JI%K(dHpVg+4+|OtGDe@c>H$` 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 N4!<*V{{x#!+jvz8Z0G<0 literal 170935 zcmV(pK=8kK+7y{*Jl1a;#!<#0BO`ldkCrkc*?U%IMkM~(BpGF|vW2v)L`L=|6j4Yv z8BvK4$x22Z&%68cxnJDlcU|Xs9N#Z&HhImd-!pXMThUFb@73-cSl_**HKih+hL`@V z_jZogEhFz_6xsCL%xlzSoIk%ipVo;J=gu9{eXo5RB{j;daUugJp=vxQ)MgsEh5N2X z@=g)aH<9_0SUJL&si)ulp80vWd{FA}UKsd^*WBIz{++pS5m}<1 zt_SRkG{Jq6a$?u-W-dyYm;Up9Aj64%FU@Y>m6%)b8Ev;azQ=tRQE`X6ZdPswnuz<;?n}7lKK-Vj-BlmYok+f}513`(A5o*z zgfQVF2t+<``z6gdf`#}c_TIEdP579?&`$q-m;$N0TXrf$>Df4*S3g!3&FTqtgBrS^ z6v}c)R_q1n_3WDADT~p+<8l`-K%XO!Evq#o5Ep{RoBUO%Um-zwx0u^%Mi*Dw(>~@M z*MER6&O4+}0yY~UScv4ulATVN-p-s#94~6ZqrlLbq94`T2vg^($|osk!6&^}J75)l z1|5mso)7AFIas5O_^Mm*Z4SPA3ALHgYP`s?{8@8!{+}-*RL&mjy=hShX$k*m@#ASS zpy#}DvWbR959&a`l4ot4^1eT`!hc4z92UKovT$cRV+1^6*?9EsD3qc|yPb0=F~s^&VhA+V{rEL17;9&CRnz2ch?fy5>@@1{}@J{;=i#^e4A zQrPl&D*HoI=qZF0y@UpD}^sM2%gdTsElxd;gxZy({)mLh%U9g5z0zX z!MELk5c(HcT#(5XyAtu+As?ak)cNY(?m@VIK6d{6vEM2%jTD-gZ6bSzc+u3YG(x*@ zyx)x>EQ@X5g4cgGhEo16BJdJ8r2!RpvU=j;%yX3w)BptyzeY0fOt?E?y+ zG+tz^^|^Etw;in&DyQ9Ta2em!FI@@8?KU5jTE%DmiZ49l7ti6j zLhmSt7_Q>lVABb^{zg6cn3CM`>P#!c!`&WAP2o{tSX#MF^<916iKm^cE6=tc5kg;7 zZIt@R=Qt>SxYS#ARcRL!7x&Vfj+`+=-D_lMrL}J1Wa@Y@Ux#G^`o6m8uu3fGpk$rr zUi{H7$?!f?;CA&^_9F~l`eawRC|pC3nP8m zhl|}m!LDK?Mq5Me6x!!Q8X9;g($N%SNq$b%@iczQU*q;qJ#z{i&&DJZ_QbT2#4ztl zdnTS9@*R|A+E3G*z`w@bw;eyL1^P#CbGI05{@_%r(A0m2PUhoyvXil3FG&R6-WVf! z>3`-MN?Dct{ZH4^L4tYD(c@RoA&^j(P(*8;$^#$6_ccC#5eYc*xYJ)4-#-9ueFP`} z41IhAulQVDN3lE|6x^wLR(R+>5u)vWPFx&Wbi$eRNEWTi@IR2~OR}S;&8tM)-BxO~ z5%s4Cb?~fZO6>xM-f8>msA^w96JgrplFuhkfvI?X*~^KO2PZBt@qU%wfAJt^(ds%u zK{&n(i0;N!Mu3p%?ut6rz2xN$lEu1b%+wH+R^`d|1=^q~nqN_(2#&$nK{tI%9)@(FJf zDDc=^td-Hr#me|2?Z5T$G9ZvFF8j4|OaxR1jJ3PB&ZeR|psiWu`b7`Sd?}uK9alht z{&UYM!$RNngD5mx7_qcv-6?jA#t3GLRd4SV*TjI)+VA}iVeoU< zQyO?pDlwPidEi^$nLF)`kWfvGq|cJC#zu9rvEy|4S8Qje%A7lrIEw4}?)ii8JckD~ zy+1^WgHzDe!C>wX6;h5;F3a2)aq}r`jGq7e)N{51MVAZ@Juh@*0PE3p^S^JF|AJlh zh=eKA*$#-@5>i&Mk*Qaci^izx(HIAj#EikUhxk791o7T{VCA0IJf3yOYFK-JrPAyZ+{I zcNQeaMO!EcNDo3LjC!O0Wvd9ZE_b~9y!rSAR*jB#=>C0J2LINb-e83{C-M2|jck(Q z?5j|?Z<$6je^e9oH=f4|g{2q4bxXqEQ+(%1|F0sNbgUigcz-zMV9iAbDZGlv@~kT@~*#{HvyG`*gvQee2qVs)z!7n&6L)vWlvOWu32E*yVgf@NoQi|7<7Y zA0%_vTGxv1Orv7&t&8LAryH>B@4LNTrNah=EatdF+tJ`$qzn+ zLeIu8Yq$PE1|+y&^?s_V&%qA&se(|e)7&sK7)vw#K^a)^qly8(}cm;B~t;y zU(eJ!X+(^mjq-X)oW;orT+RRcgz=`GJW_74eCdzZO?QXB{jM(W2)TVF2_p9S~lFqz|l|CNBKBx?PEeb#g2 z5OwCfV_R{x`qs5U@zO7x2!Y34{7!S}G?~Pk#<>$DwmHFW8Jf96<49!4G#w-_XO5@Z6v!AEPOF z-s>>6{}dR(;q#)0B(56lK$vHm!!OOn0JA0(H?7m|bwTm9=HiB1Mkh{vJ}7>i`28~C zNf*23_Xby?%*sX^!bd5OO9c0%_F&|!T#T-F!8dF@EO_Jcwdr{tOz~faJh-ziU-a-oa%b8@w|2I=GiJ8-010}jp*lj3{j`Bq4}ms3xs&q2z%$=F2>=d zhpEq&JhjlkLDlrdvAhz$Y3@%^)KVlt>Po0lRi|7s7ML%!aHcPY!(Coti!f+m3N4e| zAEvWp`tbh3JmXD2e{mFd_=qZhvfjbmPAhfNXao;f-ida+-#C>FGe>^c_QtJ3JaG9> zdxD(650`rEE{{sFtKqT$qf-zpm|*TR_heK&%^W+0-YGkGr#f)(?D3YBwO|trdla(g zWL(_=f2P!;!sja+&^YKSPCrk@2d@Ex`YJj0XiN~dWe3eAq=BsG*U{<=0x57A=Dj6< zx~T*H^fQh5N!QoWplm|iZCUyma<2w+=+{)u&|3NCQy9gNAUwZ?g&W9sl;Zb)fu!Sf zx<1JLnd@&=ATx~nw6#YT7PqbP%Qf8{*j62 zbTG(!C-lBW-vi0Z7GE~?5}T3uH`%`1=~Eh#4K?Y`Bya>nw2x4zMmM1sbgS1QYG)$* zVU^J2TVCHq0`rmuu(yq8AYwq@ZiD}(GzN-%t+?&`KB5Buu0E4krpIvmhr$i2y#Fv5 z@$5OvAx|r~{e0VS*3c~n@q;fi2EIKM1N+*;cOu#iv-?@}!5o`k8Z}z}Ox*wPJV68& zb~&l&%nHc6TIJ2W!dNrhYV`k`CGh#@H3SCZFR1sA0AJC zDSVW(ki`(;wQIhP{scJa@Xy5)%eyFV-|a|3^%QO-N^~0)3=d)VT~TMmjdDSpc|vnn zL-MKxo;p^f4YDc=6s-3H-@*4HNz@14wH$F?e#mc?NgWx9H~V zC%^;SqPut5_Tu!-X~lL}R|%do)T1Vs2L&&{>bQ^5VH{iMdS+!v@*=n`%iHYq}-jp2^KqY6g& zg?HKyri%8Vy~HQzxA^DJcv_MzL-u(;&mU*))txmsK96Fb-smfYk2LX{=h(SXnkY%k zTfLkV9unS0$Jm}xnFDP=g;HN=ME zw}j4Z+SjNNR*0;SbFC%6@gwQtNc0JLBW#f#YEAmf{vTfS zJdS%Nb>tzuuI!9ubN36vmZghyZowb~smqB!&mRix!HS-FezLsoV-zdS1f+xzJwk4# z<#fC9kQ79Zoj7>5#IFp4&o(~OQLy)-yev0}lDgfa@Yo8{5kLgdk@yPADQng|m5 z^-A!2b2V5m+2~un7;N3o8%_246Z~u7B6UkivMW4{UX~Cy;_f?6NMq2H?f;ujjr=_s819_nxGOInH%p2-z4k|)0K^K)Z-2!#LytCtG}(EbR6@~nRU zi+qzYyipGo=$WK@2;*<;=?(`(kK)R+92Vb)WL>De)7<-b)sF<{o#g~anJkGx==mr8 zhHpSONJQ%2amqgDKpov6_v;OVB6#@>c9T86ISI~siyG#%&wufc%R#xt%)1qjs4@=> zzRDOtsG+l5`savauD)#DXnQn z>lDZx8FBdht>X*6u7?l}1zfDb&6NN4#z)RXB9cwJ)28S?56aR%{X6=S`3hK>JT!Ql z)rFyot}vDFK6QANp?2h2soV|xDLcZiJNp^nEq9&Qm<$t#RF0vLy{KzG&Qg0n-CdA2%Da0Sp7fr$5>3-S8S8C{aiyfUD>me; z_{T~0M|Nnm1BO2v<8CW0PGV&;ig2m=+9R+jFVgtRiKZY|eAWF)OwJEv^3++6+I)YC zM|qz`y`t|o!pYfGlkr_41Gd>KA2b{$&W7K!u&%BNqt7Tk*`FS{QT_^gVPsy8&-Zr$ zgU&5BZtv@cxM9Bhx%)`l?tV94sz2XhFpBkKFS1ORdDS6FqR*siszr__Ue-?&pHyq1 z!`eOMWrzTf+i*=iEfj9U@O=B7kxSk;z;;{vpGJ=15~9X_Olsyt<)VCSS%5V2#b>m7 zg@)XqC$|JmUed9+ui8;y3|fBCo^oyfJpW-4{3t%6i1`D4&q1NHw{3rqL0nTaVnu8(O{6KxjV}A1)CS?iF2Cr>fz@#aWGQZR192O0v z{?JFuS3%wG;MK;?Y)OFNG9%@?0%>H|+x<1Dcpv}`?$!TdETkB4B8R~_m-A&B($1F@ zGDvyxps!9M@6oB2AvmqRo;=km?T5LemZ$v~w6;L)RQTV~vzvb)Ag;LN@J-zn(;+VF z1j2V-;Z^*AzSf(9d}O*<1fH*1dIwz{-@>mVvgG))lo}f!dUix$Shuc%ZJM(f> z-1ABm6nSr*Zc8;VL+VYH#Vzse1w7_7&lHcjb`I_wsXuMbQQXJbBKEq+1Lh`R$m>sH znoR76OZ1u9OUBW$NPVi$)ljWi4f%i>f{V|!7SP@-!FGsvq82qyWPgS9Ylks#JD#74 zY=QvYkqMT+$eGV#iebxC=7SJ9oMSyE-jpy_B6uY@GJsQS9mf?6lfKW7DB*XKMGcAV z?-E3F_eHd`lM$nQkn$iLZam{>q0xBf{nzCxUIEFh$*Kb>rG^*wXn^uM=v% z!UQY%$K{AIWl+dV%d9OE<-_Lg5qGVkebo7OTS{MgPU#+OWi75NF~omI+a)_bXBiTW>X45R19x+I~#-% z{qI5HDxK>9z6*s6ZT(6K!1dXgTH<$>D%icNv2&%uRuhMBRpc~3J8%^p$NrP6IbJu5 zr;<;1g6}x0;+p-1ZGEK;e>{18HCq0HF+X;E?$|h0(pEwz$6bl9^Xg$lHYGvGzL%RuIO>v2^n+h*14EY8>FdgzT%fyFa@x4;#wh%16331)^5)>8ZzwY*|G`Zx zJC>0iY7D-D+rLj+8U)hLz+ik}q<+ps077YSZJS1e|I4EH3b@v!PzveE!}%O%E>5 zP2^WSQCh``h@t5j?u=9jRL|b->5IPy_u4wDIYRF+$Y^y1Tu-v~fS;F(-ezE<6M79Y zYkPO^F~Kw>bncY%6l5HpYxzDS$Zf8$J+SO5t+*#P=D&` z^UX(r0*uLJe7`S>kAZs3m0hLonlXfv?y4WNa;Ha4pGdBRu-6sb`m;;9MDu_Vfj8)U zCe8X(9X)d^^PbPK^jueEp#W zntLOtS$x{HN}JY!rFV^|iWUT-Fxi$z61*8PgD19UO!pp@(ZaGz@IkD}axoYr2eRp@ zKc&FejF3i@OXMi>)_>54k23F1KchV(h40&AxM#!BUn;l{QEQnwJjipqZ=o$w<{zcP zx1;EN|4uhOkU$y@^!Go6dKJ^+mi_H>JJ&sWKyAj{Bxe$l3e)f}A~KKmjQ6p(;0rcG z9|a^Hy_8n|`==bl87kY=rb|y@guV9oY4#JJ@TX9`=_JEeGPWsZOw$x}fdX?SB0pc@ zWDFQ(UMuRFNr#cN%OCy^kHau~BMd z3e)oFTkiaqZa_$4zOjU*;u2=w?pU%H-)up+kZmoqRM#VfBQtkcc~cp?m*0kecRrmA zW*PZsmn>ohG2P(I^ZXli6=EcEmi+?-+HgoKn2zDdBphqn$qd)aD+^o(awT-{+p_-nuc@K8CZ^4xT|{R)OVyrO*fgAaJ$0z^=4dWE4n$lCve zQJFTop@LT5H}%S;^Qdm-SCAY3IR())(@_%5cZqP(UKTnQIlc;#SSuJ9>}P-&<6R^4 ztQeypMqNzCrCJJSB4k%58)r){Og z4iVg)Z;t%=&oKzUxspwE?kblc-y-+;&-GR@5LGiY?gU)!gd_8au%Yig3X~AEe!l!n zauc6z^`6vot+?R*ZG*g5vxR%uT4W!)CFW9!@~7dyQ|jafFu~BdF=>ez$uyJaj4hh=ra=-WRjv=N{Jt0&KbSq?gn!=P>RM9a$- zUle3?pZsHh>tH=cU;MYG)(2h$>YEoX4SdIsyz@3MWB$;hzn5oEu#ese8^O&YJ1+!t zV44jP5qKQGn(ltckb%x!tA6dug}=hbT3=(vTMZH{!7%SVPF z6#nJJ-eN&B{uFT%};v0Hr#0<)^d^X(U2GhEkvEM_Nc>ERiOKkBskVTd6>@9lUJQw&r z3{BT6f2odGN@qGP#+$te$Vn=QiFhRo?U_DZ-&?un=sRV8ufvG<4Ia@3sHYZAr(jn& zEm_Vgxf$Q6UiEs;A9KS(fL*=?0fQdi@n_AsMpHP!Q8Rav+VE2Pgn(nhz>u$iH46p<;aQ9q*snlsd$_s&@v;GAIVo)| zstb84+yu~+=ByWtkysoYBVdvYNmpp|E`8zq*VWnk*;U? z9-{x5;$ejEk@rzwM8GU$nykW>7zSHsofB49vPR58<|f+ zR{BPcg0c1)QS1Y6ZWyc8FOsS2ora}A+SurkWmkB-XpFkLS#Jf$xefo&&iH?L*Kt0e zmFt)N2LU;uhb9AwfgOw?5-X9W0ORSgL;JA;H1S&~9$VgLm4{tC z3L;?>F~nIk{@WgcT-;}uwSo+wBqgM>l4#hEf*(fN>I(UWTp1!SCsQ{ zKiQYeCg2bYkKLV&wdmvlJZAj;FM2}r99CYr5cbOj--3HqwWfD+TQC~SetENc-1!Hx zf=Y84%TG7q6k|nmHKt+-jJ7OP zYtQ4UCAVOuc)>x5iQ5r0o(7GFhAulIO|<6E@3?Ym*k>iCJTIsJf@fVej0SlYCHpT{ zuKH@2nc>NV31@)Q$X~p>=b1fkcUc~#wFyyDALlZ`z;@saLrzycwIKdY6zBv+MX}(B!5ec$9;0bE+YnJD9J=xp{87FG zUuWc6V6~Z1$3CYo40>W~?Mfjn4SeMrxJ=N~2uK?7-DMDQy#vC(m~GG2vIUEW-ISc| zkP>`YgUd~9_i^9z9TW2NHlm+kXU5rM*Q!a3L5{PBm0HevB5%fgi$hXM8I;7v2jwj6 zAHX?P_sHS7K0Dl(rlG$!FIxk>+B{E5QmbSfQ}?lyww$L!uVP2f)!Uo-xDi6-+uJGH z1Su_zY=*YaPDo%ixXc`vSc6l;nY{(}e9I7btPA_gpg{=3V-q`0xBfAs@%B?=i&ve= zXm4=XyizUBfwS5YKC2gB-bNO~LUwl_kvK-C;zpd9IjNu%wO2zvuagGf-cES$-^7OPUUwX zsTO~skv{k}tf};!_B6EUF&T07ea$7Um-rRN_nuZOg#h~NMbbGkf>{VzCA0b1#qSR` zFSoCK{pE)tF3b7wueQWTydNc2Q`)?0gm0lvgpaK%x)7dqTrI+{Xd9ciZngw@3>Tuh zq9Y=uz4R;2Tz^Zx8~SVqv^MuRjR|#w5y7GPj{bRUAufe`^4M!mY9T%R6Cv`;wsBD} z`Z8@|LOMptuF@&qSd9i@wsNxmp7C?2pB;AzQ^kx=xL_w*x;?*p_M)AH7Ew-JJ@a11o{(nq)ue%2l-l`*p5Xy`xnjkX%^k zi?!|lgnyk_v52%^!;w36C06(K_wCZg1!k%7M&^{_p{(4R`)NP&5s0afYZXL87mBKtb1_xBJ&oG(vM zt%3ldn?eoo-CpRlRZw<=IPvS@X7 zEXqbiS$>wwYo8vRPfQe22;kP~`r5nYgO8#;mqZx%qsSR?3ZmLz zB>^pev8^>+%Tu2WWISe%PlvmXJ@=-Y#+Td&G@nJApX0>^)n_z+ zkMe=_x}E2R(=JpXXQgM3PE{2K10OY)u-U7}kUU&dy29kAhIg(}BPZUkZXtE8uK%L{ zLw!uXB1G!@!K=8oOWe>qEo}N7LY~20;la;~ ze~}k>?Oz%zK1^ZI=nCYM< zJCe>mO1H0Uw8o`xWAxsda(+&r4Ji+8tWj;ps<-5IfKK&O$N#{L5-zUDUaWZrPoW(A~U) zwZ`bbx-T|fgGcQ7;Kz^$3W#Q~5??yg$^%Y0xsx5w&kRR7 zyfk;1+alZ^eyjJ^|1;nHfRyT;#{vSyUl8ePr1?iY;~1K6TKI!1z9rwf5Wy*&hQAUoQ$~6exltDF{xV?Pco_5#QWa>p_sNpTykS6Qn^7R1 zzFFTWoh*rg_N`|YAL&!?!(@v5vb*)*O^Au`v$|yrcp~Vn_yN6hQVIBe@$;1vQ0;-E zJM%T8asEcMtj44e{TCFCV|i8T&pjRz;)LCI(~?}_UM%&wKRUfE{RW4geHHyAMWqXw zjA)|dw|s)A4=%{q?6Fou^=pRV7wI`xTKTUw^f7w9=~I&bw;+4yFaX zd@jePba3uP;pVmH3iYu1AaI}hw)Gl#?Lrmo-U{#{(zN)NSe8~f`cIhNDOA394ScR$ z_wsfb-JoVF&KJsYSrtpC16X3B{KRpKx-(7x_TD&}WZ6Uw2;!CZF$&Cr~BEa@o=trknCj{^dv-$ z5k`9aLM%Xy$RE>?V?_b3<~;!l$XA7dUqc+V1AJBQyh)kVca?Q*tyc~NdX4zg#`~1 zXb@qf_F~?yNlHD8csfr%xu7TwJ`(q{pWNuh@Z}lQ8;;&!!S?hO3t1x;Z4?T;xWxLx zumb@F+w<2syN!`BW#AyS+0YCMT8>5&{_!)=ennw6k$Tbz%NZqc1zFiA;3D+hXQO-7 z2k{2NHc7%Sf-v!$cWOsVCIo*RX(H3F^$KA)<=ggyx%dDi4BJW&PxBIjp}MdwqOYkL zovlr5n^pCmcy=+t*o1B52n4jUh6@eWzkob_QR9T5sS>_g=un4?9{qu@l|lIv8w-C? z@jh>oOJZsT(R7Yib-MD-BZ)_vaX2bG0>27oNLY%QZec+=U6|qa;bV9?+Bd5@bMhKe z66j`BbB?m((Qa9FM#wM|q)N{etcWVjLDDz>CcBC86Z|@uXK;(!oES`YCZF^-Xx2a| zThi6S8h!_^BMxSMNtO383F_i6et`&OmpQ=Wxy!hF@6qLZb`3f#e|LGg16{9<*)l{{)4P>&E6inAimx* z)=l=l$cvgL-JRwW3>rwLV(p+`i3@`D&R@HMR`LIkUmSe**W8Pz7!9=fMp%AH07i7@ zRq}7`>%21EXQ^go&CNI}y+OzIe{_Ww^crc~Px|SU@O|!s zqid}i0hl)9^(Y=s{ezaupHl+~@7Iv~%T_%9zlAZlW-h*R&G{7y4wC@F&z(zo2zc=D75h%9%sveh@emHUfJUnI$31T4Sd~v66{&xkmGFUq%y*55-a? zL=!hbfZTJ}ZaUEi?*a(}&57C1z?=g1+DiJoB|r zltmHO#S&+1wbDN0{WWY1AC4%7z$bIsmw(gu>;K6l|HA(?Op&O#72rGnnGFYojV=!X^hU2FY6xz4wQmYYg@~hPGxmeL2PHdV3BqfQwC;QZ6Oq^MhlaJ2Q2p0OG1p+F zhWkH)Muj&%QR2iv+jMzzZXyEDXUGXxBpikO zJRRnwDrrw0K*>5+u!f|pBH+VVaqaI0cL>Buwa>>evEjL+u(iB(V+>yKu>{sH9Wq9j z;6t4_vFcG6J8Z9Xs)%u5E!pz)`F@#AbkRBpcWXCm!*9i9VeYj24E`Reia7e8>n&uH zM>mw+46lQ|lEA&r#LYz<9=Tq_*mM0A21uk?SB@TcgSoGx6er1EK3q5buABK7{f7^H z{a>FeDx62>A(3b5A9tD1-A?n+RZnLCXNC2LUdtE9U`;czd+t>37i8EQTpXgSJA!t? zv0jVk6=Y~{Pp0I5H!lRSucV4hDv7}u=8VyLKjftnf@o%HT#?T@F+RcP}Nxshcah2)0Pxq;y<39;1#JQMEOP3}HSP53M4wiu9OK9pxaJ6a3*YioE@}vbScp?Y zq488TCTt5eZOm(_K-91{5kRN7ib)-E@~*X8?I0B5a%L>RBrb?u;6G0=6OZ`!8m6_; z0r&Q`&a>>u-#Zdu+K6`03yyHY>?dwx4v%eL`D!(`wSk+1)3%HaO6O6W67 zVI2AbR(FzVBLc9pz`vcWtx%4K)w4TUAJf#4S20!n>%8&{SW9H5x_|Pjh2+p*iCr~S~da+s@>Df{0y=xqRaj6yF_u@Q?U@LH#{m)tcAM7sVkRAKR;{Y|!if_)>)V#55 z{=U7dyjcWy^d55%#Zu+t2i?>tZ3d?aTDUIgU%MJt3QFHoce`@JcyKc7CQrLj_Xljf zR~0Mq+dZ&9$JV{CcC&|o=kI(LXKA`L(pyFTc2>j|<6T7?6 z&zCkj4zHOGh&Q}dnL}Lwh)Hz`c2OEBw8tg#jujl{!wUDmO1^^uPdB|sY0nX4>3r!g zE_O)98P1M?pC@F65LB)Ioayw$7x>*{OLDz$9F4Z9i|n(#-j!IfT9@Cjy<7^eYqU8% zO((q3&31#1wQ}JOG*4O6%vejR;%$gBd*ijF1-#OoS5PeO|A&W6e_9PoD$c{#a_k51 zt7;wycBvkiE2+MNjO_Edd4xvx(D7FN8Fst#GSoGl{<<&RtVKv7!_z;JkKywU};M=%)$A-f3G?AtgY}MkaKf$dpJlGMIqkF^fhO-aZiP0_L186aqt(+eeUy~cn1siWX7A> zWCb|%=tQE9&FV`W_)q$o-NQwCP+K}gc+l*W?3=awkGURPP6y%Hk^{HDKe0h{wT=Gx zR@DGVkDi-vyfa^k0)j7YPdcg(K<-;Ko!^7TBEaU4$^n-1KaoO|NVf8dabLfV_MAL9 zRG11aDc{4-V{7(}yeSX+s~ARI+?nTf8$bC@0}Ds)I|gvm{e?z3D_!SQXCSV42X?8% z@_a$#<;?T?s$Q-L=O_HPx3kBF*vFK`BS#;yLc{55*q4-Q0n9r@Fo*d{5bpyalAkZL z!Q`EpANk^ zd-mkr^zf4B2%cj-p;Wp5aNlZwNxbUv{RQYggl(Q@S5X4HTaC5W{Od|wFS5TL!f;s@ zQMMEh;wB|kAo`0vaqmY{_)bs)iYCdtH;0L@pub3wh^RlL3ImEH?=u2O4?&;!)R(TQmR~rv6HO21mLg<} zyTw(No+JSWqfp6D$^m1j(sg*BbEa|vk5X;VPQC&?vbo;GI9r4%!!K0%kl%J#60G0+ zavv)w>D%qtTi`4z`VWa2RA=|4y@brRc1BTMg z)XsF;1&-gB$w&}V)As30!r37Fw=~sG zc7N?NC|)ko*MHV;f<*sCjmiwUOiV}&^6<;;$5eyn^NtH`jVVy-GbGFTWaJ6rvv(E* z&Y!#ow`a6%(>6NJC~6EhVxRaY1#OG#dtI55chSN!Wy&PXON;!BjDizew>{ADJgDk| zRR0;6#T|O;vH0&T)Tem#sst#cK|6lw08Bn5VwW{mM7k+L1%<4+f_%xv6o?r$Wo#2D zF9Lri?E(Fa)=pfT$$l_t;kAK<=l#Zx?)e-bymIJ5gKJ_Qo*iS!SI|C@hC^w=Pedk~ zU!XDGi}qB{ou`;de#0x%bi4!q&VA?*j1RTN%Of#&q{?G|U_ttACHKh-B51uTyLp%| zNE!1ZPfdii`wekMvDa-lWq(R6Wk0%4BpJ4Y$Jw&PU(;jUkS9?WMPYBP3}5}blq&bx zD{-$jP?!5cRxCJKraGCX_yllsVRens%V!Sn1bf7iZ)mh)@0&{QN^|@!{$+)D6@Ss^ zhEzp?x`v&94jy%6N@qv}9Kx+@;nP8NKVD!L2MM~jI~O3e#dI^d=CdA*9*bQ&%KPdh zYDfOo7Te9`AF#OhjZ6Hv?7mU8sn}~`Et192ft-j1(NY`Wr!(Vkc9CQ6dv2xURh>3rfxuHaI0aClnjb{|d??TsP@6oT8S*`uXdW@m3TzVWX zX73Ivn@4eDyX9ZZKm_Acj7Y`2lUsfN7N>HG`HrzCxT4?c`tm$WB|Th}9#`@FGc3ar zOL*74ipSj$+K}WYt=6YSS;En!;1dT=!EjgN3&W%M3K%+RmYrw}_=Hzu-P$YMmCG>r z;Z|m+UPuNj+wmTPy_g$FSaio7M}1o~54WmHJ!8tn2jlfA#!*jF=zfSzNJ}kx3O74~ zjbmFxr(k3-bK}-feG?jZq=(A>epdzAi>Fsx9v|Ss;JqWEZRrY`D4jjs;pijPj;%`H zBhhr?+^CiaFTA{ToeEkx$GrxtZ&<>6tEMP%^RNa^)16?tIUIQmGLO4P>${xz(4BLn z)j(Qf1;Tqjd%loe{|WVEmRkA466a8KegBL z)~#CJ&3*h)*y<`gu5;Cd4ssWK=(qEjl&}-)U-h5D!w6Vzy1UQqw{lGLcnY3K;!}l* z^JT}|S-RBVKCmm)W1}#GHLod2F(dx~_@_L*_~*l%8$K1#9#Cu5Nye$0*P0c|=_eqm z*-dcF@a-GOm-EaY=rR><(n}f!@I5Y~zgHHi&!zP22yQCWg89 z*Z-LGT@QfIuqWN_KiOMw^SKtV@RMr~!etVxH^0Aqh|>hCf(y3u5x7cpt;Ii3z7?D= zMa)=)i&GGje4voh*r5X%alao0H$6{4DKBHrwSd--$P-y2rx%KkL3o1XtZ8*YI*#P( z-Evx7jDGf3WS zIud^Mi-xR1X0}`Ew|Veg4~ll(MiM|s3j~m z(z)Q&1$zR{s`yXMGC2FY)`^St?i%{;er2-s_WOZ{E&`S;$?U#}N{RL`DYblri?5%% z#@Se;BJPd8=Jv}L;I{AB?6-qIUgBFq+lP#Xw$o_1Nmi;;xe|?GF{ZQZL4rz{?re#s zFl^{RcAt73ugoPUjGnoEg88AxQLK+Y>VE(xK-s^`uy_ih+zyr-bUYEzDj)QJ^Iv*8 zy3-d$3hq#ZquopTy8VosDSG1zedu2MTtfE?3ey)a|N8*zj3YjTX=(p2SzEaiLm6^V zwe9|D#I^bsYIi3yw&@Rsg6qxdsT>83Dzy1*9pG#F5(fM1IRzQT#U*TfV2*E88T^0? zN2MK~ojr5}x`FymmsPhn5K_Ox{+CPg6-e*P+x*^H>V(+ix1%FBcq^die2`Pz)m{Zl zH@H)B%5|GzL+(pAnEIv~dIt-rEd8cLF-ke&!!>`92|mhiFMZFt@euyLe2N+gGf&Z_ zRz%M9nP&kvCOc*L-hS~yb*lN$-=?KKkkCr7Pw+f5duH>dAX&m;d3) zVO`RDzY?VJfl!Ot~4oVMZ??s3nyMd zB;n)PgHQSqEj65ew2`0}JmtK+V_RMa!O|eL**LMEE+ikaA21Ztc?PDshl7PI#{Xci z-0-#hHTM>x9t2r-vrva&$NRcC&D)|{eB6q2v`X8F#O}BcjnRPT3PPUzRH`b}AH((k zI5=HPpE&oMNF zA2n=$>GT_92_meNUSk(A9WR(A{HERq=d5K$tQX9m!&NHtGWQ9_Ev$C^NK5?M$&R9* zf1)@dWK@u-&DZQQYRQ3}*>H8s#q@IU@+n;Tyqt6da--F(6P)uBcz*eiI7z?G8pPK> zbx_r(`{89{?4#q7gJw8NR$^cBDd83#8lDm-?eQW7NlGD|&=k`iRJ_c{3KI>;CKOnA^0nCmxt;`F=TcM)hks(HYjj+O*@dj_yG0SAFqUo^2dQKfLKTVencOhj9nCFeMS|A?c_&C z33ttkaCzLc-EB-%6;EyoxBYaMe~r5Cdd2Cu*=_Wk8d2u>c=aVnlA4z9XAbd#L(zwS zl4WWa=04ifC)&x$K&DI9C%(xSi&Ny(L#2F&wqP)-Xt5uHAK|`2EibzpB@c2D8`pb; z)fxm3+1|>p1~{XAWm@#PPA&nieczK7m+nl&CoXt_RCs(pM}GyE=n{e-)g#*Dj~=3A^#C(srz`+LY%sC)4UvY z4*nknFX&RhXmid>^_Bk!<}`y(ZftaOBdV}l{!^Px3?`41Q8N8GLuRuLk5iLorQYZ>PB z2jW$d?Ihw(KwZEgM(`4i1(_;!cmcFf3~^j?k;Qv z*0)O>zpkP{k;B8#Va-H{SM@@e#{EOeer{+8f`-)q4e#u$F1+Q@ z(ya>R|2PD6r-q7|HAKQJ8KMOlM^8p+9bu7jvc_LNMV9$2m2TVij9NL`5CvNYBI}eH}Sm~K10_( zm|_1P%g=L_Z?oaHgy$yTmmH#@bc_qI9XsM|SBpRY&ZeCS>->Y_NkR&STKY8ziill$ z^IrNibU!^T21UCxdc1Ak9yh*R4emYfgQZEoI8d!ecv4Ng><>ar@2p4O3vR(5A(IiM zriC)ttnCPp{G2%n9|v+Ro9N76m|i+5V=rp!hve$4+X3I)MsdrHE8FCc!32i3XGo;& zCC72~$cxZ3ua8XPvz7Vg<19*B_?(|#8ocjTkMCdW1^>zz_d}hc#Iwm*{3<+GeYs9( zhM&My+Ys$EgFEcFZ$tec>38!#cK%mo(pXtTT9rzp{Wu%__1%DNKxvNT*{#CMaSw>;1 zA@<+e7?sxCHH;l_einEpssnVTmY1uZ{=9}9p`o1z-+9RpEKBg$l-uDI(iqQG|Giqn zf=BGfzCODn<-Q+f#R|?ZT=j;Nuf7{ z)he2Gy?UNNIAxwG+irX@geH5(;Lom!Ca{Q9G*?aTJ&qCU%W~Tcd{GE{t;R?6m#`IM zU)5_e{8fz5@pE8MlG-Q$o+E@x3$*cEaMh_~brLF52f0pI5@~$YF}(lN==s6-pglPK z2|P$no;!nJb)M&@<@EIk_)_IWud+#qkOorDN2fNUpssB4^+xT%Au!16#8KT+X~9+5 z=4eOfFQj-AzE^*v>hXSg6nPfz&n*2I-yG<(=i;qJu`6`=MH1};ZA>I`cF5U03B$jw zI~tWO^;~dc;21bQzsin@51ilg z`CTftSr7|94+XP5*%^gN;$G?eBiGX)u@2~L*G(9~U#HJT;b)!L;n1D=_DML=HmI%k zKIeuT$)hd4n4x~{wK;AvZsj{UxFy51oW;G}Z)_YdPd^v=M*heh1UNNw+>ZbJ_!)A)2At#AnQexgP(px1wBi%A^UZ2Utxep-;9@#0O}fD-%1=Mf zS$lo^Hp0C=RUQ8Ja{^(J?G$ZoL&tDAe8wlppJ*J*MVe#wnKz~IAe&3{Yg`>Ae(JF} z9oll}20Pu|_wt9{d0=N`Z2UJ_aRB^G$9tMu#%{ylL*qfRiZ)7^yA%XBSJ&)d+Sh_h zav{VSsU-ek_tkB4@MBZy-6!UPTlnO5N-W7kc>)Rlr6J3?Y zHc~TCoF?`Y;`&B}9R)e>D;IQVLEoLs(H>L$0^GE*&ok4`)x$4LFXd3B>oM%@2`aAT zo1aHbeE*|9Q+Zif9Lg3f%Wb-h>wY)3nJR8-;vltP&v6qcHgHtWSCFm$bi_vsPR@jd z$o)0(BkYe{w4MZ3^wfhK3ZLoYX2*mN&uyi2%s>2isUk-}2PVwIc~=7|4#S!L=NtMD z)se9L`H60(BA^XralyJZDyrV_K(n2snQ;~F_Ay*gYN zg};+klSsB&IZih|;ZX2=nF5~;1^L-ud|9x$sm!i&mP!hbt;{`MiFBRCzq#V?!ShE= zG4eetrCR@W2^7h9JuLIGc+jSLsHGz7*Gr7woM%0BYTxG`sc3ysTTiZx*N*Ys&sv`U zhTW`24fli2bKoCvpf?V9ore*(fvlM16MXm(z;Z{{Ls%C_Dl-Y_9{_g=? zzw*FXKjK9?lJw)Y-v<(3!{|RXJ;98Bk+5f8r=IAmJC9ZJu&f%_P6cE{_p%myERkYe z%QIw0K+I8O()1fvPf9|UJ?Yr_GUd@D1p6`mCgtn}#YXombHj1-& zFYnd*yW;`ze_ktNLg6ssI%7TYeie8qVR44b#yT7azx>i*f8NWE?44_KG|qtmsGTlK zUD^C`1OviB1BAPqg5VqecI|sZo-$fIZz#@0Df{5Gb=fXK`S(LGjb=E(UB6I>8MW(Q zVhldFVg87R#+&HeZa7H?$yVpvL@m@uo0}_uB~hSmtdI`bdA3 zFzuNFQ)uoxfgS@(R1WKUKdmGA4mvBR_qDflw(yMAD~Udbg$A>Ep$4~y&*1 z*^mZgl&|*P_`ng33@K{AJH=*=DD?{>ihMiO3Fp@L#HMFRL!e6RbLiXN&10A}Uy@3` zGPwk<5DEGFdH*`FNt|%x355YA?0Xp^#oi=e!=bt%Ir&Y~9oQUq54*Cf;07MYXn%1z znKC>$xAoX9QGNv1TzPH32~wD0Q?Tkk%-Hzi%1u>5L*iX+kQ-|seBHi3!-tA4P<$M` za|PBOa<2*lADQCe*c4ZSX2T2!O+RG3zSppVfg75B_iK#HuuH13uzW)39p2p28y-Gz z_&k<-JHr}O4^3fsPWran4anZhp5P6+(MaaQk3|eUD6`O?k zA>PK-Ox|yQe@*=?rc^!ipAoh;Puv@&c&m-hucmE#=dL6`u2e_TCo8o9k3HQ^Ek4_B z#NbAPkjUYpGqC-7>L1tbQF4U8npyVoh<^&M_08;`95sHh4Ocw;J8ee_pLd!_2k03F zV0pmwiN*DUBe<*bUlUWkR~^hUYnHW|b~KP@%bwK!?f=u}xN}zcVM>SnlwwG?7h67q zb5;o-mGyMe@q#``{sUWzC$za1pJXLIy^rs;I$t?#D0U%XtxS})#z=?=?#0D-0uAao z|J3Jl&u9r5uH{Gv_*w0jhy!cK@@)i&nK5=~g;r4H0tdQ<6qro`f}x02$UR_$w+_l1CKKPajhkl>wr&GZ`0oWP(IkN_Jw{WZI#7s zOWi^X_Hl2d{`cX(s2M>@4A@n;D|>v*#zF}J+8TaZ;Y;O8?6^)#BaBNGUuk$hF~ohA zch;bBX92PPrb~;Gnksz)DnLzP&^#C+#qrb;$&m_89dAp$jP_k zUeOv<1J{L+{=28Cf8eWfM4GA$$tKJK6-9ktwHjkdaK$UK;o$*HMUwdx(EX*te-gBb z8{_-wE4`B}?ylY!ComlPymumm?;qj@&GX8u#hGD$_r`yj#MF~;*6bn-8nF;U%!hjd z-rx5(e)`hATZ_DgtXMnbrdL zTYOh}5p#TzPMl!kJHEFRt{R0|Fp><62Y)-Q66p{-4B~F;-l3-D5as1zC zDId(ge+kQ6)$YdB`~=g6`;%|D+dLeb+@W+Hf$bev$Fm)*kdYW5L(08)7GWowwJAnv zG9i$9Ds;BelLtzLAs;!-d#f;?@u-YwvQ!-A6jeK{pxgbswsk&t>6lydWkq3)YTsM z{PyOO!$C7n!^wFCEU;di?9dEf(8;E15vk5}7UXYeapDC4R4 zrW@jh@)ZAjzC*h|0ajaI2MRfXWrpv)!CgjH{5YY_hK80j5I!q;&S<9}4!{0iW|wlv zcJNzw=tpJP_YpV*5!qa2Jm!cRw{xbQv`lTFKCJsNX5NIB5@Uk+s3I|$=>D^Ss_fTq0@-31;R}c!L=bS`UK37BG zznZJdONyV+?|0=WQEC|lHWS=iJ{&TL!e%FkJ`l-nTPC+(J-ly*v8qyjERvCEuB;?O%S|EXlQTA;ffE$ zjd{T*MEtPccRHg^uyqL`FUs6hgKz(VC;i=bPl|8&&ZX{yvKG z{l8ywLrUYFHc?XZP4oo$rDhqm@gUmXfFFl6USVB`Ds)?>?-X#zVKv*X`z!KwzmrS4 zmJNWXPTrjD>qr2&=Y7@xIPZny^YP~=)8zcg;H`3#NMXuM2QG|7_UF#!X@6R!{cw=9T6p!-#cs& zPlfNd%~t5dw20h4RKE;!!{e}J-JU~uKbsY=!i{Yj$o!Zvn36X3WbQ&R+>d-ZysNB{ z0-@fUN9*b1xZqOh!BR2G{thfIjLuh|T=%n#c0uAA z$Y+lerH5;~!|h^O%kww|EwI(x=3u$)q6-r_2I`6(AuTLl8?KUI zQOlWM|6cJ7fc6Wk*=CABEOyprrR5Io#{;JTv1|7Pb#`%!>8RDo;4=|OZ~Ml+tZ2N? zpHB;KyM)>uL5BauzzRivesDfM^L}()F9^QzS0C#9eBzDKvpuetKL&3?Y0~hsOW=JD zY}cOju#cV_1kZ=xuW8)GC-6XaO`Y!{Qy`Lhk4@ai9mc=n z@8FSOrgL|>A^-dWvs;I=2Yh0Ldya3=b|NBPdRV~Y)hVdxO!$&>?|*0I295)M_t!n} z{U29q-rbUIeD4sGHnC+X2A8zLvqCAqD14MNkT~&!-4JH#bul5L#d~OxU&s)#k;=s< zYa7%5!ZRO3Wc6T>=;I1Uh?1}Ec*snQLZ(0NgnK0cJ$julx%+%S)s0_^Z@qk{i}LZ} zzfR5L{?_bhn&;y*H~P(nksxK^@gVL+OfU1lr&sMUL)CW6%E9gvL9iGLI^nr7CJwGT zbDm`t{rg@?j&kYWa!=}1s$z7 zL2fF230Q3&gjHEz@4zx$GNoGfeqFLS_Qr}M@&*YcMmhg|SK;^xhdZsaIyNpLaHe>5 zfhaWB1Ahv9+bv#P+rdi7?*rvz|CwPxBvh1!iMrzGB1&m7fKf-;y zqMNZBLJoebw>%i%<3)>~UEZ;QP0-8Dz59Llniq~Wl2%DEQ}M$#)XDbh6zvmaM?L&y z`MBW_zV}V|AoaQ{E{S;FufC*}3t!SV+#zcd142* z9&Y25h{O~w}y5M|TU3Cv_yB5z(Ql}UzX10OrNgImvY z(m;suK(wx{>@K3zBq`Q>IJm)Jo)}up9`y$z{Ii~8oj32Gr6R&2T4|~t#U3A_6%wfg z9?o4FYPHBKXeblT5jLiN1)bW)Jm0m>Z`hJ7aTa)?)QsSt5K?YJ)dy^&8ByX=$e#-n~bp*~x+f(*1k*a^SjUZ`7|YbiKYx zJhN`U4AH(WLx$c*Uf|$7`J3y--E0({Fg2@q>uiTfLn3mL#Q*zUXBZqjaTW}ITS&qXuRu$Uj z$LaB!rkqJGvh)Dll}EezY$I*qWw%S_8cJ1y9|QH%97l~S;k=hypB5x2i*KYnazZDx zH_@F~V6>Y3)g4M3e<#>`rkX)&X%-W`)uqG9OSZK0-8P^bq*Xq?i|XWqv# z$r3s{I>#G_2|3Q00_Cbts3u8h|MRVEYWe_=)? zcTl%+V}c@XAJ5NG`*=AAC&>ePFVrvw!0~B=j%f?wG(_VBzMb`YND0*^*AhMHo804M;od@M0)BjU)&K5fnglW z^yjTjE8I2~KTVazErS9k`P;iGK|PT6k9;4H)cO{D-g8%>UKQ&c&WpqXrch=YoC7c?j}t`<$B}5mgxh11jvPmMrfSS z!uB!x;fp$wOR%QmY7O~VvV#i8#wTCf_IG6hnXT*c@IVfFk8(MTs$Z4FLauORxbRpv zTfc#CG@Ry_eoyflU%2OqlixZeWB{qKH=(~>ydUf(X|+moYaazYSTBL%QhAs& z_?|Fmywp@z2K}B2hmC)cAM|P!WV7nCLeZLe`62;tToCTn^75=4RaQaw1vZTvLa|HO zy{j4VDBa>D!bvS@j7wcYapl|%=875-4ZM+fdF|1e84c*$PR4nwI~XJ7P@v}Ti({Iw zNGx$6m%P-6?>9z2Q8cBd;!ka_cKMQ!k z{<*{UkX~so?k~(FTM~Xa4G!kr9+ovh1qg9y(FuLOeHz?z?F`y72VLP*MnL(I_B8=s z2Y;ANer276=^6jq6epvKusHA`gp&Rn0ap0R$X5$r$|LK?2@ayzQEN~MR9`rJjEWG< z4Lg;^|2eFHN{afMs(;`w6bC+(jR?sfg==fAhU9zwMVN+~j>RoYZ{fhV$u`M{Dw{a< zREwTW^g}5UD>7-l1wK?kTjLLfgU#g)v+&!`~;zL zvUg`_UJceV&1AO+kE`QOd{KJcCpKji(}}5EUpW09HeWv%{U`cJ3tNeMzC!nI{6mt& zoof0z#t)b;p>fW+RUL(s_P1#h`+k;VXL8PaJ><6xY|~v!TVk0RQ4+WP^Ty`W%UG%C zGc)QrB#uXyBfc{|$aoLG7+~sq?VQ57Pqsz> zb0<2LcXpqCM?NCrY;Xf%`6v2NO%F;mCoR#On4&ua@vme$mM5Cs@mJqYv}Na{7fgnQ z8pb4Ll99^H$sNbAM}w(N%FXlDLTWI~Qhj{PVZjtKVySaE#?V3Z2g@B&&uMG4Glf?# z-g3Ty$Ll30KNnTzBVjpvLYMTOGu8_4tY-2z8R4F({gbJM!M~_{G4%LjPggxk`1l3W zUxn2}*-b(G;b!zaoePuL2>wu*&W$5GZcLN^ZpnC-wY;&$$dk3UF31?_C=Z> zUdKI9esXB&@Iu}uiWk4|Zd@(f!=#!2WA;5;7F;T6c~w_4{Qzw;^)qJ7(fg!u{i?V0@71LJnF^F5G6i*y@0uVa5)X z=ng%+6Of@1kb541+PKDEeK zHjW{3^?Cyx&Q+n=O@w^Zt92PWT|!d6ioa5^SX?vf{hd(@*NC}3Ih1JE;l#lSx}lr~ zN4WNoG+&$9NkU|Cdo1A(KUIVY?}{EN5Su_Ji)`QnQtf&SL@`U<6h0J)!$;HflzR%_ zVz0rb*6-gpN!%LX`fW@$&5hTEu0F2>NZKG3?>}|R*0Txx#Im7ITce%$li%{|v_z~8 zT=i@f9(xg;+kf2B*XsrLvQXpa{5p+snGhaYSH9~$-Dj{4NvDU%Ke!!&>kwNMjiVnq zWbQ;Q`zquU;EPjHPa9s6V^D0#S2NTj28Z>!J6ejqJizr0i(=_&+WlZp`ON3)#2scd z+_kF}`&qL8lTGeu_#7KLf?p@1f?4C)#K7H2Ak~QyI;2QzZaFcJ04Y8Vs~JxZ^yBsw z!VllP+8kh;pqf0%{Gt`sj+u=~f1>;Xqt!_rOrj*Q@n$o0~E-zgNFRT=J!ubM(da>%)kmSeion3=Go*tgS+|@N)ungakJ%2Sybb zDKviXHgtCz>4lM6!|`dh_lMBeZvOV;&HXBLTKA%LOV0ftpeeLu_WdmK3^i43ulMMR z`S9c4e4Uz3^IKFO{KyfU_HhENZw=lyZ8KV;=f-yYIY~5A;L3ddl0S+S zsv5-FcFf(P=rgE#_-M#>7(o%iu}z=7m(X~{vFo|7p%b2su9$8qS}5$tZ~1E>nOiAf zG{`0&B_X(o&F#PoC)q+yU^#E_%g!-*CQ#=kKljb^3c}Sqn#T{-ACIHadd%_8-7$Z- zRe)c)j={M( z6$@0RHG#|89cLH!1TxHxaqkGEDzv~%F2(EQ1-?6I?GMtXB}^s3dl7C5skmdCXo^?J zKCAQN2uRr~ei$Emp@vf?eLvK43ag;0!6`~a_sI~>qH(&{NzdLxfqQ;2Q`3WgD4@H0 zuQzAkwVfp?Ah{{IZ34QU7qVaTFZMvMSuLcF{n-w><}}qyc;kKSATD*b&Im_L-G(D&e^|`8t{XaIt94EuVDo{Ou%+T)TH$3Jce#8* z+V7DN8eh6;Mr{QpAuVpvS*+&36)4v_Q7p8)mq(>XkY~P|$1_xso};omrE>*$bZ);^ zqYe_pOG`Sl*_uZ`aetopjD;CtAtJ={H4}fl)IyV1R?QhI!E*FfPi%CP-5=S%J2TU6 zSDs$LR#)|!-P6JX%w->eXQMf%d})i0FLIQQ$xNRzM)G{_rfFEIypBD=DbmhmH@KVFmlJjUs7p9cokFP7iq z$BPk}T4}%jTF4j#63gCM)!yD{8P@DS^i@9}XWs-+NYhihqeQc&_Q_G@P`t5Y>}T)S zW5N&Cc@LFE9cpNr4M({<`@TWO=M|N?@7A)2QJj<`A+fK7{f0UTr^9F?Be~{=AfzzyDBVd6!s4$vS>vfLB)_u}Pg4 zu?lq(1K%>jkVP@7?qpOfk5^M4?uRMl6TL@S*e3OK-k zo7RM<5nRE)cpS1&ndRsO?Pm7~tGOWZ0> zk+i0H3gRYisFpfgw+YGN*J^6u!OxLju(@ONF)KDV0zaoYDwWR`?E;fLiBH@Z;AaGVovR zB}~vE+9zeLv|p#J_C{e#aOIfvQR2({>Syq#jM82X2y5@Y>9W6Qgq^1?XmZ;8IIN!a|cg#_&6{jyebUxrRcnZPm z38$0K%NgSK0ar^}sv>h#GHw3%xcV6)VaMN=u%1XoVR z?3@3@E_7Hi^*km$u>sPqCDsd@k>h9>Yhpf`{rw+kdWDFz=Be4hOu)d~qw@VS4qnVt zq2ezlL6T(RpBe>w0mL&;cn!Fgf55g0U=#_w-7#Fw}nqH@AM+s{jLhLNON2) z?K_D9mvFY473vY>Ga23KZqRSS6H1RCxtmT=h^mli-qZO{6-%U9OAq2wZ(#ekn0efR zN7|rEr0DU#vdoO3)~M*DYMGWZkjC7`u4xx$}hIk#%J5?Cq*0 zJ*XR>T2eA{_XWhHY;rf%tKrAfmUm{Aup3S4-EuOluF2T_A4TWkj^*2jaf&#}CS;VE zWG0bWM##r~m_sWQjBqf=z_dj@!`~F_v>pVZ77K$|nEHzC!@29WC87!p#drh#j5+>S?F@du8nW=a#4%~oJ@<>Qq* z3Xk7mqU|*&OQx$i=r)3R6{~(UgMiC+bJci+5vdjl=^I}S;-F`KKyv-pyfehFHwkm_ zj(^AYYq8Lmyw7H#ToIbDx6MKW>h-7|`3QR&bf3Bu%%pRW8*c<>xT(ZS7U4bbc=1oB z?JVrH_6TSlCyGGrRN#NoG`t@2cc~pnpI&UjC_EyU|;QXqA$Dq6D1vNH68RHhH|nsTuxm%iqD?bWhazd z>*3S$jCJ)1#baz#=9`O?t_WfCj%B@;${9vz(QbYp7k$bGb_T!kBkxPR&>cknC->Rg zWdwN_()gbGO^SZ+04(+$&xGtGpRcoals}vTUs%@)M-t(eus6wnt^SX&&g;n-{P6uP zvv0ijrj4&K!F}O>Da^<+^lMpGHLO3B#?EcKtXi;@USxQhx<|^llnsdar{( z+a>C&LBvmhQ(49KgAy;O;6>i6cVX&q87?xHd3FaLl*9NKue(Qs<3Zdn{akB#a`6Se zyiLeUl#N-!m4{wEONw5i(7(;uGGoI|g(Keg4J7Q%A1 zPuwf$Ai^Z;nA0U|aIW!V!SFTR~e8;I^g}SGsk{}VAQ{+ z;LF6EJ;sFPI73UaEAg8?foHb#^E=G*dN4AOGHC4g9h%z|5p3FZs3_7^((;aUdiY1Xa&4$WD@dS%MZerN?#*gp35P4CYpZu%O~0rT&qqBsaQ=L zMj+eZKOrCglgJm%4o|Cd@`OgOniNM)=09XLFa*D`WS7PKg?Gpr6{p9Nsa1Xt=Tt3d z{is%`-ZQgfwn>mWE%K%|a%z%h{aKH9LQjor{EX|#6L_DNWB6NEOcFgqe-{)F(^f(0 zi%F{I&a4&u%ub$|y>-nL3irPnx<>2@;ix1ZMF`2|AY@(E_gha8iN@zWT30QzT@^j<0PUu zV&$Hkp4x`<*TTZK`M9?r3a5OvcDYJ%U$y)WWjx=xgp8FU<##vo_t_5Bc2~?|&p-6F z7W^p;bSp(Ff$|N#=-4N4arYm+n;DhAPh7YEqun z9C?(6?H(XCCos6ob-563t}m8WFK;j*23B5k39Z#IxiS<~@Hku+0wWu`5ljZ}AaRe& z#aE^53J$rSd;T+>%O9E#8`^9_9-l-iou_-@jKdZbJk`ivyGfo!?l<<`kmWrG+>U4r zAg@oaMgE3CKY8ezCS(Y+-C2rz84s29fXeLiVvIPP#nf`)#m`r;)-l|$Q3(46Mf

jsq#ScB%#;BB<%?ZH8RRS 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

4(7;=6wYdA=-d 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

- This site wants permission to spend your tokens. + Review request details before you confirm.

- Spending cap request + Signature request

- This site wants permission to spend your tokens. + Review request details before you confirm.

Date: Tue, 26 Nov 2024 16:26:36 +0530 Subject: [PATCH 26/40] fix: transaction flow section layout on re-designed confirmation pages (#28720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** fix: transaction flow section layout on re-designed confirmation pages ## **Related issues** Ref: https://github.com/MetaMask/metamask-extension/issues/28015 ## **Manual testing steps** 1. Open test dapp 2. Submit token transfer confirmation 3. Check layout of the page ## **Screenshots/Recordings** ### **Before** Screenshot 2024-11-26 at 4 06 28 PM ### **After** Screenshot 2024-11-26 at 4 06 15 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. --- .../__snapshots__/transaction-flow-section.test.tsx.snap | 2 +- .../confirm/info/token-transfer/transaction-flow-section.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap index 01614eec26cd..66ca6afdee69 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap @@ -11,7 +11,7 @@ exports[` renders correctly 1`] = ` >
{ alertKey={RowAlertKey.SigningInWith} label={t('from')} ownerId={transactionMeta.id} + style={{ flexDirection: FlexDirection.Column }} > { From a6e1a746afb376e7a651ff8a3ed1328e93341406 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 26 Nov 2024 12:34:45 +0100 Subject: [PATCH 27/40] chore: Bump Snaps packages (#28678) ## **Description** Bump Snaps packages and handle any required changes. Summary of Snaps changes: - Add `snap_getInterfaceContext` JSON-RPC method - Emit `snapInstalled` and `snapUpdated` events for preinstalled Snaps - This indirectly makes preinstalled Snaps trigger cronjobs and lifecycle hooks more reliably. Closes https://github.com/MetaMask/snaps/issues/2899 Closes https://github.com/MetaMask/snaps/issues/2901 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28678?quickstart=1) --- .../controllers/permissions/specifications.js | 1 + app/scripts/metamask-controller.js | 18 +++++++- package.json | 8 ++-- yarn.lock | 46 +++++++++---------- 4 files changed, 44 insertions(+), 29 deletions(-) diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js index fffc9ae44f49..b0b2051b10f5 100644 --- a/app/scripts/controllers/permissions/specifications.js +++ b/app/scripts/controllers/permissions/specifications.js @@ -412,6 +412,7 @@ export const unrestrictedMethods = Object.freeze([ 'snap_createInterface', 'snap_updateInterface', 'snap_getInterfaceState', + 'snap_getInterfaceContext', 'snap_resolveInterface', 'snap_getCurrencyRate', ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 29e6f09a3aa9..3ca916acbf41 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -3024,7 +3024,11 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.subscribe( `${this.snapController.name}:snapInstalled`, - (truncatedSnap, origin) => { + (truncatedSnap, origin, preinstalled) => { + if (preinstalled) { + return; + } + const snapId = truncatedSnap.id; const snapCategory = this._getSnapMetadata(snapId)?.category; this.metaMetricsController.trackEvent({ @@ -3042,7 +3046,11 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.subscribe( `${this.snapController.name}:snapUpdated`, - (newSnap, oldVersion, origin) => { + (newSnap, oldVersion, origin, preinstalled) => { + if (preinstalled) { + return; + } + const snapId = newSnap.id; const snapCategory = this._getSnapMetadata(snapId)?.category; this.metaMetricsController.trackEvent({ @@ -6089,6 +6097,12 @@ export default class MetamaskController extends EventEmitter { origin, ...args, ).state, + getInterfaceContext: (...args) => + this.controllerMessenger.call( + 'SnapInterfaceController:getInterface', + origin, + ...args, + ).context, createInterface: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapInterfaceController:createInterface', diff --git a/package.json b/package.json index 45896fdabd1f..56b7c8f876bc 100644 --- a/package.json +++ b/package.json @@ -234,7 +234,7 @@ "semver@7.3.8": "^7.5.4", "@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", - "@metamask/snaps-sdk": "^6.11.0", + "@metamask/snaps-sdk": "^6.12.0", "@swc/types@0.1.5": "^0.1.6", "@babel/core": "patch:@babel/core@npm%3A7.25.9#~/.yarn/patches/@babel-core-npm-7.25.9-4ae3bff7f3.patch", "@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch", @@ -349,10 +349,10 @@ "@metamask/selected-network-controller": "^18.0.2", "@metamask/signature-controller": "^23.0.0", "@metamask/smart-transactions-controller": "^13.0.0", - "@metamask/snaps-controllers": "^9.13.0", + "@metamask/snaps-controllers": "^9.14.0", "@metamask/snaps-execution-environments": "^6.10.0", - "@metamask/snaps-rpc-methods": "^11.5.1", - "@metamask/snaps-sdk": "^6.11.0", + "@metamask/snaps-rpc-methods": "^11.6.0", + "@metamask/snaps-sdk": "^6.12.0", "@metamask/snaps-utils": "^8.6.0", "@metamask/solana-wallet-snap": "^0.1.9", "@metamask/transaction-controller": "^40.0.0", diff --git a/yarn.lock b/yarn.lock index 9f8daf6ce2d6..2bea9ec5fc46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6457,9 +6457,9 @@ __metadata: linkType: hard "@metamask/slip44@npm:^4.0.0": - version: 4.0.0 - resolution: "@metamask/slip44@npm:4.0.0" - checksum: 10/3e47e8834b0fbdabe1f126fd78665767847ddc1f9ccc8defb23007dd71fcd2e4899c8ca04857491be3630668a3765bad1e40fdfca9a61ef33236d8d08e51535e + version: 4.1.0 + resolution: "@metamask/slip44@npm:4.1.0" + checksum: 10/4265254a1800a24915bd1de15f86f196737132f9af2a084c2efc885decfc5dd87ad8f0687269d90b35e2ec64d3ea4fbff0caa793bcea6e585b1f3a290952b750 languageName: node linkType: hard @@ -6486,9 +6486,9 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^9.10.0, @metamask/snaps-controllers@npm:^9.13.0": - version: 9.13.0 - resolution: "@metamask/snaps-controllers@npm:9.13.0" +"@metamask/snaps-controllers@npm:^9.10.0, @metamask/snaps-controllers@npm:^9.14.0": + version: 9.14.0 + resolution: "@metamask/snaps-controllers@npm:9.14.0" dependencies: "@metamask/approval-controller": "npm:^7.1.1" "@metamask/base-controller": "npm:^7.0.2" @@ -6500,8 +6500,8 @@ __metadata: "@metamask/post-message-stream": "npm:^8.1.1" "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/snaps-registry": "npm:^3.2.2" - "@metamask/snaps-rpc-methods": "npm:^11.5.1" - "@metamask/snaps-sdk": "npm:^6.11.0" + "@metamask/snaps-rpc-methods": "npm:^11.6.0" + "@metamask/snaps-sdk": "npm:^6.12.0" "@metamask/snaps-utils": "npm:^8.6.0" "@metamask/utils": "npm:^10.0.0" "@xstate/fsm": "npm:^2.0.0" @@ -6520,7 +6520,7 @@ __metadata: peerDependenciesMeta: "@metamask/snaps-execution-environments": optional: true - checksum: 10/bcf60b61de067f89439cb15acbdf6f808b4bcda8e1cbc9debd693ca2c545c9d38c4e6f380191c4703bd9d28d7dd41e4ce5111664d7b474d5e86e460bcefc3637 + checksum: 10/cce5a4d7af65d70a2a2902f9a89b15145590edccf8171b3994e2ddde74c6700abf49d7b800275d7ab3b216ef3dfca3be82c4145b5986d20ec1df8b4c50b95314 languageName: node linkType: hard @@ -6555,32 +6555,32 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-rpc-methods@npm:^11.5.1": - version: 11.5.1 - resolution: "@metamask/snaps-rpc-methods@npm:11.5.1" +"@metamask/snaps-rpc-methods@npm:^11.6.0": + version: 11.6.0 + resolution: "@metamask/snaps-rpc-methods@npm:11.6.0" dependencies: "@metamask/key-tree": "npm:^9.1.2" "@metamask/permission-controller": "npm:^11.0.3" "@metamask/rpc-errors": "npm:^7.0.1" - "@metamask/snaps-sdk": "npm:^6.10.0" - "@metamask/snaps-utils": "npm:^8.5.0" + "@metamask/snaps-sdk": "npm:^6.12.0" + "@metamask/snaps-utils": "npm:^8.6.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" "@noble/hashes": "npm:^1.3.1" - checksum: 10/0f999a5dd64f1b1123366f448ae833f0e95a415791600bb535959ba67d2269fbe3c4504d47f04db71bafa79a9a87d6b832fb2e2b5ef29567078c95bce2638f35 + checksum: 10/6788717e1ccab8eb40876fce15a4b66b44f267b33fe953efa56c0fee94f651391704556eaf9f3f9e6787e0bbbe6b0fb470a9da45d68d78b587ad96b6a3f246ac languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^6.11.0": - version: 6.11.0 - resolution: "@metamask/snaps-sdk@npm:6.11.0" +"@metamask/snaps-sdk@npm:^6.12.0": + version: 6.12.0 + resolution: "@metamask/snaps-sdk@npm:6.12.0" dependencies: "@metamask/key-tree": "npm:^9.1.2" "@metamask/providers": "npm:^18.1.1" "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" - checksum: 10/0f9b507139d1544b1b3d85ff8de81b800d543012d3ee9414c607c23abe9562e0dca48de089ed94be69f5ad981730a0f443371edfe6bc2d5ffb140b28e437bfd2 + checksum: 10/b0e24fee2c90ac2f456aeb5babc180c74f56b8cf94f38abdd9b022c65a9cac4a10015e3d4053784292c184238f97704cb2b6a41f6e7a04f23ffaa8d519d2a39e languageName: node linkType: hard @@ -6615,7 +6615,7 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.5.0, @metamask/snaps-utils@npm:^8.6.0": +"@metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.6.0": version: 8.6.0 resolution: "@metamask/snaps-utils@npm:8.6.0" dependencies: @@ -26883,10 +26883,10 @@ __metadata: "@metamask/selected-network-controller": "npm:^18.0.2" "@metamask/signature-controller": "npm:^23.0.0" "@metamask/smart-transactions-controller": "npm:^13.0.0" - "@metamask/snaps-controllers": "npm:^9.13.0" + "@metamask/snaps-controllers": "npm:^9.14.0" "@metamask/snaps-execution-environments": "npm:^6.10.0" - "@metamask/snaps-rpc-methods": "npm:^11.5.1" - "@metamask/snaps-sdk": "npm:^6.11.0" + "@metamask/snaps-rpc-methods": "npm:^11.6.0" + "@metamask/snaps-sdk": "npm:^6.12.0" "@metamask/snaps-utils": "npm:^8.6.0" "@metamask/solana-wallet-snap": "npm:^0.1.9" "@metamask/test-bundler": "npm:^1.0.0" From ad20b719a1834ffe4a189d6733001248fd3057a6 Mon Sep 17 00:00:00 2001 From: cmd-ob Date: Tue, 26 Nov 2024 13:54:28 +0000 Subject: [PATCH 28/40] test: add accounts sync test with balance detection (#28715) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** E2E Test for Accounts Syncing when a user has more accounts with balances. * Adds new method for changing label from account modal context to be used with other methods * Adds new mocks for mocking `eth_getBalance` in test * Updates `unlockWallet` to be used with custom password ## **Related issues** Fixes: ## **Manual testing steps** 1. All E2E tests should pass ## **Screenshots/Recordings** Screenshot 2024-11-26 at 08 50 30 ## **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). - [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: sahar-fehri Co-authored-by: Jyoti Puri Co-authored-by: MetaMask Bot Co-authored-by: Frederik Bolding --- test/e2e/helpers.js | 21 +- .../page-objects/pages/account-list-page.ts | 27 +++ .../sync-with-account-balances.spec.ts | 225 ++++++++++++++++++ test/e2e/tests/notifications/mocks.ts | 61 +++++ 4 files changed, 322 insertions(+), 12 deletions(-) create mode 100644 test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 19c9aeecd6c7..b5962c0c079d 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -615,30 +615,27 @@ const locateAccountBalanceDOM = async ( const WALLET_PASSWORD = 'correct horse battery staple'; /** - * Unlock the wallet with the default password. + * Unlocks the wallet using the provided password. * This method is intended to replace driver.navigate and should not be called after driver.navigate. * * @param {WebDriver} driver - The webdriver instance - * @param {object} options - Options for unlocking the wallet - * @param {boolean} options.navigate - Whether to navigate to the root page prior to unlocking. Defaults to true. - * @param {boolean} options.waitLoginSuccess - Whether to wait for the login to succeed. Defaults to true. + * @param {object} [options] - Options for unlocking the wallet + * @param {boolean} [options.navigate] - Whether to navigate to the root page prior to unlocking - defaults to true + * @param {boolean} [options.waitLoginSuccess] - Whether to wait for the login to succeed - defaults to true + * @param {string} [options.password] - Password to unlock wallet - defaults to shared WALLET_PASSWORD */ async function unlockWallet( driver, - options = { - navigate: true, - waitLoginSuccess: true, - }, + { navigate = true, waitLoginSuccess = true, password = WALLET_PASSWORD } = {}, ) { - if (options.navigate !== false) { + if (navigate) { await driver.navigate(); } - await driver.fill('#password', WALLET_PASSWORD); + await driver.fill('#password', password); await driver.press('#password', driver.Key.ENTER); - if (options.waitLoginSuccess !== false) { - // No guard is necessary here, because it goes from present to absent + if (waitLoginSuccess) { await driver.assertElementNotPresent('[data-testid="unlock-page"]'); } } diff --git a/test/e2e/page-objects/pages/account-list-page.ts b/test/e2e/page-objects/pages/account-list-page.ts index f68cdaa333a0..9f96d70f4972 100644 --- a/test/e2e/page-objects/pages/account-list-page.ts +++ b/test/e2e/page-objects/pages/account-list-page.ts @@ -230,6 +230,33 @@ class AccountListPage { async changeAccountLabel(newLabel: string): Promise { console.log(`Changing account label to: ${newLabel}`); await this.driver.clickElement(this.accountMenuButton); + await this.changeLabelFromAccountDetailsModal(newLabel); + } + + /** + * Changes the account label from within an already opened account details modal. + * Note: This method assumes the account details modal is already open. + * + * Recommended usage: + * ```typescript + * await accountListPage.openAccountDetailsModal('Current Account Name'); + * await accountListPage.changeLabelFromAccountDetailsModal('New Account Name'); + * ``` + * + * @param newLabel - The new label to set for the account + * @throws Will throw an error if the modal is not open when method is called + * @example + * // To rename a specific account, first open its details modal: + * await accountListPage.openAccountDetailsModal('Current Account Name'); + * await accountListPage.changeLabelFromAccountDetailsModal('New Account Name'); + * + * // Note: Using changeAccountLabel() alone will only work for the first account + */ + async changeLabelFromAccountDetailsModal(newLabel: string): Promise { + await this.driver.waitForSelector(this.editableLabelButton); + console.log( + `Account details modal opened, changing account label to: ${newLabel}`, + ); await this.driver.clickElement(this.editableLabelButton); await this.driver.fill(this.editableLabelInput, newLabel); await this.driver.clickElement(this.saveAccountLabelButton); diff --git a/test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts new file mode 100644 index 000000000000..8ecaff943721 --- /dev/null +++ b/test/e2e/tests/notifications/account-syncing/sync-with-account-balances.spec.ts @@ -0,0 +1,225 @@ +import { Mockttp } from 'mockttp'; +import { unlockWallet, withFixtures } from '../../../helpers'; +import FixtureBuilder from '../../../fixture-builder'; +import { mockInfuraAndAccountSync } from '../mocks'; +import { + NOTIFICATIONS_TEAM_PASSWORD, + NOTIFICATIONS_TEAM_SEED_PHRASE, +} from '../constants'; +import { UserStorageMockttpController } from '../../../helpers/user-storage/userStorageMockttpController'; +import HeaderNavbar from '../../../page-objects/pages/header-navbar'; +import AccountListPage from '../../../page-objects/pages/account-list-page'; +import HomePage from '../../../page-objects/pages/homepage'; +import { completeImportSRPOnboardingFlow } from '../../../page-objects/flows/onboarding.flow'; +import { accountsSyncMockResponse } from './mockData'; +import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; + +const INITIAL_ACCOUNTS = [ + '0xaa4179e7f103701e904d27df223a39aa9c27405a', + '0xd2a4afe5c2ff0a16bf81f77ba4201a8107aa874b', + '0xd54ba25a07eb3da821face8478c3d965ded63018', + '0x2c30c098e2a560988d486c7f25798e790802f953', +]; + +const ADDITIONAL_ACCOUNTS = [ + '0x6b65DA6735119E72B72fF842Bd92e9DE0C1e4Ae0', + '0x0f205850eaC507473AA0e47cc8eB528D875E7498', +]; + +const EXPECTED_ACCOUNT_NAMES = { + INITIAL: [ + 'My First Synced Account', + 'My Second Synced Account', + 'Account 3', + 'Account 4', + ], + WITH_NEW_ACCOUNTS: [ + 'My First Synced Account', + 'My Second Synced Account', + 'Account 3', + 'Account 4', + 'Account 5', + 'Account 6', + ], +}; + +describe('Account syncing - User already has balances on multple accounts @no-mmi', function () { + if (!IS_ACCOUNT_SYNCING_ENABLED) { + return; + } + + describe('from inside MetaMask', function () { + /** + * This test verifies the complete account syncing flow in three phases: + * Phase 1: Initial setup, where we check that 4 accounts are shown due to balance detection even though the user storage only has 2 accounts. + * Phase 2: Discovery of 2 more accounts after adding balances. We still expect to only see 6 even though we had 5 accounts synced in the previous test + * Phase 3: Verification that any final changes to user storage are persisted and that we don't see any extra accounts created + */ + it('when a user has balances on more accounts than previously synced, it should be handled gracefully', async function () { + const userStorageMockttpController = new UserStorageMockttpController(); + let accountsToMock = [...INITIAL_ACCOUNTS]; + + // PHASE 1: Initial setup and account creation + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }) + .withNetworkControllerOnMainnet() + .build(), + title: this.test?.fullTitle(), + testSpecificMock: async (server: Mockttp) => { + await mockInfuraAndAccountSync( + server, + userStorageMockttpController, + { + accountsSyncResponse: accountsSyncMockResponse, + accountsToMock, + }, + ); + }, + }, + async ({ driver }) => { + // Complete initial setup with provided seed phrase + await completeImportSRPOnboardingFlow({ + driver, + seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, + password: NOTIFICATIONS_TEAM_PASSWORD, + }); + + // Verify initial state and balance + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed('1'); + await homePage.check_hasAccountSyncingSyncedAtLeastOnce(); + + // Open account menu and verify initial accounts + const header = new HeaderNavbar(driver); + await header.check_pageIsLoaded(); + await header.openAccountMenu(); + + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_numberOfAvailableAccounts(4); + + // Verify each initial account name + for (const accountName of EXPECTED_ACCOUNT_NAMES.INITIAL) { + await accountListPage.check_accountDisplayedInAccountList( + accountName, + ); + } + + // Create new account and prepare for additional accounts + await accountListPage.addNewAccount(); + accountsToMock = [...INITIAL_ACCOUNTS, ...ADDITIONAL_ACCOUNTS]; + }, + ); + + // PHASE 2: Verify discovery of new accounts with balances + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }) + .withNetworkControllerOnMainnet() + .build(), + title: this.test?.fullTitle(), + testSpecificMock: async (server: Mockttp) => { + await mockInfuraAndAccountSync( + server, + userStorageMockttpController, + { accountsToMock }, + ); + }, + }, + async ({ driver }) => { + // Complete setup again for new session + await completeImportSRPOnboardingFlow({ + driver, + seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, + password: NOTIFICATIONS_TEAM_PASSWORD, + }); + + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed('1'); + await homePage.check_hasAccountSyncingSyncedAtLeastOnce(); + + // Verify all accounts including newly discovered ones (which would have been synced / have balances) + const header = new HeaderNavbar(driver); + await header.check_pageIsLoaded(); + await header.openAccountMenu(); + + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_numberOfAvailableAccounts(6); + + for (const accountName of EXPECTED_ACCOUNT_NAMES.WITH_NEW_ACCOUNTS) { + await accountListPage.check_accountDisplayedInAccountList( + accountName, + ); + } + + // Rename Account 6 to verify update to user storage + await accountListPage.switchToAccount('Account 6'); + await header.openAccountMenu(); + await accountListPage.openAccountDetailsModal('Account 6'); + await accountListPage.changeLabelFromAccountDetailsModal( + 'My Renamed Account 6', + ); + }, + ); + + // PHASE 3: Verify name persistence across sessions + await withFixtures( + { + fixtures: new FixtureBuilder({ onboarding: true }) + .withNetworkControllerOnMainnet() + .build(), + title: this.test?.fullTitle(), + testSpecificMock: async (server: Mockttp) => { + await mockInfuraAndAccountSync( + server, + userStorageMockttpController, + { accountsToMock }, + ); + }, + }, + async ({ driver }) => { + // Complete setup for final verification + await completeImportSRPOnboardingFlow({ + driver, + seedPhrase: NOTIFICATIONS_TEAM_SEED_PHRASE, + password: NOTIFICATIONS_TEAM_PASSWORD, + }); + + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed('1'); + await homePage.check_hasAccountSyncingSyncedAtLeastOnce(); + + // Verify renamed account persists + const header = new HeaderNavbar(driver); + await header.check_pageIsLoaded(); + await header.openAccountMenu(); + + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_numberOfAvailableAccounts(6); + await accountListPage.check_accountDisplayedInAccountList( + 'My Renamed Account 6', + ); + await accountListPage.closeAccountModal(); + + // Lock and unlock wallet to ensure that number of preloaded accounts have not gone up + await homePage.headerNavbar.lockMetaMask(); + await unlockWallet(driver, { + password: NOTIFICATIONS_TEAM_PASSWORD, + waitLoginSuccess: true, + navigate: true, + }); + + await header.check_pageIsLoaded(); + await header.openAccountMenu(); + await accountListPage.check_numberOfAvailableAccounts(6); + }, + ); + }); + }); +}); diff --git a/test/e2e/tests/notifications/mocks.ts b/test/e2e/tests/notifications/mocks.ts index 748084918272..b7069447fd45 100644 --- a/test/e2e/tests/notifications/mocks.ts +++ b/test/e2e/tests/notifications/mocks.ts @@ -6,6 +6,7 @@ import { } from '@metamask/notification-services-controller'; import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { UserStorageMockttpController } from '../../helpers/user-storage/userStorageMockttpController'; +import { accountsSyncMockResponse } from './account-syncing/mockData'; const AuthMocks = AuthenticationController.Mocks; const NotificationMocks = NotificationServicesController.Mocks; @@ -105,3 +106,63 @@ function mockAPICall(server: Mockttp, response: MockResponse) { json: response.response, })); } + +type MockInfuraAndAccountSyncOptions = { + accountsToMock?: string[]; + accountsSyncResponse?: typeof accountsSyncMockResponse; +}; + +const MOCK_ETH_BALANCE = '0xde0b6b3a7640000'; +const INFURA_URL = + 'https://mainnet.infura.io/v3/00000000000000000000000000000000'; + +/** + * Sets up mock responses for Infura balance checks and account syncing + * + * @param mockServer - The Mockttp server instance + * @param userStorageMockttpController - Controller for user storage mocks + * @param options - Configuration options for mocking + */ +export async function mockInfuraAndAccountSync( + mockServer: Mockttp, + userStorageMockttpController: UserStorageMockttpController, + options: MockInfuraAndAccountSyncOptions = {}, +): Promise { + const accounts = options.accountsToMock ?? []; + + // Set up User Storage / Account Sync mock + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + mockServer, + ); + + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + mockServer, + { + getResponse: options.accountsSyncResponse ?? undefined, + }, + ); + + // Account Balances + if (accounts.length > 0) { + accounts.forEach((account) => { + mockServer + .forPost(INFURA_URL) + .withJsonBodyIncluding({ + method: 'eth_getBalance', + params: [account.toLowerCase()], + }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '1111111111111111', + result: MOCK_ETH_BALANCE, + }, + })); + }); + } + + mockNotificationServices(mockServer, userStorageMockttpController); +} From fd3ac164fd81a7f4451180cf58b0e9ee433bc880 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Tue, 26 Nov 2024 14:59:24 +0100 Subject: [PATCH 29/40] feat: enable account syncing in production (#28596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR enables account syncing in production [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28596?quickstart=1) ## **Related issues** Fixes: #28438 ## **Manual testing steps** 1. Onboard with your SRP 2. Add accounts, rename accounts 3. Uninstall the extension 4. Reinstall and onboard with the previous SRP 5. Verify that your added & renamed accounts are there ## **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. --- app/scripts/metamask-controller.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3ca916acbf41..94ebb67f3617 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -157,7 +157,6 @@ import { NotificationServicesPushController, NotificationServicesController, } from '@metamask/notification-services-controller'; -import { isProduction } from '../../shared/modules/environment'; import { methodsRequiringNetworkSwitch, methodsThatCanSwitchNetworkWithoutApproval, @@ -1609,7 +1608,7 @@ export default class MetamaskController extends EventEmitter { }, }, env: { - isAccountSyncingEnabled: !isProduction() && isManifestV3, + isAccountSyncingEnabled: isManifestV3, }, messenger: this.controllerMessenger.getRestricted({ name: 'UserStorageController', From af9ebca332af423c2f4719f614bc76f18fd5b5f9 Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Tue, 26 Nov 2024 15:18:52 +0100 Subject: [PATCH 30/40] feat: Add first time interaction warning (#28435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR implements first time interaction feature where it shows an alert if you interact with the address for the first time. Information of the first time interaction is fetched in the transaction controller when the transaction is added to the state. Core PR: https://github.com/MetaMask/core/pull/4895 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28435?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3040 ## **Manual testing steps** 1. Go to test dapp 2. Use send legacy transaction - make sure you interact here with your account for the first time 3. See warning ## **Screenshots/Recordings** ### **Before** ### **After** ![Screenshot 2024-11-13 at 11 51 14](https://github.com/user-attachments/assets/6cc1f481-788c-4945-b190-1448c5a03141) ![Screenshot 2024-11-13 at 11 51 19](https://github.com/user-attachments/assets/98413caa-ef43-4877-a37d-ea0a6da1397f) ## **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/en/messages.json | 8 +- app/scripts/metamask-controller.js | 2 + package.json | 2 +- .../app/confirm/info/row/constants.ts | 1 + ui/components/app/confirm/info/row/row.tsx | 3 +- .../transaction-flow-section.test.tsx.snap | 4 +- .../transaction-flow-section.tsx | 16 +-- .../simulation-details/simulation-details.tsx | 5 +- .../useFirstTimeInteractionAlert.test.ts | 129 ++++++++++++++++++ .../useFirstTimeInteractionAlert.ts | 35 +++++ .../hooks/useConfirmationAlerts.ts | 4 + .../__snapshots__/security-tab.test.js.snap | 2 +- yarn.lock | 10 +- 13 files changed, 198 insertions(+), 23 deletions(-) create mode 100644 ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.test.ts create mode 100644 ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 39c1b20d1a52..54c0a782a592 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -419,6 +419,9 @@ "alertMessageChangeInSimulationResults": { "message": "Estimated changes for this transaction have been updated. Review them closely before proceeding." }, + "alertMessageFirstTimeInteraction": { + "message": "You're interacting with this address for the first time. Make sure that it's correct before you continue." + }, "alertMessageGasEstimateFailed": { "message": "We’re unable to provide an accurate fee and this estimate might be high. We suggest you to input a custom gas limit, but there’s a risk the transaction will still fail." }, @@ -461,6 +464,9 @@ "alertReasonChangeInSimulationResults": { "message": "Results have changed" }, + "alertReasonFirstTimeInteraction": { + "message": "1st interaction" + }, "alertReasonGasEstimateFailed": { "message": "Inaccurate fee" }, @@ -4744,7 +4750,7 @@ "message": "Security alerts" }, "securityAlertsDescription": { - "message": "This feature alerts you to malicious activity by actively reviewing transaction and signature requests. $1", + "message": "This feature alerts you to malicious or unusual activity by actively reviewing transaction and signature requests. $1", "description": "Link to learn more about security alerts" }, "securityAndPrivacy": { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 94ebb67f3617..ff3ad58f26d4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1940,6 +1940,8 @@ export default class MetamaskController extends EventEmitter { queryEntireHistory: false, updateTransactions: false, }, + isFirstTimeInteractionEnabled: () => + this.preferencesController.state.securityAlertsEnabled, isMultichainEnabled: process.env.TRANSACTION_MULTICHAIN, isSimulationEnabled: () => this.preferencesController.state.useTransactionSimulations, diff --git a/package.json b/package.json index 56b7c8f876bc..5a3a7531a45b 100644 --- a/package.json +++ b/package.json @@ -355,7 +355,7 @@ "@metamask/snaps-sdk": "^6.12.0", "@metamask/snaps-utils": "^8.6.0", "@metamask/solana-wallet-snap": "^0.1.9", - "@metamask/transaction-controller": "^40.0.0", + "@metamask/transaction-controller": "^40.1.0", "@metamask/user-operation-controller": "^13.0.0", "@metamask/utils": "^10.0.1", "@ngraveio/bc-ur": "^1.1.12", diff --git a/ui/components/app/confirm/info/row/constants.ts b/ui/components/app/confirm/info/row/constants.ts index 415358aa5252..5c6c1ff980f7 100644 --- a/ui/components/app/confirm/info/row/constants.ts +++ b/ui/components/app/confirm/info/row/constants.ts @@ -2,6 +2,7 @@ export const TEST_ADDRESS = '0x5CfE73b6021E818B776b421B1c4Db2474086a7e1'; export enum RowAlertKey { EstimatedFee = 'estimatedFee', + FirstTimeInteraction = 'firstTimeInteraction', SigningInWith = 'signingInWith', RequestFrom = 'requestFrom', Resimulation = 'resimulation', diff --git a/ui/components/app/confirm/info/row/row.tsx b/ui/components/app/confirm/info/row/row.tsx index b628e2256575..193e62a5f2f3 100644 --- a/ui/components/app/confirm/info/row/row.tsx +++ b/ui/components/app/confirm/info/row/row.tsx @@ -33,7 +33,7 @@ export enum ConfirmInfoRowVariant { export type ConfirmInfoRowProps = { label: string; - children: React.ReactNode | string; + children?: React.ReactNode | string; tooltip?: string; variant?: ConfirmInfoRowVariant; style?: React.CSSProperties; @@ -169,6 +169,7 @@ export const ConfirmInfoRow: React.FC = ({ {expanded && + children && (typeof children === 'string' ? ( {children} diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap index 66ca6afdee69..10955f0992bc 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/transaction-flow-section.test.tsx.snap @@ -94,10 +94,10 @@ exports[` renders correctly 1`] = ` />
{ alertKey={RowAlertKey.SigningInWith} label={t('from')} ownerId={transactionMeta.id} - style={{ flexDirection: FlexDirection.Column }} + style={{ + flexDirection: FlexDirection.Column, + }} > { color={IconColor.iconMuted} /> {recipientAddress && ( - @@ -90,7 +90,7 @@ export const TransactionFlowSection = () => { chainId={chainId} /> - + )} diff --git a/ui/pages/confirmations/components/simulation-details/simulation-details.tsx b/ui/pages/confirmations/components/simulation-details/simulation-details.tsx index 357f71230a0c..412467b4ff8a 100644 --- a/ui/pages/confirmations/components/simulation-details/simulation-details.tsx +++ b/ui/pages/confirmations/components/simulation-details/simulation-details.tsx @@ -110,10 +110,7 @@ const HeaderWithAlert = ({ transactionId }: { transactionId: string }) => { paddingLeft: 0, paddingRight: 0, }} - > - {/* Intentional fragment */} - <> - + /> ); }; diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.test.ts b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.test.ts new file mode 100644 index 000000000000..964b218e8501 --- /dev/null +++ b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.test.ts @@ -0,0 +1,129 @@ +import { ApprovalType } from '@metamask/controller-utils'; +import { + TransactionMeta, + TransactionStatus, + TransactionType, +} from '@metamask/transaction-controller'; + +import { getMockConfirmState } from '../../../../../../test/data/confirmations/helper'; +import { renderHookWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers'; +import { Severity } from '../../../../../helpers/constants/design-system'; +import { RowAlertKey } from '../../../../../components/app/confirm/info/row/constants'; +import { genUnapprovedContractInteractionConfirmation } from '../../../../../../test/data/confirmations/contract-interaction'; +import { useFirstTimeInteractionAlert } from './useFirstTimeInteractionAlert'; + +const ACCOUNT_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'; +const TRANSACTION_ID_MOCK = '123-456'; + +const CONFIRMATION_MOCK = genUnapprovedContractInteractionConfirmation({ + chainId: '0x5', +}) as TransactionMeta; + +const TRANSACTION_META_MOCK = { + id: TRANSACTION_ID_MOCK, + chainId: '0x5', + status: TransactionStatus.submitted, + type: TransactionType.contractInteraction, + txParams: { + from: ACCOUNT_ADDRESS, + }, + time: new Date().getTime() - 10000, + firstTimeInteraction: true, +} as TransactionMeta; + +function runHook({ + currentConfirmation, + transactions = [], +}: { + currentConfirmation?: TransactionMeta; + transactions?: TransactionMeta[]; +} = {}) { + let pendingApprovals = {}; + if (currentConfirmation) { + pendingApprovals = { + [currentConfirmation.id as string]: { + id: currentConfirmation.id, + type: ApprovalType.Transaction, + }, + }; + transactions.push(currentConfirmation); + } + const state = getMockConfirmState({ + metamask: { + pendingApprovals, + transactions, + }, + }); + const response = renderHookWithConfirmContextProvider( + useFirstTimeInteractionAlert, + state, + ); + + return response.result.current; +} + +describe('useFirstTimeInteractionAlert', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('returns no alerts if no confirmation', () => { + expect(runHook()).toEqual([]); + }); + + it('returns no alerts if no transactions', () => { + expect( + runHook({ + currentConfirmation: CONFIRMATION_MOCK, + transactions: [], + }), + ).toEqual([]); + }); + + it('returns no alerts if firstTimeInteraction is false', () => { + const notFirstTimeConfirmation = { + ...TRANSACTION_META_MOCK, + firstTimeInteraction: false, + }; + expect( + runHook({ + currentConfirmation: notFirstTimeConfirmation, + }), + ).toEqual([]); + }); + + it('returns no alerts if firstTimeInteraction is undefined', () => { + const notFirstTimeConfirmation = { + ...TRANSACTION_META_MOCK, + firstTimeInteraction: undefined, + }; + expect( + runHook({ + currentConfirmation: notFirstTimeConfirmation, + }), + ).toEqual([]); + }); + + it('returns alert if isFirstTimeInteraction is true', () => { + const firstTimeConfirmation = { + ...CONFIRMATION_MOCK, + isFirstTimeInteraction: true, + }; + const alerts = runHook({ + currentConfirmation: firstTimeConfirmation, + }); + + expect(alerts).toEqual([ + { + actions: [], + field: RowAlertKey.FirstTimeInteraction, + isBlocking: false, + key: 'firstTimeInteractionTitle', + message: + "You're interacting with this address for the first time. Make sure that it's correct before you continue.", + reason: '1st interaction', + severity: Severity.Warning, + }, + ]); + }); +}); diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts new file mode 100644 index 000000000000..7e4a86c3802f --- /dev/null +++ b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts @@ -0,0 +1,35 @@ +import { useMemo } from 'react'; +import { TransactionMeta } from '@metamask/transaction-controller'; + +import { Alert } from '../../../../../ducks/confirm-alerts/confirm-alerts'; +import { useI18nContext } from '../../../../../hooks/useI18nContext'; +import { Severity } from '../../../../../helpers/constants/design-system'; +import { RowAlertKey } from '../../../../../components/app/confirm/info/row/constants'; +import { useConfirmContext } from '../../../context/confirm'; + +export function useFirstTimeInteractionAlert(): Alert[] { + const t = useI18nContext(); + const { currentConfirmation } = useConfirmContext(); + + const { isFirstTimeInteraction } = currentConfirmation ?? {}; + + return useMemo(() => { + // If isFirstTimeInteraction is undefined that means it's either disabled or error in accounts API + // If it's false that means account relationship found + if (!isFirstTimeInteraction) { + return []; + } + + return [ + { + actions: [], + field: RowAlertKey.FirstTimeInteraction, + isBlocking: false, + key: 'firstTimeInteractionTitle', + message: t('alertMessageFirstTimeInteraction'), + reason: t('alertReasonFirstTimeInteraction'), + severity: Severity.Warning, + }, + ]; + }, [isFirstTimeInteraction, t]); +} diff --git a/ui/pages/confirmations/hooks/useConfirmationAlerts.ts b/ui/pages/confirmations/hooks/useConfirmationAlerts.ts index efcb0beacf9e..382c7cdea511 100644 --- a/ui/pages/confirmations/hooks/useConfirmationAlerts.ts +++ b/ui/pages/confirmations/hooks/useConfirmationAlerts.ts @@ -11,6 +11,7 @@ import { useNoGasPriceAlerts } from './alerts/transactions/useNoGasPriceAlerts'; import { usePendingTransactionAlerts } from './alerts/transactions/usePendingTransactionAlerts'; import { useQueuedConfirmationsAlerts } from './alerts/transactions/useQueuedConfirmationsAlerts'; import { useResimulationAlert } from './alerts/transactions/useResimulationAlert'; +import { useFirstTimeInteractionAlert } from './alerts/transactions/useFirstTimeInteractionAlert'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) import { useSigningOrSubmittingAlerts } from './alerts/transactions/useSigningOrSubmittingAlerts'; ///: END:ONLY_INCLUDE_IF @@ -37,6 +38,7 @@ function useTransactionAlerts(): Alert[] { const noGasPriceAlerts = useNoGasPriceAlerts(); const pendingTransactionAlerts = usePendingTransactionAlerts(); const resimulationAlert = useResimulationAlert(); + const firstTimeInteractionAlert = useFirstTimeInteractionAlert(); ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) const signingOrSubmittingAlerts = useSigningOrSubmittingAlerts(); ///: END:ONLY_INCLUDE_IF @@ -52,6 +54,7 @@ function useTransactionAlerts(): Alert[] { ...noGasPriceAlerts, ...pendingTransactionAlerts, ...resimulationAlert, + ...firstTimeInteractionAlert, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) ...signingOrSubmittingAlerts, ///: END:ONLY_INCLUDE_IF @@ -66,6 +69,7 @@ function useTransactionAlerts(): Alert[] { noGasPriceAlerts, pendingTransactionAlerts, resimulationAlert, + firstTimeInteractionAlert, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) signingOrSubmittingAlerts, ///: END:ONLY_INCLUDE_IF diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index e7306f64bba7..23d2b80519a3 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -126,7 +126,7 @@ exports[`Security Tab should match snapshot 1`] = ` > - This feature alerts you to malicious activity by actively reviewing transaction and signature requests. + This feature alerts you to malicious or unusual activity by actively reviewing transaction and signature requests. Date: Tue, 26 Nov 2024 23:20:26 +0900 Subject: [PATCH 31/40] fix: use BN from bn.js instead of ethereumjs-util (#28146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - remove redundant resolutions entries - `ethereumjs-util` v5 is no longer present - fix: use `BN` from bn.js (v5) instead of `ethereumjs-util` (deprecated version) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28146?quickstart=1) ## **Related issues** #### Blocking - #28169 - #28171 - #28170 ## **Manual testing steps** ## **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** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- app/scripts/migrations/088.ts | 4 ++-- package.json | 5 ----- shared/modules/conversion.utils.ts | 3 ++- ui/helpers/utils/util.js | 6 +++--- ui/helpers/utils/util.test.js | 3 ++- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/scripts/migrations/088.ts b/app/scripts/migrations/088.ts index 274b93624a85..27f324228790 100644 --- a/app/scripts/migrations/088.ts +++ b/app/scripts/migrations/088.ts @@ -1,5 +1,5 @@ import { hasProperty, Hex, isObject, isStrictHexString } from '@metamask/utils'; -import { BN } from 'ethereumjs-util'; +import BN from 'bn.js'; import { cloneDeep, mapKeys } from 'lodash'; import log from 'loglevel'; @@ -302,6 +302,6 @@ function toHex(value: number | string | BN): Hex { } const hexString = BN.isBN(value) ? value.toString(16) - : new BN(value.toString(), 10).toString(16); + : new BN(value.toString(10), 10).toString(16); return `0x${hexString}`; } diff --git a/package.json b/package.json index 5a3a7531a45b..9ed956d639cd 100644 --- a/package.json +++ b/package.json @@ -180,11 +180,6 @@ "eslint@npm:^8.7.0": "patch:eslint@npm%3A8.57.0#~/.yarn/patches/eslint-npm-8.57.0-4286e12a3a.patch", "eth-query@^2.1.2": "patch:eth-query@npm%3A2.1.2#./.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch", "eth-query@^2.1.0": "patch:eth-query@npm%3A2.1.2#./.yarn/patches/eth-query-npm-2.1.2-7c6adc825f.patch", - "ethereumjs-util@^5.1.1": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch", - "ethereumjs-util@^5.1.2": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch", - "ethereumjs-util@^5.1.5": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch", - "ethereumjs-util@^5.0.0": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch", - "ethereumjs-util@^5.2.0": "patch:ethereumjs-util@npm%3A5.2.1#./.yarn/patches/ethereumjs-util-npm-5.2.1-72b39f4e7e.patch", "ethereumjs-util@^7.0.10": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch", "ethereumjs-util@^7.1.5": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch", "ethereumjs-util@^7.1.4": "patch:ethereumjs-util@npm%3A7.1.5#./.yarn/patches/ethereumjs-util-npm-7.1.5-5bb4d00000.patch", diff --git a/shared/modules/conversion.utils.ts b/shared/modules/conversion.utils.ts index 75da336eb8e6..5c70e5ecd683 100644 --- a/shared/modules/conversion.utils.ts +++ b/shared/modules/conversion.utils.ts @@ -1,6 +1,7 @@ import { Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; -import { addHexPrefix, BN } from 'ethereumjs-util'; +import BN from 'bn.js'; +import { addHexPrefix } from 'ethereumjs-util'; import { EtherDenomination } from '../constants/common'; import { Numeric, NumericValue } from './Numeric'; diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index eafc8e31bfe5..d687d9b82338 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -1,7 +1,7 @@ import punycode from 'punycode/punycode'; import abi from 'human-standard-token-abi'; import BigNumber from 'bignumber.js'; -import * as ethUtil from 'ethereumjs-util'; +import BN from 'bn.js'; import { DateTime } from 'luxon'; import { getFormattedIpfsUrl, @@ -168,10 +168,10 @@ export function isOriginContractAddress(to, sendTokenAddress) { // Takes wei Hex, returns wei BN, even if input is null export function numericBalance(balance) { if (!balance) { - return new ethUtil.BN(0, 16); + return new BN(0, 16); } const stripped = stripHexPrefix(balance); - return new ethUtil.BN(stripped, 16); + return new BN(stripped, 16); } // Takes hex, returns [beforeDecimal, afterDecimal] diff --git a/ui/helpers/utils/util.test.js b/ui/helpers/utils/util.test.js index d12a57675343..bdf5c9dd9b98 100644 --- a/ui/helpers/utils/util.test.js +++ b/ui/helpers/utils/util.test.js @@ -1,5 +1,6 @@ import Bowser from 'bowser'; -import { BN, toChecksumAddress } from 'ethereumjs-util'; +import BN from 'bn.js'; +import { toChecksumAddress } from 'ethereumjs-util'; import { CHAIN_IDS } from '../../../shared/constants/network'; import { addHexPrefixToObjectValues } from '../../../shared/lib/swaps-utils'; import { toPrecisionWithoutTrailingZeros } from '../../../shared/lib/transactions-controller-utils'; From 877332833d3ae4b4f1ae7339b92e0e802ab326e2 Mon Sep 17 00:00:00 2001 From: infiniteflower <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:21:21 -0500 Subject: [PATCH 32/40] feat: cross chain swaps - tx status - BridgeStatusController (#28636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR is a collection of all the background related code from #27740 (no UI changes). It has been split up in order to make it easier to review. A follow up PR containing all the UI changes from #27740 is here: https://github.com/MetaMask/metamask-extension/pull/28657 The main addition is the `BridgeStatusController` and its supporting code. If you would like to test the functionality of this PR through the UI, please do so through #27740. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28636?quickstart=1) ## **Related issues** Branched off from #27740 ## **Manual testing steps** Refer to #27740 ## **Screenshots/Recordings** Refer to #27740 ## **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/scripts/constants/sentry-state.ts | 5 + .../bridge-status-controller.test.ts.snap | 213 +++++ .../bridge-status-controller.test.ts | 739 ++++++++++++++++++ .../bridge-status/bridge-status-controller.ts | 310 ++++++++ .../controllers/bridge-status/constants.ts | 10 + .../controllers/bridge-status/types.ts | 56 ++ .../controllers/bridge-status/utils.ts | 49 ++ .../bridge-status/validators.test.ts | 238 ++++++ .../controllers/bridge-status/validators.ts | 179 +++++ app/scripts/metamask-controller.js | 31 + shared/constants/transaction.ts | 5 + shared/modules/conversion.utils.ts | 6 + shared/types/bridge-status.ts | 146 ++++ test/data/mock-state.json | 3 + ...rs-after-init-opt-in-background-state.json | 19 +- .../errors-after-init-opt-in-ui-state.json | 21 +- 16 files changed, 2011 insertions(+), 19 deletions(-) create mode 100644 app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap create mode 100644 app/scripts/controllers/bridge-status/bridge-status-controller.test.ts create mode 100644 app/scripts/controllers/bridge-status/bridge-status-controller.ts create mode 100644 app/scripts/controllers/bridge-status/constants.ts create mode 100644 app/scripts/controllers/bridge-status/types.ts create mode 100644 app/scripts/controllers/bridge-status/utils.ts create mode 100644 app/scripts/controllers/bridge-status/validators.test.ts create mode 100644 app/scripts/controllers/bridge-status/validators.ts create mode 100644 shared/types/bridge-status.ts diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index d0fbe7bcb085..289bc0a0d29c 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -122,6 +122,11 @@ export const SENTRY_BACKGROUND_STATE = { quotesRefreshCount: true, }, }, + BridgeStatusController: { + bridgeStatusState: { + txHistory: false, + }, + }, CronjobController: { jobs: false, }, diff --git a/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap b/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap new file mode 100644 index 000000000000..ebd3a938822e --- /dev/null +++ b/app/scripts/controllers/bridge-status/__snapshots__/bridge-status-controller.test.ts.snap @@ -0,0 +1,213 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BridgeStatusController constructor rehydrates the tx history state 1`] = ` +{ + "0xsrcTxHash1": { + "account": "0xaccount1", + "estimatedProcessingTimeInSeconds": 15, + "initialDestAssetBalance": undefined, + "pricingData": undefined, + "quote": { + "bridgeId": "lifi", + "bridges": [ + "across", + ], + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.63", + "symbol": "ETH", + }, + "destChainId": 10, + "destTokenAmount": "990654755978612", + "feeData": { + "metabridge": { + "amount": "8750000000000", + "asset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + }, + }, + "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776", + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + "srcChainId": 42161, + "srcTokenAmount": "991250000000000", + "steps": [ + { + "action": "bridge", + "destAmount": "990654755978612", + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.63", + "symbol": "ETH", + }, + "destChainId": 10, + "protocol": { + "displayName": "Across", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png", + "name": "across", + }, + "srcAmount": "991250000000000", + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + "srcChainId": 42161, + }, + ], + }, + "slippagePercentage": 0, + "startTime": 1729964825189, + "status": { + "srcChain": { + "chainId": 42161, + "txHash": "0xsrcTxHash1", + }, + "status": "PENDING", + }, + "targetContractAddress": "0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC", + }, +} +`; + +exports[`BridgeStatusController startPollingForBridgeTxStatus sets the inital tx history state 1`] = ` +{ + "0xsrcTxHash1": { + "account": "0xaccount1", + "estimatedProcessingTimeInSeconds": 15, + "initialDestAssetBalance": undefined, + "pricingData": undefined, + "quote": { + "bridgeId": "lifi", + "bridges": [ + "across", + ], + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.63", + "symbol": "ETH", + }, + "destChainId": 10, + "destTokenAmount": "990654755978612", + "feeData": { + "metabridge": { + "amount": "8750000000000", + "asset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + }, + }, + "requestId": "197c402f-cb96-4096-9f8c-54aed84ca776", + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + "srcChainId": 42161, + "srcTokenAmount": "991250000000000", + "steps": [ + { + "action": "bridge", + "destAmount": "990654755978612", + "destAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 10, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.63", + "symbol": "ETH", + }, + "destChainId": 10, + "protocol": { + "displayName": "Across", + "icon": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png", + "name": "across", + }, + "srcAmount": "991250000000000", + "srcAsset": { + "address": "0x0000000000000000000000000000000000000000", + "chainId": 42161, + "coinKey": "ETH", + "decimals": 18, + "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "name": "ETH", + "priceUSD": "2478.7", + "symbol": "ETH", + }, + "srcChainId": 42161, + }, + ], + }, + "slippagePercentage": 0, + "startTime": 1729964825189, + "status": { + "srcChain": { + "chainId": 42161, + "txHash": "0xsrcTxHash1", + }, + "status": "PENDING", + }, + "targetContractAddress": "0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC", + }, +} +`; diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts new file mode 100644 index 000000000000..3890f27f7f65 --- /dev/null +++ b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts @@ -0,0 +1,739 @@ +import { flushPromises } from '../../../../test/lib/timer-helpers'; +import { Numeric } from '../../../../shared/modules/Numeric'; +import { + StatusTypes, + ActionTypes, + BridgeId, +} from '../../../../shared/types/bridge-status'; +import BridgeStatusController from './bridge-status-controller'; +import { BridgeStatusControllerMessenger } from './types'; +import { DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE } from './constants'; +import * as bridgeStatusUtils from './utils'; + +const EMPTY_INIT_STATE = { + bridgeStatusState: DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, +}; + +const getMockQuote = ({ srcChainId = 42161, destChainId = 10 } = {}) => ({ + requestId: '197c402f-cb96-4096-9f8c-54aed84ca776', + srcChainId, + srcTokenAmount: '991250000000000', + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + destChainId, + destTokenAmount: '990654755978612', + destAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + feeData: { + metabridge: { + amount: '8750000000000', + asset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }, + bridgeId: 'lifi', + bridges: ['across'], + steps: [ + { + action: 'bridge' as ActionTypes, + srcChainId, + destChainId, + protocol: { + name: 'across', + displayName: 'Across', + icon: 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png', + }, + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + destAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + srcAmount: '991250000000000', + destAmount: '990654755978612', + }, + ], +}); + +const getMockStartPollingForBridgeTxStatusArgs = ({ + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, +} = {}) => ({ + statusRequest: { + bridgeId: 'lifi', + srcTxHash, + bridge: 'across', + srcChainId, + destChainId, + quote: getMockQuote({ srcChainId, destChainId }), + refuel: false, + }, + quoteResponse: { + quote: getMockQuote({ srcChainId, destChainId }), + trade: { + chainId: srcChainId, + to: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + from: account, + value: '0x038d7ea4c68000', + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038589602234000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000007f544a44c0000000000000000000000000056ca675c3633cc16bd6849e2b431d4e8de5e23bf000000000000000000000000000000000000000000000000000000000000006c5a39b10a4f4f0747826140d2c5fe6ef47965741f6f7a4734bf784bf3ae3f24520000000a000222266cc2dca0671d2a17ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b7100000000000000000000000000000000000000009ce3c510b3f58edc8d53ae708056e30926f62d0b42d5c9b61c391bb4e8a2c1917f8ed995169ffad0d79af2590303e83c57e15a9e0b248679849556c2e03a1c811b', + gasLimit: 282915, + }, + approval: null, + estimatedProcessingTimeInSeconds: 15, + }, + startTime: 1729964825189, + slippagePercentage: 0, + pricingData: undefined, + initialDestAssetBalance: undefined, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', +}); + +const MockStatusResponse = { + getPending: ({ + srcTxHash = '0xsrcTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'PENDING' as StatusTypes, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2518.47', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + token: {}, + }, + }), + getComplete: ({ + srcTxHash = '0xsrcTxHash1', + destTxHash = '0xdestTxHash1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + status: 'COMPLETE' as StatusTypes, + isExpectedToken: true, + bridge: 'across' as BridgeId, + srcChain: { + chainId: srcChainId, + txHash: srcTxHash, + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: srcChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.7', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: destChainId, + txHash: destTxHash, + amount: '990654755978611', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: destChainId, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2478.63', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }), +}; + +const MockTxHistory = { + getInit: ({ + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + [srcTxHash]: { + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + }, + }), + getPending: ({ + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + [srcTxHash]: { + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + status: MockStatusResponse.getPending({ + srcTxHash, + srcChainId, + }), + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + }, + }), + getComplete: ({ + srcTxHash = '0xsrcTxHash1', + account = '0xaccount1', + srcChainId = 42161, + destChainId = 10, + } = {}) => ({ + [srcTxHash]: { + quote: getMockQuote({ srcChainId, destChainId }), + startTime: 1729964825189, + estimatedProcessingTimeInSeconds: 15, + slippagePercentage: 0, + account, + status: MockStatusResponse.getComplete({ srcTxHash }), + targetContractAddress: '0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC', + }, + }), +}; + +const getMessengerMock = ({ + account = '0xaccount1', + srcChainId = 42161, +} = {}) => + ({ + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + return { address: account }; + } else if (method === 'NetworkController:findNetworkClientIdByChainId') { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + chainId: new Numeric(srcChainId, 10).toPrefixedHexString(), + }, + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked); + +const executePollingWithPendingStatus = async () => { + // Setup + jest.useFakeTimers(); + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + }); + const startPollingByNetworkClientIdSpy = jest.spyOn( + bridgeStatusController, + 'startPollingByNetworkClientId', + ); + const fetchBridgeTxStatusSpy = jest.spyOn( + bridgeStatusUtils, + 'fetchBridgeTxStatus', + ); + + // Execution + await bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + fetchBridgeTxStatusSpy.mockImplementationOnce(async () => { + return MockStatusResponse.getPending(); + }); + jest.advanceTimersByTime(10000); + await flushPromises(); + + return { + bridgeStatusController, + startPollingByNetworkClientIdSpy, + fetchBridgeTxStatusSpy, + }; +}; + +describe('BridgeStatusController', () => { + describe('constructor', () => { + it('should setup correctly', () => { + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + }); + expect(bridgeStatusController.state).toEqual(EMPTY_INIT_STATE); + }); + it('rehydrates the tx history state', async () => { + // Setup + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + state: { + bridgeStatusState: { + txHistory: MockTxHistory.getPending(), + }, + }, + }); + + // Execution + await bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + + // Assertion + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toMatchSnapshot(); + }); + it('restarts polling for history items that are not complete', async () => { + // Setup + jest.useFakeTimers(); + const fetchBridgeTxStatusSpy = jest.spyOn( + bridgeStatusUtils, + 'fetchBridgeTxStatus', + ); + + // Execution + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + state: { + bridgeStatusState: { + txHistory: MockTxHistory.getPending(), + }, + }, + }); + jest.advanceTimersByTime(10000); + await flushPromises(); + + // Assertions + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + }); + }); + describe('startPollingForBridgeTxStatus', () => { + it('sets the inital tx history state', async () => { + // Setup + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + }); + + // Execution + await bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + + // Assertion + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toMatchSnapshot(); + }); + it('starts polling and updates the tx history when the status response is received', async () => { + const { + bridgeStatusController, + startPollingByNetworkClientIdSpy, + fetchBridgeTxStatusSpy, + } = await executePollingWithPendingStatus(); + + // Assertions + expect(startPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalled(); + expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( + MockTxHistory.getPending(), + ); + }); + it('stops polling when the status response is complete', async () => { + // Setup + jest.useFakeTimers(); + const bridgeStatusController = new BridgeStatusController({ + messenger: getMessengerMock(), + }); + const fetchBridgeTxStatusSpy = jest.spyOn( + bridgeStatusUtils, + 'fetchBridgeTxStatus', + ); + const stopPollingByNetworkClientIdSpy = jest.spyOn( + bridgeStatusController, + 'stopPollingByPollingToken', + ); + + // Execution + await bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + fetchBridgeTxStatusSpy.mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }); + jest.advanceTimersByTime(10000); + await flushPromises(); + + // Assertions + expect(stopPollingByNetworkClientIdSpy).toHaveBeenCalledTimes(1); + expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( + MockTxHistory.getComplete(), + ); + }); + }); + describe('resetState', () => { + it('resets the state', async () => { + const { bridgeStatusController } = + await executePollingWithPendingStatus(); + + expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( + MockTxHistory.getPending(), + ); + bridgeStatusController.resetState(); + expect(bridgeStatusController.state.bridgeStatusState.txHistory).toEqual( + EMPTY_INIT_STATE.bridgeStatusState.txHistory, + ); + }); + }); + describe('wipeBridgeStatus', () => { + it('wipes the bridge status for the given address', async () => { + // Setup + jest.useFakeTimers(); + + let getSelectedAccountCalledTimes = 0; + const messengerMock = { + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + let account; + if (getSelectedAccountCalledTimes === 0) { + account = '0xaccount1'; + } else { + account = '0xaccount2'; + } + getSelectedAccountCalledTimes += 1; + return { address: account }; + } else if ( + method === 'NetworkController:findNetworkClientIdByChainId' + ) { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + chainId: new Numeric(42161, 10).toPrefixedHexString(), + }, + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked; + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + }); + const fetchBridgeTxStatusSpy = jest + .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }) + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete({ + srcTxHash: '0xsrcTxHash2', + destTxHash: '0xdestTxHash2', + }); + }); + + // Start polling for 0xaccount1 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs(), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + + // Start polling for 0xaccount2 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + srcTxHash: '0xsrcTxHash2', + account: '0xaccount2', + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2); + + // Check that both accounts have a tx history entry + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toHaveProperty('0xsrcTxHash1'); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory, + ).toHaveProperty('0xsrcTxHash2'); + + // Wipe the status for 1 account only + bridgeStatusController.wipeBridgeStatus({ + address: '0xaccount1', + ignoreNetwork: false, + }); + + // Assertions + const txHistoryItems = Object.values( + bridgeStatusController.state.bridgeStatusState.txHistory, + ); + expect(txHistoryItems).toHaveLength(1); + expect(txHistoryItems[0].account).toEqual('0xaccount2'); + }); + it('wipes the bridge status for all networks if ignoreNetwork is true', () => { + // Setup + jest.useFakeTimers(); + const messengerMock = { + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + return { address: '0xaccount1' }; + } else if ( + method === 'NetworkController:findNetworkClientIdByChainId' + ) { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + chainId: new Numeric(42161, 10).toPrefixedHexString(), + }, + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked; + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + }); + const fetchBridgeTxStatusSpy = jest + .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }) + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete({ + srcTxHash: '0xsrcTxHash2', + }); + }); + + // Start polling for chainId 42161 to chainId 1 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + account: '0xaccount1', + srcTxHash: '0xsrcTxHash1', + srcChainId: 42161, + destChainId: 1, + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + + // Start polling for chainId 10 to chainId 123 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + account: '0xaccount1', + srcTxHash: '0xsrcTxHash2', + srcChainId: 10, + destChainId: 123, + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2); + + // Check we have a tx history entry for each chainId + expect( + bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1'] + .quote.srcChainId, + ).toEqual(42161); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1'] + .quote.destChainId, + ).toEqual(1); + + expect( + bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2'] + .quote.srcChainId, + ).toEqual(10); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2'] + .quote.destChainId, + ).toEqual(123); + + bridgeStatusController.wipeBridgeStatus({ + address: '0xaccount1', + ignoreNetwork: true, + }); + + // Assertions + const txHistoryItems = Object.values( + bridgeStatusController.state.bridgeStatusState.txHistory, + ); + expect(txHistoryItems).toHaveLength(0); + }); + it('wipes the bridge status only for the current network if ignoreNetwork is false', () => { + // Setup + jest.useFakeTimers(); + const messengerMock = { + call: jest.fn((method: string) => { + if (method === 'AccountsController:getSelectedAccount') { + return { address: '0xaccount1' }; + } else if ( + method === 'NetworkController:findNetworkClientIdByChainId' + ) { + return 'networkClientId'; + } else if (method === 'NetworkController:getState') { + return { selectedNetworkClientId: 'networkClientId' }; + } else if (method === 'NetworkController:getNetworkClientById') { + return { + configuration: { + // This is what controls the selectedNetwork and what gets wiped in this test + chainId: new Numeric(42161, 10).toPrefixedHexString(), + }, + }; + } + return null; + }), + publish: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + } as unknown as jest.Mocked; + const bridgeStatusController = new BridgeStatusController({ + messenger: messengerMock, + }); + const fetchBridgeTxStatusSpy = jest + .spyOn(bridgeStatusUtils, 'fetchBridgeTxStatus') + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete(); + }) + .mockImplementationOnce(async () => { + return MockStatusResponse.getComplete({ + srcTxHash: '0xsrcTxHash2', + }); + }); + + // Start polling for chainId 42161 to chainId 1 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + account: '0xaccount1', + srcTxHash: '0xsrcTxHash1', + srcChainId: 42161, + destChainId: 1, + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(1); + + // Start polling for chainId 10 to chainId 123 + bridgeStatusController.startPollingForBridgeTxStatus( + getMockStartPollingForBridgeTxStatusArgs({ + account: '0xaccount1', + srcTxHash: '0xsrcTxHash2', + srcChainId: 10, + destChainId: 123, + }), + ); + jest.advanceTimersByTime(10_000); + expect(fetchBridgeTxStatusSpy).toHaveBeenCalledTimes(2); + + // Check we have a tx history entry for each chainId + expect( + bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1'] + .quote.srcChainId, + ).toEqual(42161); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash1'] + .quote.destChainId, + ).toEqual(1); + + expect( + bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2'] + .quote.srcChainId, + ).toEqual(10); + expect( + bridgeStatusController.state.bridgeStatusState.txHistory['0xsrcTxHash2'] + .quote.destChainId, + ).toEqual(123); + + bridgeStatusController.wipeBridgeStatus({ + address: '0xaccount1', + ignoreNetwork: false, + }); + + // Assertions + const txHistoryItems = Object.values( + bridgeStatusController.state.bridgeStatusState.txHistory, + ); + expect(txHistoryItems).toHaveLength(1); + expect(txHistoryItems[0].quote.srcChainId).toEqual(10); + expect(txHistoryItems[0].quote.destChainId).toEqual(123); + }); + }); +}); diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.ts new file mode 100644 index 000000000000..18010ae0de3d --- /dev/null +++ b/app/scripts/controllers/bridge-status/bridge-status-controller.ts @@ -0,0 +1,310 @@ +import { StateMetadata } from '@metamask/base-controller'; +import { StaticIntervalPollingController } from '@metamask/polling-controller'; +import { Hex } from '@metamask/utils'; +// eslint-disable-next-line import/no-restricted-paths +import { + StartPollingForBridgeTxStatusArgs, + StatusRequest, + StatusTypes, + BridgeStatusControllerState, +} from '../../../../shared/types/bridge-status'; +import { decimalToPrefixedHex } from '../../../../shared/modules/conversion.utils'; +import { + BRIDGE_STATUS_CONTROLLER_NAME, + DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, + REFRESH_INTERVAL_MS, +} from './constants'; +import { BridgeStatusControllerMessenger } from './types'; +import { fetchBridgeTxStatus } from './utils'; + +const metadata: StateMetadata<{ + bridgeStatusState: BridgeStatusControllerState; +}> = { + // We want to persist the bridge status state so that we can show the proper data for the Activity list + // basically match the behavior of TransactionController + bridgeStatusState: { + persist: true, + anonymous: false, + }, +}; + +type SrcTxHash = string; +export type FetchBridgeTxStatusArgs = { + statusRequest: StatusRequest; +}; +export default class BridgeStatusController extends StaticIntervalPollingController< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + { bridgeStatusState: BridgeStatusControllerState }, + BridgeStatusControllerMessenger +> { + #pollingTokensBySrcTxHash: Record = {}; + + constructor({ + messenger, + state, + }: { + messenger: BridgeStatusControllerMessenger; + state?: Partial<{ + bridgeStatusState: BridgeStatusControllerState; + }>; + }) { + super({ + name: BRIDGE_STATUS_CONTROLLER_NAME, + metadata, + messenger, + // Restore the persisted state + state: { + ...state, + bridgeStatusState: { + ...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, + ...state?.bridgeStatusState, + }, + }, + }); + + // Register action handlers + this.messagingSystem.registerActionHandler( + `${BRIDGE_STATUS_CONTROLLER_NAME}:startPollingForBridgeTxStatus`, + this.startPollingForBridgeTxStatus.bind(this), + ); + this.messagingSystem.registerActionHandler( + `${BRIDGE_STATUS_CONTROLLER_NAME}:wipeBridgeStatus`, + this.wipeBridgeStatus.bind(this), + ); + + // Set interval + this.setIntervalLength(REFRESH_INTERVAL_MS); + + // If you close the extension, but keep the browser open, the polling continues + // If you close the browser, the polling stops + // Check for historyItems that do not have a status of complete and restart polling + this.#restartPollingForIncompleteHistoryItems(); + } + + resetState = () => { + this.update((_state) => { + _state.bridgeStatusState = { + ...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, + }; + }); + }; + + wipeBridgeStatus = ({ + address, + ignoreNetwork, + }: { + address: string; + ignoreNetwork: boolean; + }) => { + // Wipe all networks for this address + if (ignoreNetwork) { + this.update((_state) => { + _state.bridgeStatusState = { + ...DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE, + }; + }); + } else { + const { selectedNetworkClientId } = this.messagingSystem.call( + 'NetworkController:getState', + ); + const selectedNetworkClient = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + selectedNetworkClientId, + ); + const selectedChainId = selectedNetworkClient.configuration.chainId; + + this.#wipeBridgeStatusByChainId(address, selectedChainId); + } + }; + + #restartPollingForIncompleteHistoryItems = () => { + // Check for historyItems that do not have a status of complete and restart polling + const { bridgeStatusState } = this.state; + const historyItems = Object.values(bridgeStatusState.txHistory); + const incompleteHistoryItems = historyItems + .filter( + (historyItem) => historyItem.status.status !== StatusTypes.COMPLETE, + ) + .filter((historyItem) => { + // Check if we are already polling this tx, if so, skip restarting polling for that + const srcTxHash = historyItem.status.srcChain.txHash; + const pollingToken = this.#pollingTokensBySrcTxHash[srcTxHash]; + return !pollingToken; + }); + + incompleteHistoryItems.forEach((historyItem) => { + const statusRequest = { + bridgeId: historyItem.quote.bridgeId, + srcTxHash: historyItem.status.srcChain.txHash, + bridge: historyItem.quote.bridges[0], + srcChainId: historyItem.quote.srcChainId, + destChainId: historyItem.quote.destChainId, + quote: historyItem.quote, + refuel: Boolean(historyItem.quote.refuel), + }; + + const hexSourceChainId = decimalToPrefixedHex(statusRequest.srcChainId); + const networkClientId = this.messagingSystem.call( + 'NetworkController:findNetworkClientIdByChainId', + hexSourceChainId, + ); + + // We manually call startPollingByNetworkClientId() here rather than go through startPollingForBridgeTxStatus() + // because we don't want to overwrite the existing historyItem in state + const options: FetchBridgeTxStatusArgs = { statusRequest }; + this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash] = + this.startPollingByNetworkClientId(networkClientId, options); + }); + }; + + startPollingForBridgeTxStatus = ( + startPollingForBridgeTxStatusArgs: StartPollingForBridgeTxStatusArgs, + ) => { + const { + statusRequest, + quoteResponse, + startTime, + slippagePercentage, + pricingData, + initialDestAssetBalance, + targetContractAddress, + } = startPollingForBridgeTxStatusArgs; + const hexSourceChainId = decimalToPrefixedHex(statusRequest.srcChainId); + + const { bridgeStatusState } = this.state; + const { address: account } = this.#getSelectedAccount(); + + // Write all non-status fields to state so we can reference the quote in Activity list without the Bridge API + // We know it's in progress but not the exact status yet + this.update((_state) => { + _state.bridgeStatusState = { + ...bridgeStatusState, + txHistory: { + ...bridgeStatusState.txHistory, + [statusRequest.srcTxHash]: { + quote: quoteResponse.quote, + startTime, + estimatedProcessingTimeInSeconds: + quoteResponse.estimatedProcessingTimeInSeconds, + slippagePercentage, + pricingData, + initialDestAssetBalance, + targetContractAddress, + account, + status: { + // We always have a PENDING status when we start polling for a tx, don't need the Bridge API for that + // Also we know the bare minimum fields for status at this point in time + status: StatusTypes.PENDING, + srcChain: { + chainId: statusRequest.srcChainId, + txHash: statusRequest.srcTxHash, + }, + }, + }, + }, + }; + }); + + const networkClientId = this.messagingSystem.call( + 'NetworkController:findNetworkClientIdByChainId', + hexSourceChainId, + ); + this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash] = + this.startPollingByNetworkClientId(networkClientId, { statusRequest }); + }; + + // This will be called after you call this.startPollingByNetworkClientId() + // The args passed in are the args you passed in to startPollingByNetworkClientId() + _executePoll = async ( + _networkClientId: string, + fetchBridgeTxStatusArgs: FetchBridgeTxStatusArgs, + ) => { + await this.#fetchBridgeTxStatus(fetchBridgeTxStatusArgs); + }; + + #getSelectedAccount() { + return this.messagingSystem.call('AccountsController:getSelectedAccount'); + } + + #fetchBridgeTxStatus = async ({ statusRequest }: FetchBridgeTxStatusArgs) => { + const { bridgeStatusState } = this.state; + + try { + // We try here because we receive 500 errors from Bridge API if we try to fetch immediately after submitting the source tx + // Oddly mostly happens on Optimism, never on Arbitrum. By the 2nd fetch, the Bridge API responds properly. + const status = await fetchBridgeTxStatus(statusRequest); + + // No need to purge these on network change or account change, TransactionController does not purge either. + // TODO In theory we can skip checking status if it's not the current account/network + // we need to keep track of the account that this is associated with as well so that we don't show it in Activity list for other accounts + // First stab at this will not stop polling when you are on a different account + this.update((_state) => { + const bridgeHistoryItem = + _state.bridgeStatusState.txHistory[statusRequest.srcTxHash]; + + _state.bridgeStatusState = { + ...bridgeStatusState, + txHistory: { + ...bridgeStatusState.txHistory, + [statusRequest.srcTxHash]: { + ...bridgeHistoryItem, + status, + }, + }, + }; + }); + + const pollingToken = + this.#pollingTokensBySrcTxHash[statusRequest.srcTxHash]; + if (status.status === StatusTypes.COMPLETE && pollingToken) { + this.stopPollingByPollingToken(pollingToken); + } + } catch (e) { + console.log('Failed to fetch bridge tx status', e); + } + }; + + // Wipes the bridge status for the given address and chainId + // Will match either source or destination chainId to the selectedChainId + #wipeBridgeStatusByChainId = (address: string, selectedChainId: Hex) => { + const sourceTxHashesToDelete = Object.keys( + this.state.bridgeStatusState.txHistory, + ).filter((sourceTxHash) => { + const bridgeHistoryItem = + this.state.bridgeStatusState.txHistory[sourceTxHash]; + + const hexSourceChainId = decimalToPrefixedHex( + bridgeHistoryItem.quote.srcChainId, + ); + const hexDestChainId = decimalToPrefixedHex( + bridgeHistoryItem.quote.destChainId, + ); + + return ( + bridgeHistoryItem.account === address && + (hexSourceChainId === selectedChainId || + hexDestChainId === selectedChainId) + ); + }); + + sourceTxHashesToDelete.forEach((sourceTxHash) => { + const pollingToken = this.#pollingTokensBySrcTxHash[sourceTxHash]; + + if (pollingToken) { + this.stopPollingByPollingToken( + this.#pollingTokensBySrcTxHash[sourceTxHash], + ); + } + }); + + this.update((_state) => { + _state.bridgeStatusState.txHistory = sourceTxHashesToDelete.reduce( + (acc, sourceTxHash) => { + delete acc[sourceTxHash]; + return acc; + }, + _state.bridgeStatusState.txHistory, + ); + }); + }; +} diff --git a/app/scripts/controllers/bridge-status/constants.ts b/app/scripts/controllers/bridge-status/constants.ts new file mode 100644 index 000000000000..83208bdc73d8 --- /dev/null +++ b/app/scripts/controllers/bridge-status/constants.ts @@ -0,0 +1,10 @@ +import { BridgeStatusControllerState } from '../../../../shared/types/bridge-status'; + +export const REFRESH_INTERVAL_MS = 10 * 1000; + +export const BRIDGE_STATUS_CONTROLLER_NAME = 'BridgeStatusController'; + +export const DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE: BridgeStatusControllerState = + { + txHistory: {}, + }; diff --git a/app/scripts/controllers/bridge-status/types.ts b/app/scripts/controllers/bridge-status/types.ts new file mode 100644 index 000000000000..040cd1e0c9bd --- /dev/null +++ b/app/scripts/controllers/bridge-status/types.ts @@ -0,0 +1,56 @@ +import { + ControllerGetStateAction, + ControllerStateChangeEvent, + RestrictedControllerMessenger, +} from '@metamask/base-controller'; +import { + NetworkControllerFindNetworkClientIdByChainIdAction, + NetworkControllerGetNetworkClientByIdAction, + NetworkControllerGetStateAction, +} from '@metamask/network-controller'; +import { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller'; +import { + BridgeStatusAction, + BridgeStatusControllerState, +} from '../../../../shared/types/bridge-status'; +import { BRIDGE_STATUS_CONTROLLER_NAME } from './constants'; +import BridgeStatusController from './bridge-status-controller'; + +type BridgeStatusControllerAction< + FunctionName extends keyof BridgeStatusController, +> = { + type: `${typeof BRIDGE_STATUS_CONTROLLER_NAME}:${FunctionName}`; + handler: BridgeStatusController[FunctionName]; +}; + +// Maps to BridgeController function names +type BridgeStatusControllerActions = + | BridgeStatusControllerAction + | BridgeStatusControllerAction + | ControllerGetStateAction< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + BridgeStatusControllerState + >; + +type BridgeStatusControllerEvents = ControllerStateChangeEvent< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + BridgeStatusControllerState +>; + +type AllowedActions = + | NetworkControllerFindNetworkClientIdByChainIdAction + | NetworkControllerGetStateAction + | NetworkControllerGetNetworkClientByIdAction + | AccountsControllerGetSelectedAccountAction; +type AllowedEvents = never; + +/** + * The messenger for the BridgeStatusController. + */ +export type BridgeStatusControllerMessenger = RestrictedControllerMessenger< + typeof BRIDGE_STATUS_CONTROLLER_NAME, + BridgeStatusControllerActions | AllowedActions, + BridgeStatusControllerEvents | AllowedEvents, + AllowedActions['type'], + AllowedEvents['type'] +>; diff --git a/app/scripts/controllers/bridge-status/utils.ts b/app/scripts/controllers/bridge-status/utils.ts new file mode 100644 index 000000000000..323e7e2faeab --- /dev/null +++ b/app/scripts/controllers/bridge-status/utils.ts @@ -0,0 +1,49 @@ +import { + BRIDGE_API_BASE_URL, + BRIDGE_CLIENT_ID, +} from '../../../../shared/constants/bridge'; +import fetchWithCache from '../../../../shared/lib/fetch-with-cache'; +import { + StatusResponse, + StatusRequest, +} from '../../../../shared/types/bridge-status'; +import { validateResponse, validators } from './validators'; + +const CLIENT_ID_HEADER = { 'X-Client-Id': BRIDGE_CLIENT_ID }; + +export const BRIDGE_STATUS_BASE_URL = `${BRIDGE_API_BASE_URL}/getTxStatus`; + +export const fetchBridgeTxStatus = async (statusRequest: StatusRequest) => { + // Assemble params + const { quote, ...statusRequestNoQuote } = statusRequest; + const statusRequestNoQuoteFormatted = Object.fromEntries( + Object.entries(statusRequestNoQuote).map(([key, value]) => [ + key, + value.toString(), + ]), + ); + const params = new URLSearchParams(statusRequestNoQuoteFormatted); + + // Fetch + const url = `${BRIDGE_STATUS_BASE_URL}?${params.toString()}`; + + const rawTxStatus = await fetchWithCache({ + url, + fetchOptions: { method: 'GET', headers: CLIENT_ID_HEADER }, + cacheOptions: { cacheRefreshTime: 0 }, + functionName: 'fetchBridgeTxStatus', + }); + + // Validate + const isValid = validateResponse( + validators, + rawTxStatus, + BRIDGE_STATUS_BASE_URL, + ); + if (!isValid) { + throw new Error('Invalid response from bridge'); + } + + // Return + return rawTxStatus; +}; diff --git a/app/scripts/controllers/bridge-status/validators.test.ts b/app/scripts/controllers/bridge-status/validators.test.ts new file mode 100644 index 000000000000..18ca81d7a5b2 --- /dev/null +++ b/app/scripts/controllers/bridge-status/validators.test.ts @@ -0,0 +1,238 @@ +import { StatusResponse } from '../../../../shared/types/bridge-status'; +import { validateResponse, validators } from './validators'; + +const BridgeTxStatusResponses = { + STATUS_PENDING_VALID: { + status: 'PENDING', + bridge: 'across', + srcChain: { + chainId: 42161, + txHash: + '0x76a65e4cea35d8732f0e3250faed00ba764ad5a0e7c51cb1bafbc9d76ac0b325', + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 42161, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2550.12', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: '10', + token: {}, + }, + }, + STATUS_PENDING_VALID_MISSING_FIELDS: { + status: 'PENDING', + srcChain: { + chainId: 42161, + txHash: + '0x5cbda572c686a5a57fe62735325e408f9164f77a4787df29ce13edef765adaa9', + }, + }, + STATUS_PENDING_VALID_MISSING_FIELDS_2: { + status: 'PENDING', + bridge: 'hop', + srcChain: { + chainId: 42161, + txHash: + '0x5cbda572c686a5a57fe62735325e408f9164f77a4787df29ce13edef765adaa9', + amount: '991250000000000', + token: { + chainId: 42161, + address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + symbol: 'ETH', + name: 'Ethereum', + decimals: 18, + icon: 'https://media.socket.tech/tokens/all/ETH', + logoURI: 'https://media.socket.tech/tokens/all/ETH', + chainAgnosticId: null, + }, + }, + }, + STATUS_PENDING_INVALID_MISSING_FIELDS: { + status: 'PENDING', + bridge: 'across', + srcChain: { + chainId: 42161, + txHash: + '0x76a65e4cea35d8732f0e3250faed00ba764ad5a0e7c51cb1bafbc9d76ac0b325', + amount: '991250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 42161, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2550.12', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + token: {}, + }, + }, + STATUS_COMPLETE_VALID: { + status: 'COMPLETE', + isExpectedToken: true, + bridge: 'across', + srcChain: { + chainId: 10, + txHash: + '0x9fdc426692aba1f81e145834602ed59ed331054e5b91a09a673cb12d4b4f6a33', + amount: '4956250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 10, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2649.21', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: '42161', + txHash: + '0x3a494e672717f9b1f2b64a48a19985842d82d0747400fccebebc7a4e99c8eaab', + amount: '4926701727965948', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 42161, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2648.72', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }, + STATUS_COMPLETE_VALID_MISSING_FIELDS: { + status: 'COMPLETE', + bridge: 'across', + srcChain: { + chainId: 10, + txHash: + '0x9fdc426692aba1f81e145834602ed59ed331054e5b91a09a673cb12d4b4f6a33', + amount: '4956250000000000', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 10, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2649.21', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + destChain: { + chainId: '42161', + txHash: + '0x3a494e672717f9b1f2b64a48a19985842d82d0747400fccebebc7a4e99c8eaab', + amount: '4926701727965948', + token: { + address: '0x0000000000000000000000000000000000000000', + chainId: 42161, + symbol: 'ETH', + decimals: 18, + name: 'ETH', + coinKey: 'ETH', + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + priceUSD: '2648.72', + icon: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, + }, + STATUS_COMPLETE_INVALID_MISSING_FIELDS: { + status: 'COMPLETE', + isExpectedToken: true, + bridge: 'across', + }, +}; + +describe('validators', () => { + describe('bridgeStatusValidator', () => { + // @ts-expect-error - it.each is a function + it.each([ + { + input: BridgeTxStatusResponses.STATUS_PENDING_VALID, + expected: true, + description: 'valid pending bridge status', + }, + { + input: BridgeTxStatusResponses.STATUS_PENDING_VALID_MISSING_FIELDS, + expected: true, + description: 'valid pending bridge status missing fields', + }, + { + input: BridgeTxStatusResponses.STATUS_PENDING_VALID_MISSING_FIELDS_2, + expected: true, + description: 'valid pending bridge status missing fields 2', + }, + { + input: BridgeTxStatusResponses.STATUS_PENDING_INVALID_MISSING_FIELDS, + expected: false, + description: 'pending bridge status with missing fields', + }, + { + input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID, + expected: true, + description: 'valid complete bridge status', + }, + { + input: BridgeTxStatusResponses.STATUS_COMPLETE_INVALID_MISSING_FIELDS, + expected: false, + description: 'complete bridge status with missing fields', + }, + { + input: BridgeTxStatusResponses.STATUS_COMPLETE_VALID_MISSING_FIELDS, + expected: true, + description: 'complete bridge status with missing fields', + }, + { + input: undefined, + expected: false, + description: 'undefined', + }, + { + input: null, + expected: false, + description: 'null', + }, + { + input: {}, + expected: false, + description: 'empty object', + }, + ])( + 'should return $expected for $description', + ({ input, expected }: { input: unknown; expected: boolean }) => { + const res = validateResponse( + validators, + input, + 'dummyurl.com', + ); + expect(res).toBe(expected); + }, + ); + }); +}); diff --git a/app/scripts/controllers/bridge-status/validators.ts b/app/scripts/controllers/bridge-status/validators.ts new file mode 100644 index 000000000000..69e788025b01 --- /dev/null +++ b/app/scripts/controllers/bridge-status/validators.ts @@ -0,0 +1,179 @@ +import { validHex, validateData } from '../../../../shared/lib/swaps-utils'; +import { isValidHexAddress } from '../../../../shared/modules/hexstring-utils'; +import { + BridgeId, + DestChainStatus, + SrcChainStatus, + Asset, + StatusTypes, +} from '../../../../shared/types/bridge-status'; +import { BRIDGE_STATUS_BASE_URL } from './utils'; + +type Validator = { + property: keyof ExpectedResponse | string; + type: string; + validator: (value: DataToValidate) => boolean; +}; + +export const validateResponse = ( + validators: Validator[], + data: unknown, + urlUsed: string, +): data is ExpectedResponse => { + if (data === null || data === undefined) { + return false; + } + return validateData(validators, data, urlUsed); +}; + +const assetValidators = [ + { + property: 'chainId', + type: 'number', + validator: (v: unknown): v is number => typeof v === 'number', + }, + { + property: 'address', + type: 'string', + validator: (v: unknown): v is string => isValidHexAddress(v as string), + }, + { + property: 'symbol', + type: 'string', + validator: (v: unknown): v is string => typeof v === 'string', + }, + { + property: 'name', + type: 'string', + validator: (v: unknown): v is string => typeof v === 'string', + }, + { + property: 'decimals', + type: 'number', + validator: (v: unknown): v is number => typeof v === 'number', + }, + { + property: 'icon', + type: 'string|undefined', + validator: (v: unknown): v is string | undefined => + v === undefined || typeof v === 'string', + }, +]; + +const assetValidator = (v: unknown): v is Asset => + validateResponse(assetValidators, v, BRIDGE_STATUS_BASE_URL); + +const srcChainStatusValidators = [ + { + property: 'chainId', + // For some reason, API returns destChain.chainId as a string, it's a number everywhere else + type: 'number|string', + validator: (v: unknown): v is number | string => + typeof v === 'number' || typeof v === 'string', + }, + { + property: 'txHash', + type: 'string', + validator: validHex, + }, + { + property: 'amount', + type: 'string|undefined', + validator: (v: unknown): v is string | undefined => + v === undefined || typeof v === 'string', + }, + { + property: 'token', + type: 'object|undefined', + validator: (v: unknown): v is object | undefined => + v === undefined || assetValidator(v), + }, +]; + +const srcChainStatusValidator = (v: unknown): v is SrcChainStatus => + validateResponse( + srcChainStatusValidators, + v, + BRIDGE_STATUS_BASE_URL, + ); + +const destChainStatusValidators = [ + { + property: 'chainId', + // For some reason, API returns destChain.chainId as a string, it's a number everywhere else + type: 'number|string', + validator: (v: unknown): v is number | string => + typeof v === 'number' || typeof v === 'string', + }, + { + property: 'amount', + type: 'string|undefined', + validator: (v: unknown): v is string | undefined => + v === undefined || typeof v === 'string', + }, + { + property: 'txHash', + type: 'string|undefined', + validator: (v: unknown): v is string | undefined => + v === undefined || typeof v === 'string', + }, + { + property: 'token', + type: 'object|undefined', + validator: (v: unknown): v is Asset | undefined => + v === undefined || + (v && typeof v === 'object' && Object.keys(v).length === 0) || + assetValidator(v), + }, +]; + +const destChainStatusValidator = (v: unknown): v is DestChainStatus => + validateResponse( + destChainStatusValidators, + v, + BRIDGE_STATUS_BASE_URL, + ); + +export const validators = [ + { + property: 'status', + type: 'string', + validator: (v: unknown): v is StatusTypes => + Object.values(StatusTypes).includes(v as StatusTypes), + }, + { + property: 'srcChain', + type: 'object', + validator: srcChainStatusValidator, + }, + { + property: 'destChain', + type: 'object|undefined', + validator: (v: unknown): v is object | unknown => + v === undefined || destChainStatusValidator(v), + }, + { + property: 'bridge', + type: 'string|undefined', + validator: (v: unknown): v is BridgeId | undefined => + v === undefined || Object.values(BridgeId).includes(v as BridgeId), + }, + { + property: 'isExpectedToken', + type: 'boolean|undefined', + validator: (v: unknown): v is boolean | undefined => + v === undefined || typeof v === 'boolean', + }, + { + property: 'isUnrecognizedRouterAddress', + type: 'boolean|undefined', + validator: (v: unknown): v is boolean | undefined => + v === undefined || typeof v === 'boolean', + }, + // TODO: add refuel validator + // { + // property: 'refuel', + // type: 'object', + // validator: (v: unknown) => Object.values(RefuelStatusResponse).includes(v), + // }, +]; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ff3ad58f26d4..55888558c6d6 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -243,6 +243,7 @@ import { getProviderConfig } from '../../shared/modules/selectors/networks'; import { endTrace, trace } from '../../shared/lib/trace'; // eslint-disable-next-line import/no-restricted-paths import { isSnapId } from '../../ui/helpers/utils/snaps'; +import { BridgeStatusAction } from '../../shared/types/bridge-status'; import { BalancesController as MultichainBalancesController } from './lib/accounts/BalancesController'; import { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -371,6 +372,8 @@ import { import createTracingMiddleware from './lib/createTracingMiddleware'; import { PatchStore } from './lib/PatchStore'; import { sanitizeUIState } from './lib/state-utils'; +import BridgeStatusController from './controllers/bridge-status/bridge-status-controller'; +import { BRIDGE_STATUS_CONTROLLER_NAME } from './controllers/bridge-status/constants'; export const METAMASK_CONTROLLER_EVENTS = { // Fired after state changes that impact the extension badge (unapproved msg count) @@ -2185,6 +2188,22 @@ export default class MetamaskController extends EventEmitter { ), }); + const bridgeStatusControllerMessenger = + this.controllerMessenger.getRestricted({ + name: BRIDGE_STATUS_CONTROLLER_NAME, + allowedActions: [ + 'AccountsController:getSelectedAccount', + 'NetworkController:getNetworkClientById', + 'NetworkController:findNetworkClientIdByChainId', + 'NetworkController:getState', + ], + allowedEvents: [], + }); + this.bridgeStatusController = new BridgeStatusController({ + messenger: bridgeStatusControllerMessenger, + state: initState.BridgeStatusController, + }); + const smartTransactionsControllerMessenger = this.controllerMessenger.getRestricted({ name: 'SmartTransactionsController', @@ -2424,6 +2443,7 @@ export default class MetamaskController extends EventEmitter { SignatureController: this.signatureController, SwapsController: this.swapsController, BridgeController: this.bridgeController, + BridgeStatusController: this.bridgeStatusController, EnsController: this.ensController, ApprovalController: this.approvalController, PPOMController: this.ppomController, @@ -4015,6 +4035,13 @@ export default class MetamaskController extends EventEmitter { `${BRIDGE_CONTROLLER_NAME}:${BridgeUserAction.UPDATE_QUOTE_PARAMS}`, ), + // Bridge Status + [BridgeStatusAction.START_POLLING_FOR_BRIDGE_TX_STATUS]: + this.controllerMessenger.call.bind( + this.controllerMessenger, + `${BRIDGE_STATUS_CONTROLLER_NAME}:${BridgeStatusAction.START_POLLING_FOR_BRIDGE_TX_STATUS}`, + ), + // Smart Transactions fetchSmartTransactionFees: smartTransactionsController.getFees.bind( smartTransactionsController, @@ -5005,6 +5032,10 @@ export default class MetamaskController extends EventEmitter { address: selectedAddress, ignoreNetwork: false, }); + this.bridgeStatusController.wipeBridgeStatus({ + address: selectedAddress, + ignoreNetwork: false, + }); this.networkController.resetConnection(); return selectedAddress; diff --git a/shared/constants/transaction.ts b/shared/constants/transaction.ts index 24b89140f941..38311de3be76 100644 --- a/shared/constants/transaction.ts +++ b/shared/constants/transaction.ts @@ -113,6 +113,11 @@ export enum TransactionGroupCategory { * Transaction group representing a token swap through MetaMask Swaps, where the final token is sent to another address. */ swapAndSend = 'swapAndSend', + /** + * Transaction group representing a token bridge through MetaMask Bridge, + * where the final token is sent to another chain. + */ + bridge = 'bridge', } /** diff --git a/shared/modules/conversion.utils.ts b/shared/modules/conversion.utils.ts index 5c70e5ecd683..ad83e9e8c634 100644 --- a/shared/modules/conversion.utils.ts +++ b/shared/modules/conversion.utils.ts @@ -185,6 +185,12 @@ export function decimalToHex(decimal: number | string | BigNumber | BN) { return new Numeric(decimal, 10).toBase(16).toString(); } +export function decimalToPrefixedHex( + decimal: number | string | BigNumber | BN, +): Hex { + return new Numeric(decimal, 10).toPrefixedHexString() as Hex; +} + export function hexToDecimal(hexValue: number | string | BigNumber | BN) { return new Numeric(hexValue, 16).toBase(10).toString(); } diff --git a/shared/types/bridge-status.ts b/shared/types/bridge-status.ts new file mode 100644 index 000000000000..601a2209aaf9 --- /dev/null +++ b/shared/types/bridge-status.ts @@ -0,0 +1,146 @@ +// eslint-disable-next-line import/no-restricted-paths +import { ChainId, Quote, QuoteResponse } from '../../ui/pages/bridge/types'; + +// All fields need to be types not interfaces, same with their children fields +// o/w you get a type error + +export enum StatusTypes { + UNKNOWN = 'UNKNOWN', + FAILED = 'FAILED', + PENDING = 'PENDING', + COMPLETE = 'COMPLETE', +} + +export type StatusRequest = { + bridgeId: string; // lifi, socket, squid + srcTxHash: string; // lifi, socket, squid + bridge: string; // lifi, socket, squid + srcChainId: ChainId; // lifi, socket, squid + destChainId: ChainId; // lifi, socket, squid + quote?: Quote; // squid + refuel?: boolean; // lifi +}; + +export type Asset = { + chainId: ChainId; + address: string; + symbol: string; + name: string; + decimals: number; + icon?: string; +}; + +export type SrcChainStatus = { + chainId: ChainId; + txHash: string; + amount?: string; + token?: Asset; +}; + +export type DestChainStatus = { + chainId: ChainId; + txHash?: string; + amount?: string; + token?: Record | Asset; +}; + +export enum BridgeId { + HOP = 'hop', + CELER = 'celer', + CELERCIRCLE = 'celercircle', + CONNEXT = 'connext', + POLYGON = 'polygon', + AVALANCHE = 'avalanche', + MULTICHAIN = 'multichain', + AXELAR = 'axelar', + ACROSS = 'across', + STARGATE = 'stargate', +} + +export enum FeeType { + METABRIDGE = 'metabridge', + REFUEL = 'refuel', +} + +export type FeeData = { + amount: string; + asset: Asset; +}; + +export type Protocol = { + displayName?: string; + icon?: string; + name?: string; // for legacy quotes +}; + +export enum ActionTypes { + BRIDGE = 'bridge', + SWAP = 'swap', + REFUEL = 'refuel', +} + +export type Step = { + action: ActionTypes; + srcChainId: ChainId; + destChainId?: ChainId; + srcAsset: Asset; + destAsset: Asset; + srcAmount: string; + destAmount: string; + protocol: Protocol; +}; + +export type StatusResponse = { + status: StatusTypes; + srcChain: SrcChainStatus; + destChain?: DestChainStatus; + bridge?: BridgeId; + isExpectedToken?: boolean; + isUnrecognizedRouterAddress?: boolean; + refuel?: RefuelStatusResponse; +}; + +export type RefuelStatusResponse = object & StatusResponse; + +export type RefuelData = object & Step; + +export type BridgeHistoryItem = { + quote: Quote; + status: StatusResponse; + startTime?: number; + estimatedProcessingTimeInSeconds: number; + slippagePercentage: number; + completionTime?: number; + pricingData?: { + quotedGasInUsd: number; + quotedReturnInUsd: number; + amountSentInUsd: number; + quotedRefuelSrcAmountInUsd?: number; + quotedRefuelDestAmountInUsd?: number; + }; + initialDestAssetBalance?: number; + targetContractAddress?: string; + account: string; +}; + +export enum BridgeStatusAction { + START_POLLING_FOR_BRIDGE_TX_STATUS = 'startPollingForBridgeTxStatus', + WIPE_BRIDGE_STATUS = 'wipeBridgeStatus', + GET_STATE = 'getState', +} + +export type StartPollingForBridgeTxStatusArgs = { + statusRequest: StatusRequest; + quoteResponse: QuoteResponse; + startTime?: BridgeHistoryItem['startTime']; + slippagePercentage: BridgeHistoryItem['slippagePercentage']; + pricingData?: BridgeHistoryItem['pricingData']; + initialDestAssetBalance?: BridgeHistoryItem['initialDestAssetBalance']; + targetContractAddress?: BridgeHistoryItem['targetContractAddress']; +}; + +export type SourceChainTxHash = string; + +export type BridgeStatusControllerState = { + txHistory: Record; +}; diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 734845f0ca9a..b315dfa203eb 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -2035,6 +2035,9 @@ } } } + }, + "bridgeStatusState": { + "txHistory": {} } }, "ramps": { 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 e8ca8579a7a4..3c2430fbe063 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 @@ -67,38 +67,39 @@ "srcNetworkAllowlist": { "0": "string", "1": "string", "2": "string" }, "destNetworkAllowlist": { "0": "string", "1": "string", "2": "string" } }, + "srcTokens": {}, + "srcTopAssets": {}, "destTokens": {}, "destTopAssets": {}, "quoteRequest": { - "slippage": 0.5, - "srcTokenAddress": "0x0000000000000000000000000000000000000000" + "srcTokenAddress": "0x0000000000000000000000000000000000000000", + "slippage": 0.5 }, "quotes": {}, - "quotesRefreshCount": 0, - "srcTokens": {}, - "srcTopAssets": {} + "quotesRefreshCount": 0 } }, + "BridgeStatusController": { "bridgeStatusState": { "txHistory": "object" } }, "CronjobController": { "jobs": "object" }, "CurrencyController": { + "currentCurrency": "usd", "currencyRates": { "ETH": { "conversionDate": "number", "conversionRate": 1700, "usdConversionRate": 1700 }, - "LineaETH": { + "SepoliaETH": { "conversionDate": "number", "conversionRate": 1700, "usdConversionRate": 1700 }, - "SepoliaETH": { + "LineaETH": { "conversionDate": "number", "conversionRate": 1700, "usdConversionRate": 1700 } - }, - "currentCurrency": "usd" + } }, "DecryptMessageController": { "unapprovedDecryptMsgs": "object", 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 cbc9ff5b74c4..8b2efef3e517 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 @@ -47,7 +47,6 @@ "completedOnboarding": true, "knownMethodData": "object", "use4ByteResolution": true, - "showIncomingTransactions": "object", "participateInMetaMetrics": true, "dataCollectionForMarketing": "boolean", "nextNonce": null, @@ -57,12 +56,12 @@ "conversionRate": 1700, "usdConversionRate": 1700 }, - "LineaETH": { + "SepoliaETH": { "conversionDate": "number", "conversionRate": 1700, "usdConversionRate": 1700 }, - "SepoliaETH": { + "LineaETH": { "conversionDate": "number", "conversionRate": 1700, "usdConversionRate": 1700 @@ -137,7 +136,6 @@ "forgottenPassword": false, "ipfsGateway": "string", "isIpfsGatewayEnabled": "boolean", - "isMultiAccountBalancesEnabled": "boolean", "useAddressBarEnsResolution": true, "ledgerTransportType": "webhid", "snapRegistryList": "object", @@ -147,6 +145,8 @@ "useTransactionSimulations": true, "enableMV3TimestampSave": true, "useExternalServices": "boolean", + "isMultiAccountBalancesEnabled": "boolean", + "showIncomingTransactions": "object", "metaMetricsId": "fake-metrics-id", "marketingCampaignCookieId": null, "eventsBeforeMetricsOptIn": "object", @@ -239,11 +239,11 @@ "accounts": "object", "accountsByChainId": "object", "marketData": "object", - "signatureRequests": "object", "unapprovedDecryptMsgs": "object", "unapprovedDecryptMsgCount": 0, "unapprovedEncryptionPublicKeyMsgs": "object", "unapprovedEncryptionPublicKeyMsgCount": 0, + "signatureRequests": "object", "unapprovedPersonalMsgs": "object", "unapprovedTypedMessages": "object", "unapprovedPersonalMsgCount": 0, @@ -283,17 +283,18 @@ "srcNetworkAllowlist": { "0": "string", "1": "string", "2": "string" }, "destNetworkAllowlist": { "0": "string", "1": "string", "2": "string" } }, + "srcTokens": {}, + "srcTopAssets": {}, "destTokens": {}, "destTopAssets": {}, "quoteRequest": { - "slippage": 0.5, - "srcTokenAddress": "0x0000000000000000000000000000000000000000" + "srcTokenAddress": "0x0000000000000000000000000000000000000000", + "slippage": 0.5 }, "quotes": {}, - "quotesRefreshCount": 0, - "srcTokens": {}, - "srcTopAssets": {} + "quotesRefreshCount": 0 }, + "bridgeStatusState": { "txHistory": "object" }, "ensEntries": "object", "ensResolutionsByAddress": "object", "pendingApprovals": "object", From 6dda4443aaf341643d62e5b3d042bea90b4f017b Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Tue, 26 Nov 2024 15:20:03 +0000 Subject: [PATCH 33/40] feat: Enable redesigned transaction confirmations for all users (#28321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Migrates all users to the redesigned transactions. The setting will be automatically toggled on. This change initially broke a number of end-to-end tests. After an analysis, I grouped these failing tests into three categories: 1. User flows that have to be tested before enabling the redesign to all users, 🔴 This includes multichain and transaction insights tests. All tests on these files were tweaked to pass on redesigned confirmations. Specific tests that initially broke for new confirmations were also duplicated and included the temporary helper method `tempToggleSettingRedesignedTransactionConfirmations` so they were run for the old confirmations that we still need to support. 2. User flows that we don't need to migrate immediately, but will have to be migrated or adapted when old confirmations flows are deleted, 🟡 For these tests, we simply added `tempToggleSettingRedesignedTransactionConfirmations`. Once we remove the old flows, we can modify the tests to support the new flows. We didn't think it was as urgent to test these flows with the redesigned screens. 3. User flows that we don't need to migrate to redesigned confirmations. 🟢 For these tests, we simply added `tempToggleSettingRedesignedTransactionConfirmations`. Once we remove the old confirmation screens, we can delete these tests because they are no longer relevant, or otherwise already tested in the redesigned confirmation specific tests. ### Summary of the e2e tests that broke with the migration 1. User flows that have to be tested before enabling the redesign to all users, 🔴 - test/e2e/snaps/test-snap-txinsights-v2.spec.js ✅ - test/e2e/snaps/test-snap-txinsights.spec.js ✅ - test/e2e/json-rpc/switchEthereumChain.spec.js ✅ - test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js ✅ - test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js ✅ - test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js ✅ - test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js ✅ - test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js ✅ - test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js ✅ - test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js ✅ - test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js ✅ - test/e2e/tests/request-queuing/switch-network.spec.js ✅ - test/e2e/tests/request-queuing/ui.spec.js ✅ - test/e2e/tests/account/snap-account-transfers.spec.ts ✅ 2. User flows that we don't need to migrate immediately, but will have to be migrated or adapted when old confirmations flows are deleted, 🟡 - test/e2e/json-rpc/eth_sendTransaction.spec.js - test/e2e/tests/account/add-account.spec.ts - test/e2e/tests/petnames/petnames-transactions.spec.js - test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js - test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js - test/e2e/tests/tokens/custom-token-send-transfer.spec.js - test/e2e/tests/tokens/nft/erc721-interaction.spec.js - test/e2e/tests/tokens/nft/erc1155-interaction.spec.js - test/e2e/tests/tokens/nft/send-nft.spec.js - test/e2e/tests/transaction/change-assets.spec.js - test/e2e/tests/transaction/edit-gas-fee.spec.js - test/e2e/tests/transaction/gas-estimates.spec.js - test/e2e/tests/transaction/multiple-transactions.spec.js - test/e2e/tests/transaction/navigate-transactions.spec.js - test/e2e/tests/transaction/send-edit.spec.js - test/e2e/tests/transaction/send-eth.spec.js - test/e2e/tests/transaction/send-hex-address.spec.js 3. User flows that we don't need to migrate to redesigned confirmations. 🟢 - test/e2e/tests/dapp-interactions/contract-interactions.spec.js - test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js - test/e2e/tests/dapp-interactions/failing-contract.spec.js - test/e2e/tests/network/network-error.spec.js - test/e2e/tests/settings/4byte-directory.spec.js - test/e2e/tests/settings/show-hex-data.spec.js - test/e2e/tests/tokens/custom-token-add-approve.spec.js - test/e2e/tests/tokens/increase-token-allowance.spec.js - test/e2e/tests/transaction/simple-send.spec.ts [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28321?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3026 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **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/scripts/migrations/132.test.ts | 73 + app/scripts/migrations/132.ts | 58 + app/scripts/migrations/index.js | 1 + test/e2e/helpers.js | 41 + test/e2e/json-rpc/eth_sendTransaction.spec.js | 3 + test/e2e/json-rpc/switchEthereumChain.spec.js | 1146 +++++++----- .../flows/send-transaction.flow.ts | 72 + .../e2e/snaps/test-snap-txinsights-v2.spec.js | 318 ++-- test/e2e/snaps/test-snap-txinsights.spec.js | 326 ++-- test/e2e/tests/account/add-account.spec.ts | 31 +- .../account/snap-account-transfers.spec.ts | 437 +++-- test/e2e/tests/confirmations/helpers.ts | 8 +- .../tests/confirmations/navigation.spec.ts | 8 +- .../signatures/malicious-signatures.spec.ts | 8 +- .../signatures/nft-permit.spec.ts | 6 +- .../confirmations/signatures/permit.spec.ts | 6 +- .../signatures/personal-sign.spec.ts | 6 +- .../signatures/sign-typed-data-v3.spec.ts | 6 +- .../signatures/sign-typed-data-v4.spec.ts | 6 +- .../signatures/sign-typed-data.spec.ts | 6 +- .../confirmations/signatures/siwe.spec.ts | 6 +- ...55-revoke-set-approval-for-all-redesign.ts | 6 +- ...1155-set-approval-for-all-redesign.spec.ts | 6 +- .../erc20-token-send-redesign.spec.ts | 10 +- ...21-revoke-set-approval-for-all-redesign.ts | 6 +- ...c721-set-approval-for-all-redesign.spec.ts | 6 +- .../transactions/native-send-redesign.spec.ts | 10 +- .../nft-token-send-redesign.spec.ts | 14 +- .../contract-interactions.spec.js | 4 +- .../dapp-interactions/dapp-tx-edit.spec.js | 5 + .../failing-contract.spec.js | 5 + test/e2e/tests/network/network-error.spec.js | 3 + .../petnames/petnames-transactions.spec.js | 6 + .../ppom-blockaid-alert-simple-send.spec.js | 4 +- .../batch-txs-per-dapp-diff-network.spec.js | 307 +++- .../batch-txs-per-dapp-extra-tx.spec.js | 477 +++-- .../batch-txs-per-dapp-same-network.spec.js | 394 ++-- .../dapp1-send-dapp2-signTypedData.spec.js | 430 +++-- ...-switch-dapp2-eth-request-accounts.spec.js | 359 ++-- .../dapp1-switch-dapp2-send.spec.js | 781 +++++--- ...multi-dapp-sendTx-revokePermission.spec.js | 356 ++-- .../multiple-networks-dapps-txs.spec.js | 347 ++-- .../request-queuing/switch-network.spec.js | 218 ++- test/e2e/tests/request-queuing/ui.spec.js | 1595 +++++++++++------ .../metamask-responsive-ui.spec.js | 4 + .../tests/settings/4byte-directory.spec.js | 5 + test/e2e/tests/settings/show-hex-data.spec.js | 4 + .../tokens/custom-token-add-approve.spec.js | 10 +- .../tokens/custom-token-send-transfer.spec.js | 14 +- .../tokens/increase-token-allowance.spec.js | 3 + .../tokens/nft/erc1155-interaction.spec.js | 13 + .../tokens/nft/erc721-interaction.spec.js | 15 + test/e2e/tests/tokens/nft/send-nft.spec.js | 3 + .../tests/transaction/change-assets.spec.js | 9 + .../tests/transaction/edit-gas-fee.spec.js | 9 + .../tests/transaction/gas-estimates.spec.js | 13 + .../transaction/multiple-transactions.spec.js | 5 + .../transaction/navigate-transactions.spec.js | 17 +- test/e2e/tests/transaction/send-edit.spec.js | 4 + test/e2e/tests/transaction/send-eth.spec.js | 9 + .../transaction/send-hex-address.spec.js | 6 + .../e2e/tests/transaction/simple-send.spec.ts | 9 +- .../experimental-tab.component.tsx | 1 + 63 files changed, 5385 insertions(+), 2689 deletions(-) create mode 100644 app/scripts/migrations/132.test.ts create mode 100644 app/scripts/migrations/132.ts diff --git a/app/scripts/migrations/132.test.ts b/app/scripts/migrations/132.test.ts new file mode 100644 index 000000000000..fb53f90a38fb --- /dev/null +++ b/app/scripts/migrations/132.test.ts @@ -0,0 +1,73 @@ +import { migrate, version } from './132'; + +const oldVersion = 131; + +describe('migration #132', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if no preferences controller state is set', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('adds preferences property to the controller if it is not set and set the preference to true if migration runs', async () => { + const oldState = { PreferencesController: {} }; + + const expectedState = { + PreferencesController: { + preferences: { + redesignedTransactionsEnabled: true, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(expectedState); + }); + + it('changes property to true if migration runs', async () => { + const oldState = { + PreferencesController: { + preferences: { + redesignedTransactionsEnabled: false, + }, + }, + }; + + const expectedState = { + PreferencesController: { + preferences: { + redesignedTransactionsEnabled: true, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(expectedState); + }); +}); diff --git a/app/scripts/migrations/132.ts b/app/scripts/migrations/132.ts new file mode 100644 index 000000000000..ec12595d389a --- /dev/null +++ b/app/scripts/migrations/132.ts @@ -0,0 +1,58 @@ +import { isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 132; + +/** + * This migration sets `redesignedTransactionsEnabled` as true by default in preferences in PreferencesController. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState( + state: Record, +): Record { + if (!isObject(state?.PreferencesController)) { + return state; + } + + if (!isObject(state.PreferencesController?.preferences)) { + state.PreferencesController = { + ...state.PreferencesController, + preferences: {}, + }; + } + + const preferencesControllerState = state.PreferencesController as Record< + string, + unknown + >; + + const preferences = preferencesControllerState.preferences as Record< + string, + unknown + >; + + // `redesignedTransactionsEnabled` was previously set to `false` by + // default in `124.ts` + preferences.redesignedTransactionsEnabled = true; + + return state; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index d2c63eb2e35c..6cde292ba55d 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -152,6 +152,7 @@ const migrations = [ require('./129'), require('./130'), require('./131'), + require('./132'), ]; export default migrations; diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index b5962c0c079d..75eefb12f7c1 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -857,6 +857,46 @@ async function tempToggleSettingRedesignedConfirmations(driver) { ); } +/** + * Rather than using the FixtureBuilder#withPreferencesController to set the setting + * we need to manually set the setting because the migration #132 overrides this. + * We should be able to remove this when we delete the redesignedTransactionsEnabled setting. + * + * @param driver + */ +async function tempToggleSettingRedesignedTransactionConfirmations(driver) { + // Ensure we are on the extension window + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + + // Open settings menu button + await driver.clickElement('[data-testid="account-options-menu-button"]'); + + // fix race condition with mmi build + if (process.env.MMI) { + await driver.waitForSelector('[data-testid="global-menu-mmi-portfolio"]'); + } + + // Click settings from dropdown menu + await driver.clickElement('[data-testid="global-menu-settings"]'); + + // Click Experimental tab + const experimentalTabRawLocator = { + text: 'Experimental', + tag: 'div', + }; + await driver.clickElement(experimentalTabRawLocator); + + // Click redesigned transactions toggle + await driver.clickElement( + '[data-testid="toggle-redesigned-transactions-container"]', + ); + + // Close settings page + await driver.clickElement( + '.settings-page__header__title-container__close-button', + ); +} + /** * Opens the account options menu safely, handling potential race conditions * with the MMI build. @@ -925,6 +965,7 @@ module.exports = { editGasFeeForm, clickNestedButton, tempToggleSettingRedesignedConfirmations, + tempToggleSettingRedesignedTransactionConfirmations, openMenuSafe, sentryRegEx, }; diff --git a/test/e2e/json-rpc/eth_sendTransaction.spec.js b/test/e2e/json-rpc/eth_sendTransaction.spec.js index c17421cca042..ed740d17bfc1 100644 --- a/test/e2e/json-rpc/eth_sendTransaction.spec.js +++ b/test/e2e/json-rpc/eth_sendTransaction.spec.js @@ -4,6 +4,7 @@ const { unlockWallet, WINDOW_TITLES, generateGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -68,6 +69,8 @@ describe('eth_sendTransaction', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // eth_sendTransaction await driver.openNewPage(`http://127.0.0.1:8080`); const request = JSON.stringify({ diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js index 60ba4eb9aacb..6a8576b3f136 100644 --- a/test/e2e/json-rpc/switchEthereumChain.spec.js +++ b/test/e2e/json-rpc/switchEthereumChain.spec.js @@ -8,509 +8,669 @@ const { unlockWallet, switchToNotificationWindow, WINDOW_TITLES, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { isManifestV3 } = require('../../../shared/modules/mv3.utils'); describe('Switch Ethereum Chain for two dapps', function () { - it('switches the chainId of two dapps when switchEthereumChain of one dapp is confirmed', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], + describe('Old confirmation screens', function () { + it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - const dappOne = await openDapp(driver, undefined, DAPP_URL); - const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Initiate switchEthereumChain on Dapp Two - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Confirm switchEthereumChain - await switchToNotificationWindow(driver, 4); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // Switch to Dapp One - await driver.switchToWindow(dappOne); - assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); - - // Wait for chain id element to change, there's a page reload. - await driver.waitForSelector({ - css: '#chainId', - text: '0x53a', - }); - - // Dapp One ChainId assertion - await driver.findElement({ css: '#chainId', text: '0x53a' }); - - // Switch to Dapp Two - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - // Dapp Two ChainId Assertion - await driver.findElement({ css: '#chainId', text: '0x53a' }); - }, - ); - }); - - it('queues switchEthereumChain request from second dapp after send tx request', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open settings menu button + const accountOptionsMenuSelector = + '[data-testid="account-options-menu-button"]'; + await driver.waitForSelector(accountOptionsMenuSelector); + await driver.clickElement(accountOptionsMenuSelector); + + // Click settings from dropdown menu + const globalMenuSettingsSelector = + '[data-testid="global-menu-settings"]'; + await driver.waitForSelector(globalMenuSettingsSelector); + await driver.clickElement(globalMenuSettingsSelector); + + // Click Experimental tab + const experimentalTabRawLocator = { + text: 'Experimental', + tag: 'div', + }; + await driver.clickElement(experimentalTabRawLocator); + + // Toggle off request queue setting (on by default now) + await driver.clickElement( + '[data-testid="experimental-setting-toggle-request-queue"]', + ); + + // open two dapps + const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); + const dappOne = await openDapp(driver, undefined, DAPP_URL); + + // Connect Dapp One + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch and connect Dapp Two + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const editButtons = await driver.findElements('[data-testid="edit"]'); + + // Click the edit button for networks + await editButtons[1].click(); + + // Disconnect Mainnet + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); + + await driver.clickElement( + '[data-testid="connect-more-chains-button"]', + ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to notification of switchEthereumChain + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + + // Switch back to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + await driver.delay(2000); + + // Switch to notification that should still be switchEthereumChain request but with an warning. + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // THIS IS BROKEN + // await driver.findElement({ + // span: 'span', + // text: 'Switching networks will cancel all pending confirmations', + // }); + + // Cancel switchEthereumChain with queued pending tx + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + + // Delay for second notification of the pending tx + await driver.delay(1000); + + // Switch to new pending tx notification + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findElement({ + text: 'Sending ETH', + tag: 'span', + }); + + // Confirm pending tx + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - await openDapp(driver, undefined, DAPP_URL); - await openDapp(driver, undefined, DAPP_ONE_URL); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch to Dapp One and connect it - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findClickableElement({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const editButtons = await driver.findElements('[data-testid="edit"]'); - - await editButtons[1].click(); - - // Disconnect Localhost 8545 - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); - - await driver.clickElement('[data-testid="connect-more-chains-button"]'); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch to Dapp Two - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - // Initiate send transaction on Dapp two - await driver.clickElement('#sendButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - - // Switch to Dapp One - await driver.switchToWindowWithUrl(DAPP_URL); - - // Switch Ethereum chain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp One - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - await switchToNotificationWindow(driver, 4); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - // Delay here after notification for second notification popup for switchEthereumChain - await driver.delay(1000); - - // Switch and confirm to queued notification for switchEthereumChain - await switchToNotificationWindow(driver, 4); - - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ css: '#chainId', text: '0x539' }); - }, - ); + ); + }); }); - it('queues send tx after switchEthereum request with a warning, confirming removes pending tx', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], + describe('Redesigned confirmation screens', function () { + it('switches the chainId of two dapps when switchEthereumChain of one dapp is confirmed', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); - const dappOne = await openDapp(driver, undefined, DAPP_URL); - - // Connect Dapp One - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch and connect Dapp Two - - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const editButtons = await driver.findElements('[data-testid="edit"]'); - - // Click the edit button for networks - await editButtons[1].click(); - - // Disconnect Mainnet - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); - - await driver.clickElement('[data-testid="connect-more-chains-button"]'); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp Two - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - // Switch back to dapp one - await driver.switchToWindow(dappOne); - assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); - - // Initiate send tx on dapp one - await driver.clickElement('#sendButton'); - await driver.delay(2000); - - // Switch to notification that should still be switchEthereumChain request but with a warning. - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // THIS IS BROKEN - // await driver.findElement({ - // span: 'span', - // text: 'Switching networks will cancel all pending confirmations', - // }); - - // Confirm switchEthereumChain with queued pending tx - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // Window handles should only be expanded mm, dapp one, dapp 2, and the offscreen document - // if this is an MV3 build(3 or 4 total) - await driver.wait(async () => { - const windowHandles = await driver.getAllWindowHandles(); - const numberOfWindowHandlesToExpect = isManifestV3 ? 4 : 3; - return windowHandles.length === numberOfWindowHandlesToExpect; - }); - }, - ); - }); - - it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [{ port: 8546, chainId: 1338 }], + async ({ driver }) => { + await unlockWallet(driver); + + // Open settings menu button + const accountOptionsMenuSelector = + '[data-testid="account-options-menu-button"]'; + await driver.waitForSelector(accountOptionsMenuSelector); + await driver.clickElement(accountOptionsMenuSelector); + + // Click settings from dropdown menu + const globalMenuSettingsSelector = + '[data-testid="global-menu-settings"]'; + await driver.waitForSelector(globalMenuSettingsSelector); + await driver.clickElement(globalMenuSettingsSelector); + + // Click Experimental tab + const experimentalTabRawLocator = { + text: 'Experimental', + tag: 'div', + }; + await driver.clickElement(experimentalTabRawLocator); + + // Toggle off request queue setting (on by default now) + await driver.clickElement( + '[data-testid="experimental-setting-toggle-request-queue"]', + ); + + // open two dapps + const dappOne = await openDapp(driver, undefined, DAPP_URL); + const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Confirm switchEthereumChain + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Switch to Dapp One + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Wait for chain id element to change, there's a page reload. + await driver.waitForSelector({ + css: '#chainId', + text: '0x53a', + }); + + // Dapp One ChainId assertion + await driver.findElement({ css: '#chainId', text: '0x53a' }); + + // Switch to Dapp Two + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // Dapp Two ChainId Assertion + await driver.findElement({ css: '#chainId', text: '0x53a' }); + }, + ); + }); + + it('queues switchEthereumChain request from second dapp after send tx request', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open settings menu button + const accountOptionsMenuSelector = + '[data-testid="account-options-menu-button"]'; + await driver.waitForSelector(accountOptionsMenuSelector); + await driver.clickElement(accountOptionsMenuSelector); + + // Click settings from dropdown menu + const globalMenuSettingsSelector = + '[data-testid="global-menu-settings"]'; + await driver.waitForSelector(globalMenuSettingsSelector); + await driver.clickElement(globalMenuSettingsSelector); + + // Click Experimental tab + const experimentalTabRawLocator = { + text: 'Experimental', + tag: 'div', + }; + await driver.clickElement(experimentalTabRawLocator); + + // Toggle off request queue setting (on by default now) + await driver.clickElement( + '[data-testid="experimental-setting-toggle-request-queue"]', + ); + + // open two dapps + await openDapp(driver, undefined, DAPP_URL); + await openDapp(driver, undefined, DAPP_ONE_URL); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch to Dapp One and connect it + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findClickableElement({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const editButtons = await driver.findElements('[data-testid="edit"]'); + + await editButtons[1].click(); + + // Disconnect Localhost 8545 + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); + + await driver.clickElement( + '[data-testid="connect-more-chains-button"]', + ); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch to Dapp Two + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + // Initiate send transaction on Dapp two + await driver.clickElement('#sendButton'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + + // Switch to Dapp One + await driver.switchToWindowWithUrl(DAPP_URL); + + // Switch Ethereum chain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + // Delay here after notification for second notification popup for switchEthereumChain + await driver.delay(1000); + + // Switch and confirm to queued notification for switchEthereumChain + await switchToNotificationWindow(driver, 4); + + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ css: '#chainId', text: '0x539' }); + }, + ); + }); + + it('queues send tx after switchEthereum request with a warning, confirming removes pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open settings menu button + const accountOptionsMenuSelector = + '[data-testid="account-options-menu-button"]'; + await driver.waitForSelector(accountOptionsMenuSelector); + await driver.clickElement(accountOptionsMenuSelector); + + // Click settings from dropdown menu + const globalMenuSettingsSelector = + '[data-testid="global-menu-settings"]'; + await driver.waitForSelector(globalMenuSettingsSelector); + await driver.clickElement(globalMenuSettingsSelector); + + // Click Experimental tab + const experimentalTabRawLocator = { + text: 'Experimental', + tag: 'div', + }; + await driver.clickElement(experimentalTabRawLocator); + + // Toggle off request queue setting (on by default now) + await driver.clickElement( + '[data-testid="experimental-setting-toggle-request-queue"]', + ); + + // open two dapps + const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); + const dappOne = await openDapp(driver, undefined, DAPP_URL); + + // Connect Dapp One + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch and connect Dapp Two + + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const editButtons = await driver.findElements('[data-testid="edit"]'); + + // Click the edit button for networks + await editButtons[1].click(); + + // Disconnect Mainnet + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); + + await driver.clickElement( + '[data-testid="connect-more-chains-button"]', + ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + // Switch back to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + await driver.delay(2000); + + // Switch to notification that should still be switchEthereumChain request but with a warning. + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // THIS IS BROKEN + // await driver.findElement({ + // span: 'span', + // text: 'Switching networks will cancel all pending confirmations', + // }); + + // Confirm switchEthereumChain with queued pending tx + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Window handles should only be expanded mm, dapp one, dapp 2, and the offscreen document + // if this is an MV3 build(3 or 4 total) + await driver.wait(async () => { + const windowHandles = await driver.getAllWindowHandles(); + const numberOfWindowHandlesToExpect = isManifestV3 ? 4 : 3; + return windowHandles.length === numberOfWindowHandlesToExpect; + }); + }, + ); + }); + + it('queues send tx after switchEthereum request with a warning, if switchEthereum request is cancelled should show pending tx', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [{ port: 8546, chainId: 1338 }], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open settings menu button + const accountOptionsMenuSelector = + '[data-testid="account-options-menu-button"]'; + await driver.waitForSelector(accountOptionsMenuSelector); + await driver.clickElement(accountOptionsMenuSelector); + + // Click settings from dropdown menu + const globalMenuSettingsSelector = + '[data-testid="global-menu-settings"]'; + await driver.waitForSelector(globalMenuSettingsSelector); + await driver.clickElement(globalMenuSettingsSelector); + + // Click Experimental tab + const experimentalTabRawLocator = { + text: 'Experimental', + tag: 'div', + }; + await driver.clickElement(experimentalTabRawLocator); + + // Toggle off request queue setting (on by default now) + await driver.clickElement( + '[data-testid="experimental-setting-toggle-request-queue"]', + ); + + // open two dapps + const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); + const dappOne = await openDapp(driver, undefined, DAPP_URL); + + // Connect Dapp One + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Switch and connect Dapp Two + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const editButtons = await driver.findElements('[data-testid="edit"]'); + + // Click the edit button for networks + await editButtons[1].click(); + + // Disconnect Mainnet + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); + + await driver.clickElement( + '[data-testid="connect-more-chains-button"]', + ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + await driver.switchToWindow(dappTwo); + assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Switch to notification of switchEthereumChain + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + + // Switch back to dapp one + await driver.switchToWindow(dappOne); + assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); + + // Initiate send tx on dapp one + await driver.clickElement('#sendButton'); + await driver.delay(2000); + + // Switch to notification that should still be switchEthereumChain request but with an warning. + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Cancel switchEthereumChain with queued pending tx + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + + // Delay for second notification of the pending tx + await driver.delay(1000); + + // Switch to new pending tx notification + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findElement({ + text: 'Transfer request', + tag: 'h3', + }); + + await driver.findElement({ + text: '0 ETH', + tag: 'h2', + }); + + // Confirm pending tx + await driver.findClickableElements({ + text: 'Confirm', + tag: 'button', + }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open settings menu button - const accountOptionsMenuSelector = - '[data-testid="account-options-menu-button"]'; - await driver.waitForSelector(accountOptionsMenuSelector); - await driver.clickElement(accountOptionsMenuSelector); - - // Click settings from dropdown menu - const globalMenuSettingsSelector = - '[data-testid="global-menu-settings"]'; - await driver.waitForSelector(globalMenuSettingsSelector); - await driver.clickElement(globalMenuSettingsSelector); - - // Click Experimental tab - const experimentalTabRawLocator = { - text: 'Experimental', - tag: 'div', - }; - await driver.clickElement(experimentalTabRawLocator); - - // Toggle off request queue setting (on by default now) - await driver.clickElement( - '[data-testid="experimental-setting-toggle-request-queue"]', - ); - - // open two dapps - const dappTwo = await openDapp(driver, undefined, DAPP_ONE_URL); - const dappOne = await openDapp(driver, undefined, DAPP_URL); - - // Connect Dapp One - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Switch and connect Dapp Two - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - const editButtons = await driver.findElements('[data-testid="edit"]'); - - // Click the edit button for networks - await editButtons[1].click(); - - // Disconnect Mainnet - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); - - await driver.clickElement('[data-testid="connect-more-chains-button"]'); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - await driver.switchToWindow(dappTwo); - assert.equal(await driver.getCurrentUrl(), `${DAPP_ONE_URL}/`); - - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); - - // Initiate switchEthereumChain on Dapp Two - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Switch to notification of switchEthereumChain - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - - // Switch back to dapp one - await driver.switchToWindow(dappOne); - assert.equal(await driver.getCurrentUrl(), `${DAPP_URL}/`); - - // Initiate send tx on dapp one - await driver.clickElement('#sendButton'); - await driver.delay(2000); - - // Switch to notification that should still be switchEthereumChain request but with an warning. - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // THIS IS BROKEN - // await driver.findElement({ - // span: 'span', - // text: 'Switching networks will cancel all pending confirmations', - // }); - - // Cancel switchEthereumChain with queued pending tx - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - - // Delay for second notification of the pending tx - await driver.delay(1000); - - // Switch to new pending tx notification - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findElement({ - text: 'Sending ETH', - tag: 'span', - }); - - // Confirm pending tx - await driver.findClickableElements({ - text: 'Confirm', - tag: 'button', - }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - }, - ); + ); + }); }); }); diff --git a/test/e2e/page-objects/flows/send-transaction.flow.ts b/test/e2e/page-objects/flows/send-transaction.flow.ts index 3f56076e3934..8af88f01aca6 100644 --- a/test/e2e/page-objects/flows/send-transaction.flow.ts +++ b/test/e2e/page-objects/flows/send-transaction.flow.ts @@ -3,6 +3,7 @@ import ConfirmTxPage from '../pages/send/confirm-tx-page'; import SendTokenPage from '../pages/send/send-token-page'; import { Driver } from '../../webdriver/driver'; import SnapSimpleKeyringPage from '../pages/snap-simple-keyring-page'; +import TransactionConfirmation from '../pages/confirmations/redesign/transaction-confirmation'; /** * This function initiates the steps required to send a transaction from the homepage to final confirmation. @@ -47,6 +48,42 @@ export const sendTransactionToAddress = async ({ await confirmTxPage.confirmTx(); }; +/** + * This function initiates the steps required to send a transaction from the homepage to final confirmation. + * + * @param params - An object containing the parameters. + * @param params.driver - The webdriver instance. + * @param params.recipientAddress - The recipient address. + * @param params.amount - The amount of the asset to be sent in the transaction. + */ +export const sendRedesignedTransactionToAddress = async ({ + driver, + recipientAddress, + amount, +}: { + driver: Driver; + recipientAddress: string; + amount: string; +}): Promise => { + console.log( + `Start flow to send amount ${amount} to recipient ${recipientAddress} on home screen`, + ); + // click send button on homepage to start flow + 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 transactionConfirmationPage = new TransactionConfirmation(driver); + await transactionConfirmationPage.clickFooterConfirmButton(); +}; + /** * This function initiates the steps required to send a transaction from the homepage to final confirmation. * @@ -132,3 +169,38 @@ export const sendTransactionWithSnapAccount = async ({ ); } }; + +/** + * This function initiates the steps required to send a transaction from snap account on homepage to final confirmation. + * + * @param params - An object containing the parameters. + * @param params.driver - The webdriver instance. + * @param params.recipientAddress - The recipient address. + * @param params.amount - The amount of the asset to be sent in the transaction. + * @param params.isSyncFlow - Indicates whether synchronous approval option is on for the snap. Defaults to true. + * @param params.approveTransaction - Indicates whether the transaction should be approved. Defaults to true. + */ +export const sendRedesignedTransactionWithSnapAccount = async ({ + driver, + recipientAddress, + amount, + isSyncFlow = true, + approveTransaction = true, +}: { + driver: Driver; + recipientAddress: string; + amount: string; + isSyncFlow?: boolean; + approveTransaction?: boolean; +}): Promise => { + await sendRedesignedTransactionToAddress({ + driver, + recipientAddress, + amount, + }); + if (!isSyncFlow) { + await new SnapSimpleKeyringPage(driver).approveRejectSnapAccountTransaction( + approveTransaction, + ); + } +}; diff --git a/test/e2e/snaps/test-snap-txinsights-v2.spec.js b/test/e2e/snaps/test-snap-txinsights-v2.spec.js index a249e9daa79b..ba2d468a870a 100644 --- a/test/e2e/snaps/test-snap-txinsights-v2.spec.js +++ b/test/e2e/snaps/test-snap-txinsights-v2.spec.js @@ -3,166 +3,172 @@ const { withFixtures, unlockWallet, WINDOW_TITLES, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); describe('Test Snap TxInsights-v2', function () { - it('tests tx insights v2 functionality', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // navigate to test snaps page and connect - await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); - - // wait for page to load - await driver.waitForSelector({ - text: 'Installed Snaps', - tag: 'h2', - }); - - // find and scroll to the transaction-insights test snap - const snapButton1 = await driver.findElement( - '#connecttransaction-insights', - ); - await driver.scrollToElement(snapButton1); - - // added delay for firefox (deflake) - await driver.delayFirefox(1000); - - // wait for and click connect - await driver.waitForSelector('#connecttransaction-insights'); - await driver.clickElement('#connecttransaction-insights'); - - // switch to metamask extension - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click connect - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // wait for and click connect - await driver.waitForSelector({ text: 'Confirm' }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // wait for and click ok and wait for window to close - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'OK', - tag: 'button', - }); - - // switch to test-snaps page - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // wait for and click get accounts - await driver.waitForSelector('#getAccounts'); - await driver.clickElement('#getAccounts'); - - // switch back to MetaMask window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click confirm and wait for window to close - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // switch to test-snaps page and send tx - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - await driver.clickElement('#sendInsights'); - - // delay added for rendering (deflake) - await driver.delay(2000); - - // switch back to MetaMask window and switch to tx insights pane - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // find confirm button - await driver.findClickableElement({ - text: 'Confirm', - tag: 'button', - }); - - // wait for and click insights snap tab - await driver.waitForSelector({ - text: 'Insights Example Snap', - tag: 'button', - }); - await driver.clickElement({ - text: 'Insights Example Snap', - tag: 'button', - }); - - // check that txinsightstest tab contains the right info - await driver.waitForSelector({ - css: '.snap-ui-renderer__content', - text: 'ERC-20', - }); - - // click confirm to continue - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // check for warning from txinsights - await driver.waitForSelector({ - css: '.snap-delineator__header__text', - text: 'Warning from Insights Example Snap', - }); - - // check info in warning - await driver.waitForSelector({ - css: '.snap-ui-renderer__text', - text: 'ERC-20', - }); - - // click the warning confirm checkbox - await driver.clickElement('.mm-checkbox__input'); - - // click confirm button to send transaction - await driver.clickElement({ - css: '.mm-box--color-error-inverse', - text: 'Confirm', - tag: 'button', - }); - - // switch back to MetaMask tab - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // switch to activity pane - await driver.clickElement({ - tag: 'button', - text: 'Activity', - }); - // wait for transaction confirmation - await driver.waitForSelector({ - css: '.transaction-status-label', - text: 'Confirmed', - }); - }, - ); + describe('Old confirmation screens', function () { + it('tests tx insights v2 functionality', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // navigate to test snaps page and connect + await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); + + // wait for page to load + await driver.waitForSelector({ + text: 'Installed Snaps', + tag: 'h2', + }); + + // find and scroll to the transaction-insights test snap + const snapButton1 = await driver.findElement( + '#connecttransaction-insights', + ); + await driver.scrollToElement(snapButton1); + + // added delay for firefox (deflake) + await driver.delayFirefox(1000); + + // wait for and click connect + await driver.waitForSelector('#connecttransaction-insights'); + await driver.clickElement('#connecttransaction-insights'); + + // switch to metamask extension + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click connect + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // wait for and click connect + await driver.waitForSelector({ text: 'Confirm' }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + // wait for and click ok and wait for window to close + await driver.waitForSelector({ text: 'OK' }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'OK', + tag: 'button', + }); + + // switch to test-snaps page + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // wait for and click get accounts + await driver.waitForSelector('#getAccounts'); + await driver.clickElement('#getAccounts'); + + // switch back to MetaMask window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click confirm and wait for window to close + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // switch to test-snaps page and send tx + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + await driver.clickElement('#sendInsights'); + + // delay added for rendering (deflake) + await driver.delay(2000); + + // switch back to MetaMask window and switch to tx insights pane + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // find confirm button + await driver.findClickableElement({ + text: 'Confirm', + tag: 'button', + }); + + // wait for and click insights snap tab + await driver.waitForSelector({ + text: 'Insights Example Snap', + tag: 'button', + }); + await driver.clickElement({ + text: 'Insights Example Snap', + tag: 'button', + }); + + // check that txinsightstest tab contains the right info + await driver.waitForSelector({ + css: '.snap-ui-renderer__content', + text: 'ERC-20', + }); + + // click confirm to continue + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + // check for warning from txinsights + await driver.waitForSelector({ + css: '.snap-delineator__header__text', + text: 'Warning from Insights Example Snap', + }); + + // check info in warning + await driver.waitForSelector({ + css: '.snap-ui-renderer__text', + text: 'ERC-20', + }); + + // click the warning confirm checkbox + await driver.clickElement('.mm-checkbox__input'); + + // click confirm button to send transaction + await driver.clickElement({ + css: '.mm-box--color-error-inverse', + text: 'Confirm', + tag: 'button', + }); + + // switch back to MetaMask tab + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // switch to activity pane + await driver.clickElement({ + tag: 'button', + text: 'Activity', + }); + + // wait for transaction confirmation + await driver.waitForSelector({ + css: '.transaction-status-label', + text: 'Confirmed', + }); + }, + ); + }); }); }); diff --git a/test/e2e/snaps/test-snap-txinsights.spec.js b/test/e2e/snaps/test-snap-txinsights.spec.js index 21feafd06cb9..0171759587b5 100644 --- a/test/e2e/snaps/test-snap-txinsights.spec.js +++ b/test/e2e/snaps/test-snap-txinsights.spec.js @@ -3,117 +3,229 @@ const { withFixtures, unlockWallet, WINDOW_TITLES, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); describe('Test Snap TxInsights', function () { - it('tests tx insights functionality', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // navigate to test snaps page and connect - await driver.driver.get(TEST_SNAPS_WEBSITE_URL); - - // wait for page to load - await driver.waitForSelector({ - text: 'Installed Snaps', - tag: 'h2', - }); - - // find and scroll to the transaction-insights test snap - const snapButton1 = await driver.findElement( - '#connecttransaction-insights', - ); - await driver.scrollToElement(snapButton1); - - // added delay for firefox (deflake) - await driver.delayFirefox(1000); - - // wait for and click connect - await driver.waitForSelector('#connecttransaction-insights'); - await driver.clickElement('#connecttransaction-insights'); - - // switch to metamask extension - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click connect - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // wait for and click confirm - await driver.waitForSelector({ text: 'Confirm' }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - - // wait for and click ok and wait for window to close - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'OK', - tag: 'button', - }); - - // switch to test-snaps page and get accounts - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // click get accounts - await driver.clickElement('#getAccounts'); - - // switch back to MetaMask window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and click next and wait for window to close - await driver.waitForSelector({ - text: 'Connect', - tag: 'button', - }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // switch to test-snaps page - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); - - // click send tx - await driver.clickElement('#sendInsights'); - - // delay added for rendering (deflake) - await driver.delay(2000); - - // switch back to MetaMask window - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // wait for and switch to insight snap pane - await driver.waitForSelector({ - text: 'Insights Example Snap', - tag: 'button', - }); - await driver.clickElement({ - text: 'Insights Example Snap', - tag: 'button', - }); - - // check that txinsightstest tab contains the right info - await driver.waitForSelector({ - css: '.snap-ui-renderer__content', - text: 'ERC-20', - }); - }, - ); + describe('Old confirmation screens', function () { + it('tests tx insights functionality', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // navigate to test snaps page and connect + await driver.driver.get(TEST_SNAPS_WEBSITE_URL); + + // wait for page to load + await driver.waitForSelector({ + text: 'Installed Snaps', + tag: 'h2', + }); + + // find and scroll to the transaction-insights test snap + const snapButton1 = await driver.findElement( + '#connecttransaction-insights', + ); + await driver.scrollToElement(snapButton1); + + // added delay for firefox (deflake) + await driver.delayFirefox(1000); + + // wait for and click connect + await driver.waitForSelector('#connecttransaction-insights'); + await driver.clickElement('#connecttransaction-insights'); + + // switch to metamask extension + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click connect + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // wait for and click confirm + await driver.waitForSelector({ text: 'Confirm' }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + // wait for and click ok and wait for window to close + await driver.waitForSelector({ text: 'OK' }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'OK', + tag: 'button', + }); + + // switch to test-snaps page and get accounts + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // click get accounts + await driver.clickElement('#getAccounts'); + + // switch back to MetaMask window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click next and wait for window to close + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // switch to test-snaps page + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // click send tx + await driver.clickElement('#sendInsights'); + + // delay added for rendering (deflake) + await driver.delay(2000); + + // switch back to MetaMask window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and switch to insight snap pane + await driver.waitForSelector({ + text: 'Insights Example Snap', + tag: 'button', + }); + await driver.clickElement({ + text: 'Insights Example Snap', + tag: 'button', + }); + + // check that txinsightstest tab contains the right info + await driver.waitForSelector({ + css: '.snap-ui-renderer__content', + text: 'ERC-20', + }); + }, + ); + }); + }); + + describe('Redesigned confirmation screens', function () { + it('tests tx insights functionality', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // navigate to test snaps page and connect + await driver.driver.get(TEST_SNAPS_WEBSITE_URL); + + // wait for page to load + await driver.waitForSelector({ + text: 'Installed Snaps', + tag: 'h2', + }); + + // find and scroll to the transaction-insights test snap + const snapButton1 = await driver.findElement( + '#connecttransaction-insights', + ); + await driver.scrollToElement(snapButton1); + + // added delay for firefox (deflake) + await driver.delayFirefox(1000); + + // wait for and click connect + await driver.waitForSelector('#connecttransaction-insights'); + await driver.clickElement('#connecttransaction-insights'); + + // switch to metamask extension + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click connect + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // wait for and click confirm + await driver.waitForSelector({ text: 'Confirm' }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + // wait for and click ok and wait for window to close + await driver.waitForSelector({ text: 'OK' }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'OK', + tag: 'button', + }); + + // switch to test-snaps page and get accounts + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // click get accounts + await driver.clickElement('#getAccounts'); + + // switch back to MetaMask window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and click next and wait for window to close + await driver.waitForSelector({ + text: 'Connect', + tag: 'button', + }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // switch to test-snaps page + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // click send tx + await driver.clickElement('#sendInsights'); + + // delay added for rendering (deflake) + await driver.delay(2000); + + // switch back to MetaMask window + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // wait for and switch to insight snap pane + await driver.waitForSelector({ + text: 'Insights Example Snap', + tag: 'span', + }); + + // check that txinsightstest tab contains the right info + await driver.waitForSelector({ + css: 'p', + text: 'ERC-20', + }); + }, + ); + }); }); }); diff --git a/test/e2e/tests/account/add-account.spec.ts b/test/e2e/tests/account/add-account.spec.ts index 8824fcb70950..906e01f50b4d 100644 --- a/test/e2e/tests/account/add-account.spec.ts +++ b/test/e2e/tests/account/add-account.spec.ts @@ -1,18 +1,19 @@ +import { E2E_SRP } from '../../default-fixture'; +import FixtureBuilder from '../../fixture-builder'; import { - withFixtures, WALLET_PASSWORD, defaultGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, + withFixtures, } from '../../helpers'; -import { E2E_SRP } from '../../default-fixture'; -import FixtureBuilder from '../../fixture-builder'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import { completeImportSRPOnboardingFlow } from '../../page-objects/flows/onboarding.flow'; +import { sendTransactionToAccount } from '../../page-objects/flows/send-transaction.flow'; import AccountListPage from '../../page-objects/pages/account-list-page'; import HeaderNavbar from '../../page-objects/pages/header-navbar'; import HomePage from '../../page-objects/pages/homepage'; import LoginPage from '../../page-objects/pages/login-page'; import ResetPasswordPage from '../../page-objects/pages/reset-password-page'; -import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; -import { completeImportSRPOnboardingFlow } from '../../page-objects/flows/onboarding.flow'; -import { sendTransactionToAccount } from '../../page-objects/flows/send-transaction.flow'; describe('Add account', function () { it('should not affect public address when using secret recovery phrase to recover account with non-zero balance @no-mmi', async function () { @@ -24,17 +25,21 @@ describe('Add account', function () { }, async ({ driver, ganacheServer }) => { await completeImportSRPOnboardingFlow({ driver }); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + const homePage = new HomePage(driver); await homePage.check_pageIsLoaded(); await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); const headerNavbar = new HeaderNavbar(driver); await headerNavbar.openAccountMenu(); - // Create new account with default name Account 2 + // Create new account with default name `newAccountName` + const newAccountName = 'Account 2'; const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); await accountListPage.addNewAccount(); - await headerNavbar.check_accountLabel('Account 2'); + await headerNavbar.check_accountLabel(newAccountName); await homePage.check_expectedBalanceIsDisplayed(); // Switch back to the first account and transfer some balance to 2nd account so they will not be removed after recovering SRP @@ -46,7 +51,7 @@ describe('Add account', function () { await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); await sendTransactionToAccount({ driver, - recipientAccount: 'Account 2', + recipientAccount: newAccountName, amount: '2.8', gasFee: '0.000042', totalFee: '2.800042', @@ -67,9 +72,11 @@ describe('Add account', function () { await homePage.check_localBlockchainBalanceIsDisplayed(ganacheServer); await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - await accountListPage.check_accountDisplayedInAccountList('Account 2'); - await accountListPage.switchToAccount('Account 2'); - await headerNavbar.check_accountLabel('Account 2'); + await accountListPage.check_accountDisplayedInAccountList( + newAccountName, + ); + await accountListPage.switchToAccount(newAccountName); + await headerNavbar.check_accountLabel(newAccountName); await homePage.check_expectedBalanceIsDisplayed('2.8'); }, ); diff --git a/test/e2e/tests/account/snap-account-transfers.spec.ts b/test/e2e/tests/account/snap-account-transfers.spec.ts index ee2466cf28e1..af2a61a62a39 100644 --- a/test/e2e/tests/account/snap-account-transfers.spec.ts +++ b/test/e2e/tests/account/snap-account-transfers.spec.ts @@ -2,6 +2,7 @@ import { Suite } from 'mocha'; import { multipleGanacheOptions, PRIVATE_KEY_TWO, + tempToggleSettingRedesignedTransactionConfirmations, WINDOW_TITLES, withFixtures, } from '../../helpers'; @@ -15,151 +16,311 @@ import HomePage from '../../page-objects/pages/homepage'; import SnapSimpleKeyringPage from '../../page-objects/pages/snap-simple-keyring-page'; import { installSnapSimpleKeyring } from '../../page-objects/flows/snap-simple-keyring.flow'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; -import { sendTransactionWithSnapAccount } from '../../page-objects/flows/send-transaction.flow'; +import { + sendRedesignedTransactionWithSnapAccount, + sendTransactionWithSnapAccount, +} from '../../page-objects/flows/send-transaction.flow'; describe('Snap Account Transfers @no-mmi', function (this: Suite) { - it('can import a private key and transfer 1 ETH (sync flow)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ - driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - await installSnapSimpleKeyring(driver); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 - await sendTransactionWithSnapAccount({ + // TODO: Remove the old confirmations screen tests once migration has been complete. + // See: https://github.com/MetaMask/MetaMask-planning/issues/3030 + describe('Old confirmation screens', function () { + it('can import a private key and transfer 1 ETH (sync flow)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - gasFee: '0.000042', - totalFee: '1.000042', - }); - await headerNavbar.check_pageIsLoaded(); - await headerNavbar.openAccountMenu(); - const accountList = new AccountListPage(driver); - await accountList.check_pageIsLoaded(); - - // check the balance of the 2 accounts are updated - await accountList.check_accountBalanceDisplayed('26'); - await accountList.check_accountBalanceDisplayed('24'); - }, - ); - }); + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + await installSnapSimpleKeyring(driver); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 + await sendTransactionWithSnapAccount({ + driver, + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + gasFee: '0.000042', + totalFee: '1.000042', + }); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + const accountList = new AccountListPage(driver); + await accountList.check_pageIsLoaded(); + + // check the balance of the 2 accounts are updated + await accountList.check_accountBalanceDisplayed('26'); + await accountList.check_accountBalanceDisplayed('24'); + }, + ); + }); + + it('can import a private key and transfer 1 ETH (async flow approve)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); - it('can import a private key and transfer 1 ETH (async flow approve)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - }, - async ({ - driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - await installSnapSimpleKeyring(driver, false); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 and approve the transaction - await sendTransactionWithSnapAccount({ + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + await installSnapSimpleKeyring(driver, false); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 and approve the transaction + await sendTransactionWithSnapAccount({ + driver, + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + gasFee: '0.000042', + totalFee: '1.000042', + isSyncFlow: false, + }); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + const accountList = new AccountListPage(driver); + await accountList.check_pageIsLoaded(); + + // check the balance of the 2 accounts are updated + await accountList.check_accountBalanceDisplayed('26'); + await accountList.check_accountBalanceDisplayed('24'); + }, + ); + }); + + it('can import a private key and transfer 1 ETH (async flow reject)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + ignoredConsoleErrors: ['Request rejected by user or snap.'], + }, + async ({ driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - gasFee: '0.000042', - totalFee: '1.000042', - isSyncFlow: false, - }); - await headerNavbar.check_pageIsLoaded(); - await headerNavbar.openAccountMenu(); - const accountList = new AccountListPage(driver); - await accountList.check_pageIsLoaded(); - - // check the balance of the 2 accounts are updated - await accountList.check_accountBalanceDisplayed('26'); - await accountList.check_accountBalanceDisplayed('24'); - }, - ); + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + await installSnapSimpleKeyring(driver, false); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // Import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 and reject the transaction + await sendTransactionWithSnapAccount({ + driver, + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + gasFee: '0.000042', + totalFee: '1.000042', + isSyncFlow: false, + approveTransaction: false, + }); + + // check the transaction is failed in MetaMask activity list + const homepage = new HomePage(driver); + await homepage.check_pageIsLoaded(); + await homepage.check_failedTxNumberDisplayedInActivity(); + }, + ); + }); }); - it('can import a private key and transfer 1 ETH (async flow reject)', async function () { - await withFixtures( - { - fixtures: new FixtureBuilder().build(), - ganacheOptions: multipleGanacheOptions, - title: this.test?.fullTitle(), - ignoredConsoleErrors: ['Request rejected by user or snap.'], - }, - async ({ - driver, - ganacheServer, - }: { - driver: Driver; - ganacheServer?: Ganache; - }) => { - await loginWithBalanceValidation(driver, ganacheServer); - await installSnapSimpleKeyring(driver, false); - const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); - - // Import snap account with private key on snap simple keyring page. - await snapSimpleKeyringPage.importAccountWithPrivateKey( - PRIVATE_KEY_TWO, - ); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - const headerNavbar = new HeaderNavbar(driver); - await headerNavbar.check_accountLabel('SSK Account'); - - // send 1 ETH from snap account to account 1 and reject the transaction - await sendTransactionWithSnapAccount({ + describe('Redesigned confirmation screens', function () { + it('can import a private key and transfer 1 ETH (sync flow)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver, - recipientAddress: DEFAULT_FIXTURE_ACCOUNT, - amount: '1', - gasFee: '0.000042', - totalFee: '1.000042', - isSyncFlow: false, - approveTransaction: false, - }); - - // check the transaction is failed in MetaMask activity list - const homepage = new HomePage(driver); - await homepage.check_pageIsLoaded(); - await homepage.check_failedTxNumberDisplayedInActivity(); - }, - ); + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + await installSnapSimpleKeyring(driver); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 + await sendRedesignedTransactionWithSnapAccount({ + driver, + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + }); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + const accountList = new AccountListPage(driver); + await accountList.check_pageIsLoaded(); + + // check the balance of the 2 accounts are updated + await accountList.check_accountBalanceDisplayed('26'); + await accountList.check_accountBalanceDisplayed('24'); + }, + ); + }); + + it('can import a private key and transfer 1 ETH (async flow approve)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + await installSnapSimpleKeyring(driver, false); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 and approve the transaction + await sendRedesignedTransactionWithSnapAccount({ + driver, + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + isSyncFlow: false, + }); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + const accountList = new AccountListPage(driver); + await accountList.check_pageIsLoaded(); + + // check the balance of the 2 accounts are updated + await accountList.check_accountBalanceDisplayed('26'); + await accountList.check_accountBalanceDisplayed('24'); + }, + ); + }); + + it('can import a private key and transfer 1 ETH (async flow reject)', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: multipleGanacheOptions, + title: this.test?.fullTitle(), + ignoredConsoleErrors: ['Request rejected by user or snap.'], + }, + async ({ + driver, + ganacheServer, + }: { + driver: Driver; + ganacheServer?: Ganache; + }) => { + await loginWithBalanceValidation(driver, ganacheServer); + + await installSnapSimpleKeyring(driver, false); + const snapSimpleKeyringPage = new SnapSimpleKeyringPage(driver); + + // Import snap account with private key on snap simple keyring page. + await snapSimpleKeyringPage.importAccountWithPrivateKey( + PRIVATE_KEY_TWO, + ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_accountLabel('SSK Account'); + + // send 1 ETH from snap account to account 1 and reject the transaction + await sendRedesignedTransactionWithSnapAccount({ + driver, + recipientAddress: DEFAULT_FIXTURE_ACCOUNT, + amount: '1', + isSyncFlow: false, + approveTransaction: false, + }); + + // check the transaction is failed in MetaMask activity list + const homepage = new HomePage(driver); + await homepage.check_pageIsLoaded(); + await homepage.check_failedTxNumberDisplayedInActivity(); + }, + ); + }); }); }); diff --git a/test/e2e/tests/confirmations/helpers.ts b/test/e2e/tests/confirmations/helpers.ts index 355f664ec61c..2b1078549c5b 100644 --- a/test/e2e/tests/confirmations/helpers.ts +++ b/test/e2e/tests/confirmations/helpers.ts @@ -14,7 +14,7 @@ export async function scrollAndConfirmAndAssertConfirm(driver: Driver) { await driver.clickElement('[data-testid="confirm-footer-button"]'); } -export function withRedesignConfirmationFixtures( +export function withTransactionEnvelopeTypeFixtures( // Default params first is discouraged because it makes it hard to call the function without the // optional parameters. But it doesn't apply here because we're always passing in a variable for // title. It's optional because it's sometimes unset. @@ -35,12 +35,6 @@ export function withRedesignConfirmationFixtures( metaMetricsId: 'fake-metrics-id', participateInMetaMetrics: true, }) - .withPreferencesController({ - preferences: { - redesignedConfirmationsEnabled: true, - isRedesignedConfirmationsDeveloperEnabled: true, - }, - }) .build(), ganacheOptions: transactionEnvelopeType === TransactionEnvelopeType.legacy diff --git a/test/e2e/tests/confirmations/navigation.spec.ts b/test/e2e/tests/confirmations/navigation.spec.ts index 38d29ad3ad77..97985381b08b 100644 --- a/test/e2e/tests/confirmations/navigation.spec.ts +++ b/test/e2e/tests/confirmations/navigation.spec.ts @@ -8,11 +8,11 @@ import { WINDOW_TITLES, } from '../../helpers'; import { Driver } from '../../webdriver/driver'; -import { withRedesignConfirmationFixtures } from './helpers'; +import { withTransactionEnvelopeTypeFixtures } from './helpers'; describe('Navigation Signature - Different signature types', function (this: Suite) { it('initiates and queues multiple signatures and confirms', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver }: { driver: Driver }) => { @@ -52,7 +52,7 @@ describe('Navigation Signature - Different signature types', function (this: Sui }); it('initiates and queues a mix of signatures and transactions and navigates', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver }: { driver: Driver }) => { @@ -100,7 +100,7 @@ describe('Navigation Signature - Different signature types', function (this: Sui }); it('initiates multiple signatures and rejects all', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver }: { driver: Driver }) => { diff --git a/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts b/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts index 328ad7811e1b..5876a4d5e17f 100644 --- a/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts +++ b/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts @@ -7,7 +7,7 @@ import { Driver } from '../../../webdriver/driver'; import { mockSignatureRejected, scrollAndConfirmAndAssertConfirm, - withRedesignConfirmationFixtures, + withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; import { @@ -22,7 +22,7 @@ import { describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this: Suite) { it('displays alert for domain binding and confirms', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver }: TestSuiteArguments) => { @@ -45,7 +45,7 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this }); it('initiates and rejects from confirmation screen', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ @@ -93,7 +93,7 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this }); it('initiates and rejects from alert friction modal', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ diff --git a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts index 4aeda07a3758..eccdfff78a7c 100644 --- a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts @@ -9,7 +9,7 @@ import { mockSignatureApproved, mockSignatureRejected, scrollAndConfirmAndAssertConfirm, - withRedesignConfirmationFixtures, + withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; import { @@ -26,7 +26,7 @@ import { describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { it('initiates and confirms and emits the correct events', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ @@ -77,7 +77,7 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { }); it('initiates and rejects and emits the correct events', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ diff --git a/test/e2e/tests/confirmations/signatures/permit.spec.ts b/test/e2e/tests/confirmations/signatures/permit.spec.ts index bc74b9fd2f5f..f6c8fc972b5f 100644 --- a/test/e2e/tests/confirmations/signatures/permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/permit.spec.ts @@ -14,7 +14,7 @@ import { mockSignatureApproved, mockSignatureRejected, scrollAndConfirmAndAssertConfirm, - withRedesignConfirmationFixtures, + withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; import { @@ -31,7 +31,7 @@ import { describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) { it('initiates and confirms and emits the correct events', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ @@ -76,7 +76,7 @@ describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) { }); it('initiates and rejects and emits the correct events', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ diff --git a/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts b/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts index 8444deab7c61..5f87c2d6b6e8 100644 --- a/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts +++ b/test/e2e/tests/confirmations/signatures/personal-sign.spec.ts @@ -8,7 +8,7 @@ import { Driver } from '../../../webdriver/driver'; import { mockSignatureApproved, mockSignatureRejected, - withRedesignConfirmationFixtures, + withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; import { @@ -25,7 +25,7 @@ import { describe('Confirmation Signature - Personal Sign @no-mmi', function (this: Suite) { it('initiates and confirms', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ @@ -66,7 +66,7 @@ describe('Confirmation Signature - Personal Sign @no-mmi', function (this: Suite }); it('initiates and rejects', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts index a7f2e7b81691..7ea7f0879279 100644 --- a/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts +++ b/test/e2e/tests/confirmations/signatures/sign-typed-data-v3.spec.ts @@ -9,7 +9,7 @@ import { mockSignatureApproved, mockSignatureRejected, scrollAndConfirmAndAssertConfirm, - withRedesignConfirmationFixtures, + withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; import { @@ -26,7 +26,7 @@ import { describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this: Suite) { it('initiates and confirms', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ @@ -70,7 +70,7 @@ describe('Confirmation Signature - Sign Typed Data V3 @no-mmi', function (this: }); it('initiates and rejects', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts index 33b94be6b332..4dfe9f04972f 100644 --- a/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts +++ b/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts @@ -9,7 +9,7 @@ import { mockSignatureApproved, mockSignatureRejected, scrollAndConfirmAndAssertConfirm, - withRedesignConfirmationFixtures, + withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; import { @@ -26,7 +26,7 @@ import { describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: Suite) { it('initiates and confirms', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ @@ -74,7 +74,7 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: }); it('initiates and rejects', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts index 2f1c33fe4d07..e7f8e1446f5c 100644 --- a/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts +++ b/test/e2e/tests/confirmations/signatures/sign-typed-data.spec.ts @@ -8,7 +8,7 @@ import { Driver } from '../../../webdriver/driver'; import { mockSignatureApproved, mockSignatureRejected, - withRedesignConfirmationFixtures, + withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; import { @@ -25,7 +25,7 @@ import { describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Suite) { it('initiates and confirms', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ @@ -66,7 +66,7 @@ describe('Confirmation Signature - Sign Typed Data @no-mmi', function (this: Sui }); it('initiates and rejects', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ diff --git a/test/e2e/tests/confirmations/signatures/siwe.spec.ts b/test/e2e/tests/confirmations/signatures/siwe.spec.ts index 9f261a28f569..4c7bec0ae121 100644 --- a/test/e2e/tests/confirmations/signatures/siwe.spec.ts +++ b/test/e2e/tests/confirmations/signatures/siwe.spec.ts @@ -8,7 +8,7 @@ import { mockSignatureApproved, mockSignatureRejected, scrollAndConfirmAndAssertConfirm, - withRedesignConfirmationFixtures, + withTransactionEnvelopeTypeFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; import { @@ -29,7 +29,7 @@ import { describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) { it('initiates and confirms', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ @@ -74,7 +74,7 @@ describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) { }); it('initiates and rejects', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ diff --git a/test/e2e/tests/confirmations/transactions/erc1155-revoke-set-approval-for-all-redesign.ts b/test/e2e/tests/confirmations/transactions/erc1155-revoke-set-approval-for-all-redesign.ts index 33a7760a050d..f79bf6835e14 100644 --- a/test/e2e/tests/confirmations/transactions/erc1155-revoke-set-approval-for-all-redesign.ts +++ b/test/e2e/tests/confirmations/transactions/erc1155-revoke-set-approval-for-all-redesign.ts @@ -7,7 +7,7 @@ import SetApprovalForAllTransactionConfirmation from '../../../page-objects/page import TestDapp from '../../../page-objects/pages/test-dapp'; import ContractAddressRegistry from '../../../seeder/contract-address-registry'; import { Driver } from '../../../webdriver/driver'; -import { withRedesignConfirmationFixtures } from '../helpers'; +import { withTransactionEnvelopeTypeFixtures } from '../helpers'; import { mocked4BytesSetApprovalForAll } from './erc721-revoke-set-approval-for-all-redesign'; import { TestSuiteArguments } from './shared'; @@ -16,7 +16,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); describe('Confirmation Redesign ERC1155 Revoke setApprovalForAll', function () { describe('Submit an revoke transaction @no-mmi', function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -28,7 +28,7 @@ describe('Confirmation Redesign ERC1155 Revoke setApprovalForAll', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { diff --git a/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts index da9da0a59873..cbb85482e6f1 100644 --- a/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/erc1155-set-approval-for-all-redesign.spec.ts @@ -6,7 +6,7 @@ import SetApprovalForAllTransactionConfirmation from '../../../page-objects/page import TestDapp from '../../../page-objects/pages/test-dapp'; import ContractAddressRegistry from '../../../seeder/contract-address-registry'; import { Driver } from '../../../webdriver/driver'; -import { withRedesignConfirmationFixtures } from '../helpers'; +import { withTransactionEnvelopeTypeFixtures } from '../helpers'; import { TestSuiteArguments } from './shared'; const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); @@ -14,7 +14,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); describe('Confirmation Redesign ERC1155 setApprovalForAll', function () { describe('Submit a transaction @no-mmi', function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -29,7 +29,7 @@ describe('Confirmation Redesign ERC1155 setApprovalForAll', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { diff --git a/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts index f5293161172f..2fbe2c553a06 100644 --- a/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/erc20-token-send-redesign.spec.ts @@ -14,7 +14,7 @@ import SendTokenPage from '../../../page-objects/pages/send/send-token-page'; import TestDapp from '../../../page-objects/pages/test-dapp'; import ContractAddressRegistry from '../../../seeder/contract-address-registry'; import { Driver } from '../../../webdriver/driver'; -import { withRedesignConfirmationFixtures } from '../helpers'; +import { withTransactionEnvelopeTypeFixtures } from '../helpers'; import { TestSuiteArguments } from './shared'; const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); @@ -22,7 +22,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () { describe('Wallet initiated', async function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -37,7 +37,7 @@ describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -54,7 +54,7 @@ describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () { describe('dApp initiated', async function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -69,7 +69,7 @@ describe('Confirmation Redesign ERC20 Token Send @no-mmi', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { diff --git a/test/e2e/tests/confirmations/transactions/erc721-revoke-set-approval-for-all-redesign.ts b/test/e2e/tests/confirmations/transactions/erc721-revoke-set-approval-for-all-redesign.ts index 95768c35de3f..27e2eb31a04e 100644 --- a/test/e2e/tests/confirmations/transactions/erc721-revoke-set-approval-for-all-redesign.ts +++ b/test/e2e/tests/confirmations/transactions/erc721-revoke-set-approval-for-all-redesign.ts @@ -7,7 +7,7 @@ import SetApprovalForAllTransactionConfirmation from '../../../page-objects/page import TestDapp from '../../../page-objects/pages/test-dapp'; import ContractAddressRegistry from '../../../seeder/contract-address-registry'; import { Driver } from '../../../webdriver/driver'; -import { withRedesignConfirmationFixtures } from '../helpers'; +import { withTransactionEnvelopeTypeFixtures } from '../helpers'; import { TestSuiteArguments } from './shared'; const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); @@ -15,7 +15,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); describe('Confirmation Redesign ERC721 Revoke setApprovalForAll', function () { describe('Submit an revoke transaction @no-mmi', function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -27,7 +27,7 @@ describe('Confirmation Redesign ERC721 Revoke setApprovalForAll', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { diff --git a/test/e2e/tests/confirmations/transactions/erc721-set-approval-for-all-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/erc721-set-approval-for-all-redesign.spec.ts index ba3c877973e5..4e1f54511d40 100644 --- a/test/e2e/tests/confirmations/transactions/erc721-set-approval-for-all-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/erc721-set-approval-for-all-redesign.spec.ts @@ -6,7 +6,7 @@ import SetApprovalForAllTransactionConfirmation from '../../../page-objects/page import TestDapp from '../../../page-objects/pages/test-dapp'; import ContractAddressRegistry from '../../../seeder/contract-address-registry'; import { Driver } from '../../../webdriver/driver'; -import { withRedesignConfirmationFixtures } from '../helpers'; +import { withTransactionEnvelopeTypeFixtures } from '../helpers'; import { TestSuiteArguments } from './shared'; const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); @@ -14,7 +14,7 @@ const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); describe('Confirmation Redesign ERC721 setApprovalForAll', function () { describe('Submit a transaction @no-mmi', function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -29,7 +29,7 @@ describe('Confirmation Redesign ERC721 setApprovalForAll', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { diff --git a/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts index e8226977d019..46318e113c35 100644 --- a/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/native-send-redesign.spec.ts @@ -11,7 +11,7 @@ import HomePage from '../../../page-objects/pages/homepage'; import SendTokenPage from '../../../page-objects/pages/send/send-token-page'; import TestDapp from '../../../page-objects/pages/test-dapp'; import { Driver } from '../../../webdriver/driver'; -import { withRedesignConfirmationFixtures } from '../helpers'; +import { withTransactionEnvelopeTypeFixtures } from '../helpers'; import { TestSuiteArguments } from './shared'; const TOKEN_RECIPIENT_ADDRESS = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'; @@ -19,7 +19,7 @@ const TOKEN_RECIPIENT_ADDRESS = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'; describe('Confirmation Redesign Native Send @no-mmi', function () { describe('Wallet initiated', async function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver }: TestSuiteArguments) => { @@ -29,7 +29,7 @@ describe('Confirmation Redesign Native Send @no-mmi', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver }: TestSuiteArguments) => { @@ -41,7 +41,7 @@ describe('Confirmation Redesign Native Send @no-mmi', function () { describe('dApp initiated', async function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver }: TestSuiteArguments) => { @@ -51,7 +51,7 @@ describe('Confirmation Redesign Native Send @no-mmi', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver }: TestSuiteArguments) => { diff --git a/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts b/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts index 8d319b62c482..6fc048c7f11b 100644 --- a/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts +++ b/test/e2e/tests/confirmations/transactions/nft-token-send-redesign.spec.ts @@ -16,7 +16,7 @@ import SendTokenPage from '../../../page-objects/pages/send/send-token-page'; import TestDapp from '../../../page-objects/pages/test-dapp'; import ContractAddressRegistry from '../../../seeder/contract-address-registry'; import { Driver } from '../../../webdriver/driver'; -import { withRedesignConfirmationFixtures } from '../helpers'; +import { withTransactionEnvelopeTypeFixtures } from '../helpers'; import { TestSuiteArguments } from './shared'; const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); @@ -27,7 +27,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () { describe('ERC721', function () { describe('Wallet initiated', async function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -42,7 +42,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -59,7 +59,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () { describe('dApp initiated', async function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -74,7 +74,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -93,7 +93,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () { describe('ERC1155', function () { describe('Wallet initiated', async function () { it('Sends a type 0 transaction (Legacy)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.legacy, async ({ driver, contractRegistry }: TestSuiteArguments) => { @@ -108,7 +108,7 @@ describe('Confirmation Redesign Token Send @no-mmi', function () { }); it('Sends a type 2 transaction (EIP1559)', async function () { - await withRedesignConfirmationFixtures( + await withTransactionEnvelopeTypeFixtures( this.test?.fullTitle(), TransactionEnvelopeType.feeMarket, async ({ driver, contractRegistry }: TestSuiteArguments) => { diff --git a/test/e2e/tests/dapp-interactions/contract-interactions.spec.js b/test/e2e/tests/dapp-interactions/contract-interactions.spec.js index e7de105e0112..a685954b5857 100644 --- a/test/e2e/tests/dapp-interactions/contract-interactions.spec.js +++ b/test/e2e/tests/dapp-interactions/contract-interactions.spec.js @@ -7,8 +7,8 @@ const { WINDOW_TITLES, locateAccountBalanceDOM, clickNestedButton, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); - const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); const FixtureBuilder = require('../../fixture-builder'); @@ -32,6 +32,8 @@ describe('Deploy contract and call contract methods', function () { ); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // deploy contract await openDapp(driver, contractAddress); diff --git a/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js b/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js index 131ebdf4ee73..ad168a2b9332 100644 --- a/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js +++ b/test/e2e/tests/dapp-interactions/dapp-tx-edit.spec.js @@ -4,6 +4,7 @@ const { openDapp, WINDOW_TITLES, withFixtures, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); const FixtureBuilder = require('../../fixture-builder'); @@ -27,6 +28,8 @@ describe('Editing confirmations of dapp initiated contract interactions', functi ); await logInWithBalanceValidation(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // deploy contract await openDapp(driver, contractAddress); // wait for deployed contract, calls and confirms a contract method where ETH is sent @@ -59,6 +62,8 @@ describe('Editing confirmations of dapp initiated contract interactions', functi async ({ driver }) => { await logInWithBalanceValidation(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openDapp(driver); await driver.clickElement('#sendButton'); diff --git a/test/e2e/tests/dapp-interactions/failing-contract.spec.js b/test/e2e/tests/dapp-interactions/failing-contract.spec.js index 5770adb1a3b9..c05938d668e0 100644 --- a/test/e2e/tests/dapp-interactions/failing-contract.spec.js +++ b/test/e2e/tests/dapp-interactions/failing-contract.spec.js @@ -6,6 +6,7 @@ const { WINDOW_TITLES, generateGanacheOptions, clickNestedButton, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); const FixtureBuilder = require('../../fixture-builder'); @@ -29,6 +30,8 @@ describe('Failing contract interaction ', function () { ); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openDapp(driver, contractAddress); let windowHandles = await driver.getAllWindowHandles(); const extension = windowHandles[0]; @@ -93,6 +96,8 @@ describe('Failing contract interaction on non-EIP1559 network', function () { ); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openDapp(driver, contractAddress); let windowHandles = await driver.getAllWindowHandles(); const extension = windowHandles[0]; diff --git a/test/e2e/tests/network/network-error.spec.js b/test/e2e/tests/network/network-error.spec.js index 4d45734edf77..61842f482151 100644 --- a/test/e2e/tests/network/network-error.spec.js +++ b/test/e2e/tests/network/network-error.spec.js @@ -4,6 +4,7 @@ const { logInWithBalanceValidation, openActionMenuAndStartSendFlow, generateGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { GAS_API_BASE_URL } = require('../../../../shared/constants/swaps'); @@ -58,6 +59,8 @@ describe('Gas API fallback', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openActionMenuAndStartSendFlow(driver); await driver.fill( 'input[placeholder="Enter public address (0x) or domain name"]', diff --git a/test/e2e/tests/petnames/petnames-transactions.spec.js b/test/e2e/tests/petnames/petnames-transactions.spec.js index cc19e44a55eb..55c295c51c3a 100644 --- a/test/e2e/tests/petnames/petnames-transactions.spec.js +++ b/test/e2e/tests/petnames/petnames-transactions.spec.js @@ -5,6 +5,7 @@ const { unlockWallet, defaultGanacheOptions, openActionMenuAndStartSendFlow, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { @@ -49,6 +50,9 @@ describe('Petnames - Transactions', function () { }, async ({ driver }) => { await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openDapp(driver); await createDappSendTransaction(driver); await switchToNotificationWindow(driver, 3); @@ -94,6 +98,8 @@ describe('Petnames - Transactions', function () { }, async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createWalletSendTransaction(driver, ADDRESS_MOCK); await expectName(driver, ABBREVIATED_ADDRESS_MOCK, false); diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js index 9e904af6513e..5368c2617f13 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-simple-send.spec.js @@ -1,12 +1,12 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../../fixture-builder'); - const { defaultGanacheOptions, withFixtures, sendScreenToConfirmScreen, logInWithBalanceValidation, WINDOW_TITLES, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { mockMultiNetworkBalancePolling, @@ -209,6 +209,8 @@ describe('Simple Send Security Alert - Blockaid @no-mmi', function () { async ({ driver }) => { await logInWithBalanceValidation(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await sendScreenToConfirmScreen( driver, '0xB8c77482e45F1F44dE1745F52C74426C631bDD52', diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js index deb189404fa8..958c1351d8b3 100644 --- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js +++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-diff-network.spec.js @@ -9,129 +9,246 @@ const { WINDOW_TITLES, defaultGanacheOptions, largeDelayMs, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); -const { PAGES } = require('../../webdriver/driver'); describe('Request Queuing for Multiple Dapps and Txs on different networks', function () { - it('should batch confirmation txs for different dapps on different networks.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Old confirmation screens', function () { + it('should batch confirmation txs for different dapps on different networks.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); + await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - // Dapp one send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); + // Dapp one send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - await driver.delay(largeDelayMs); + await driver.delay(largeDelayMs); - // Dapp two send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); + // Dapp two send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); + await driver.waitForSelector( + By.xpath("//div[normalize-space(.)='1 of 2']"), + ); - // Reject All Transactions - await driver.clickElement('.page-container__footer-secondary a'); + // Reject All Transactions + await driver.clickElement('.page-container__footer-secondary a'); - // TODO: Do we want to confirm here? - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); + // TODO: Do we want to confirm here? + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); - // Wait for confirmation to close - // TODO: find a better way to handle different dialog ids - await driver.delay(2000); + // Wait for confirmation to close + // TODO: find a better way to handle different dialog ids + await driver.delay(2000); - // Wait for new confirmations queued from second dapp to open - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Wait for new confirmations queued from second dapp to open + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); + await driver.waitForSelector( + By.xpath("//div[normalize-space(.)='1 of 2']"), + ); - // Check correct network on confirm tx. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - }, - ); + // Check correct network on confirm tx. + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + }, + ); + }); + }); + + describe('Redesigned confirmation screens', function () { + it('should batch confirmation txs for different dapps on different networks.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Dapp one send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + + await driver.delay(largeDelayMs); + + // Dapp two send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); + + // Wait for confirmation to close + await driver.delay(2000); + + // Wait for new confirmations queued from second dapp to open + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + + // Check correct network on confirm tx. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js index 265b28d0f56d..066acacab23a 100644 --- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js +++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-extra-tx.spec.js @@ -9,169 +9,326 @@ const { unlockWallet, WINDOW_TITLES, withFixtures, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); -const { PAGES } = require('../../webdriver/driver'); describe('Request Queuing for Multiple Dapps and Txs on different networks', function () { - it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Old confirmation flows', function () { + it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - - async ({ driver }) => { - await unlockWallet(driver); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp 1 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Ensure Dapp One is on Localhost 8546 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - // Should auto switch without prompt since already approved via connect - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); - - // 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.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // Dapp 1 send 2 tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - - await driver.waitUntilXWindowHandles(4); - - // Dapp 2 send 2 tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); - // We cannot wait for the dialog, since it is already opened from before - await driver.delay(largeDelayMs); - - // Dapp 1 send 1 tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - // We cannot switch directly, as the dialog is sometimes closed and re-opened - await driver.delay(largeDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); - - // Reject All Transactions - await driver.clickElement('.page-container__footer-secondary a'); - - // TODO: Do we want to confirm here? - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_URL); - - // Wait for new confirmations queued from second dapp to open - // We need a big delay to make sure dialog is not invalidated - // TODO: find a better way to handle different dialog ids - await driver.delay(2000); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); - - // Check correct network on confirm tx. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - - // Reject All Transactions - await driver.clickElement('.page-container__footer-secondary a'); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject all', - tag: 'button', - }); - - // Wait for new confirmations queued from second dapp to open - // We need a big delay to make sure dialog is not invalidated - // TODO: find a better way to handle different dialog ids - await driver.delay(2000); - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - }, - ); + + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp 1 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Ensure Dapp One is on Localhost 8546 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Should auto switch without prompt since already approved via connect + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // 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.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // Dapp 1 send 2 tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + + await driver.waitUntilXWindowHandles(4); + + // Dapp 2 send 2 tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + // We cannot wait for the dialog, since it is already opened from before + await driver.delay(largeDelayMs); + + // Dapp 1 send 1 tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + // We cannot switch directly, as the dialog is sometimes closed and re-opened + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//div[normalize-space(.)='1 of 2']"), + ); + + // Reject All Transactions + await driver.clickElement('.page-container__footer-secondary a'); + + // TODO: Do we want to confirm here? + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + // Wait for new confirmations queued from second dapp to open + // We need a big delay to make sure dialog is not invalidated + // TODO: find a better way to handle different dialog ids + await driver.delay(2000); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//div[normalize-space(.)='1 of 2']"), + ); + + // Check correct network on confirm tx. + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + + // Reject All Transactions + await driver.clickElement('.page-container__footer-secondary a'); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); + + // Wait for new confirmations queued from second dapp to open + // We need a big delay to make sure dialog is not invalidated + // TODO: find a better way to handle different dialog ids + await driver.delay(2000); + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + }, + ); + }); + }); + + describe('Redesigned confirmation flows', function () { + it('should batch confirmation txs for different dapps on different networks adds extra tx after.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp 1 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Ensure Dapp One is on Localhost 8546 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Should auto switch without prompt since already approved via connect + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // 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.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + // Dapp 1 send 2 tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + + await driver.waitUntilXWindowHandles(4); + + // Dapp 2 send 2 tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + // We cannot wait for the dialog, since it is already opened from before + await driver.delay(largeDelayMs); + + // Dapp 1 send 1 tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + // We cannot switch directly, as the dialog is sometimes closed and re-opened + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + + // Reject All Transactions + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + // Wait for new confirmations queued from second dapp to open + // We need a big delay to make sure dialog is not invalidated + // TODO: find a better way to handle different dialog ids + await driver.delay(2000); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + + // Check correct network on confirm tx. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + + // Reject All Transactions + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject all', + tag: 'button', + }); + + // Wait for new confirmations queued from second dapp to open + // We need a big delay to make sure dialog is not invalidated + // TODO: find a better way to handle different dialog ids + await driver.delay(2000); + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js b/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js index c30d6a73c063..d3241c95c9d5 100644 --- a/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js +++ b/test/e2e/tests/request-queuing/batch-txs-per-dapp-same-network.spec.js @@ -10,163 +10,317 @@ const { WINDOW_TITLES, defaultGanacheOptions, largeDelayMs, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); -const { PAGES } = require('../../webdriver/driver'); describe('Request Queuing for Multiple Dapps and Txs on same networks', function () { - it('should batch confirmation txs for different dapps on same networks ', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerTripleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 3 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Old confirmation screens', function () { + it('should batch confirmation txs for different dapps on same networks ', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + dappOptions: { numberOfDapps: 3 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); + await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Connect to dapp 1 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + // Connect to dapp 1 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - await driver.delay(regularDelayMs); + await driver.delay(regularDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); - await driver.switchToWindowWithUrl(DAPP_URL); + await driver.switchToWindowWithUrl(DAPP_URL); - let switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x3e8' }], - }); + let switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x3e8' }], + }); - // Ensure Dapp One is on Localhost 7777 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); + // Ensure Dapp One is on Localhost 7777 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - // Should auto switch without prompt since already approved via connect + // Should auto switch without prompt since already approved via connect - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // 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'); + // Connect to dapp 2 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - await driver.delay(regularDelayMs); + await driver.delay(regularDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); + switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); - // Ensure Dapp Two is on Localhost 8545 - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); + // Ensure Dapp Two is on Localhost 8545 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - // Should auto switch without prompt since already approved via connect + // Should auto switch without prompt since already approved via connect - // Dapp one send two tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); + // Dapp one send two tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - await driver.delay(largeDelayMs); + await driver.delay(largeDelayMs); - // Dapp two send two tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); - await driver.clickElement('#sendButton'); + // Dapp two send two tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); + await driver.waitForSelector( + By.xpath("//div[normalize-space(.)='1 of 2']"), + ); - // Check correct network on confirm tx. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 7777', - }); + // Check correct network on confirm tx. + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 7777', + }); - // Reject All Transactions - await driver.clickElement('.page-container__footer-secondary a'); + // Reject All Transactions + await driver.clickElement('.page-container__footer-secondary a'); - await driver.clickElement({ text: 'Reject all', tag: 'button' }); // TODO: Do we want to confirm here? + await driver.clickElement({ text: 'Reject all', tag: 'button' }); // TODO: Do we want to confirm here? - // Wait for confirmation to close - await driver.waitUntilXWindowHandles(4); + // Wait for confirmation to close + await driver.waitUntilXWindowHandles(4); - // Wait for new confirmations queued from second dapp to open - await driver.delay(largeDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Wait for new confirmations queued from second dapp to open + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector( - By.xpath("//div[normalize-space(.)='1 of 2']"), - ); + await driver.waitForSelector( + By.xpath("//div[normalize-space(.)='1 of 2']"), + ); - // Check correct network on confirm tx. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - }, - ); + // Check correct network on confirm tx. + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + }, + ); + }); + }); + + describe('Redesigned confirmation screens', function () { + it('should batch confirmation txs for different dapps on same networks ', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + dappOptions: { numberOfDapps: 3 }, + 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 1 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + let switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x3e8' }], + }); + + // Ensure Dapp One is on Localhost 7777 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Should auto switch without prompt since already approved via connect + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // 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.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Ensure Dapp Two is on Localhost 8545 + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + // Should auto switch without prompt since already approved via connect + + // Dapp one send two tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + + await driver.delay(largeDelayMs); + + // Dapp two send two tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + + // Check correct network on confirm tx. + await driver.findElement({ + css: 'p', + text: 'Localhost 7777', + }); + + // Reject All Transactions + await driver.clickElement({ text: 'Reject all', tag: 'button' }); + + // Wait for confirmation to close + await driver.waitUntilXWindowHandles(4); + + // Wait for new confirmations queued from second dapp to open + await driver.delay(largeDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector( + By.xpath("//p[normalize-space(.)='1 of 2']"), + ); + + // Check correct network on confirm tx. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + }, + ); + }); }); }); 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 index 5814d8a60a2b..28b19efbeb04 100644 --- a/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js +++ b/test/e2e/tests/request-queuing/dapp1-send-dapp2-signTypedData.spec.js @@ -10,148 +10,300 @@ const { tempToggleSettingRedesignedConfirmations, WINDOW_TITLES, largeDelayMs, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); +const { PAGES } = require('../../webdriver/driver'); 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, - }, - ], + describe('Old confirmation screens', 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(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); - - // Open and connect Dapp One - await openDapp(driver, undefined, DAPP_URL); - - 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.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - // Open and connect to Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - 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: 'Connect', - tag: 'button', - }); - - // Switch Dapp Two to Localhost 8546 - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - let switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x53a' }], - }); - - // Initiate switchEthereumChain on Dapp one - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); - - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x53a', - }); - - // Should auto switch without prompt since already approved via connect - - // Switch back to Dapp One - await driver.switchToWindowWithUrl(DAPP_URL); - - // switch chain for Dapp One - 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.waitForSelector({ - css: '[id="chainId"]', - text: '0x3e8', - }); - // Should auto switch without prompt since already approved via connect - - await driver.switchToWindowWithUrl(DAPP_URL); - - // eth_sendTransaction request - await driver.clickElement('#sendButton'); - await driver.waitUntilXWindowHandles(3); - - 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.waitForSelector({ - css: '[data-testid="network-display"]', - text: 'Localhost 7777', - }); - - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - await driver.delay(largeDelayMs); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - // Check correct network on the signTypedData confirmation. - await driver.waitForSelector({ - css: '[data-testid="signature-request-network-display"]', - text: 'Localhost 8546', - }); - - await driver.clickElement({ text: 'Reject', tag: 'button' }); - }, - ); + async ({ driver }) => { + await unlockWallet(driver); + await tempToggleSettingRedesignedConfirmations(driver); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open and connect Dapp One + await openDapp(driver, undefined, DAPP_URL); + + 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.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + // Open and connect to Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + 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: 'Connect', + tag: 'button', + }); + + // Switch Dapp Two to Localhost 8546 + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + let switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp one + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + await driver.waitForSelector({ + css: '[id="chainId"]', + text: '0x53a', + }); + + // Should auto switch without prompt since already approved via connect + + // Switch back to Dapp One + await driver.switchToWindowWithUrl(DAPP_URL); + + // switch chain for Dapp One + 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.waitForSelector({ + css: '[id="chainId"]', + text: '0x3e8', + }); + // Should auto switch without prompt since already approved via connect + + await driver.switchToWindowWithUrl(DAPP_URL); + + // eth_sendTransaction request + await driver.clickElement('#sendButton'); + await driver.waitUntilXWindowHandles(3); + + 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.waitForSelector({ + css: '[data-testid="network-display"]', + text: 'Localhost 7777', + }); + + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + await driver.delay(largeDelayMs); + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Check correct network on the signTypedData confirmation. + await driver.waitForSelector({ + css: '[data-testid="signature-request-network-display"]', + text: 'Localhost 8546', + }); + + await driver.clickElement({ text: 'Reject', tag: 'button' }); + }, + ); + }); + }); + + describe('Redesigned confirmation screens', 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 and connect Dapp One + await openDapp(driver, undefined, DAPP_URL); + + 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.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + // Open and connect to Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + 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: 'Connect', + tag: 'button', + }); + + // Switch Dapp Two to Localhost 8546 + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + let switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x53a' }], + }); + + // Initiate switchEthereumChain on Dapp one + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + await driver.waitForSelector({ + css: '[id="chainId"]', + text: '0x53a', + }); + + // Should auto switch without prompt since already approved via connect + + // Switch back to Dapp One + await driver.switchToWindowWithUrl(DAPP_URL); + + // switch chain for Dapp One + 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.waitForSelector({ + css: '[id="chainId"]', + text: '0x3e8', + }); + // Should auto switch without prompt since already approved via connect + + await driver.switchToWindowWithUrl(DAPP_URL); + + // eth_sendTransaction request + await driver.clickElement('#sendButton'); + await driver.waitUntilXWindowHandles(3); + + 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.waitForSelector({ + css: 'p', + text: 'Localhost 7777', + }); + + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + await driver.delay(largeDelayMs); + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Check correct network on the signTypedData confirmation. + await driver.waitForSelector({ + css: 'p', + text: 'Localhost 8546', + }); + + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js index 7a212533de4b..67efbfe6fee9 100644 --- a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js +++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js @@ -1,5 +1,4 @@ const { strict: assert } = require('assert'); - const FixtureBuilder = require('../../fixture-builder'); const { withFixtures, @@ -10,149 +9,247 @@ const { regularDelayMs, WINDOW_TITLES, defaultGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing Dapp 1 Send Tx -> Dapp 2 Request Accounts Tx', function () { - it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withPermissionControllerConnectedToTestDapp() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Old confirmation screens', function () { + it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withPermissionControllerConnectedToTestDapp() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Dapp Send Button - await driver.clickElement('#sendButton'); - await driver.delay(regularDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.waitForSelector({ - text: 'Reject', - tag: 'button', - }); - - await driver.delay(regularDelayMs); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Leave the confirmation pending - await openDapp(driver, undefined, DAPP_ONE_URL); - - const accountsOnload = await ( - await driver.findElement('#accounts') - ).getText(); - assert.deepStrictEqual(accountsOnload, ''); - - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - const accountsBeforeConnect = await ( - await driver.findElement('#accounts') - ).getText(); - assert.deepStrictEqual(accountsBeforeConnect, ''); - - // Reject the pending confirmation from the first dapp - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Reject', - tag: 'button', - }); - - // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp - await driver.delay(regularDelayMs); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - - await driver.waitForSelector({ - text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - css: '#accounts', - }); - }, - ); + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Dapp Send Button + await driver.clickElement('#sendButton'); + await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector({ + text: 'Reject', + tag: 'button', + }); + + await driver.delay(regularDelayMs); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Leave the confirmation pending + await openDapp(driver, undefined, DAPP_ONE_URL); + + const accountsOnload = await ( + await driver.findElement('#accounts') + ).getText(); + assert.deepStrictEqual(accountsOnload, ''); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + const accountsBeforeConnect = await ( + await driver.findElement('#accounts') + ).getText(); + assert.deepStrictEqual(accountsBeforeConnect, ''); + + // Reject the pending confirmation from the first dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Reject', + tag: 'button', + }); + + // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp + await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + await driver.waitForSelector({ + text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + css: '#accounts', + }); + }, + ); + }); }); - it('should not queue the `eth_requestAccounts` requests when the requesting dapp already has connected accounts', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withPermissionControllerConnectedToTwoTestDapps() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Redesigned confirmation screens', function () { + it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withPermissionControllerConnectedToTestDapp() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Dapp Send Button + await driver.clickElement('#sendButton'); + await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Dapp Send Button - await driver.clickElement('#sendButton'); + await driver.waitForSelector({ + text: 'Cancel', + tag: 'button', + }); - // Leave the confirmation pending + await driver.delay(regularDelayMs); - await openDapp(driver, undefined, DAPP_ONE_URL); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - const ethRequestAccounts = JSON.stringify({ - jsonrpc: '2.0', - method: 'eth_requestAccounts', - }); + // Leave the confirmation pending + await openDapp(driver, undefined, DAPP_ONE_URL); - const accounts = await driver.executeScript( - `return window.ethereum.request(${ethRequestAccounts})`, - ); + const accountsOnload = await ( + await driver.findElement('#accounts') + ).getText(); + assert.deepStrictEqual(accountsOnload, ''); - assert.deepStrictEqual(accounts, [ - '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - ]); - }, - ); + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + const accountsBeforeConnect = await ( + await driver.findElement('#accounts') + ).getText(); + assert.deepStrictEqual(accountsBeforeConnect, ''); + + // Reject the pending confirmation from the first dapp + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Cancel', + tag: 'button', + }); + + // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp + await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + await driver.waitForSelector({ + text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + css: '#accounts', + }); + }, + ); + }); + + it('should not queue the `eth_requestAccounts` requests when the requesting dapp already has connected accounts', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withPermissionControllerConnectedToTwoTestDapps() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Dapp Send Button + await driver.clickElement('#sendButton'); + + // Leave the confirmation pending + + await openDapp(driver, undefined, DAPP_ONE_URL); + + const ethRequestAccounts = JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_requestAccounts', + }); + + const accounts = await driver.executeScript( + `return window.ethereum.request(${ethRequestAccounts})`, + ); + + assert.deepStrictEqual(accounts, [ + '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + ]); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js index c98e0eb229c6..24c09ee18d09 100644 --- a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js +++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js @@ -7,304 +7,619 @@ const { unlockWallet, WINDOW_TITLES, withFixtures, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () { - it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', 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, - }, - ], + describe('Old confirmation screens', function () { + it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', 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(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - const editButtons = await driver.findElements('[data-testid="edit"]'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await editButtons[1].click(); + const editButtons = await driver.findElements('[data-testid="edit"]'); - // Disconnect Localhost 8545 - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); + await editButtons[1].click(); - await driver.clickElement('[data-testid="connect-more-chains-button"]'); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + // Disconnect Localhost 8545 + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.clickElement( + '[data-testid="connect-more-chains-button"]', + ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // 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.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findElement({ + text: 'Use your enabled networks', + tag: 'p', + }); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + await driver.clickElement('#sendButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + // Wait for switch confirmation to close then tx confirmation to show. + // There is an extra window appearing and disappearing + // so we leave this delay until the issue is fixed (#27360) + await driver.delay(5000); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + // Check correct network on the send confirmation. + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); + + it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', 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); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await tempToggleSettingRedesignedTransactionConfirmations(driver); - await driver.switchToWindowWithUrl(DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - // Initiate switchEthereumChain on Dapp One - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const editButtons = await driver.findElements('[data-testid="edit"]'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.findElement({ - text: 'Use your enabled networks', - tag: 'p', - }); + await editButtons[1].click(); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + // Disconnect Localhost 8545 + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); - await driver.clickElement('#sendButton'); + await driver.clickElement( + '[data-testid="connect-more-chains-button"]', + ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Wait for switch confirmation to close then tx confirmation to show. - // There is an extra window appearing and disappearing - // so we leave this delay until the issue is fixed (#27360) - await driver.delay(5000); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Connect to dapp 2 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - // Check correct network on the send confirmation. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.switchToWindowWithUrl(DAPP_URL); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + await driver.clickElement('#sendButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + // Wait for switch confirmation to close then tx confirmation to show. + // There is an extra window appearing and disappearing + // so we leave this delay until the issue is fixed (#27360) + await driver.delay(5000); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Check correct network on the send confirmation. + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); + + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); + + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); }); - it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', 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, - }, - ], + describe('Redesigned confirmation screens', function () { + it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', 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(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - const editButtons = await driver.findElements('[data-testid="edit"]'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await editButtons[1].click(); + const editButtons = await driver.findElements('[data-testid="edit"]'); - // Disconnect Localhost 8545 - await driver.clickElement({ - text: 'Localhost 8545', - tag: 'p', - }); + await editButtons[1].click(); - await driver.clickElement('[data-testid="connect-more-chains-button"]'); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + // Disconnect Localhost 8545 + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.clickElement( + '[data-testid="connect-more-chains-button"]', + ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // 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'); - // Connect to dapp 2 - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.switchToWindowWithUrl(DAPP_URL); - await driver.switchToWindowWithUrl(DAPP_URL); + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); - // switchEthereumChain request - const switchEthereumChainRequest = JSON.stringify({ - jsonrpc: '2.0', - method: 'wallet_switchEthereumChain', - params: [{ chainId: '0x539' }], - }); + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - // Initiate switchEthereumChain on Dapp One - await driver.executeScript( - `window.ethereum.request(${switchEthereumChainRequest})`, - ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.findElement({ + text: 'Use your enabled networks', + tag: 'p', + }); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.clickElement('#sendButton'); + await driver.clickElement('#sendButton'); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ text: 'Cancel', tag: 'button' }); - await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); - // Wait for switch confirmation to close then tx confirmation to show. - // There is an extra window appearing and disappearing - // so we leave this delay until the issue is fixed (#27360) - await driver.delay(5000); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Wait for switch confirmation to close then tx confirmation to show. + // There is an extra window appearing and disappearing + // so we leave this delay until the issue is fixed (#27360) + await driver.delay(5000); - // Check correct network on the send confirmation. - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Confirm', - tag: 'button', - }); + // Check correct network on the send confirmation. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); + + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); + + it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', 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.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const editButtons = await driver.findElements('[data-testid="edit"]'); + + await editButtons[1].click(); + + // Disconnect Localhost 8545 + await driver.clickElement({ + text: 'Localhost 8545', + tag: 'p', + }); + + await driver.clickElement( + '[data-testid="connect-more-chains-button"]', + ); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // 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.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + // Initiate switchEthereumChain on Dapp One + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + await driver.clickElement('#sendButton'); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + // Wait for switch confirmation to close then tx confirmation to show. + // There is an extra window appearing and disappearing + // so we leave this delay until the issue is fixed (#27360) + await driver.delay(5000); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Check correct network on the send confirmation. + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Confirm', + tag: 'button', + }); + + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); + + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js b/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js index 06d232635131..3a413f147e06 100644 --- a/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js +++ b/test/e2e/tests/request-queuing/multi-dapp-sendTx-revokePermission.spec.js @@ -7,127 +7,247 @@ const { DAPP_ONE_URL, WINDOW_TITLES, defaultGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); -const { PAGES } = require('../../webdriver/driver'); describe('Request Queuing for Multiple Dapps and Txs on different networks revokePermissions', function () { - it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Old confirmation screens', function () { + it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - - async ({ driver }) => { - await unlockWallet(driver); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - // Wait for the first dapp's connect confirmation to disappear - await driver.waitUntilXWindowHandles(2); - - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); - - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); - - // Dapp 1 send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x539', - }); - await driver.clickElement('#sendButton'); - - await driver.waitUntilXWindowHandles(4); - await driver.delay(3000); - - // Dapp 2 send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x53a', - }); - await driver.clickElement('#sendButton'); - await driver.waitUntilXWindowHandles(4); - - // Dapp 1 revokePermissions - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.findElement({ - css: '[id="chainId"]', - text: '0x539', - }); - await driver.assertElementNotPresent({ - css: '[id="chainId"]', - text: '0x53a', - }); - - // Confirmation will close then reopen - await driver.clickElement('#revokeAccountsPermission'); - // TODO: find a better way to handle different dialog ids - await driver.delay(3000); - - // Check correct network on confirm tx. - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - }, - ); + + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Dapp 1 send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x539', + }); + await driver.clickElement('#sendButton'); + + await driver.waitUntilXWindowHandles(4); + await driver.delay(3000); + + // Dapp 2 send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.waitUntilXWindowHandles(4); + + // Dapp 1 revokePermissions + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x539', + }); + await driver.assertElementNotPresent({ + css: '[id="chainId"]', + text: '0x53a', + }); + + // Confirmation will close then reopen + await driver.clickElement('#revokeAccountsPermission'); + // TODO: find a better way to handle different dialog ids + await driver.delay(3000); + + // Check correct network on confirm tx. + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + }, + ); + }); + }); + + describe('New confirmation screens', function () { + it('should close transaction for revoked permission of eth_accounts but show queued tx from second dapp on a different network.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Dapp 1 send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x539', + }); + await driver.clickElement('#sendButton'); + + await driver.waitUntilXWindowHandles(4); + await driver.delay(3000); + + // Dapp 2 send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x53a', + }); + await driver.clickElement('#sendButton'); + await driver.waitUntilXWindowHandles(4); + + // Dapp 1 revokePermissions + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.findElement({ + css: '[id="chainId"]', + text: '0x539', + }); + await driver.assertElementNotPresent({ + css: '[id="chainId"]', + text: '0x53a', + }); + + // Confirmation will close then reopen + await driver.clickElement('#revokeAccountsPermission'); + // TODO: find a better way to handle different dialog ids + await driver.delay(3000); + + // Check correct network on confirm tx. + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.findElement({ + css: 'p', + text: 'Localhost 8546', + }); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js b/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js index 38fe1d7204d2..f831ff1ff38d 100644 --- a/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js +++ b/test/e2e/tests/request-queuing/multiple-networks-dapps-txs.spec.js @@ -8,142 +8,277 @@ const { WINDOW_TITLES, defaultGanacheOptions, largeDelayMs, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); -const { PAGES } = require('../../webdriver/driver'); describe('Request Queuing for Multiple Dapps and Txs on different networks.', function () { - it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - dappOptions: { numberOfDapps: 2 }, - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Old confirmation screens', function () { + it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); + await tempToggleSettingRedesignedTransactionConfirmations(driver); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - // Connect to dapp 1 - await driver.clickElement({ text: 'Connect', tag: 'button' }); + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); - // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. - // Open Dapp Two - await openDapp(driver, undefined, DAPP_ONE_URL); + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - // Connect to dapp 2 - await driver.clickElement({ text: 'Connect', tag: 'button' }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); - await driver.clickElementAndWaitForWindowToClose({ - text: 'Connect', - tag: 'button', - }); + // Dapp one send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); - // Dapp one send tx - await driver.switchToWindowWithUrl(DAPP_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); + await driver.waitUntilXWindowHandles(4); - await driver.waitUntilXWindowHandles(4); + // Dapp two send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); - // Dapp two send tx - await driver.switchToWindowWithUrl(DAPP_ONE_URL); - await driver.delay(largeDelayMs); - await driver.clickElement('#sendButton'); + // First switch network + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // First switch network - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Wait for confirm tx after switch network confirmation. + await driver.delay(largeDelayMs); - // Wait for confirm tx after switch network confirmation. - await driver.delay(largeDelayMs); + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Reject Transaction + await driver.findClickableElement({ text: 'Reject', tag: 'button' }); + await driver.clickElement( + '[data-testid="page-container-footer-cancel"]', + ); - // Reject Transaction - await driver.findClickableElement({ text: 'Reject', tag: 'button' }); - await driver.clickElement( - '[data-testid="page-container-footer-cancel"]', - ); + // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx. + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); - // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx. - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.clickElement( - '[data-testid="account-overview__activity-tab"]', - ); + // Check for unconfirmed transaction in tx list + await driver.wait(async () => { + const unconfirmedTxes = await driver.findElements( + '.transaction-list-item--unconfirmed', + ); + return unconfirmedTxes.length === 1; + }, 10000); - // Check for unconfirmed transaction in tx list - await driver.wait(async () => { - const unconfirmedTxes = await driver.findElements( - '.transaction-list-item--unconfirmed', + // Click Unconfirmed Tx + await driver.clickElement('.transaction-list-item--unconfirmed'); + + await driver.assertElementNotPresent({ + tag: 'p', + text: 'Network switched to Localhost 8546', + }); + + // Confirm Tx + await driver.clickElement( + '[data-testid="page-container-footer-next"]', ); - return unconfirmedTxes.length === 1; - }, 10000); - // Click Unconfirmed Tx - await driver.clickElement('.transaction-list-item--unconfirmed'); + // Check for Confirmed Transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); + }); + + describe('Redesigned confirmation screens', function () { + it('should switch to the dapps network automatically when handling sendTransaction calls @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - await driver.assertElementNotPresent({ - tag: 'p', - text: 'Network switched to Localhost 8546', - }); + // Connect to dapp 1 + await driver.clickElement({ text: 'Connect', tag: 'button' }); - // Confirm Tx - await driver.clickElement('[data-testid="page-container-footer-next"]'); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - // Check for Confirmed Transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Connect to dapp 2 + await driver.clickElement({ text: 'Connect', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElementAndWaitForWindowToClose({ + text: 'Connect', + tag: 'button', + }); + + // Dapp one send tx + await driver.switchToWindowWithUrl(DAPP_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + + await driver.waitUntilXWindowHandles(4); + + // Dapp two send tx + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + await driver.delay(largeDelayMs); + await driver.clickElement('#sendButton'); + + // First switch network + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Wait for confirm tx after switch network confirmation. + await driver.delay(largeDelayMs); + + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Reject Transaction + await driver.findClickableElement({ text: 'Cancel', tag: 'button' }); + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + + // TODO: No second confirmation from dapp two will show, have to go back to the extension to see the switch chain & dapp two's tx. + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); + + // Check for unconfirmed transaction in tx list + await driver.wait(async () => { + const unconfirmedTxes = await driver.findElements( + '.transaction-list-item--unconfirmed', + ); + return unconfirmedTxes.length === 1; + }, 10000); + + // Click Unconfirmed Tx + await driver.clickElement('.transaction-list-item--unconfirmed'); + + await driver.assertElementNotPresent({ + tag: 'p', + text: 'Network switched to Localhost 8546', + }); + + // Confirm Tx + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Check for Confirmed Transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/switch-network.spec.js b/test/e2e/tests/request-queuing/switch-network.spec.js index 5949800f9840..913bdf459a26 100644 --- a/test/e2e/tests/request-queuing/switch-network.spec.js +++ b/test/e2e/tests/request-queuing/switch-network.spec.js @@ -7,88 +7,178 @@ const { regularDelayMs, WINDOW_TITLES, defaultGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { PAGES } = require('../../webdriver/driver'); describe('Request Queuing Switch Network on Dapp Send Tx while on different networks.', function () { - it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPermissionControllerConnectedToTestDapp() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Old confirmation screens', function () { + it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - // Open dapp - await openDapp(driver, undefined, DAPP_URL); + await tempToggleSettingRedesignedTransactionConfirmations(driver); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + // Open dapp + await openDapp(driver, undefined, DAPP_URL); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + // Queue confirm tx should first auto switch network + await driver.clickElement('#sendButton'); - // Queue confirm tx should first auto switch network - await driver.clickElement('#sendButton'); + await driver.delay(regularDelayMs); + + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Confirm Transaction + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement( + '[data-testid="page-container-footer-next"]', + ); - await driver.delay(regularDelayMs); + await driver.delay(regularDelayMs); - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.navigate(PAGES.HOME); - // Confirm Transaction - await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); - await driver.clickElement('[data-testid="page-container-footer-next"]'); + // Check correct network switched and on the correct network + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8545', + }); - await driver.delay(regularDelayMs); + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); + }); - // Switch back to the extension - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.navigate(PAGES.HOME); + describe('Redesigned confirmation screens', function () { + it('should switch to the dapps network automatically when mm network differs, dapp tx is on correct network', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); - // Check correct network switched and on the correct network - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8545', - }); + // Open dapp + await openDapp(driver, undefined, DAPP_URL); - // Check for transaction - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .activity-list-item', + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, ); - return confirmedTxes.length === 1; - }, 10000); - }, - ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + // Queue confirm tx should first auto switch network + await driver.clickElement('#sendButton'); + + await driver.delay(regularDelayMs); + + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // Confirm Transaction + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + await driver.delay(regularDelayMs); + + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.navigate(PAGES.HOME); + + // Check correct network switched and on the correct network + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8545', + }); + + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/ui.spec.js b/test/e2e/tests/request-queuing/ui.spec.js index b857d4307d5b..707c252396b7 100644 --- a/test/e2e/tests/request-queuing/ui.spec.js +++ b/test/e2e/tests/request-queuing/ui.spec.js @@ -14,6 +14,7 @@ const { tempToggleSettingRedesignedConfirmations, veryLargeDelayMs, DAPP_TWO_URL, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { PAGES } = require('../../webdriver/driver'); const { @@ -136,6 +137,38 @@ async function switchToDialogPopoverValidateDetails(driver, expectedDetails) { assert.equal(chainId, expectedDetails.chainId); } +async function switchToDialogPopoverValidateDetailsRedesign( + driver, + expectedDetails, +) { + // Switches to the MetaMask Dialog window for confirmation + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.findElement({ + css: 'p', + text: expectedDetails.networkText, + }); + + // Get state details + await driver.waitForControllersLoaded(); + const notificationWindowState = await driver.executeScript(() => + window.stateHooks?.getCleanAppState?.(), + ); + + const { + metamask: { selectedNetworkClientId, networkConfigurationsByChainId }, + } = notificationWindowState; + + const { chainId } = Object.values(networkConfigurationsByChainId).find( + ({ rpcEndpoints }) => + rpcEndpoints.some( + ({ networkClientId }) => networkClientId === selectedNetworkClientId, + ), + ); + + assert.equal(chainId, expectedDetails.chainId); +} + async function rejectTransaction(driver) { await driver.clickElementAndWaitForWindowToClose({ tag: 'button', @@ -143,6 +176,13 @@ async function rejectTransaction(driver) { }); } +async function rejectTransactionRedesign(driver) { + await driver.clickElementAndWaitForWindowToClose({ + tag: 'button', + text: 'Cancel', + }); +} + async function confirmTransaction(driver) { await driver.clickElement({ tag: 'button', text: 'Confirm' }); } @@ -190,573 +230,1036 @@ async function validateBalanceAndActivity( } describe('Request-queue UI changes', function () { - it('should show network specific to domain @no-mmi', async function () { - const port = 8546; - const chainId = 1338; // 0x53a - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Old confirmation screens', function () { + it('should show network specific to domain @no-mmi', async function () { + const port = 8546; + const chainId = 1338; // 0x53a + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), }, - dappOptions: { numberOfDapps: 2 }, - 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, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Localhost 8546', - }); - - // Go to the first dapp, ensure it uses localhost - await selectDappClickSend(driver, DAPP_URL); - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); - await rejectTransaction(driver); - - // Go to the second dapp, ensure it uses Ethereum Mainnet - await selectDappClickSend(driver, DAPP_ONE_URL); - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x53a', - networkText: 'Localhost 8546', - originText: DAPP_ONE_URL, - }); - await rejectTransaction(driver); - }, - ); - }); + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + + // Go to the first dapp, ensure it uses localhost + await selectDappClickSend(driver, DAPP_URL); + await switchToDialogPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + await rejectTransaction(driver); + + // Go to the second dapp, ensure it uses Ethereum Mainnet + await selectDappClickSend(driver, DAPP_ONE_URL); + await switchToDialogPopoverValidateDetails(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() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - // Ganache for network 1 - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - // Ganache for network 3 - { - port: 7777, - chainId: 1000, - ganacheOptions2: defaultGanacheOptions, - }, - ], + 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() + .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(), }, - 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, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); - - if (!IS_FIREFOX) { - // Open the third dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8'); - } - - // 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 switchToDialogPopoverValidateDetails(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 switchToDialogPopoverValidateDetails(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 + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); + + if (!IS_FIREFOX) { + // Open the third dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8'); + } + + // 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 switchToDialogPopoverValidateDetails(driver, { - chainId: '0x3e8', - networkText: 'Localhost 7777', - originText: DAPP_TWO_URL, + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, }); - // Confirm transaction + // Confirm transaction, wait for first confirmation window to close, second to display 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, - ); + await driver.delay(veryLargeDelayMs); - // Wait for transaction to be completed on final confirmation - await driver.delay(veryLargeDelayMs); + // Switch to the new Notification window, ensure second transaction showing + await switchToDialogPopoverValidateDetails(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); - if (!IS_FIREFOX) { - // Start on the last joined network, whose send transaction was just confirmed + // 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 switchToDialogPopoverValidateDetails(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"]', + ); + + // Switch to first network, whose send transaction was just confirmed + await switchToNetworkByName(driver, 'Localhost 8545'); 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"]', - ); - - // Switch to first network, whose send transaction was just confirmed - await switchToNetworkByName(driver, 'Localhost 8545'); - await validateBalanceAndActivity(driver, '24.9998'); - }, - ); - }); + }, + ); + }); - it('should gracefully handle deleted network @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesController({ - preferences: { showTestNetworks: true }, - }) - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + it('should gracefully handle deleted network @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesController({ + preferences: { showTestNetworks: true }, + }) + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), }, - dappOptions: { numberOfDapps: 2 }, - 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, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - await driver.clickElement('[data-testid="network-display"]'); - - const networkRow = await driver.findElement({ - css: '.multichain-network-list-item', - text: 'Localhost 8545', - }); - - const networkMenu = await driver.findNestedElement( - networkRow, - `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`, - ); - - await networkMenu.click(); - await driver.clickElement( - '[data-testid="network-list-item-options-delete"]', - ); - - await driver.clickElement({ tag: 'button', text: 'Delete' }); - - // Go back to first dapp, try an action, ensure deleted network doesn't block UI - // The current globally selected network, Ethereum Mainnet, should be used - await selectDappClickSend(driver, DAPP_URL); - await driver.delay(veryLargeDelayMs); - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x1', - networkText: 'Ethereum Mainnet', - originText: DAPP_URL, - }); - }, - ); - }); + async ({ driver }) => { + await unlockWallet(driver); - it('should signal from UI to dapp the network change @no-mmi', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: defaultGanacheOptions, - title: this.test.fullTitle(), - driverOptions: { constrainWindowSize: true }, - }, - async ({ driver }) => { - // Navigate to extension home screen - await unlockWallet(driver); - - // Open the first dapp which starts on chain '0x539 - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Ensure the dapp starts on the correct network - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x539', - }); - - // Open the popup with shimmed activeTabOrigin - await openPopupWithActiveTabOrigin(driver, DAPP_URL); - - // Switch to mainnet - await switchToNetworkByName(driver, 'Ethereum Mainnet'); - - // Switch back to the Dapp tab - await driver.switchToWindowWithUrl(DAPP_URL); - - // Check to make sure the dapp network changed - await driver.waitForSelector({ - css: '[id="chainId"]', - text: '0x1', - }); - }, - ); - }); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + await driver.clickElement('[data-testid="network-display"]'); - it('should autoswitch networks to the last used network for domain', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + const networkRow = await driver.findElement({ + css: '.multichain-network-list-item', + text: 'Localhost 8545', + }); + + const networkMenu = await driver.findNestedElement( + networkRow, + `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`, + ); + + await networkMenu.click(); + await driver.clickElement( + '[data-testid="network-list-item-options-delete"]', + ); + + await driver.clickElement({ tag: 'button', text: 'Delete' }); + + // Go back to first dapp, try an action, ensure deleted network doesn't block UI + // The current globally selected network, Ethereum Mainnet, should be used + await selectDappClickSend(driver, DAPP_URL); + await driver.delay(veryLargeDelayMs); + await switchToDialogPopoverValidateDetails(driver, { + chainId: '0x1', + networkText: 'Ethereum Mainnet', + originText: DAPP_URL, + }); }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - // Open fullscreen - await unlockWallet(driver); - - // Open the first dapp which starts on chain '0x539 - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open tab 2, switch to Ethereum Mainnet - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Open the popup with shimmed activeTabOrigin - await openPopupWithActiveTabOrigin(driver, DAPP_URL); - - // Ensure network was reset to original - await driver.findElement({ - css: '.multichain-app-header__contents--avatar-network .mm-text', - text: 'Localhost 8545', - }); - - // Ensure toast is shown to the user - await driver.findElement({ - css: '.toast-text', - text: 'Localhost 8545 is now active on 127.0.0.1:8080', - }); - }, - ); - }); + ); + }); - it('should autoswitch networks when last confirmation from another network is rejected', async function () { - const port = 8546; - const chainId = 1338; - - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + it('should autoswitch networks when last confirmation from another network is rejected', async function () { + const port = 8546; + const chainId = 1338; + + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + driverOptions: { constrainWindowSize: true }, }, - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - driverOptions: { constrainWindowSize: true }, - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Open the first dapp which starts on chain '0x539 - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open tab 2, switch to Ethereum Mainnet - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - await driver.waitForSelector({ - css: '.error-message-text', - text: 'You are on the Ethereum Mainnet.', - }); - await driver.delay(veryLargeDelayMs); - - // Start a Send on Ethereum Mainnet - await driver.clickElement('#sendButton'); - await driver.delay(regularDelayMs); - - // Open the popup with shimmed activeTabOrigin - await openPopupWithActiveTabOrigin(driver, DAPP_URL); - - // Ensure the confirmation pill shows Ethereum Mainnet - await driver.waitForSelector({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - // Reject the confirmation - await driver.clickElement( - '[data-testid="page-container-footer-cancel"]', - ); - - // Wait for network to automatically change to localhost - await driver.waitForSelector({ - css: '.multichain-app-header__contents--avatar-network .mm-text', - text: 'Localhost 8545', - }); - - // Ensure toast is shown to the user - await driver.waitForSelector({ - css: '.toast-text', - text: 'Localhost 8545 is now active on 127.0.0.1:8080', - }); - }, - ); - }); + async ({ driver }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + // Open tab 2, switch to Ethereum Mainnet + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + await driver.waitForSelector({ + css: '.error-message-text', + text: 'You are on the Ethereum Mainnet.', + }); + await driver.delay(veryLargeDelayMs); + + // Start a Send on Ethereum Mainnet + await driver.clickElement('#sendButton'); + await driver.delay(regularDelayMs); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Ensure the confirmation pill shows Ethereum Mainnet + await driver.waitForSelector({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Reject the confirmation + await driver.clickElement( + '[data-testid="page-container-footer-cancel"]', + ); + + // Wait for network to automatically change to localhost + await driver.waitForSelector({ + css: '.multichain-app-header__contents--avatar-network .mm-text', + text: 'Localhost 8545', + }); + + // Ensure toast is shown to the user + await driver.waitForSelector({ + css: '.toast-text', + text: 'Localhost 8545 is now active on 127.0.0.1:8080', + }); }, - // This test intentionally quits Ganache while the extension is using it, causing - // PollingBlockTracker errors and others. These are expected. - ignoredConsoleErrors: ['ignore-all'], - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer, secondaryGanacheServer }) => { - await unlockWallet(driver); - await tempToggleSettingRedesignedConfirmations(driver); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.waitForSelector({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - // Kill ganache servers - await ganacheServer.quit(); - await secondaryGanacheServer[0].quit(); - - // Go back to first dapp, try an action, ensure network connection failure doesn't block UI - await selectDappClickPersonalSign(driver, DAPP_URL); - - // When the network is down, there is a performance degradation that causes the - // popup to take a few seconds to open in MV3 (issue #25690) - await driver.waitUntilXWindowHandles(4, 1000, 15000); - - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); - }, - ); + ); + }); + + it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + // This test intentionally quits Ganache while the extension is using it, causing + // PollingBlockTracker errors and others. These are expected. + ignoredConsoleErrors: ['ignore-all'], + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver, ganacheServer, secondaryGanacheServer }) => { + await unlockWallet(driver); + await tempToggleSettingRedesignedConfirmations(driver); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.waitForSelector({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Kill ganache servers + await ganacheServer.quit(); + await secondaryGanacheServer[0].quit(); + + // Go back to first dapp, try an action, ensure network connection failure doesn't block UI + await selectDappClickPersonalSign(driver, DAPP_URL); + + // When the network is down, there is a performance degradation that causes the + // popup to take a few seconds to open in MV3 (issue #25690) + await driver.waitUntilXWindowHandles(4, 1000, 15000); + + await switchToDialogPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + }, + ); + }); + + it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + // Presently confirmations take up to 10 seconds to display on a dead network + driverOptions: { timeOut: 30000 }, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + // This test intentionally quits Ganache while the extension is using it, causing + // PollingBlockTracker errors and others. These are expected. + ignoredConsoleErrors: ['ignore-all'], + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver, ganacheServer, secondaryGanacheServer }) => { + await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Kill ganache servers + await ganacheServer.quit(); + await secondaryGanacheServer[0].quit(); + + // Go back to first dapp, try an action, ensure network connection failure doesn't block UI + await selectDappClickSend(driver, DAPP_URL); + + // When the network is down, there is a performance degradation that causes the + // popup to take a few seconds to open in MV3 (issue #25690) + await driver.waitUntilXWindowHandles(4, 1000, 15000); + + await switchToDialogPopoverValidateDetails(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + }, + ); + }); }); - it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - // Presently confirmations take up to 10 seconds to display on a dead network - driverOptions: { timeOut: 30000 }, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], + describe('Redesigned confirmation screens', function () { + it('should show network specific to domain @no-mmi', async function () { + const port = 8546; + const chainId = 1338; // 0x53a + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), }, - // This test intentionally quits Ganache while the extension is using it, causing - // PollingBlockTracker errors and others. These are expected. - ignoredConsoleErrors: ['ignore-all'], - dappOptions: { numberOfDapps: 2 }, - title: this.test.fullTitle(), - }, - async ({ driver, ganacheServer, secondaryGanacheServer }) => { - await unlockWallet(driver); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - // Open the first dapp - await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); - - // Open the second dapp and switch chains - await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); - - // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await driver.findElement({ - css: '[data-testid="network-display"]', - text: 'Ethereum Mainnet', - }); - - // Kill ganache servers - await ganacheServer.quit(); - await secondaryGanacheServer[0].quit(); - - // Go back to first dapp, try an action, ensure network connection failure doesn't block UI - await selectDappClickSend(driver, DAPP_URL); - - // When the network is down, there is a performance degradation that causes the - // popup to take a few seconds to open in MV3 (issue #25690) - await driver.waitUntilXWindowHandles(4, 1000, 15000); - - await switchToDialogPopoverValidateDetails(driver, { - chainId: '0x539', - networkText: 'Localhost 8545', - originText: DAPP_URL, - }); - }, - ); + async ({ driver }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + + // Go to the first dapp, ensure it uses localhost + await selectDappClickSend(driver, DAPP_URL); + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + await rejectTransactionRedesign(driver); + + // Go to the second dapp, ensure it uses Ethereum Mainnet + await selectDappClickSend(driver, DAPP_ONE_URL); + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); + await rejectTransactionRedesign(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() + .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); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x53a'); + + if (!IS_FIREFOX) { + // Open the third dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_TWO_URL, '0x3e8'); + } + + // 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 switchToDialogPopoverValidateDetailsRedesign(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 switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x53a', + networkText: 'Localhost 8546', + originText: DAPP_ONE_URL, + }); + + // Reject this transaction, wait for second confirmation window to close, third to display + await rejectTransactionRedesign(driver); + await driver.delay(veryLargeDelayMs); + + if (!IS_FIREFOX) { + // Switch to the new Notification window, ensure third transaction showing + await switchToDialogPopoverValidateDetailsRedesign(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"]', + ); + + // Switch to first network, whose send transaction was just confirmed + await switchToNetworkByName(driver, 'Localhost 8545'); + await validateBalanceAndActivity(driver, '24.9998'); + }, + ); + }); + + it('should gracefully handle deleted network @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesController({ + preferences: { showTestNetworks: true }, + }) + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + await driver.clickElement('[data-testid="network-display"]'); + + const networkRow = await driver.findElement({ + css: '.multichain-network-list-item', + text: 'Localhost 8545', + }); + + const networkMenu = await driver.findNestedElement( + networkRow, + `[data-testid="network-list-item-options-button-${CHAIN_IDS.LOCALHOST}"]`, + ); + + await networkMenu.click(); + await driver.clickElement( + '[data-testid="network-list-item-options-delete"]', + ); + + await driver.clickElement({ tag: 'button', text: 'Delete' }); + + // Go back to first dapp, try an action, ensure deleted network doesn't block UI + // The current globally selected network, Ethereum Mainnet, should be used + await selectDappClickSend(driver, DAPP_URL); + await driver.delay(veryLargeDelayMs); + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x1', + networkText: 'Ethereum Mainnet', + originText: DAPP_URL, + }); + }, + ); + }); + + it('should signal from UI to dapp the network change @no-mmi', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.fullTitle(), + driverOptions: { constrainWindowSize: true }, + }, + async ({ driver }) => { + // Navigate to extension home screen + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Ensure the dapp starts on the correct network + await driver.waitForSelector({ + css: '[id="chainId"]', + text: '0x539', + }); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Switch to mainnet + await switchToNetworkByName(driver, 'Ethereum Mainnet'); + + // Switch back to the Dapp tab + await driver.switchToWindowWithUrl(DAPP_URL); + + // Check to make sure the dapp network changed + await driver.waitForSelector({ + css: '[id="chainId"]', + text: '0x1', + }); + }, + ); + }); + + it('should autoswitch networks to the last used network for domain', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + // Open fullscreen + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open tab 2, switch to Ethereum Mainnet + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Ensure network was reset to original + await driver.findElement({ + css: '.multichain-app-header__contents--avatar-network .mm-text', + text: 'Localhost 8545', + }); + + // Ensure toast is shown to the user + await driver.findElement({ + css: '.toast-text', + text: 'Localhost 8545 is now active on 127.0.0.1:8080', + }); + }, + ); + }); + + it('should autoswitch networks when last confirmation from another network is rejected', async function () { + const port = 8546; + const chainId = 1338; + + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + driverOptions: { constrainWindowSize: true }, + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open the first dapp which starts on chain '0x539 + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open tab 2, switch to Ethereum Mainnet + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + await driver.waitForSelector({ + css: '.error-message-text', + text: 'You are on the Ethereum Mainnet.', + }); + await driver.delay(veryLargeDelayMs); + + // Start a Send on Ethereum Mainnet + await driver.clickElement('#sendButton'); + await driver.delay(regularDelayMs); + + // Open the popup with shimmed activeTabOrigin + await openPopupWithActiveTabOrigin(driver, DAPP_URL); + + // Ensure the confirmation pill shows Ethereum Mainnet + await driver.waitForSelector({ + css: 'p', + text: 'Ethereum Mainnet', + }); + + // Reject the confirmation + await driver.clickElement({ css: 'button', text: 'Cancel' }); + + // Wait for network to automatically change to localhost + await driver.waitForSelector({ + css: '.multichain-app-header__contents--avatar-network .mm-text', + text: 'Localhost 8545', + }); + + // Ensure toast is shown to the user + await driver.waitForSelector({ + css: '.toast-text', + text: 'Localhost 8545 is now active on 127.0.0.1:8080', + }); + }, + ); + }); + + it('should gracefully handle network connectivity failure for signatures @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + // This test intentionally quits Ganache while the extension is using it, causing + // PollingBlockTracker errors and others. These are expected. + ignoredConsoleErrors: ['ignore-all'], + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver, ganacheServer, secondaryGanacheServer }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.waitForSelector({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Kill ganache servers + await ganacheServer.quit(); + await secondaryGanacheServer[0].quit(); + + // Go back to first dapp, try an action, ensure network connection failure doesn't block UI + await selectDappClickPersonalSign(driver, DAPP_URL); + + // When the network is down, there is a performance degradation that causes the + // popup to take a few seconds to open in MV3 (issue #25690) + await driver.waitUntilXWindowHandles(4, 1000, 15000); + + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + }, + ); + }); + + it('should gracefully handle network connectivity failure for confirmations @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + // Presently confirmations take up to 10 seconds to display on a dead network + driverOptions: { timeOut: 30000 }, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + // This test intentionally quits Ganache while the extension is using it, causing + // PollingBlockTracker errors and others. These are expected. + ignoredConsoleErrors: ['ignore-all'], + dappOptions: { numberOfDapps: 2 }, + title: this.test.fullTitle(), + }, + async ({ driver, ganacheServer, secondaryGanacheServer }) => { + await unlockWallet(driver); + + // Open the first dapp + await openDappAndSwitchChain(driver, DAPP_URL, '0x539'); + + // Open the second dapp and switch chains + await openDappAndSwitchChain(driver, DAPP_ONE_URL, '0x1'); + + // Go to wallet fullscreen, ensure that the global network changed to Ethereum Mainnet + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Ethereum Mainnet', + }); + + // Kill ganache servers + await ganacheServer.quit(); + await secondaryGanacheServer[0].quit(); + + // Go back to first dapp, try an action, ensure network connection failure doesn't block UI + await selectDappClickSend(driver, DAPP_URL); + + // When the network is down, there is a performance degradation that causes the + // popup to take a few seconds to open in MV3 (issue #25690) + await driver.waitUntilXWindowHandles(4, 1000, 15000); + + await switchToDialogPopoverValidateDetailsRedesign(driver, { + chainId: '0x539', + networkText: 'Localhost 8545', + originText: DAPP_URL, + }); + }, + ); + }); }); }); diff --git a/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js b/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js index 958854a5252c..c1ff9f3477ab 100644 --- a/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/responsive-ui/metamask-responsive-ui.spec.js @@ -6,6 +6,7 @@ const { logInWithBalanceValidation, openActionMenuAndStartSendFlow, withFixtures, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -21,6 +22,7 @@ describe('MetaMask Responsive UI', function () { }, async ({ driver }) => { await driver.navigate(); + // agree to terms of use await driver.clickElement('[data-testid="onboarding-terms-checkbox"]'); @@ -129,6 +131,8 @@ describe('MetaMask Responsive UI', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Send ETH from inside MetaMask // starts to send a transaction await openActionMenuAndStartSendFlow(driver); diff --git a/test/e2e/tests/settings/4byte-directory.spec.js b/test/e2e/tests/settings/4byte-directory.spec.js index 483ff1e0149a..b36f72f0575c 100644 --- a/test/e2e/tests/settings/4byte-directory.spec.js +++ b/test/e2e/tests/settings/4byte-directory.spec.js @@ -6,6 +6,7 @@ const { unlockWallet, withFixtures, WINDOW_TITLES, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); @@ -27,6 +28,8 @@ describe('4byte setting', function () { ); await logInWithBalanceValidation(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // deploy contract await openDapp(driver, contractAddress); @@ -63,6 +66,8 @@ describe('4byte setting', function () { ); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // goes to the settings screen await openMenuSafe(driver); await driver.clickElement({ text: 'Settings', tag: 'div' }); diff --git a/test/e2e/tests/settings/show-hex-data.spec.js b/test/e2e/tests/settings/show-hex-data.spec.js index 4bef79ca0a3b..353847a544b4 100644 --- a/test/e2e/tests/settings/show-hex-data.spec.js +++ b/test/e2e/tests/settings/show-hex-data.spec.js @@ -2,6 +2,7 @@ const { defaultGanacheOptions, withFixtures, logInWithBalanceValidation, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -78,6 +79,9 @@ describe('Check the toggle for hex data', function () { }, async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await toggleHexData(driver); await clickOnLogo(driver); await sendTransactionAndVerifyHexData(driver); diff --git a/test/e2e/tests/tokens/custom-token-add-approve.spec.js b/test/e2e/tests/tokens/custom-token-add-approve.spec.js index 4e85aae76fd6..9226ad1a36f1 100644 --- a/test/e2e/tests/tokens/custom-token-add-approve.spec.js +++ b/test/e2e/tests/tokens/custom-token-add-approve.spec.js @@ -1,5 +1,4 @@ const { strict: assert } = require('assert'); - const { clickNestedButton, defaultGanacheOptions, @@ -8,6 +7,7 @@ const { openDapp, WINDOW_TITLES, withFixtures, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); @@ -84,6 +84,8 @@ describe('Create token, approve token and approve token without gas', function ( ); await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // create token await openDapp(driver, contractAddress); @@ -182,6 +184,8 @@ describe('Create token, approve token and approve token without gas', function ( ); await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // create token await openDapp(driver, contractAddress); @@ -317,6 +321,8 @@ describe('Create token, approve token and approve token without gas', function ( ); await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // create token await openDapp(driver, contractAddress); const windowHandles = await driver.getAllWindowHandles(); @@ -398,6 +404,8 @@ describe('Create token, approve token and approve token without gas', function ( ); await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openDapp(driver, contractAddress); const windowHandles = await driver.getAllWindowHandles(); const extension = windowHandles[0]; diff --git a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js index 40b1872011bd..0c5498a82cca 100644 --- a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js +++ b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js @@ -8,6 +8,8 @@ const { editGasFeeForm, WINDOW_TITLES, clickNestedButton, + tempToggleSettingRedesignedTransactionConfirmations, + veryLargeDelayMs, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); @@ -28,6 +30,8 @@ describe('Transfer custom tokens @no-mmi', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // go to custom tokens view on extension, perform send tokens await driver.clickElement({ css: '[data-testid="multichain-token-list-item-value"]', @@ -115,10 +119,15 @@ describe('Transfer custom tokens @no-mmi', function () { ); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // transfer token from dapp await openDapp(driver, contractAddress); + await driver.delay(veryLargeDelayMs); + await driver.clickElement({ text: 'Transfer Tokens', tag: 'button' }); - await switchToNotificationWindow(driver); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await driver.waitForSelector({ text: '1.5 TST', tag: 'h1' }); // edit gas fee @@ -174,8 +183,11 @@ describe('Transfer custom tokens @no-mmi', function () { ); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // transfer token from dapp await openDapp(driver, contractAddress); + await driver.delay(veryLargeDelayMs); await driver.clickElement({ text: 'Transfer Tokens Without Gas', tag: 'button', diff --git a/test/e2e/tests/tokens/increase-token-allowance.spec.js b/test/e2e/tests/tokens/increase-token-allowance.spec.js index 7df956e9df43..9ce8db2cc065 100644 --- a/test/e2e/tests/tokens/increase-token-allowance.spec.js +++ b/test/e2e/tests/tokens/increase-token-allowance.spec.js @@ -10,6 +10,7 @@ const { ACCOUNT_2, WINDOW_TITLES, clickNestedButton, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); @@ -38,6 +39,8 @@ describe('Increase Token Allowance', function () { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + const contractAddress = await contractRegistry.getContractAddress( smartContract, ); diff --git a/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js b/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js index c635d465353a..388247bb3fcd 100644 --- a/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js +++ b/test/e2e/tests/tokens/nft/erc1155-interaction.spec.js @@ -7,6 +7,8 @@ const { unlockWallet, WINDOW_TITLES, defaultGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, + veryLargeDelayMs, } = require('../../../helpers'); const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); const FixtureBuilder = require('../../../fixture-builder'); @@ -38,6 +40,8 @@ describe('ERC1155 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp and wait for deployed contract await openDapp(driver, contract); await driver.findClickableElement('#deployButton'); @@ -118,6 +122,8 @@ describe('ERC1155 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openDapp(driver, contract); await driver.fill('#batchTransferTokenIds', '1, 2, 3'); @@ -170,6 +176,8 @@ describe('ERC1155 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Create a set approval for all erc1155 token request in test dapp await openDapp(driver, contract); await driver.clickElement('#setApprovalForAllERC1155Button'); @@ -254,8 +262,13 @@ describe('ERC1155 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Create a revoke approval for all erc1155 token request in test dapp await openDapp(driver, contract); + + await driver.delay(veryLargeDelayMs); + await driver.clickElement('#revokeERC1155Button'); // Wait for notification popup and check the displayed message diff --git a/test/e2e/tests/tokens/nft/erc721-interaction.spec.js b/test/e2e/tests/tokens/nft/erc721-interaction.spec.js index 35750bae6d2c..8b634ffc3ce3 100644 --- a/test/e2e/tests/tokens/nft/erc721-interaction.spec.js +++ b/test/e2e/tests/tokens/nft/erc721-interaction.spec.js @@ -6,6 +6,7 @@ const { WINDOW_TITLES, defaultGanacheOptions, clickNestedButton, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../../helpers'); const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); const FixtureBuilder = require('../../../fixture-builder'); @@ -28,6 +29,8 @@ describe('ERC721 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp and wait for deployed contract await openDapp(driver, contract); await driver.findClickableElement('#deployButton'); @@ -91,6 +94,8 @@ describe('ERC721 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp and wait for deployed contract await openDapp(driver, contract); await driver.findClickableElement('#deployButton'); @@ -212,6 +217,8 @@ describe('ERC721 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp and wait for deployed contract await openDapp(driver, contract); await driver.findClickableElement('#deployButton'); @@ -310,6 +317,8 @@ describe('ERC721 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp and wait for deployed contract await openDapp(driver, contract); await driver.findClickableElement('#deployButton'); @@ -357,6 +366,8 @@ describe('ERC721 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp and wait for deployed contract await openDapp(driver, contract); await driver.findClickableElement('#deployButton'); @@ -424,6 +435,8 @@ describe('ERC721 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp and wait for deployed contract await openDapp(driver, contract); await driver.findClickableElement('#deployButton'); @@ -490,6 +503,8 @@ describe('ERC721 NFTs testdapp interaction', function () { const contract = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Open Dapp and wait for deployed contract await openDapp(driver, contract); await driver.findClickableElement('#deployButton'); diff --git a/test/e2e/tests/tokens/nft/send-nft.spec.js b/test/e2e/tests/tokens/nft/send-nft.spec.js index 35585bbaf2ea..13cb310f1416 100644 --- a/test/e2e/tests/tokens/nft/send-nft.spec.js +++ b/test/e2e/tests/tokens/nft/send-nft.spec.js @@ -4,6 +4,7 @@ const { logInWithBalanceValidation, unlockWallet, withFixtures, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../../helpers'); const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); const FixtureBuilder = require('../../../fixture-builder'); @@ -24,6 +25,8 @@ describe('Send NFT', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Fill the send NFT form and confirm the transaction await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); await driver.clickElement('.nft-item__container'); diff --git a/test/e2e/tests/transaction/change-assets.spec.js b/test/e2e/tests/transaction/change-assets.spec.js index 7ce971fd8d80..f6a997c164e9 100644 --- a/test/e2e/tests/transaction/change-assets.spec.js +++ b/test/e2e/tests/transaction/change-assets.spec.js @@ -3,6 +3,7 @@ const { defaultGanacheOptions, withFixtures, logInWithBalanceValidation, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); @@ -22,6 +23,8 @@ describe('Change assets', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Wait for balance to load await driver.delay(500); @@ -100,6 +103,8 @@ describe('Change assets', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Click the Send button await driver.clickElement({ css: '[data-testid="multichain-token-list-button"] span', @@ -179,6 +184,8 @@ describe('Change assets', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Choose the nft await driver.clickElement('[data-testid="account-overview__nfts-tab"]'); await driver.clickElement('[data-testid="nft-default-image"]'); @@ -266,6 +273,8 @@ describe('Change assets', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Create second account await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( diff --git a/test/e2e/tests/transaction/edit-gas-fee.spec.js b/test/e2e/tests/transaction/edit-gas-fee.spec.js index 918831f8f3ad..3e7655750594 100644 --- a/test/e2e/tests/transaction/edit-gas-fee.spec.js +++ b/test/e2e/tests/transaction/edit-gas-fee.spec.js @@ -9,6 +9,7 @@ const { unlockWallet, generateGanacheOptions, WINDOW_TITLES, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -22,6 +23,9 @@ describe('Editing Confirm Transaction', function () { }, async ({ driver }) => { await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createInternalTransaction(driver); await driver.findElement({ @@ -95,6 +99,9 @@ describe('Editing Confirm Transaction', function () { }, async ({ driver }) => { await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createInternalTransaction(driver); await driver.findElement({ @@ -172,6 +179,8 @@ describe('Editing Confirm Transaction', function () { // login to extension await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createDappTransaction(driver, { maxFeePerGas: '0x2000000000', maxPriorityFeePerGas: '0x1000000000', diff --git a/test/e2e/tests/transaction/gas-estimates.spec.js b/test/e2e/tests/transaction/gas-estimates.spec.js index 263dfc85d904..f12275ad4d9f 100644 --- a/test/e2e/tests/transaction/gas-estimates.spec.js +++ b/test/e2e/tests/transaction/gas-estimates.spec.js @@ -3,6 +3,7 @@ const { logInWithBalanceValidation, openActionMenuAndStartSendFlow, generateGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { CHAIN_IDS } = require('../../../../shared/constants/network'); @@ -27,6 +28,8 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openActionMenuAndStartSendFlow(driver); await driver.fill( @@ -69,6 +72,8 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openActionMenuAndStartSendFlow(driver); await driver.fill( @@ -108,6 +113,8 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openActionMenuAndStartSendFlow(driver); await driver.fill( @@ -143,6 +150,8 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openActionMenuAndStartSendFlow(driver); await driver.fill( 'input[placeholder="Enter public address (0x) or domain name"]', @@ -189,6 +198,8 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openActionMenuAndStartSendFlow(driver); await driver.fill( 'input[placeholder="Enter public address (0x) or domain name"]', @@ -218,6 +229,8 @@ describe('Gas estimates generated by MetaMask', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await openActionMenuAndStartSendFlow(driver); await driver.fill( 'input[placeholder="Enter public address (0x) or domain name"]', diff --git a/test/e2e/tests/transaction/multiple-transactions.spec.js b/test/e2e/tests/transaction/multiple-transactions.spec.js index 4d913cb07edb..b7ad05b3a93d 100644 --- a/test/e2e/tests/transaction/multiple-transactions.spec.js +++ b/test/e2e/tests/transaction/multiple-transactions.spec.js @@ -6,6 +6,7 @@ const { unlockWallet, generateGanacheOptions, WINDOW_TITLES, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -23,6 +24,8 @@ describe('Multiple transactions', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // initiates a transaction from the dapp await openDapp(driver); // creates first transaction @@ -85,6 +88,8 @@ describe('Multiple transactions', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // initiates a transaction from the dapp await openDapp(driver); // creates first transaction diff --git a/test/e2e/tests/transaction/navigate-transactions.spec.js b/test/e2e/tests/transaction/navigate-transactions.spec.js index 63170d027874..16e8f9374cb5 100644 --- a/test/e2e/tests/transaction/navigate-transactions.spec.js +++ b/test/e2e/tests/transaction/navigate-transactions.spec.js @@ -1,7 +1,6 @@ const { createDappTransaction, } = require('../../page-objects/flows/transaction'); - const { default: ConfirmationNavigation, } = require('../../page-objects/pages/confirmations/legacy/navigation'); @@ -13,6 +12,7 @@ const { unlockWallet, generateGanacheOptions, WINDOW_TITLES, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -32,6 +32,9 @@ describe('Navigate transactions', function () { }, async ({ driver }) => { await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createMultipleTransactions(driver, TRANSACTION_COUNT); const navigation = new ConfirmationNavigation(driver); @@ -73,6 +76,9 @@ describe('Navigate transactions', function () { }, async ({ driver }) => { await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createMultipleTransactions(driver, TRANSACTION_COUNT); const navigation = new ConfirmationNavigation(driver); @@ -107,6 +113,9 @@ describe('Navigate transactions', function () { }, async ({ driver }) => { await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createMultipleTransactions(driver, TRANSACTION_COUNT); // reject transaction @@ -131,6 +140,9 @@ describe('Navigate transactions', function () { }, async ({ driver }) => { await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createMultipleTransactions(driver, TRANSACTION_COUNT); // confirm transaction @@ -155,6 +167,9 @@ describe('Navigate transactions', function () { }, async ({ driver, ganacheServer }) => { await unlockWallet(driver); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createMultipleTransactions(driver, TRANSACTION_COUNT); // reject transactions diff --git a/test/e2e/tests/transaction/send-edit.spec.js b/test/e2e/tests/transaction/send-edit.spec.js index 953f2ebf3569..8d19d6d071b1 100644 --- a/test/e2e/tests/transaction/send-edit.spec.js +++ b/test/e2e/tests/transaction/send-edit.spec.js @@ -8,6 +8,7 @@ const { withFixtures, unlockWallet, generateGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -21,6 +22,7 @@ describe('Editing Confirm Transaction', function () { }, async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); await createInternalTransaction(driver); await driver.findElement({ @@ -96,6 +98,8 @@ describe('Editing Confirm Transaction', function () { }, async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await createInternalTransaction(driver); await driver.findElement({ diff --git a/test/e2e/tests/transaction/send-eth.spec.js b/test/e2e/tests/transaction/send-eth.spec.js index 5cbcb8309a18..9ee1b58dc170 100644 --- a/test/e2e/tests/transaction/send-eth.spec.js +++ b/test/e2e/tests/transaction/send-eth.spec.js @@ -9,6 +9,7 @@ const { editGasFeeForm, WINDOW_TITLES, defaultGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -106,6 +107,8 @@ describe('Send ETH', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await driver.delay(1000); await openActionMenuAndStartSendFlow(driver); @@ -256,6 +259,8 @@ describe('Send ETH', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // initiates a send from the dapp await openDapp(driver); await driver.clickElement({ text: 'Send', tag: 'button' }); @@ -332,6 +337,8 @@ describe('Send ETH', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // initiates a transaction from the dapp await openDapp(driver); await driver.clickElement({ text: 'Create Token', tag: 'button' }); @@ -435,6 +442,8 @@ describe('Send ETH', function () { async ({ driver }) => { await unlockWallet(driver); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await driver.assertElementNotPresent('.loading-overlay__spinner'); const balance = await driver.findElement( '[data-testid="eth-overview__primary-currency"]', diff --git a/test/e2e/tests/transaction/send-hex-address.spec.js b/test/e2e/tests/transaction/send-hex-address.spec.js index d93f1a0d5484..b6ad969c6735 100644 --- a/test/e2e/tests/transaction/send-hex-address.spec.js +++ b/test/e2e/tests/transaction/send-hex-address.spec.js @@ -3,6 +3,7 @@ const { withFixtures, logInWithBalanceValidation, openActionMenuAndStartSendFlow, + tempToggleSettingRedesignedTransactionConfirmations, } = require('../../helpers'); const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); const FixtureBuilder = require('../../fixture-builder'); @@ -120,6 +121,8 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Send TST await driver.clickElement( '[data-testid="account-overview__asset-tab"]', @@ -181,6 +184,9 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { }, async ({ driver, ganacheServer }) => { await logInWithBalanceValidation(driver, ganacheServer); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + // Send TST await driver.clickElement( '[data-testid="account-overview__asset-tab"]', diff --git a/test/e2e/tests/transaction/simple-send.spec.ts b/test/e2e/tests/transaction/simple-send.spec.ts index 25f2368a9cfc..7d2f4835cdca 100644 --- a/test/e2e/tests/transaction/simple-send.spec.ts +++ b/test/e2e/tests/transaction/simple-send.spec.ts @@ -1,7 +1,11 @@ import { Suite } from 'mocha'; import { Driver } from '../../webdriver/driver'; import { Ganache } from '../../seeder/ganache'; -import { withFixtures, defaultGanacheOptions } from '../../helpers'; +import { + withFixtures, + defaultGanacheOptions, + tempToggleSettingRedesignedTransactionConfirmations, +} from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; import { sendTransactionToAddress } from '../../page-objects/flows/send-transaction.flow'; @@ -23,6 +27,9 @@ describe('Simple send eth', function (this: Suite) { ganacheServer?: Ganache; }) => { await loginWithBalanceValidation(driver, ganacheServer); + + await tempToggleSettingRedesignedTransactionConfirmations(driver); + await sendTransactionToAddress({ driver, recipientAddress: '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', diff --git a/ui/pages/settings/experimental-tab/experimental-tab.component.tsx b/ui/pages/settings/experimental-tab/experimental-tab.component.tsx index d81eb04966a9..5bea76d80dbe 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.component.tsx +++ b/ui/pages/settings/experimental-tab/experimental-tab.component.tsx @@ -178,6 +178,7 @@ export default class ExperimentalTab extends PureComponent description: t('redesignedTransactionsToggleDescription'), toggleValue: redesignedTransactionsEnabled, toggleCallback: (value) => setRedesignedTransactionsEnabled(!value), + toggleContainerDataTestId: 'toggle-redesigned-transactions-container', toggleDataTestId: 'toggle-redesigned-transactions', toggleOffLabel: t('off'), toggleOnLabel: t('on'), From be2b439924773a9dc5995c21ac33b446572a86e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Regadas?= Date: Tue, 26 Nov 2024 16:14:59 +0000 Subject: [PATCH 34/40] chore: adds Solana support for the account overview (#28411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** We added support for the Solana account overview. Now when we select a Solana address the user will be able to see its details in the home view. Also since the overview is the same for SOL and BTC, in order to not repeat components, we've renamed as "non-evm" the existing BTC ones, and reused them. ![Screenshot 2024-11-13 at 13 53 42](https://github.com/user-attachments/assets/649c1b1c-2da2-4f12-a18d-3549d8739c0e) ## **Related issues** Fixes: ## **Manual testing steps** As of right now, manually testing is a bit complex, it needs to run the snap manually and the extension, since we 1st need to publish a new release to npm with more up to date work. The snap version we have in npm is outdated and won't support this flow. That said, if you want to go ahead and run locally the steps are the following: 1. Clone the [ Solana Snap monorepo](https://github.com/MetaMask/snap-solana-wallet) and run it locally with `yarn` and then `yarn start` 2. In the extension, at this branch, apply the following changes and run the extension as flask: ``` At builds.yml add the solana feature to the flask build: features: - build-flask - keyring-snaps + - solana At shared/lib/accounts/solana-wallet-snap.ts point the snap ID to the snap localhost: -export const SOLANA_WALLET_SNAP_ID: SnapId = SolanaWalletSnap.snapId as SnapId; +//export const SOLANA_WALLET_SNAP_ID: SnapId = SolanaWalletSnap.snapId as SnapId; +export const SOLANA_WALLET_SNAP_ID: SnapId = "local:http://localhost:8080/"; ``` 3. Manually install the snap via the snap dapp at http://localhost:3000 4. Enable the Solana account via Settings > Experimental > Enable Solana account 5. Create a Solana account from the account-list menu and see the account overview of it ## **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. --------- Co-authored-by: MetaMask Bot Co-authored-by: Charly Chevalier --- app/_locales/en/messages.json | 3 + .../lib/accounts/BalancesController.test.ts | 4 + .../lib/accounts/BalancesController.ts | 79 ++++++++++++++++--- app/scripts/metamask-controller.test.js | 2 +- package.json | 3 +- shared/constants/multichain/assets.ts | 18 +++++ shared/constants/multichain/networks.ts | 6 ++ shared/constants/network.ts | 8 +- .../errors-after-init-opt-in-ui-state.json | 16 ++-- test/jest/mocks.ts | 5 +- ui/components/app/wallet-overview/index.js | 2 +- ...ories.tsx => non-evm-overview.stories.tsx} | 6 +- ...iew.test.tsx => non-evm-overview.test.tsx} | 50 ++++++++---- ...{btc-overview.tsx => non-evm-overview.tsx} | 16 +++- .../account-list-menu/account-list-menu.tsx | 2 + .../account-overview-btc.stories.tsx | 12 --- .../account-overview-non-evm.stories.tsx | 19 +++++ ....tsx => account-overview-non-evm.test.tsx} | 12 +-- ...w-btc.tsx => account-overview-non-evm.tsx} | 10 ++- .../account-overview/account-overview.tsx | 11 ++- .../token-list-item/token-list-item.tsx | 9 ++- ui/hooks/useCurrencyDisplay.js | 11 +-- ui/selectors/multichain.ts | 32 +++++--- 23 files changed, 243 insertions(+), 93 deletions(-) rename ui/components/app/wallet-overview/{btc-overview.stories.tsx => non-evm-overview.stories.tsx} (70%) rename ui/components/app/wallet-overview/{btc-overview.test.tsx => non-evm-overview.test.tsx} (92%) rename ui/components/app/wallet-overview/{btc-overview.tsx => non-evm-overview.tsx} (73%) delete mode 100644 ui/components/multichain/account-overview/account-overview-btc.stories.tsx create mode 100644 ui/components/multichain/account-overview/account-overview-non-evm.stories.tsx rename ui/components/multichain/account-overview/{account-overview-btc.test.tsx => account-overview-non-evm.test.tsx} (88%) rename ui/components/multichain/account-overview/{account-overview-btc.tsx => account-overview-non-evm.tsx} (66%) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 54c0a782a592..e9e9cc807ccd 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3205,6 +3205,9 @@ "networkNamePolygon": { "message": "Polygon" }, + "networkNameSolana": { + "message": "Solana" + }, "networkNameTestnet": { "message": "Testnet" }, diff --git a/app/scripts/lib/accounts/BalancesController.test.ts b/app/scripts/lib/accounts/BalancesController.test.ts index e8ddd89f021e..982df0289fea 100644 --- a/app/scripts/lib/accounts/BalancesController.test.ts +++ b/app/scripts/lib/accounts/BalancesController.test.ts @@ -6,6 +6,7 @@ import { InternalAccount, } from '@metamask/keyring-api'; import { createMockInternalAccount } from '../../../../test/jest/mocks'; +import { MultichainNetworks } from '../../../../shared/constants/multichain/networks'; import { BalancesController, AllowedActions, @@ -25,6 +26,9 @@ const mockBtcAccount = createMockInternalAccount({ name: 'mock-btc-snap', enabled: true, }, + options: { + scope: MultichainNetworks.BITCOIN_TESTNET, + }, }); const mockBalanceResult = { diff --git a/app/scripts/lib/accounts/BalancesController.ts b/app/scripts/lib/accounts/BalancesController.ts index e657fe47e64f..588053d6ea2a 100644 --- a/app/scripts/lib/accounts/BalancesController.ts +++ b/app/scripts/lib/accounts/BalancesController.ts @@ -13,6 +13,7 @@ import { type CaipAssetType, type InternalAccount, isEvmAccountType, + SolAccountType, } from '@metamask/keyring-api'; import type { HandleSnapRequest } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; @@ -23,6 +24,8 @@ import type { AccountsControllerAccountRemovedEvent, AccountsControllerListMultichainAccountsAction, } from '@metamask/accounts-controller'; +import { MultichainNetworks } from '../../../../shared/constants/multichain/networks'; +import { MULTICHAIN_NETWORK_TO_ASSET_TYPES } from '../../../../shared/constants/multichain/assets'; import { isBtcMainnetAddress } from '../../../../shared/lib/multichain'; import { BalancesTracker } from './BalancesTracker'; @@ -122,13 +125,17 @@ const balancesControllerMetadata = { }, }; -const BTC_TESTNET_ASSETS = ['bip122:000000000933ea01ad0ee984209779ba/slip44:0']; -const BTC_MAINNET_ASSETS = ['bip122:000000000019d6689c085ae165831e93/slip44:0']; const BTC_AVG_BLOCK_TIME = 10 * 60 * 1000; // 10 minutes in milliseconds +const SOLANA_AVG_BLOCK_TIME = 400; // 400 milliseconds // NOTE: We set an interval of half the average block time to mitigate when our interval // is de-synchronized with the actual block time. -export const BALANCES_UPDATE_TIME = BTC_AVG_BLOCK_TIME / 2; +export const BTC_BALANCES_UPDATE_TIME = BTC_AVG_BLOCK_TIME / 2; + +const BALANCE_CHECK_INTERVALS = { + [BtcAccountType.P2wpkh]: BTC_BALANCES_UPDATE_TIME, + [SolAccountType.DataAccount]: SOLANA_AVG_BLOCK_TIME, +}; /** * The BalancesController is responsible for fetching and caching account @@ -165,7 +172,7 @@ export class BalancesController extends BaseController< // Register all non-EVM accounts into the tracker for (const account of this.#listAccounts()) { if (this.#isNonEvmAccount(account)) { - this.#tracker.track(account.id, BALANCES_UPDATE_TIME); + this.#tracker.track(account.id, this.#getBlockTimeFor(account)); } } @@ -193,6 +200,23 @@ export class BalancesController extends BaseController< this.#tracker.stop(); } + /** + * Gets the block time for a given account. + * + * @param account - The account to get the block time for. + * @returns The block time for the account. + */ + #getBlockTimeFor(account: InternalAccount): number { + if (account.type in BALANCE_CHECK_INTERVALS) { + return BALANCE_CHECK_INTERVALS[ + account.type as keyof typeof BALANCE_CHECK_INTERVALS + ]; + } + throw new Error( + `Unsupported account type for balance tracking: ${account.type}`, + ); + } + /** * Lists the multichain accounts coming from the `AccountsController`. * @@ -207,15 +231,16 @@ export class BalancesController extends BaseController< /** * Lists the accounts that we should get balances for. * - * Currently, we only get balances for P2WPKH accounts, but this will change - * in the future when we start support other non-EVM account types. - * * @returns A list of accounts that we should get balances for. */ #listAccounts(): InternalAccount[] { const accounts = this.#listMultichainAccounts(); - return accounts.filter((account) => account.type === BtcAccountType.P2wpkh); + return accounts.filter( + (account) => + account.type === SolAccountType.DataAccount || + account.type === BtcAccountType.P2wpkh, + ); } /** @@ -249,12 +274,13 @@ export class BalancesController extends BaseController< const partialState: BalancesControllerState = { balances: {} }; if (account.metadata.snap) { + const scope = this.#getScopeFrom(account); + const assetTypes = MULTICHAIN_NETWORK_TO_ASSET_TYPES[scope]; + partialState.balances[account.id] = await this.#getBalances( account.id, account.metadata.snap.id, - isBtcMainnetAddress(account.address) - ? BTC_MAINNET_ASSETS - : BTC_TESTNET_ASSETS, + assetTypes, ); } @@ -312,7 +338,7 @@ export class BalancesController extends BaseController< return; } - this.#tracker.track(account.id, BTC_AVG_BLOCK_TIME); + this.#tracker.track(account.id, this.#getBlockTimeFor(account)); // NOTE: Unfortunately, we cannot update the balance right away here, because // messenger's events are running synchronously and fetching the balance is // asynchronous. @@ -376,4 +402,33 @@ export class BalancesController extends BaseController< })) as Promise, }); } + + /** + * Gets the network scope for a given account. + * + * @param account - The account to get the scope for. + * @returns The network scope for the account. + * @throws If the account type is unknown or unsupported. + */ + #getScopeFrom(account: InternalAccount): MultichainNetworks { + // TODO: Use the new `account.scopes` once available in the `keyring-api`. + + // For Bitcoin accounts, we get the scope based on the address format. + if (account.type === BtcAccountType.P2wpkh) { + if (isBtcMainnetAddress(account.address)) { + return MultichainNetworks.BITCOIN; + } + return MultichainNetworks.BITCOIN_TESTNET; + } + + // For Solana accounts, we know we have a `scope` on the account's `options` bag. + if (account.type === SolAccountType.DataAccount) { + if (!account.options.scope) { + throw new Error('Solana account scope is undefined'); + } + return account.options.scope as MultichainNetworks; + } + + throw new Error(`Unsupported non-EVM account type: ${account.type}`); + } } diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 880df69aa00f..0e08a0ac27c0 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -41,7 +41,7 @@ import { createMockInternalAccount } from '../../test/jest/mocks'; import { mockNetworkState } from '../../test/stub/networks'; import { BalancesController as MultichainBalancesController, - BALANCES_UPDATE_TIME as MULTICHAIN_BALANCES_UPDATE_TIME, + BTC_BALANCES_UPDATE_TIME as MULTICHAIN_BALANCES_UPDATE_TIME, } from './lib/accounts/BalancesController'; import { BalancesTracker as MultichainBalancesTracker } from './lib/accounts/BalancesTracker'; import { deferredPromise } from './lib/util'; diff --git a/package.json b/package.json index 9ed956d639cd..b7adb1fcdcee 100644 --- a/package.json +++ b/package.json @@ -752,7 +752,8 @@ "resolve-url-loader>es6-iterator>d>es5-ext": false, "resolve-url-loader>es6-iterator>d>es5-ext>esniff>es5-ext": false, "level>classic-level": false, - "jest-preview": false + "jest-preview": false, + "@metamask/solana-wallet-snap>@solana/web3.js>bigint-buffer": false } }, "packageManager": "yarn@4.5.1" diff --git a/shared/constants/multichain/assets.ts b/shared/constants/multichain/assets.ts index 23462d57d05a..58e0869ff045 100644 --- a/shared/constants/multichain/assets.ts +++ b/shared/constants/multichain/assets.ts @@ -1,3 +1,4 @@ +import { CaipAssetType } from '@metamask/keyring-api'; import { MultichainNetworks } from './networks'; export const MULTICHAIN_NATIVE_CURRENCY_TO_CAIP19 = { @@ -13,3 +14,20 @@ export enum MultichainNativeAssets { SOLANA_DEVNET = `${MultichainNetworks.SOLANA_DEVNET}/slip44:501`, SOLANA_TESTNET = `${MultichainNetworks.SOLANA_TESTNET}/slip44:501`, } + +/** + * Maps network identifiers to their corresponding native asset types. + * Each network is mapped to an array containing its native asset for consistency. + */ +export const MULTICHAIN_NETWORK_TO_ASSET_TYPES: Record< + MultichainNetworks, + CaipAssetType[] +> = { + [MultichainNetworks.SOLANA]: [MultichainNativeAssets.SOLANA], + [MultichainNetworks.SOLANA_TESTNET]: [MultichainNativeAssets.SOLANA_TESTNET], + [MultichainNetworks.SOLANA_DEVNET]: [MultichainNativeAssets.SOLANA_DEVNET], + [MultichainNetworks.BITCOIN]: [MultichainNativeAssets.BITCOIN], + [MultichainNetworks.BITCOIN_TESTNET]: [ + MultichainNativeAssets.BITCOIN_TESTNET, + ], +}; diff --git a/shared/constants/multichain/networks.ts b/shared/constants/multichain/networks.ts index f5a45138d88a..659228ba1199 100644 --- a/shared/constants/multichain/networks.ts +++ b/shared/constants/multichain/networks.ts @@ -1,4 +1,5 @@ import { CaipChainId } from '@metamask/utils'; +import { BtcAccountType, SolAccountType } from '@metamask/keyring-api'; import { isBtcMainnetAddress, isBtcTestnetAddress, @@ -33,6 +34,11 @@ export enum MultichainNetworks { SOLANA_TESTNET = 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', } +export const MULTICHAIN_ACCOUNT_TYPE_TO_MAINNET = { + [BtcAccountType.P2wpkh]: MultichainNetworks.BITCOIN, + [SolAccountType.DataAccount]: MultichainNetworks.SOLANA, +} as const; + export const BITCOIN_TOKEN_IMAGE_URL = './images/bitcoin-logo.svg'; export const SOLANA_TOKEN_IMAGE_URL = './images/solana-logo.svg'; diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 3fca971c338e..41f1d9fd0d95 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -301,7 +301,6 @@ export const CURRENCY_SYMBOLS = { AVALANCHE: 'AVAX', BNB: 'BNB', BUSD: 'BUSD', - BTC: 'BTC', // Do we wanna mix EVM and non-EVM here? CELO: 'CELO', DAI: 'DAI', GNOSIS: 'XDAI', @@ -322,8 +321,15 @@ export const CURRENCY_SYMBOLS = { ONE: 'ONE', } as const; +// Non-EVM currency symbols +export const NON_EVM_CURRENCY_SYMBOLS = { + BTC: 'BTC', + SOL: 'SOL', +} as const; + const CHAINLIST_CURRENCY_SYMBOLS_MAP = { ...CURRENCY_SYMBOLS, + ...NON_EVM_CURRENCY_SYMBOLS, BASE: 'ETH', LINEA_MAINNET: 'ETH', OPBNB: 'BNB', 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 8b2efef3e517..01f94b55d5c7 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 @@ -35,13 +35,11 @@ "petnamesEnabled": true, "showMultiRpcModal": "boolean", "isRedesignedConfirmationsDeveloperEnabled": "boolean", - "redesignedConfirmationsEnabled": true, - "redesignedTransactionsEnabled": "boolean", "tokenSortConfig": "object", - "tokenNetworkFilter": { - "0x539": "boolean" - }, - "shouldShowAggregatedBalancePopover": "boolean" + "shouldShowAggregatedBalancePopover": "boolean", + "tokenNetworkFilter": { "0x539": "boolean" }, + "redesignedConfirmationsEnabled": true, + "redesignedTransactionsEnabled": "boolean" }, "firstTimeFlowType": "import", "completedOnboarding": true, @@ -176,10 +174,7 @@ "gasEstimateType": "none", "nonRPCGasFeeApisDisabled": "boolean", "tokenList": "object", - "tokensChainsCache": { - "0x539": "object" - }, - "tokenBalances": "object", + "tokensChainsCache": { "0x539": "object" }, "preventPollingOnNetworkRestart": false, "tokens": "object", "ignoredTokens": "object", @@ -187,6 +182,7 @@ "allTokens": {}, "allIgnoredTokens": {}, "allDetectedTokens": {}, + "tokenBalances": "object", "smartTransactionsState": { "fees": {}, "feesByChainId": "object", diff --git a/test/jest/mocks.ts b/test/jest/mocks.ts index b0750b022e78..8822b96315b6 100644 --- a/test/jest/mocks.ts +++ b/test/jest/mocks.ts @@ -9,6 +9,7 @@ import { import { KeyringTypes } from '@metamask/keyring-controller'; import { v4 as uuidv4 } from 'uuid'; import { keyringTypeToName } from '@metamask/accounts-controller'; +import { Json } from '@metamask/utils'; import { DraftTransaction, draftTransactionInitialState, @@ -186,6 +187,7 @@ export function createMockInternalAccount({ keyringType = KeyringTypes.hd, lastSelected = 0, snapOptions = undefined, + options = undefined, }: { name?: string; address?: string; @@ -197,6 +199,7 @@ export function createMockInternalAccount({ name: string; id: string; }; + options?: Record; } = {}) { let methods; @@ -236,7 +239,7 @@ export function createMockInternalAccount({ snap: snapOptions, lastSelected, }, - options: {}, + options: options ?? {}, methods, type, }; diff --git a/ui/components/app/wallet-overview/index.js b/ui/components/app/wallet-overview/index.js index 54536007bc41..82003b364199 100644 --- a/ui/components/app/wallet-overview/index.js +++ b/ui/components/app/wallet-overview/index.js @@ -1,2 +1,2 @@ export { default as EthOverview } from './eth-overview'; -export { default as BtcOverview } from './btc-overview'; +export { default as NonEvmOverview } from './non-evm-overview'; diff --git a/ui/components/app/wallet-overview/btc-overview.stories.tsx b/ui/components/app/wallet-overview/non-evm-overview.stories.tsx similarity index 70% rename from ui/components/app/wallet-overview/btc-overview.stories.tsx rename to ui/components/app/wallet-overview/non-evm-overview.stories.tsx index 43dff2554bef..2e8ae16045ce 100644 --- a/ui/components/app/wallet-overview/btc-overview.stories.tsx +++ b/ui/components/app/wallet-overview/non-evm-overview.stories.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import BtcOverview from './btc-overview'; +import NonEvmOverview from './non-evm-overview'; export default { title: 'Components/App/WalletOverview/BtcOverview', - component: BtcOverview, + component: NonEvmOverview, parameters: { docs: { description: { @@ -14,6 +14,6 @@ export default { }, }; -const Template = (args) => ; +const Template = (args) => ; export const Default = Template.bind({}); diff --git a/ui/components/app/wallet-overview/btc-overview.test.tsx b/ui/components/app/wallet-overview/non-evm-overview.test.tsx similarity index 92% rename from ui/components/app/wallet-overview/btc-overview.test.tsx rename to ui/components/app/wallet-overview/non-evm-overview.test.tsx index 3c5697cb5853..aa49eb77e79d 100644 --- a/ui/components/app/wallet-overview/btc-overview.test.tsx +++ b/ui/components/app/wallet-overview/non-evm-overview.test.tsx @@ -17,7 +17,7 @@ import { MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import useMultiPolling from '../../../hooks/useMultiPolling'; -import BtcOverview from './btc-overview'; +import NonEvmOverview from './non-evm-overview'; // We need to mock `dispatch` since we use it for `setDefaultHomeActiveTabName`. const mockDispatch = jest.fn().mockReturnValue(() => jest.fn()); @@ -134,7 +134,7 @@ function makePortfolioUrl(path: string, getParams: Record) { return `${PORTOFOLIO_URL}/${path}?${params.toString()}`; } -describe('BtcOverview', () => { +describe('NonEvmOverview', () => { beforeEach(() => { setBackgroundConnection({ setBridgeFeatureFlags: jest.fn() } as never); // Clear previous mock implementations @@ -156,8 +156,11 @@ describe('BtcOverview', () => { }); }); - it('shows the primary balance as BTC when showNativeTokenAsMainBalance if true', async () => { - const { queryByTestId } = renderWithProvider(, getStore()); + it('shows the primary balance using the native token when showNativeTokenAsMainBalance if true', async () => { + const { queryByTestId } = renderWithProvider( + , + getStore(), + ); const primaryBalance = queryByTestId(BTC_OVERVIEW_PRIMARY_CURRENCY); expect(primaryBalance).toBeInTheDocument(); @@ -166,7 +169,7 @@ describe('BtcOverview', () => { it('shows the primary balance as fiat when showNativeTokenAsMainBalance if false', async () => { const { queryByTestId } = renderWithProvider( - , + , getStore({ metamask: { ...mockMetamaskStore, @@ -186,7 +189,7 @@ describe('BtcOverview', () => { it('shows a spinner if balance is not available', async () => { const { container } = renderWithProvider( - , + , getStore({ metamask: { ...mockMetamaskStore, @@ -203,7 +206,10 @@ describe('BtcOverview', () => { }); it('buttons Swap/Bridge are disabled', () => { - const { queryByTestId } = renderWithProvider(, getStore()); + const { queryByTestId } = renderWithProvider( + , + getStore(), + ); for (const buttonTestId of [BTC_OVERVIEW_SWAP, BTC_OVERVIEW_BRIDGE]) { const button = queryByTestId(buttonTestId); @@ -213,13 +219,19 @@ describe('BtcOverview', () => { }); it('shows the "Buy & Sell" button', () => { - const { queryByTestId } = renderWithProvider(, getStore()); + const { queryByTestId } = renderWithProvider( + , + getStore(), + ); const buyButton = queryByTestId(BTC_OVERVIEW_BUY); expect(buyButton).toBeInTheDocument(); }); it('"Buy & Sell" button is disabled if BTC is not buyable', () => { - const { queryByTestId } = renderWithProvider(, getStore()); + const { queryByTestId } = renderWithProvider( + , + getStore(), + ); const buyButton = queryByTestId(BTC_OVERVIEW_BUY); expect(buyButton).toBeInTheDocument(); @@ -234,7 +246,7 @@ describe('BtcOverview', () => { }); const { queryByTestId } = renderWithProvider( - , + , storeWithBtcBuyable, ); @@ -252,7 +264,7 @@ describe('BtcOverview', () => { }); const { queryByTestId } = renderWithProvider( - , + , storeWithBtcBuyable, ); @@ -283,7 +295,7 @@ describe('BtcOverview', () => { const mockTrackEvent = jest.fn(); const { queryByTestId } = renderWithProvider( - + , storeWithBtcBuyable, ); @@ -307,7 +319,10 @@ describe('BtcOverview', () => { }); it('always show the Receive button', () => { - const { queryByTestId } = renderWithProvider(, getStore()); + const { queryByTestId } = renderWithProvider( + , + getStore(), + ); const receiveButton = queryByTestId(BTC_OVERVIEW_RECEIVE); expect(receiveButton).toBeInTheDocument(); }); @@ -332,7 +347,7 @@ describe('BtcOverview', () => { }); const { queryByTestId } = renderWithProvider( - , + , storeWithBtcBuyable, ); @@ -343,7 +358,10 @@ describe('BtcOverview', () => { }); it('always show the Send button', () => { - const { queryByTestId } = renderWithProvider(, getStore()); + const { queryByTestId } = renderWithProvider( + , + getStore(), + ); const sendButton = queryByTestId(BTC_OVERVIEW_SEND); expect(sendButton).toBeInTheDocument(); expect(sendButton).not.toBeDisabled(); @@ -353,7 +371,7 @@ describe('BtcOverview', () => { const mockTrackEvent = jest.fn(); const { queryByTestId } = renderWithProvider( - + , getStore(), ); diff --git a/ui/components/app/wallet-overview/btc-overview.tsx b/ui/components/app/wallet-overview/non-evm-overview.tsx similarity index 73% rename from ui/components/app/wallet-overview/btc-overview.tsx rename to ui/components/app/wallet-overview/non-evm-overview.tsx index fb315d3ab3b0..8905a3a938f1 100644 --- a/ui/components/app/wallet-overview/btc-overview.tsx +++ b/ui/components/app/wallet-overview/non-evm-overview.tsx @@ -1,5 +1,8 @@ import React from 'react'; import { useSelector } from 'react-redux'; +///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) +import { BtcAccountType } from '@metamask/keyring-api'; +///: END:ONLY_INCLUDE_IF import { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) getMultichainIsMainnet, @@ -14,11 +17,11 @@ import { useMultichainSelector } from '../../../hooks/useMultichainSelector'; import { getSelectedInternalAccount } from '../../../selectors'; import { CoinOverview } from './coin-overview'; -type BtcOverviewProps = { +type NonEvmOverviewProps = { className?: string; }; -const BtcOverview = ({ className }: BtcOverviewProps) => { +const NonEvmOverview = ({ className }: NonEvmOverviewProps) => { const { chainId } = useSelector(getMultichainProviderConfig); const balance = useSelector(getMultichainSelectedAccountCachedBalance); const account = useSelector(getSelectedInternalAccount); @@ -28,6 +31,11 @@ const BtcOverview = ({ className }: BtcOverviewProps) => { account, ); const isBtcBuyable = useSelector(getIsBitcoinBuyable); + + // TODO: Update this to add support to check if Solana is buyable when the Send flow starts + const accountType = account.type; + const isBtc = accountType === BtcAccountType.P2wpkh; + const isBuyableChain = isBtc ? isBtcBuyable && isBtcMainnetAccount : false; ///: END:ONLY_INCLUDE_IF return ( @@ -42,10 +50,10 @@ const BtcOverview = ({ className }: BtcOverviewProps) => { isSwapsChain={false} ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) isBridgeChain={false} - isBuyableChain={isBtcBuyable && isBtcMainnetAccount} + isBuyableChain={isBuyableChain} ///: END:ONLY_INCLUDE_IF /> ); }; -export default BtcOverview; +export default NonEvmOverview; diff --git a/ui/components/multichain/account-list-menu/account-list-menu.tsx b/ui/components/multichain/account-list-menu/account-list-menu.tsx index 29d79e8537b1..030b57ebe242 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.tsx +++ b/ui/components/multichain/account-list-menu/account-list-menu.tsx @@ -17,6 +17,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { BtcAccountType, EthAccountType, + SolAccountType, ///: BEGIN:ONLY_INCLUDE_IF(build-flask) InternalAccount, KeyringAccountType, @@ -232,6 +233,7 @@ export const AccountListMenu = ({ EthAccountType.Eoa, EthAccountType.Erc4337, BtcAccountType.P2wpkh, + SolAccountType.DataAccount, ], }: AccountListMenuProps) => { const t = useI18nContext(); diff --git a/ui/components/multichain/account-overview/account-overview-btc.stories.tsx b/ui/components/multichain/account-overview/account-overview-btc.stories.tsx deleted file mode 100644 index 2afc54e22b23..000000000000 --- a/ui/components/multichain/account-overview/account-overview-btc.stories.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { AccountOverviewBtc } from './account-overview-btc' -import { AccountOverviewCommonProps } from './common'; - -export default { - title: 'Components/Multichain/AccountOverviewBtc', - component: AccountOverviewBtc, -}; - -export const DefaultStory = ( - args: JSX.IntrinsicAttributes & AccountOverviewCommonProps -) => ; diff --git a/ui/components/multichain/account-overview/account-overview-non-evm.stories.tsx b/ui/components/multichain/account-overview/account-overview-non-evm.stories.tsx new file mode 100644 index 000000000000..de3ac5484baf --- /dev/null +++ b/ui/components/multichain/account-overview/account-overview-non-evm.stories.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { AccountOverviewNonEvm } from './account-overview-non-evm'; +import { AccountOverviewCommonProps } from './common'; +import { BtcAccountType, SolAccountType } from '@metamask/keyring-api'; + +export default { + title: 'Components/Multichain/AccountOverviewNonEvm', + component: AccountOverviewNonEvm, + args: { + accountType: BtcAccountType.P2wpkh, + }, +}; + +export const DefaultStory = ( + args: JSX.IntrinsicAttributes & + AccountOverviewCommonProps & { + accountType: BtcAccountType.P2wpkh | SolAccountType.DataAccount; + }, +) => ; diff --git a/ui/components/multichain/account-overview/account-overview-btc.test.tsx b/ui/components/multichain/account-overview/account-overview-non-evm.test.tsx similarity index 88% rename from ui/components/multichain/account-overview/account-overview-btc.test.tsx rename to ui/components/multichain/account-overview/account-overview-non-evm.test.tsx index b171840a540e..17989cbf31a6 100644 --- a/ui/components/multichain/account-overview/account-overview-btc.test.tsx +++ b/ui/components/multichain/account-overview/account-overview-non-evm.test.tsx @@ -5,9 +5,9 @@ import { renderWithProvider } from '../../../../test/jest/rendering'; import { setBackgroundConnection } from '../../../store/background-connection'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import { - AccountOverviewBtc, - AccountOverviewBtcProps, -} from './account-overview-btc'; + AccountOverviewNonEvm, + AccountOverviewNonEvmProps, +} from './account-overview-non-evm'; jest.mock('../../../store/actions', () => ({ tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), @@ -26,14 +26,14 @@ jest.mock('react-redux', () => { }; }); -const defaultProps: AccountOverviewBtcProps = { +const defaultProps: AccountOverviewNonEvmProps = { defaultHomeActiveTabName: null, onTabClick: jest.fn(), setBasicFunctionalityModalOpen: jest.fn(), onSupportLinkClick: jest.fn(), }; -const render = (props: AccountOverviewBtcProps = defaultProps) => { +const render = (props: AccountOverviewNonEvmProps = defaultProps) => { const store = configureStore({ metamask: { ...mockState.metamask, @@ -47,7 +47,7 @@ const render = (props: AccountOverviewBtcProps = defaultProps) => { }, }); - return renderWithProvider(, store); + return renderWithProvider(, store); }; describe('AccountOverviewBtc', () => { diff --git a/ui/components/multichain/account-overview/account-overview-btc.tsx b/ui/components/multichain/account-overview/account-overview-non-evm.tsx similarity index 66% rename from ui/components/multichain/account-overview/account-overview-btc.tsx rename to ui/components/multichain/account-overview/account-overview-non-evm.tsx index dd58b2eef414..dd7db4484306 100644 --- a/ui/components/multichain/account-overview/account-overview-btc.tsx +++ b/ui/components/multichain/account-overview/account-overview-non-evm.tsx @@ -1,11 +1,13 @@ import React from 'react'; -import { BtcOverview } from '../../app/wallet-overview'; +import { NonEvmOverview } from '../../app/wallet-overview'; import { AccountOverviewLayout } from './account-overview-layout'; import { AccountOverviewCommonProps } from './common'; -export type AccountOverviewBtcProps = AccountOverviewCommonProps; +export type AccountOverviewNonEvmProps = AccountOverviewCommonProps; -export const AccountOverviewBtc = (props: AccountOverviewBtcProps) => { +export const AccountOverviewNonEvm = ({ + ...props +}: AccountOverviewNonEvmProps) => { return ( { > { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask,build-mmi) - + ///: END:ONLY_INCLUDE_IF } diff --git a/ui/components/multichain/account-overview/account-overview.tsx b/ui/components/multichain/account-overview/account-overview.tsx index 3d6121e41471..f3f3e427a688 100644 --- a/ui/components/multichain/account-overview/account-overview.tsx +++ b/ui/components/multichain/account-overview/account-overview.tsx @@ -1,13 +1,17 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { BtcAccountType, EthAccountType } from '@metamask/keyring-api'; +import { + BtcAccountType, + EthAccountType, + SolAccountType, +} from '@metamask/keyring-api'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { BannerAlert, BannerAlertSeverity } from '../../component-library'; import { getSelectedInternalAccount } from '../../../selectors'; import { AccountOverviewEth } from './account-overview-eth'; -import { AccountOverviewBtc } from './account-overview-btc'; import { AccountOverviewUnknown } from './account-overview-unknown'; import { AccountOverviewCommonProps } from './common'; +import { AccountOverviewNonEvm } from './account-overview-non-evm'; export type AccountOverviewProps = AccountOverviewCommonProps & { useExternalServices: boolean; @@ -25,7 +29,8 @@ export function AccountOverview(props: AccountOverviewProps) { case EthAccountType.Erc4337: return ; case BtcAccountType.P2wpkh: - return ; + case SolAccountType.DataAccount: + return ; default: return ; } diff --git a/ui/components/multichain/token-list-item/token-list-item.tsx b/ui/components/multichain/token-list-item/token-list-item.tsx index ef49ec3126cb..5ee4c19c8c52 100644 --- a/ui/components/multichain/token-list-item/token-list-item.tsx +++ b/ui/components/multichain/token-list-item/token-list-item.tsx @@ -56,7 +56,10 @@ import { MetaMetricsEventCategory, MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; -import { CURRENCY_SYMBOLS } from '../../../../shared/constants/network'; +import { + CURRENCY_SYMBOLS, + NON_EVM_CURRENCY_SYMBOLS, +} from '../../../../shared/constants/network'; import { hexToDecimal } from '../../../../shared/modules/conversion.utils'; import { NETWORKS_ROUTE } from '../../../helpers/constants/routes'; @@ -141,8 +144,10 @@ export const TokenListItem = ({ switch (title) { case CURRENCY_SYMBOLS.ETH: return t('networkNameEthereum'); - case CURRENCY_SYMBOLS.BTC: + case NON_EVM_CURRENCY_SYMBOLS.BTC: return t('networkNameBitcoin'); + case NON_EVM_CURRENCY_SYMBOLS.SOL: + return t('networkNameSolana'); default: return title; } diff --git a/ui/hooks/useCurrencyDisplay.js b/ui/hooks/useCurrencyDisplay.js index 12b2cfc06ec3..03561153ed53 100644 --- a/ui/hooks/useCurrencyDisplay.js +++ b/ui/hooks/useCurrencyDisplay.js @@ -62,7 +62,8 @@ function formatEthCurrencyDisplay({ return null; } -function formatBtcCurrencyDisplay({ +function formatNonEvmAssetCurrencyDisplay({ + tokenSymbol, isNativeCurrency, isUserPreferredCurrency, currency, @@ -77,7 +78,7 @@ function formatBtcCurrencyDisplay({ // We use `Numeric` here, so we handle those amount the same way than for EVMs (it's worth // noting that if `inputValue` is not properly defined, the amount will be set to '0', see // `Numeric` constructor for that) - return new Numeric(inputValue, 10).toString(); // BTC usually uses 10 digits + return new Numeric(inputValue, 10).toString(); } else if (isUserPreferredCurrency && conversionRate) { const amount = getTokenFiatAmount( @@ -85,7 +86,7 @@ function formatBtcCurrencyDisplay({ Number(conversionRate), // native to fiat conversion rate currentCurrency, inputValue, - 'BTC', + tokenSymbol, false, false, ) ?? '0'; // if the conversion fails, return 0 @@ -162,8 +163,8 @@ export function useCurrencyDisplay( } if (!isEvm) { - // TODO: We would need to update this for other non-EVM coins - return formatBtcCurrencyDisplay({ + return formatNonEvmAssetCurrencyDisplay({ + tokenSymbol: nativeCurrency, isNativeCurrency, isUserPreferredCurrency, currency, diff --git a/ui/selectors/multichain.ts b/ui/selectors/multichain.ts index 1914dbce2dd8..903b5d0a4a71 100644 --- a/ui/selectors/multichain.ts +++ b/ui/selectors/multichain.ts @@ -9,6 +9,7 @@ import { MultichainProviderConfig, MULTICHAIN_PROVIDER_CONFIGS, MultichainNetworks, + MULTICHAIN_ACCOUNT_TYPE_TO_MAINNET, } from '../../shared/constants/multichain/networks'; import { getCompletedOnboarding, @@ -18,7 +19,7 @@ import { // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { BalancesControllerState } from '../../app/scripts/lib/accounts/BalancesController'; -import { MultichainNativeAssets } from '../../shared/constants/multichain/assets'; +import { MULTICHAIN_NETWORK_TO_ASSET_TYPES } from '../../shared/constants/multichain/assets'; import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, TEST_NETWORK_IDS, @@ -333,11 +334,15 @@ export function getMultichainIsMainnet( ) { const selectedAccount = account ?? getSelectedInternalAccount(state); const providerConfig = getMultichainProviderConfig(state, selectedAccount); - return getMultichainIsEvm(state, account) - ? getIsMainnet(state) - : // TODO: For now we only check for bitcoin, but we will need to - // update this for other non-EVM networks later! - providerConfig.chainId === MultichainNetworks.BITCOIN; + + if (getMultichainIsEvm(state, account)) { + return getIsMainnet(state); + } + + const mainnet = ( + MULTICHAIN_ACCOUNT_TYPE_TO_MAINNET as Record + )[selectedAccount.type]; + return providerConfig.chainId === mainnet ?? false; } export function getMultichainIsTestnet( @@ -370,12 +375,17 @@ export const getMultichainCoinRates = (state: MultichainState) => { return state.metamask.rates; }; -function getBtcCachedBalance(state: MultichainState) { +function getNonEvmCachedBalance(state: MultichainState) { const balances = getMultichainBalances(state); const account = getSelectedInternalAccount(state); - const asset = getMultichainIsMainnet(state) - ? MultichainNativeAssets.BITCOIN - : MultichainNativeAssets.BITCOIN_TESTNET; + const network = getMultichainCurrentNetwork(state); + + // We assume that there's at least one asset type in and that is the native + // token for that network. + const asset = + MULTICHAIN_NETWORK_TO_ASSET_TYPES[ + network.chainId as MultichainNetworks + ]?.[0]; return balances?.[account.id]?.[asset]?.amount; } @@ -394,7 +404,7 @@ export function getMultichainSelectedAccountCachedBalance( ) { return getMultichainIsEvm(state) ? getSelectedAccountCachedBalance(state) - : getBtcCachedBalance(state); + : getNonEvmCachedBalance(state); } export const getMultichainSelectedAccountCachedBalanceIsZero = createSelector( From 9fccd00a10a4dc6633113beedfc050e09009614c Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Tue, 26 Nov 2024 08:17:25 -0800 Subject: [PATCH 35/40] fix: Pass along decimal balance from asset-page to swaps UI (#28707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Decimal balance are needed to be passed along with the rest of the token info in order to properly prepopulate the swaps UI. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28707?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28509 ## **Manual testing steps** 1. Navigate from AssetList to TokenDetails to Swap UI 2. Populate text field with a balance amount 3. If balance is lower than the token balance, do not show the warning. ## **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. --- ui/pages/asset/components/asset-page.test.tsx | 20 ++++++++++++------- ui/pages/asset/components/asset-page.tsx | 10 +++++++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/ui/pages/asset/components/asset-page.test.tsx b/ui/pages/asset/components/asset-page.test.tsx index 60e322f8825e..28e232a0ba0b 100644 --- a/ui/pages/asset/components/asset-page.test.tsx +++ b/ui/pages/asset/components/asset-page.test.tsx @@ -45,6 +45,8 @@ jest.mock('../../../hooks/useMultiPolling', () => ({ default: jest.fn(), })); +const selectedAccountAddress = 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3'; + describe('AssetPage', () => { const mockStore = { localeMessages: { @@ -52,13 +54,17 @@ describe('AssetPage', () => { }, metamask: { tokenList: {}, - tokenBalances: {}, + tokenBalances: { + [selectedAccountAddress]: { + [CHAIN_IDS.MAINNET]: {}, + }, + }, marketData: {}, allTokens: {}, accountsByChainId: { '0x1': { - 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3': { - address: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', + [selectedAccountAddress]: { + address: selectedAccountAddress, balance: '0x00', }, }, @@ -80,9 +86,9 @@ describe('AssetPage', () => { preferences: {}, internalAccounts: { accounts: { - 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3': { - address: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', - id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', + [selectedAccountAddress]: { + address: selectedAccountAddress, + id: selectedAccountAddress, metadata: { name: 'Test Account', keyring: { @@ -94,7 +100,7 @@ describe('AssetPage', () => { type: EthAccountType.Eoa, }, }, - selectedAccount: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', + selectedAccount: selectedAccountAddress, }, keyrings: [ { diff --git a/ui/pages/asset/components/asset-page.tsx b/ui/pages/asset/components/asset-page.tsx index 68d7f9d18e65..9100126d54fe 100644 --- a/ui/pages/asset/components/asset-page.tsx +++ b/ui/pages/asset/components/asset-page.tsx @@ -54,6 +54,7 @@ import { useTokenBalances } from '../../../hooks/useTokenBalances'; import { useMultichainSelector } from '../../../hooks/useMultichainSelector'; import { getMultichainShouldShowFiat } from '../../../selectors/multichain'; import { getPortfolioUrl } from '../../../helpers/utils/portfolio'; +import { hexToDecimal } from '../../../../shared/modules/conversion.utils'; import AssetChart from './chart/asset-chart'; import TokenButtons from './token-buttons'; @@ -151,6 +152,9 @@ const AssetPage = ({ ? toChecksumHexAddress(asset.address) : getNativeTokenAddress(chainId); + const tokenHexBalance = + selectedAccountTokenBalancesAcrossChains?.[chainId]?.[address as Hex]; + const balance = calculateTokenBalance({ isNative: type === AssetType.native, chainId, @@ -187,10 +191,10 @@ const AssetPage = ({ tokenMarketDetails.allTimeHigh > 0 || tokenMarketDetails.allTimeLow > 0); - // this is needed in order to assign the correct balances to TokenButtons before sending/swapping - // without this, the balances we be populated as zero until the user refreshes the screen: https://github.com/MetaMask/metamask-extension/issues/28509 + // this is needed in order to assign the correct balances to TokenButtons before navigating to send/swap screens + asset.balance = { - value: '', // decimal value not needed + value: hexToDecimal(tokenHexBalance), display: String(balance), fiat: String(tokenFiatAmount), }; From 43e9514f4525faa85f67802f5d0e8d70b2323e4d Mon Sep 17 00:00:00 2001 From: Nidhi Kumari Date: Tue, 26 Nov 2024 17:00:20 +0000 Subject: [PATCH 36/40] fix:updated account name and length for dapp connections (#28725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR is to: 1. Show correct account names when dapp tries to connect with MM and account is imported 2. Show correct length of accounts when more than one account is connected ## **Related issues** Fixes: [3685-planning](https://github.com/MetaMask/MetaMask-planning/issues/3685 ) #28312 ## **Manual testing steps** 1. Import an account in MM 2. Initiate connection request from Dapp, check correct name of account is shown on connections page 3. After connecting, select more than account, check length of accounts shown is correct ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/bb7281c6-0f92-4160-a09b-9a69fe69d671 ### **After** https://github.com/user-attachments/assets/96d45514-74b1-451c-a136-889821b31e22 ## **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. --- .../review-permissions-page/site-cell/site-cell-tooltip.js | 2 +- .../pages/review-permissions-page/site-cell/site-cell.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell-tooltip.js b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell-tooltip.js index 2e4eef35d594..84ede0d4dd4a 100644 --- a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell-tooltip.js +++ b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell-tooltip.js @@ -79,7 +79,7 @@ export const SiteCellTooltip = ({ accounts, networks }) => { data-testid="accounts-list-item-connected-account-name" ellipsis > - {acc.label || acc.metadata.name} + {acc.metadata.name || acc.label} ); diff --git a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx index d5ca0b816d48..fcb104937e28 100644 --- a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx +++ b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx @@ -72,13 +72,13 @@ export const SiteCell: React.FC = ({ const accountMessageConnectedState = selectedAccounts.length === 1 ? t('connectedWithAccountName', [ - selectedAccounts[0].label || selectedAccounts[0].metadata.name, + selectedAccounts[0].metadata.name || selectedAccounts[0].label, ]) - : t('connectedWithAccount', [accounts.length]); + : t('connectedWithAccount', [selectedAccounts.length]); const accountMessageNotConnectedState = selectedAccounts.length === 1 ? t('requestingForAccount', [ - selectedAccounts[0].label || selectedAccounts[0].metadata.name, + selectedAccounts[0].metadata.name || selectedAccounts[0].label, ]) : t('requestingFor'); From 815c445b1e8cade8f594579817c114bcc99e2a87 Mon Sep 17 00:00:00 2001 From: Jony Bursztyn Date: Tue, 26 Nov 2024 17:11:54 +0000 Subject: [PATCH 37/40] fix: Fix avatar size for current network (#28731) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Screenshot 2024-11-26 at 15 31 52 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28731?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask 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/assets/asset-list/network-filter/network-filter.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx index d032712be9c1..9b5cb3797e7c 100644 --- a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx +++ b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx @@ -110,6 +110,7 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => { display={Display.Flex} justifyContent={JustifyContent.spaceBetween} width={BlockSize.Full} + gap={3} > { @@ -194,6 +196,7 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => { From d24389c685fc5efc4da168da4e49fb533aecbcfb Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Tue, 26 Nov 2024 22:44:21 +0530 Subject: [PATCH 38/40] fix: Revert "feat: Changing title for permit requests (#28537)" (#28734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Revert changes in the PR: https://github.com/MetaMask/metamask-extension/pull/28537 ## **Related issues** Ref: https://github.com/MetaMask/MetaMask-planning/issues/3633 ## **Manual testing steps** 1. Enable permit signature decoding locally 2. Go to test dapp 3. Check title and description of permit pages ## **Screenshots/Recordings** TODO ## **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. --- .../signatures/nft-permit.spec.ts | 4 +-- .../confirmations/signatures/permit.test.tsx | 4 +-- .../components/confirm/title/title.test.tsx | 35 +++++++++++++++++++ .../components/confirm/title/title.tsx | 22 ++++++++++++ .../__snapshots__/confirm.test.tsx.snap | 12 +++---- 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts index eccdfff78a7c..de70d25b359b 100644 --- a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts @@ -126,9 +126,9 @@ async function assertInfoValues(driver: Driver) { text: '0x581c3...45947', }); - const title = driver.findElement({ text: 'Signature request' }); + const title = driver.findElement({ text: 'Withdrawal request' }); const description = driver.findElement({ - text: 'Review request details before you confirm.', + text: 'This site wants permission to withdraw your NFTs', }); const primaryType = driver.findElement({ text: 'Permit' }); const spender = driver.findElement({ diff --git a/test/integration/confirmations/signatures/permit.test.tsx b/test/integration/confirmations/signatures/permit.test.tsx index ba51deb7336c..7af3be743f5f 100644 --- a/test/integration/confirmations/signatures/permit.test.tsx +++ b/test/integration/confirmations/signatures/permit.test.tsx @@ -191,9 +191,9 @@ describe('Permit Confirmation', () => { }); await waitFor(() => { - expect(screen.getByText('Signature request')).toBeInTheDocument(); + expect(screen.getByText('Spending cap request')).toBeInTheDocument(); expect( - screen.getByText('Review request details before you confirm.'), + screen.getByText('This site wants permission to spend your tokens.'), ).toBeInTheDocument(); }); }); diff --git a/ui/pages/confirmations/components/confirm/title/title.test.tsx b/ui/pages/confirmations/components/confirm/title/title.test.tsx index b20b67b05c97..3d4d6672940d 100644 --- a/ui/pages/confirmations/components/confirm/title/title.test.tsx +++ b/ui/pages/confirmations/components/confirm/title/title.test.tsx @@ -8,8 +8,13 @@ import { getMockPersonalSignConfirmStateForRequest, getMockSetApprovalForAllConfirmState, getMockTypedSignConfirmState, + getMockTypedSignConfirmStateForRequest, } from '../../../../../../test/data/confirmations/helper'; import { unapprovedPersonalSignMsg } from '../../../../../../test/data/confirmations/personal_sign'; +import { + permitNFTSignatureMsg, + permitSignatureMsg, +} from '../../../../../../test/data/confirmations/typed_sign'; import { renderWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers'; import { tEn } from '../../../../../../test/lib/i18n-helpers'; import { @@ -54,6 +59,36 @@ describe('ConfirmTitle', () => { ).toBeInTheDocument(); }); + it('should render the title and description for a permit signature', () => { + const mockStore = configureMockStore([])( + getMockTypedSignConfirmStateForRequest(permitSignatureMsg), + ); + const { getByText } = renderWithConfirmContextProvider( + , + mockStore, + ); + + expect(getByText('Spending cap request')).toBeInTheDocument(); + expect( + getByText('This site wants permission to spend your tokens.'), + ).toBeInTheDocument(); + }); + + it('should render the title and description for a NFT permit signature', () => { + const mockStore = configureMockStore([])( + getMockTypedSignConfirmStateForRequest(permitNFTSignatureMsg), + ); + const { getByText } = renderWithConfirmContextProvider( + , + mockStore, + ); + + expect(getByText('Withdrawal request')).toBeInTheDocument(); + expect( + getByText('This site wants permission to withdraw your NFTs'), + ).toBeInTheDocument(); + }); + it('should render the title and description for typed signature', () => { const mockStore = configureMockStore([])(getMockTypedSignConfirmState()); const { getByText } = renderWithConfirmContextProvider( diff --git a/ui/pages/confirmations/components/confirm/title/title.tsx b/ui/pages/confirmations/components/confirm/title/title.tsx index 5fa3cc5b96f9..a926c0f6b482 100644 --- a/ui/pages/confirmations/components/confirm/title/title.tsx +++ b/ui/pages/confirmations/components/confirm/title/title.tsx @@ -4,6 +4,7 @@ import { } from '@metamask/transaction-controller'; import React, { memo, useMemo } from 'react'; +import { TokenStandard } from '../../../../../../shared/constants/transaction'; import GeneralAlert from '../../../../../components/app/alert-system/general-alert/general-alert'; import { Box, Text } from '../../../../../components/component-library'; import { @@ -13,6 +14,7 @@ import { } from '../../../../../helpers/constants/design-system'; import useAlerts from '../../../../../hooks/useAlerts'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; +import { TypedSignSignaturePrimaryTypes } from '../../../constants'; import { useConfirmContext } from '../../../context/confirm'; import { Confirmation, SignatureRequestType } from '../../../types/confirm'; import { isSIWESignatureRequest } from '../../../utils'; @@ -59,6 +61,8 @@ const getTitle = ( customSpendingCap?: string, isRevokeSetApprovalForAll?: boolean, pending?: boolean, + primaryType?: keyof typeof TypedSignSignaturePrimaryTypes, + tokenStandard?: string, ) => { if (pending) { return ''; @@ -75,6 +79,12 @@ const getTitle = ( } return t('confirmTitleSignature'); case TransactionType.signTypedData: + if (primaryType === TypedSignSignaturePrimaryTypes.PERMIT) { + if (tokenStandard === TokenStandard.ERC721) { + return t('setApprovalForAllRedesignedTitle'); + } + return t('confirmTitlePermitTokens'); + } return t('confirmTitleSignature'); case TransactionType.tokenMethodApprove: if (isNFT) { @@ -103,6 +113,8 @@ const getDescription = ( customSpendingCap?: string, isRevokeSetApprovalForAll?: boolean, pending?: boolean, + primaryType?: keyof typeof TypedSignSignaturePrimaryTypes, + tokenStandard?: string, ) => { if (pending) { return ''; @@ -119,6 +131,12 @@ const getDescription = ( } return t('confirmTitleDescSign'); case TransactionType.signTypedData: + if (primaryType === TypedSignSignaturePrimaryTypes.PERMIT) { + if (tokenStandard === TokenStandard.ERC721) { + return t('confirmTitleDescApproveTransaction'); + } + return t('confirmTitleDescPermitSignature'); + } return t('confirmTitleDescSign'); case TransactionType.tokenMethodApprove: if (isNFT) { @@ -177,6 +195,8 @@ const ConfirmTitle: React.FC = memo(() => { customSpendingCap, isRevokeSetApprovalForAll, spendingCapPending || revokePending, + primaryType, + tokenStandard, ), [ currentConfirmation, @@ -199,6 +219,8 @@ const ConfirmTitle: React.FC = memo(() => { customSpendingCap, isRevokeSetApprovalForAll, spendingCapPending || revokePending, + primaryType, + tokenStandard, ), [ currentConfirmation, diff --git a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap index 1d504025a44d..9f718a4b8a03 100644 --- a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap +++ b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap @@ -457,12 +457,12 @@ exports[`Confirm should match snapshot for signature - typed sign - V4 - PermitB

- Signature request + Spending cap request

- Review request details before you confirm. + This site wants permission to spend your tokens.

- Signature request + Spending cap request

- Review request details before you confirm. + This site wants permission to spend your tokens.

- Signature request + Spending cap request

- Review request details before you confirm. + This site wants permission to spend your tokens.

Date: Tue, 26 Nov 2024 19:00:22 +0100 Subject: [PATCH 39/40] chore: Remove unnecessary event prop (#28546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Removes an unnecessary event prop `smart_transaction_duplicated`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28546?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. This event prop won't be available anymore in some events after submitting a smart transaction. ## **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. --- shared/modules/metametrics.test.ts | 2 -- shared/modules/metametrics.ts | 3 --- .../tests/smart-transactions/mock-requests-for-swap-test.ts | 2 -- 3 files changed, 7 deletions(-) diff --git a/shared/modules/metametrics.test.ts b/shared/modules/metametrics.test.ts index 9d6d17bc8040..b6fe2f8ac0a2 100644 --- a/shared/modules/metametrics.test.ts +++ b/shared/modules/metametrics.test.ts @@ -92,7 +92,6 @@ describe('getSmartTransactionMetricsProperties', () => { cancellationReason: 'not_cancelled', deadlineRatio: 0.6400288486480713, minedHash: txHash, - duplicated: true, timedOut: true, proxied: true, minedTx: 'success', @@ -112,7 +111,6 @@ describe('getSmartTransactionMetricsProperties', () => { expect(result).toStrictEqual({ gas_included: true, is_smart_transaction: true, - smart_transaction_duplicated: true, smart_transaction_proxied: true, smart_transaction_timed_out: true, }); diff --git a/shared/modules/metametrics.ts b/shared/modules/metametrics.ts index b689891da1fb..389a2778a5b8 100644 --- a/shared/modules/metametrics.ts +++ b/shared/modules/metametrics.ts @@ -6,7 +6,6 @@ import { TransactionMetricsRequest } from '../../app/scripts/lib/transaction/met type SmartTransactionMetricsProperties = { is_smart_transaction: boolean; gas_included: boolean; - smart_transaction_duplicated?: boolean; smart_transaction_timed_out?: boolean; smart_transaction_proxied?: boolean; }; @@ -31,8 +30,6 @@ export const getSmartTransactionMetricsProperties = ( if (!smartTransactionStatusMetadata) { return properties; } - properties.smart_transaction_duplicated = - smartTransactionStatusMetadata.duplicated; properties.smart_transaction_timed_out = smartTransactionStatusMetadata.timedOut; properties.smart_transaction_proxied = smartTransactionStatusMetadata.proxied; diff --git a/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts b/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts index 78d1497dc9cf..bf9740c257ad 100644 --- a/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts +++ b/test/e2e/tests/smart-transactions/mock-requests-for-swap-test.ts @@ -60,7 +60,6 @@ const GET_BATCH_STATUS_RESPONSE_PENDING = { minedTx: 'not_mined', wouldRevertMessage: null, minedHash: '', - duplicated: false, timedOut: false, proxied: false, type: 'sentinel', @@ -77,7 +76,6 @@ const GET_BATCH_STATUS_RESPONSE_SUCCESS = { wouldRevertMessage: null, minedHash: '0xec9d6214684d6dc191133ae4a7ec97db3e521fff9cfe5c4f48a84cb6c93a5fa5', - duplicated: true, timedOut: true, proxied: false, type: 'sentinel', From c272b254f05195167b0349004643e883743f2c6d Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:12:21 -0800 Subject: [PATCH 40/40] fix: Provide selector that enables cross-chain polling, regardless of network filter state (#28662) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** We cannot rely on the same selector for all cases, as not all UI is tightly coupled to the tokenNetworkFilter, else we will not be able to compute aggregated balances across chains, when filtered by current network. Since polling for balances is UI based, we can use a different selector on the network-filter, which should execute polling loops only when the dropdown is toggled open. With the current behavior, the aggregated balance will only display when "All Networks" filter is selected, and when the "Current Network" is selected, it will aggregate balances only for that chain. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28662?quickstart=1) ## **Related issues** Fixes: Current chain aggregated balance showing up in cross chain aggregated balance when current network is filterd. ## **Manual testing steps** 1. Go to this page... 2. 3. ## **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** - [ ] 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. --- .../network-filter/network-filter.tsx | 4 +-- ui/selectors/selectors.js | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx index 9b5cb3797e7c..de68d8d6e13e 100644 --- a/ui/components/app/assets/asset-list/network-filter/network-filter.tsx +++ b/ui/components/app/assets/asset-list/network-filter/network-filter.tsx @@ -5,9 +5,9 @@ import { getCurrentChainId, getCurrentNetwork, getPreferences, - getChainIdsToPoll, getShouldHideZeroBalanceTokens, getSelectedAccount, + getAllChainsToPoll, } from '../../../../../selectors'; import { getNetworkConfigurationsByChainId } from '../../../../../../shared/modules/selectors/networks'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; @@ -50,7 +50,7 @@ const NetworkFilter = ({ handleClose }: SortControlProps) => { const shouldHideZeroBalanceTokens = useSelector( getShouldHideZeroBalanceTokens, ); - const allChainIDs = useSelector(getChainIdsToPoll); + const allChainIDs = useSelector(getAllChainsToPoll); const { formattedTokensWithBalancesPerChain } = useGetFormattedTokensPerChain( selectedAccount, shouldHideZeroBalanceTokens, diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index eea467ec0f16..154864dc9af4 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -2369,6 +2369,42 @@ export const getAllEnabledNetworks = createDeepEqualSelector( ), ); +/* + * USE THIS WITH CAUTION + * + * Only use this selector if you are absolutely sure that your UI component needs + * data from _all chains_ to compute a value. Else, use `getChainIdsToPoll`. + * + * Examples: + * - Components that should NOT use this selector: + * - Token list: This only needs to poll for chains based on the network filter + * (potentially only one chain). In this case, use `getChainIdsToPoll`. + * - Components that SHOULD use this selector: + * - Aggregated balance: This needs to display data regardless of network filter + * selection (always showing aggregated balances across all chains). + * + * Key Considerations: + * - This selector can cause expensive computations. It should only be used when + * necessary, and where possible, optimized to use `getChainIdsToPoll` instead. + * - Logic Overview: + * - If `PORTFOLIO_VIEW` is not enabled, the selector returns only the `currentChainId`. + * - Otherwise, it includes all chains from `networkConfigurations`, excluding + * `TEST_CHAINS`, while ensuring the `currentChainId` is included. + */ +export const getAllChainsToPoll = createDeepEqualSelector( + getNetworkConfigurationsByChainId, + getCurrentChainId, + (networkConfigurations, currentChainId) => { + if (!process.env.PORTFOLIO_VIEW) { + return [currentChainId]; + } + + return Object.keys(networkConfigurations).filter( + (chainId) => chainId === currentChainId || !TEST_CHAINS.includes(chainId), + ); + }, +); + export const getChainIdsToPoll = createDeepEqualSelector( getNetworkConfigurationsByChainId, getCurrentChainId,

{)LCLh|H1ko=-#Plv$?al5(a?X&IO;k7&Kl%e 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 TGZ8;@IXwp@1{?neO5oSrb3o-; diff --git a/test/e2e/mock-cdn/ppom-version-headers.json b/test/e2e/mock-cdn/ppom-version-headers.json index ad50d161d1dd..1ab52017d4a0 100644 --- a/test/e2e/mock-cdn/ppom-version-headers.json +++ b/test/e2e/mock-cdn/ppom-version-headers.json @@ -1,3 +1,3 @@ { - "Etag": "W/\"7aa74f7c18a5cb2601e4fc6afcadc9cc\"" + "Etag": "W/\"0b264c1a98f3bb20ea7741b37850b69a\"" } diff --git a/test/e2e/mock-cdn/ppom-version.json b/test/e2e/mock-cdn/ppom-version.json index b529f71a0f1c..59216b0ce281 100644 --- a/test/e2e/mock-cdn/ppom-version.json +++ b/test/e2e/mock-cdn/ppom-version.json @@ -152,302 +152,302 @@ { "name": "stale_diff", "chainId": "0x38", - "version": "0.0.482", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x38/0.0.482", - "timestamp": "2024-10-27T12:49:57.410484" + "version": "0.0.483", + "checksum": "b979504b7de98aad15400df3e366ab734725c11a30d4f893c7dc9ac07bdc230a", + "signature": "1333191445c23cebd097f6405d50298f7866fda9f209522054292d66f2c01d227f9663597976af133dfcfe16fcd325d8c65ac51345863a5412211b979f1fc80e", + "hashSignature": "ce019245ed127bbb5c0cff6c06d7485ed4eb79df1870b4f513e75e230ee6719637bdcb7fc22046315ccb8d88996ab77edd7cc53ffa359740d66f3ef4f6260e00", + "filePath": "stale_diff/0x38/0.0.483", + "timestamp": "2024-10-28T13:04:21.808185" }, { "name": "config", "chainId": "0x38", - "version": "0.0.482", + "version": "0.0.483", "checksum": "e29783f8be2392ee395055382aa7b19a78564f99915bb749492577712c18c6f0", "signature": "0e4e28c1e10d8e0243fd6009787dc5742a76cc5460c2565459094aa112fe2cbddb62001b8d5771ed75e836d842f87baf2a8bdc099003797ebc6c7a80079f4701", "hashSignature": "ac2ba4a855e4e271f1ce7f4f6b67ac1b10a56378ab3f31a638aff4d5f5ccccc9385d976eec2fb304427810995ed22bd755adac62d15d4fcf6fd4934ee3883d00", - "filePath": "config/0x38/0.0.482", - "timestamp": "2024-10-27T12:49:57.410737" + "filePath": "config/0x38/0.0.483", + "timestamp": "2024-10-28T13:04:21.808484" }, { "name": "stale_diff", "chainId": "0x1", - "version": "0.0.525", - "checksum": "ae3059765d220e8cda72aa43638916b9baac84f264a39a1d147a5b144378df62", - "signature": "3ed03cfa8bee704816a9ceb270fda86d7b007f0fe510d6acc40f96b15819c114fbd768d8845d75ab803c981eb151b4b0a24af705a27e1f96381bdc6dc5e3b50f", - "hashSignature": "9394bc16a948ab41ee74c0270a900092cbb8707fe72d3576fd75f0b87c698089c0a10b45a20ea47691a90cee427e605f81838b87424291902a9b54fec19e0709", - "filePath": "stale_diff/0x1/0.0.525", - "timestamp": "2024-10-27T12:50:03.871663" + "version": "0.0.526", + "checksum": "d0182b5ad9ce2bdf7c237fdf344e25c83eaab132e770db5d259f50c98894249e", + "signature": "51403083c28a827d20a2d998a8f25e6ed64e79fa8a330e7760a4090987c0162ab14b0ed12c4bb9c82dfe2d198e242e4211b7c882ae64bce4bdbcc37cca68990e", + "hashSignature": "b6196a6ef87f47fcbf91d84367a26e05491ef1ca7d7204f7cd4e2f78edbdacff842b3189ff4a5c73ff7eae3f10f1d0d2f677954658556c5af2add0176daa1b00", + "filePath": "stale_diff/0x1/0.0.526", + "timestamp": "2024-10-28T13:04:33.812986" }, { "name": "config", "chainId": "0x1", - "version": "0.0.525", + "version": "0.0.526", "checksum": "abe69e1c8f6084d26b1d556bb3ae4919628ac4bf3b468bea95e3c14254bdf290", "signature": "52ffaf9e1a543f8164ea93558467f7f4e02c15650daf92f1a1e374848c53b91dcca96037fd6d7bd63b13e7fcf88a1bcc9fe7c7915d8d6949bd153e6bf6b1a403", "hashSignature": "83c1edb28635049e4c99d8610782506818ef071de95df49f9242f229991924b4ea829782b0ac125de3f763fc7415baaebf3732a920fb4d01861e1fdd5cb86207", - "filePath": "config/0x1/0.0.525", - "timestamp": "2024-10-27T12:50:03.871914" + "filePath": "config/0x1/0.0.526", + "timestamp": "2024-10-28T13:04:33.813277" }, { "name": "stale_diff", "chainId": "0x89", - "version": "0.0.481", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x89/0.0.481", - "timestamp": "2024-10-27T12:50:09.561729" + "version": "0.0.482", + "checksum": "5cd3b461f37480a0f84c5769156ed4d362f0dade06c1bf3fc266ed3de7fadb6d", + "signature": "5f049aab6c8a5c18dcdf1d730b1c306ae3728f288af4bcc22e3cc6a243bbe2673444a6e4316b2dbd084e471aec85f102f864011c1e7438367b243b43d1b3e604", + "hashSignature": "78ae36e78e6c3c3d2aa48a86c19cbd667b907c1ada62aaac8a34e3982be16ec9727041264bf61d445469de2fd56a94f8c4289e17a560f25cb31240c8d41ca303", + "filePath": "stale_diff/0x89/0.0.482", + "timestamp": "2024-10-28T13:04:41.430575" }, { "name": "config", "chainId": "0x89", - "version": "0.0.481", + "version": "0.0.482", "checksum": "eb3b41ae8c3bbf9595dcded8e1b9090c8ed2427e236375652f6fd701c6e134b4", "signature": "79552c7fa525e05c9c086fe8d8ecb49375be796176877e17332fa1137d4c653f224873bdac2b6fd4fe63fcfe6d404778684116e98fdb0563f63ae1efcfafe60d", "hashSignature": "9c8a84e430578290eceea60ba7dec3695e0cfd343f7ac3e2667c8634afa3dd65d6ec25b112ac8260dbe3e6bc9e00ba906c4212975652354b1423f6ee0ec7b20e", - "filePath": "config/0x89/0.0.481", - "timestamp": "2024-10-27T12:50:09.561989" + "filePath": "config/0x89/0.0.482", + "timestamp": "2024-10-28T13:04:41.430884" }, { "name": "stale_diff", "chainId": "0xa", - "version": "0.0.481", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0xa/0.0.481", - "timestamp": "2024-10-27T12:50:15.189820" + "version": "0.0.482", + "checksum": "8f41a8616fc5fb1e55de136d6e0aef1e834049a1452e148325bbbb059233acb8", + "signature": "e7e1a80c48a32902cc526dae4ae8e05980f059de0447bfbfe533c26ff03f8721c004e4f35888708964a19bebc5f0d91753f741dabe0418a0d8a8af38f71e0406", + "hashSignature": "056b583909986fee8cb115d2a0827a704e391b8a3f3392d9d42be17e60344b397fab2ae31561c4211a67dfcf6af86b1e22facac7c47393cb3b978a9396888d0a", + "filePath": "stale_diff/0xa/0.0.482", + "timestamp": "2024-10-28T13:04:49.257520" }, { "name": "config", "chainId": "0xa", - "version": "0.0.481", + "version": "0.0.482", "checksum": "eb3b41ae8c3bbf9595dcded8e1b9090c8ed2427e236375652f6fd701c6e134b4", "signature": "79552c7fa525e05c9c086fe8d8ecb49375be796176877e17332fa1137d4c653f224873bdac2b6fd4fe63fcfe6d404778684116e98fdb0563f63ae1efcfafe60d", "hashSignature": "9c8a84e430578290eceea60ba7dec3695e0cfd343f7ac3e2667c8634afa3dd65d6ec25b112ac8260dbe3e6bc9e00ba906c4212975652354b1423f6ee0ec7b20e", - "filePath": "config/0xa/0.0.481", - "timestamp": "2024-10-27T12:50:15.190077" + "filePath": "config/0xa/0.0.482", + "timestamp": "2024-10-28T13:04:49.257852" }, { "name": "stale_diff", "chainId": "0xa4b1", - "version": "0.0.481", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0xa4b1/0.0.481", - "timestamp": "2024-10-27T12:50:21.791169" + "version": "0.0.482", + "checksum": "d7de3157af44ddbb82ca8913f6396a4690e61f6d7179ed65b0cc2bf591b6da3a", + "signature": "d3c8a37b8778f2db1e91a49839f1b0b24f2c96321e7b0c659d947c360f68dd61ea83e3983ee116fd2fc23fb78b8fee1dc9e00e89463c86b8d5c06dd36cd0110e", + "hashSignature": "327ca5b7af1931368881c98f762741f54152c75e06878e89bb341eed1095a58ca88fccd04eb6a1018c7d539947b0bbec37bb60a5c6eadb0221865b1d59d4030f", + "filePath": "stale_diff/0xa4b1/0.0.482", + "timestamp": "2024-10-28T13:05:00.218564" }, { "name": "config", "chainId": "0xa4b1", - "version": "0.0.481", + "version": "0.0.482", "checksum": "eb3b41ae8c3bbf9595dcded8e1b9090c8ed2427e236375652f6fd701c6e134b4", "signature": "79552c7fa525e05c9c086fe8d8ecb49375be796176877e17332fa1137d4c653f224873bdac2b6fd4fe63fcfe6d404778684116e98fdb0563f63ae1efcfafe60d", "hashSignature": "9c8a84e430578290eceea60ba7dec3695e0cfd343f7ac3e2667c8634afa3dd65d6ec25b112ac8260dbe3e6bc9e00ba906c4212975652354b1423f6ee0ec7b20e", - "filePath": "config/0xa4b1/0.0.481", - "timestamp": "2024-10-27T12:50:21.791427" + "filePath": "config/0xa4b1/0.0.482", + "timestamp": "2024-10-28T13:05:00.218867" }, { "name": "stale_diff", "chainId": "0xa86a", - "version": "0.0.481", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0xa86a/0.0.481", - "timestamp": "2024-10-27T12:50:27.602732" + "version": "0.0.482", + "checksum": "88ab495f0dc39bb9bb0d607b562ef8b16ededfd48aab7b5825126b4f19c02ccf", + "signature": "84e1b810e93bd3cd0732ffbdf9f7003140da90758c5ebbefc9516979bb47b3c9a4117e757883c4089c44f165f507d43885700bb50e6126fb037e1f62b8e75708", + "hashSignature": "b38ce4c3a012de90e8949df5b32a8cf74a3a9edf2d602197d01ebfd4b1cd8fd32defbb0d363f93abf8241cd7224614723cb7fba7e744fccd82bee3b082138e0b", + "filePath": "stale_diff/0xa86a/0.0.482", + "timestamp": "2024-10-28T13:05:09.315523" }, { "name": "config", "chainId": "0xa86a", - "version": "0.0.481", + "version": "0.0.482", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0xa86a/0.0.481", - "timestamp": "2024-10-27T12:50:27.602991" + "filePath": "config/0xa86a/0.0.482", + "timestamp": "2024-10-28T13:05:09.315795" }, { "name": "stale_diff", "chainId": "0xe708", - "version": "0.0.435", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0xe708/0.0.435", - "timestamp": "2024-10-27T12:50:39.077328" + "version": "0.0.436", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0xe708/0.0.436", + "timestamp": "2024-10-28T13:05:28.153528" }, { "name": "config", "chainId": "0xe708", - "version": "0.0.435", + "version": "0.0.436", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0xe708/0.0.435", - "timestamp": "2024-10-27T12:50:39.077595" + "filePath": "config/0xe708/0.0.436", + "timestamp": "2024-10-28T13:05:28.153794" }, { "name": "stale_diff", "chainId": "0x2105", - "version": "0.0.370", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x2105/0.0.370", - "timestamp": "2024-10-27T12:50:33.715820" + "version": "0.0.371", + "checksum": "2ac83e8d092ae95bdcc2e8078e458f0c3f2ea9bf46c00fb40b37b070bb92dc63", + "signature": "6c04fcdc49fd49502da3a73d6e3c721004d40eec65f2dec02cfbe8a2a5f253d3747d85253894508bba57d850d647707b26f2c78a5dfbff8038985ae413474f05", + "hashSignature": "d791983edf3b111bf4448acc6b54cf4f9072358c7748a6e247768899d0ae36c1cdc70c45caa9e8c6d34f151256792e365f9224eef236610067c4489d48bc840f", + "filePath": "stale_diff/0x2105/0.0.371", + "timestamp": "2024-10-28T13:05:19.542338" }, { "name": "config", "chainId": "0x2105", - "version": "0.0.370", + "version": "0.0.371", "checksum": "eb3b41ae8c3bbf9595dcded8e1b9090c8ed2427e236375652f6fd701c6e134b4", "signature": "79552c7fa525e05c9c086fe8d8ecb49375be796176877e17332fa1137d4c653f224873bdac2b6fd4fe63fcfe6d404778684116e98fdb0563f63ae1efcfafe60d", "hashSignature": "9c8a84e430578290eceea60ba7dec3695e0cfd343f7ac3e2667c8634afa3dd65d6ec25b112ac8260dbe3e6bc9e00ba906c4212975652354b1423f6ee0ec7b20e", - "filePath": "config/0x2105/0.0.370", - "timestamp": "2024-10-27T12:50:33.716077" + "filePath": "config/0x2105/0.0.371", + "timestamp": "2024-10-28T13:05:19.542688" }, { "name": "stale_diff", "chainId": "0xaa36a7", - "version": "0.0.289", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0xaa36a7/0.0.289", - "timestamp": "2024-10-27T12:50:44.187191" + "version": "0.0.290", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0xaa36a7/0.0.290", + "timestamp": "2024-10-28T13:05:35.180841" }, { "name": "config", "chainId": "0xaa36a7", - "version": "0.0.289", + "version": "0.0.290", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0xaa36a7/0.0.289", - "timestamp": "2024-10-27T12:50:44.187491" + "filePath": "config/0xaa36a7/0.0.290", + "timestamp": "2024-10-28T13:05:35.181112" }, { "name": "stale_diff", "chainId": "0xcc", - "version": "0.0.238", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0xcc/0.0.238", - "timestamp": "2024-10-27T12:50:49.423599" + "version": "0.0.239", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0xcc/0.0.239", + "timestamp": "2024-10-28T13:05:42.918569" }, { "name": "config", "chainId": "0xcc", - "version": "0.0.238", + "version": "0.0.239", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0xcc/0.0.238", - "timestamp": "2024-10-27T12:50:49.423903" + "filePath": "config/0xcc/0.0.239", + "timestamp": "2024-10-28T13:05:42.918850" }, { "name": "stale_diff", "chainId": "0x0", - "version": "0.0.179", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x0/0.0.179", - "timestamp": "2024-10-27T12:50:57.113651" + "version": "0.0.180", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0x0/0.0.180", + "timestamp": "2024-10-28T13:05:51.610174" }, { "name": "config", "chainId": "0x0", - "version": "0.0.179", + "version": "0.0.180", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0x0/0.0.179", - "timestamp": "2024-10-27T12:50:57.113910" + "filePath": "config/0x0/0.0.180", + "timestamp": "2024-10-28T13:05:51.610489" }, { "name": "stale_diff", "chainId": "0x144", - "version": "0.0.258", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x144/0.0.258", - "timestamp": "2024-10-27T12:51:10.842031" + "version": "0.0.259", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0x144/0.0.259", + "timestamp": "2024-10-28T13:05:59.914265" }, { "name": "config", "chainId": "0x144", - "version": "0.0.258", + "version": "0.0.259", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0x144/0.0.258", - "timestamp": "2024-10-27T12:51:10.842292" + "filePath": "config/0x144/0.0.259", + "timestamp": "2024-10-28T13:05:59.914582" }, { "name": "stale_diff", "chainId": "0x82750", - "version": "0.0.180", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x82750/0.0.180", - "timestamp": "2024-10-27T12:51:16.756456" + "version": "0.0.181", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0x82750/0.0.181", + "timestamp": "2024-10-28T13:06:08.538356" }, { "name": "config", "chainId": "0x82750", - "version": "0.0.180", + "version": "0.0.181", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0x82750/0.0.180", - "timestamp": "2024-10-27T12:51:16.756829" + "filePath": "config/0x82750/0.0.181", + "timestamp": "2024-10-28T13:06:08.538665" }, { "name": "stale_diff", "chainId": "0x1b58", - "version": "0.0.180", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x1b58/0.0.180", - "timestamp": "2024-10-27T12:51:23.355191" + "version": "0.0.181", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0x1b58/0.0.181", + "timestamp": "2024-10-28T13:06:16.179180" }, { "name": "config", "chainId": "0x1b58", - "version": "0.0.180", + "version": "0.0.181", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0x1b58/0.0.180", - "timestamp": "2024-10-27T12:51:23.355470" + "filePath": "config/0x1b58/0.0.181", + "timestamp": "2024-10-28T13:06:16.179453" }, { "name": "stale_diff", "chainId": "0x138d5", - "version": "0.0.180", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x138d5/0.0.180", - "timestamp": "2024-10-27T12:51:29.701505" + "version": "0.0.181", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0x138d5/0.0.181", + "timestamp": "2024-10-28T13:06:24.630573" }, { "name": "config", "chainId": "0x138d5", - "version": "0.0.180", + "version": "0.0.181", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0x138d5/0.0.180", - "timestamp": "2024-10-27T12:51:29.701771" + "filePath": "config/0x138d5/0.0.181", + "timestamp": "2024-10-28T13:06:24.630854" }, { "name": "stale", @@ -462,42 +462,42 @@ { "name": "stale_diff", "chainId": "0x1b6e6", - "version": "0.0.114", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x1b6e6/0.0.114", - "timestamp": "2024-10-27T12:51:43.249992" + "version": "0.0.115", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0x1b6e6/0.0.115", + "timestamp": "2024-10-28T13:06:41.431736" }, { "name": "config", "chainId": "0x1b6e6", - "version": "0.0.114", + "version": "0.0.115", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0x1b6e6/0.0.114", - "timestamp": "2024-10-27T12:51:43.250277" + "filePath": "config/0x1b6e6/0.0.115", + "timestamp": "2024-10-28T13:06:41.432043" }, { "name": "stale_diff", "chainId": "0x138d4", - "version": "0.0.75", - "checksum": "3bab009ec87420bb6de56069cb3b76614c891688beb233bfe408aa60757478fb", - "signature": "481f19700f7399df4eefc8162e0229ea2a81fbfcbd6c327fac4ad79aaa7ac3aa8215b36bbdc74986c008cd60ffec428aa7ea04cd166faad938e1f9f4cef4b804", - "hashSignature": "f4991d29cefb09343ed5b3332416e2bab7428499373ea4bb0f6dd9bc1955eed33f80f383f70350a74ccddbea07e52eb4a144559626214a6c0f8c726b28be2f03", - "filePath": "stale_diff/0x138d4/0.0.75", - "timestamp": "2024-10-27T12:51:36.220430" + "version": "0.0.76", + "checksum": "bf5bd9d3a9db56228750731c953aed9ba51ac879cd9b5f65ed5076051b5a6754", + "signature": "ae2fe7d9b1655ebc126684570c00579e2cf0f34f1af999beb6ff83d38d770060674b0c41477809d2cd621ffc5dc6bdbb1240e00644fd3ed15527ac951462cf08", + "hashSignature": "0610331fd64fa3b95bb269329979583f96bbaf4c7298fc76e3dba09a877c28af7d7a0ecab4e1e8bcae0173c917c2c09949210fd7a3582120110c500a61e89f02", + "filePath": "stale_diff/0x138d4/0.0.76", + "timestamp": "2024-10-28T13:06:31.754098" }, { "name": "config", "chainId": "0x138d4", - "version": "0.0.75", + "version": "0.0.76", "checksum": "3e1772693c4e2fa91ae00c4b79d546f36e97525daa60baab372c145e979e19f4", "signature": "f56c9387e07892aceb1018db6dc7e32cce0528c131b6ac1822e12e87dd43d40fdb9f3e17a9c6d7fd25a095e000db19acaf3f93c42142c48f35b06f07998f0f0e", "hashSignature": "ae3fbbe3f87e48e537e9c1c9601fa5706ed72145250e5f4a2d7a4902d59e2c770c7c1ef88bde15505fa6bcbf24f85ef1b5ad48b76447a3479f7f7a462f082302", - "filePath": "config/0x138d4/0.0.75", - "timestamp": "2024-10-27T12:51:36.220693" + "filePath": "config/0x138d4/0.0.76", + "timestamp": "2024-10-28T13:06:31.754438" }, { "name": "stale", diff --git a/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts b/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts index ce197e462c80..550dc5f2acd9 100644 --- a/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts +++ b/test/e2e/tests/ppom/mocks/mock-server-json-rpc.ts @@ -14,6 +14,10 @@ type RequestConfig = [ // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any result?: any; + /** optional result value returned in JSON response */ + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + error?: any; }, ]; @@ -48,14 +52,21 @@ export async function mockServerJsonRpc( listOfRequestConfigs: RequestConfig[], ) { for (const [method, options] of listOfRequestConfigs) { - const { methodResultVariant, params, result: _result } = options || {}; + const { + methodResultVariant, + params, + result: _result, + error: _error, + } = options || {}; const result = _result || + _error || mockJsonRpcResult[method][methodResultVariant || DEFAULT_VARIANT]; await mockServer - .forPost() + .forPost(/infura/u) + .always() .withJsonBodyIncluding(params ? { method, params } : { method }) // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js new file mode 100644 index 000000000000..e35c23f22c49 --- /dev/null +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-contract-interaction.spec.js @@ -0,0 +1,238 @@ +const FixtureBuilder = require('../../fixture-builder'); + +const { + WINDOW_TITLES, + defaultGanacheOptions, + openDapp, + unlockWallet, + withFixtures, +} = require('../../helpers'); +const { mockServerJsonRpc } = require('./mocks/mock-server-json-rpc'); + +async function mockInfura(mockServer) { + await mockServerJsonRpc(mockServer, [ + ['eth_blockNumber'], + ['eth_estimateGas'], + [ + 'eth_call', + { + params: [ + { + to: '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39', + data: '0xf0002ea90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005cfe73b6021e818b776b421b1c4db2474086a7e100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000', + }, + ], + result: + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000006c8aafe1077a8', + }, + ], + [ + 'eth_call', + { + params: [ + { + to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000', + data: '0x01ffc9a780ac58cd00000000000000000000000000000000000000000000000000000000', + }, + ], + error: { + code: -32000, + message: 'execution reverted', + }, + }, + ], + [ + 'eth_call', + { + params: [ + { + to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000', + data: '0x01ffc9a7d9b67a2600000000000000000000000000000000000000000000000000000000', + }, + ], + error: { + code: -32000, + message: 'execution reverted', + }, + }, + ], + [ + 'eth_call', + { + params: [ + { + to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000', + data: '0x95d89b41', + }, + ], + error: { + code: -32000, + message: 'execution reverted', + }, + }, + ], + [ + 'eth_call', + { + params: [ + { + to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000', + data: '0x313ce567', + }, + ], + error: { + code: -32000, + message: 'execution reverted', + }, + }, + ], + [ + 'eth_getStorageAt', + { + params: [ + '0x00008f1149168c1d2fa1eba1ad3e9cd644510000', + '0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3', + ], + result: + '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + ], + [ + 'eth_getStorageAt', + { + params: [ + '0x00008f1149168c1d2fa1eba1ad3e9cd644510000', + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + ], + result: + '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + ], + ['eth_getBlockByNumber'], + ['eth_getBalance'], + [ + 'eth_getCode', + { + params: ['0x00008f1149168c1d2fa1eba1ad3e9cd644510000'], + result: + '0x6080604052600436106100545760003560e01c8062f714ce1461005957806312065fe0146100825780633158952e146100ad578063715018a6146100b75780638da5cb5b146100ce578063f2fde38b146100f9575b600080fd5b34801561006557600080fd5b50610080600480360381019061007b91906104d4565b610122565b005b34801561008e57600080fd5b50610097610227565b6040516100a49190610523565b60405180910390f35b6100b561022f565b005b3480156100c357600080fd5b506100cc610231565b005b3480156100da57600080fd5b506100e3610245565b6040516100f0919061054d565b60405180910390f35b34801561010557600080fd5b50610120600480360381019061011b9190610568565b61026e565b005b61012a6102f1565b4782111561016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161016490610618565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036101dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101d3906106aa565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050158015610222573d6000803e3d6000fd5b505050565b600047905090565b565b6102396102f1565b610243600061036f565b565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6102766102f1565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102e5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102dc9061073c565b60405180910390fd5b6102ee8161036f565b50565b6102f9610433565b73ffffffffffffffffffffffffffffffffffffffff16610317610245565b73ffffffffffffffffffffffffffffffffffffffff161461036d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610364906107a8565b60405180910390fd5b565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600033905090565b600080fd5b6000819050919050565b61045381610440565b811461045e57600080fd5b50565b6000813590506104708161044a565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104a182610476565b9050919050565b6104b181610496565b81146104bc57600080fd5b50565b6000813590506104ce816104a8565b92915050565b600080604083850312156104eb576104ea61043b565b5b60006104f985828601610461565b925050602061050a858286016104bf565b9150509250929050565b61051d81610440565b82525050565b60006020820190506105386000830184610514565b92915050565b61054781610496565b82525050565b6000602082019050610562600083018461053e565b92915050565b60006020828403121561057e5761057d61043b565b5b600061058c848285016104bf565b91505092915050565b600082825260208201905092915050565b7f52657175657374656420616d6f756e7420657863656564732074686520636f6e60008201527f74726163742062616c616e63652e000000000000000000000000000000000000602082015250565b6000610602602e83610595565b915061060d826105a6565b604082019050919050565b60006020820190508181036000830152610631816105f5565b9050919050565b7f526563697069656e7420616464726573732063616e6e6f74206265207468652060008201527f7a65726f20616464726573732e00000000000000000000000000000000000000602082015250565b6000610694602d83610595565b915061069f82610638565b604082019050919050565b600060208201905081810360008301526106c381610687565b9050919050565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b6000610726602683610595565b9150610731826106ca565b604082019050919050565b6000602082019050818103600083015261075581610719565b9050919050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b6000610792602083610595565b915061079d8261075c565b602082019050919050565b600060208201905081810360008301526107c181610785565b905091905056fea2646970667358221220ac74f30418aa2326105b7dea03d605de28d6069773bd4b434837ceb2008a023a64736f6c63430008130033', + }, + ], + [ + 'eth_getTransactionCount', + { + params: ['0x5cfe73b6021e818b776b421b1c4db2474086a7e1'], + result: '0x0', + }, + ], + ]); + + await mockServer + .forPost(/infura/u) + .withJsonBodyIncluding({ + method: 'debug_traceCall', + params: [ + { + accessList: [], + data: '0xef5cfb8c0000000000000000000000000b3e87a076ac4b0d1975f0f232444af6deb96c59', + from: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + gas: '0x1c9c380', + maxFeePerGas: '0x1fc3f678c', + maxPriorityFeePerGas: '0x0', + to: '0x00008f1149168c1d2fa1eba1ad3e9cd644510000', + type: '0x02', + }, + ], + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).id, + error: { + code: -32601, + message: + 'The method debug_traceCall does not exist/is not available', + }, + }, + }; + }); + + await mockServer + .forGet('https://www.4byte.directory/api/v1/signatures/') + .always() + .withQuery({ hex_signature: '0xef5cfb8c' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + count: 1, + next: null, + previous: null, + results: [ + { + id: 187294, + created_at: '2021-05-12T10:20:16.502438Z', + text_signature: 'claimRewards(address)', + hex_signature: '0xef5cfb8c', + bytes_signature: 'ï\\ûŒ', + }, + ], + }, + })); +} + +describe('PPOM Blockaid Alert - Malicious Contract interaction @no-mmi', function () { + it('should show banner alert', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPermissionControllerConnectedToTestDapp({ + useLocalhostHostname: true, + }) + .withPreferencesController({ + securityAlertsEnabled: true, + preferences: { + redesignedTransactionsEnabled: true, + redesignedConfirmationsEnabled: true, + isRedesignedConfirmationsDeveloperEnabled: true, + }, + }) + .build(), + defaultGanacheOptions, + testSpecificMock: mockInfura, + title: this.test.fullTitle(), + }, + + async ({ driver }) => { + await unlockWallet(driver); + await openDapp(driver, null, 'http://localhost:8080'); + + const expectedTitle = 'This is a deceptive request'; + const expectedDescription = + 'If you approve this request, a third party known for scams will take all your assets.'; + + // Click TestDapp button to send JSON-RPC request + await driver.clickElement('#maliciousContractInteractionButton'); + + // Wait for confirmation pop-up + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.assertElementNotPresent('.loading-indicator'); + + await driver.findElement({ + css: '[data-testid="confirm-banner-alert"]', + text: expectedTitle, + }); + + await driver.findElement({ + css: '[data-testid="confirm-banner-alert"]', + text: expectedDescription, + }); + }, + ); + }); +}); From b148c25974c962701bb2ccb6bfa263506be28b99 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 25 Nov 2024 20:42:02 +0530 Subject: [PATCH 16/40] feat: change description of enabling simulation message in settings (#28536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Change description of setting top enable simulation to include signatures. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3642 ## **Manual testing steps** 1. Go to settings page 2. Check setting to enable simulation ## **Screenshots/Recordings** Screenshot 2024-11-19 at 3 39 22 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 - [ ] 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: MetaMask Bot --- app/_locales/de/messages.json | 3 --- app/_locales/el/messages.json | 3 --- app/_locales/en/messages.json | 2 +- app/_locales/es/messages.json | 3 --- app/_locales/fr/messages.json | 3 --- app/_locales/hi/messages.json | 3 --- app/_locales/id/messages.json | 3 --- app/_locales/ja/messages.json | 3 --- app/_locales/ko/messages.json | 3 --- app/_locales/pt/messages.json | 3 --- app/_locales/ru/messages.json | 3 --- app/_locales/tl/messages.json | 3 --- app/_locales/tr/messages.json | 3 --- app/_locales/vi/messages.json | 3 --- app/_locales/zh_CN/messages.json | 3 --- .../security-tab/__snapshots__/security-tab.test.js.snap | 2 +- 16 files changed, 2 insertions(+), 44 deletions(-) diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 172fd05a5a73..0c8ba19030f2 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Wir konnten das Gas nicht schätzen. Es könnte einen Fehler im Contract geben und diese Transaktion könnte fehlschlagen." }, - "simulationsSettingDescription": { - "message": "Schalten Sie diese Option ein, um die Saldoänderungen von Transaktionen zu schätzen, bevor Sie diese bestätigen. Dies ist keine Garantie für das endgültige Ergebnis Ihrer Transaktionen. $1" - }, "simulationsSettingSubHeader": { "message": "Geschätzte Saldoänderungen" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 132265ee4167..31cd6809080b 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Δεν ήμασταν σε θέση να εκτιμήσουμε το τέλος συναλλαγής. Μπορεί να υπάρχει σφάλμα στο συμβόλαιο και η συναλλαγή αυτή να αποτύχει." }, - "simulationsSettingDescription": { - "message": "Ενεργοποιήστε το για να εκτιμήσετε τις αλλαγές στο υπόλοιπο των συναλλαγών πριν τις επιβεβαιώσετε. Αυτό δεν εγγυάται το τελικό αποτέλεσμα στις συναλλαγές σας. $1" - }, "simulationsSettingSubHeader": { "message": "Εκτίμηση μεταβολών υπολοίπου" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 060ba2a43dca..39c1b20d1a52 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -5085,7 +5085,7 @@ "message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail." }, "simulationsSettingDescription": { - "message": "Turn this on to estimate balance changes of transactions before you confirm them. This doesn't guarantee the final outcome of your transactions. $1" + "message": "Turn this on to estimate balance changes of transactions and signatures before you confirm them. This doesn't guarantee their final outcome. $1" }, "simulationsSettingSubHeader": { "message": "Estimate balance changes" diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 746c099ae75e..f2afe012534a 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "No pudimos estimar el gas. Podría haber un error en el contrato y esta transacción podría fallar." }, - "simulationsSettingDescription": { - "message": "Active esta opción para estimar los cambios de saldo de las transacciones antes de confirmarlas. Esto no garantiza el resultado final de sus transacciones. $1" - }, "simulationsSettingSubHeader": { "message": "Estimar cambios de saldo" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index bcfa71f5661b..99c3a6da2333 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Nous n’avons pas pu estimer le prix de carburant. Par conséquent, il se peut qu’il y ait une erreur dans le contrat et que cette transaction échoue." }, - "simulationsSettingDescription": { - "message": "Activez cette option pour estimer les changements de solde des transactions avant de les confirmer. Cela ne garantit pas le résultat final de vos transactions. $1" - }, "simulationsSettingSubHeader": { "message": "Estimer les changements de solde" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index f5484e624348..92d8d0bc7fa8 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "हम गैस का अनुमान नहीं लगा पाए। कॉन्ट्रैक्ट में कोई गड़बड़ी हो सकती है और यह ट्रांसेक्शन विफल हो सकता है।" }, - "simulationsSettingDescription": { - "message": "ट्रांसेक्शन को कन्फर्म करने से पहले बैलेंस अमाउंट में बदलाव का अनुमान लगाने के लिए इसे चालू करें। यह आपके ट्रांसेक्शन के फाइनल आउटकम की गारंटी नहीं देता है।$1" - }, "simulationsSettingSubHeader": { "message": "बैलेंस अमाउंट में बदलावों का अनुमान लगाएं" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index e7c719318f69..a474fc1afa1a 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Kami tidak dapat memperkirakan gas. Tampaknya ada kesalahan dalam kontrak dan transaksi ini berpotensi gagal." }, - "simulationsSettingDescription": { - "message": "Aktifkan untuk mengestimasikan perubahan saldo transaksi sebelum Anda mengonfirmasikannya. Ini tidak menjamin hasil akhir transaksi Anda. $1" - }, "simulationsSettingSubHeader": { "message": "Estimasikan perubahan saldo" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index a3e917d46600..80982f5b4144 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "ガス代を見積もることができませんでした。コントラクトにエラーがある可能性があり、このトランザクションは失敗するかもしれません。" }, - "simulationsSettingDescription": { - "message": "トランザクションを確定する前に残高の増減を予測するには、この機能をオンにします。これはトランザクションの最終的な結果を保証するものではありません。$1" - }, "simulationsSettingSubHeader": { "message": "予測される残高の増減" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index e1eeda4487a6..5fc7deb6a9ef 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "가스를 추정할 수 없었습니다. 계약에 오류가 있을 수 있으며 이 트랜잭션이 실패할 수 있습니다." }, - "simulationsSettingDescription": { - "message": "이 기능을 켜면 트랜잭션을 확정하기 전에 트랜잭션의 잔액 변동을 추정할 수 있습니다. 이 기능이 트랜잭션의 최종 결과를 보장하지는 않습니다. $1" - }, "simulationsSettingSubHeader": { "message": "예상 잔액 변동" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 1e39077afa51..18e1c97fb129 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Não conseguimos estimar o preço do gás. Pode haver um erro no contrato, e essa transação poderá falhar." }, - "simulationsSettingDescription": { - "message": "Ative para estimar as alterações de saldo das transações antes de confirmá-las. Isso não garante o resultado das suas transações. $1" - }, "simulationsSettingSubHeader": { "message": "Estimar alterações de saldo" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index f68ee11792a1..43e617a0aecd 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Мы не смогли оценить размер платы за газ. В контракте может быть ошибка, и эта транзакция может завершиться неудачно." }, - "simulationsSettingDescription": { - "message": "Включите эту опцию, чтобы спрогнозировать изменения баланса транзакций перед их подтверждением. Это не гарантирует окончательный результат ваших транзакций. $1" - }, "simulationsSettingSubHeader": { "message": "Спрогнозировать изменения баланса" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 4f6e3098171d..5bc72667356e 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Hindi namin nagawang tantyahin ang gas. Maaaring may error sa kontrata at maaaring mabigo ang transaksyong ito." }, - "simulationsSettingDescription": { - "message": "I-on ito para gumawa ng pagtataya sa mga pagbabago ng balanse ng mga transaksyon bago mo kumpirmahin ang mga iyon. Hindi nito iginagarantiya ang panghuling resulta ng iyong mga transaksyon. $1" - }, "simulationsSettingSubHeader": { "message": "Tinatayang mga pagbabago sa balanse" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 771089050317..a71a64576142 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Gaz tahmini yapamadık. Sözleşmede bir hata olabilir ve bu işlem başarısız olabilir." }, - "simulationsSettingDescription": { - "message": "Onaylamadan önce işlemlerdeki bakiye değişikliklerini tahmin etmek için bunu açın. İşlemlerinizin nihai sonucunu garanti etmez. $1" - }, "simulationsSettingSubHeader": { "message": "Bakiye değişikliklerini tahmin edin" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 1983185816c0..5fb6ee43bfb9 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "Chúng tôi không thể ước tính gas. Có thể đã xảy ra lỗi trong hợp đồng và giao dịch này có thể thất bại." }, - "simulationsSettingDescription": { - "message": "Bật tính năng này để ước tính thay đổi số dư của các giao dịch trước khi bạn xác nhận. Điều này không đảm bảo cho kết quả cuối cùng của giao dịch. $1" - }, "simulationsSettingSubHeader": { "message": "Ước tính thay đổi số dư" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index b6c050f6b264..ca1d0e3f7b48 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -4920,9 +4920,6 @@ "simulationErrorMessageV2": { "message": "我们无法估算燃料。合约中可能存在错误,这笔交易可能会失败。" }, - "simulationsSettingDescription": { - "message": "开启此选项,以便在确认交易之前估计交易余额的变化。这并不能保证交易的最终结果。$1" - }, "simulationsSettingSubHeader": { "message": "预计余额变化" }, diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index 0927d04f89cb..e7306f64bba7 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -740,7 +740,7 @@ exports[`Security Tab should match snapshot 1`] = ` > - Turn this on to estimate balance changes of transactions before you confirm them. This doesn't guarantee the final outcome of your transactions. + Turn this on to estimate balance changes of transactions and signatures before you confirm them. This doesn't guarantee their final outcome. Date: Mon, 25 Nov 2024 11:46:11 -0330 Subject: [PATCH 17/40] chore: Restrict MMI test runs (#28655) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The standard MMI e2e test suite now only runs on long-running branches, and for MMI-related changes. This should reduce CircleCI credit usage substantially. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28655?quickstart=1) ## **Related issues** No issue. This is just to reduce credit usage. ## **Manual testing steps** We should see in the logs for the `test-e2e-mmi` job on this PR that the tests were skipped. ## **Screenshots/Recordings** N/A ## **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. --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0817f18c36a4..3e3ccba9005e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -178,6 +178,7 @@ workflows: - prep-build-test-mmi: requires: - prep-deps + - check-mmi-trigger - prep-build-test-mmi-playwright: requires: - prep-deps @@ -803,6 +804,7 @@ jobs: - run: corepack enable - attach_workspace: at: . + - run: *check-mmi-trigger - run: name: Build extension for testing command: yarn build:test:mmi @@ -1197,6 +1199,7 @@ jobs: - run: sudo corepack enable - attach_workspace: at: . + - run: *check-mmi-trigger - run: name: Move test build to dist command: mv ./dist-test-mmi ./dist @@ -1286,6 +1289,7 @@ jobs: - run: sudo corepack enable - attach_workspace: at: . + - run: *check-mmi-trigger - run: name: Move test build to dist command: mv ./dist-test-mmi ./dist From 42c132eaeead2bffef536c41ac243a82af852f91 Mon Sep 17 00:00:00 2001 From: Jack Clancy Date: Mon, 25 Nov 2024 15:56:57 +0000 Subject: [PATCH 18/40] fix: swaps approval checking for approvals between 0 and unlimited (#28680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** For approval amounts that were greater than 0, but less than the amount the user was trying to swap. The swaps controller was filtering out quotes for users. This PR fixes a longstanding bug in the swaps controller, where we only checked if the approval amount was 0. Instead of when the approval is 0 OR approval amount is less than swap amount. This PR updates the logic to correctly reflect this case [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28680?quickstart=1) ## **Related issues** Fixes issue reported in Slack here https://consensys.slack.com/archives/C0220SURQ3E/p1732474780474849 ## **Manual testing steps** **Testing wallet with seeded state:** 1. Load [this wallet](https://share.1password.com/s#fZ0NxJ-NZWOMxhbKHBTIgE_0tkMir4wY2KdZI-iZu4Y) into your MetaMask 2. Try and Swap 1 USDC for Polygon on Polygon 3. Quotes should be returned New Testing Wallet 1. Create a swaps approval for some small amount 2. Try and swap a larger amount 3. Swaps quotes should be returned ## **Screenshots/Recordings** ### **Before** image ### **After** image ## **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. --- app/scripts/controllers/swaps/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/swaps/index.ts b/app/scripts/controllers/swaps/index.ts index 308021cd14cf..9160eb0b66e1 100644 --- a/app/scripts/controllers/swaps/index.ts +++ b/app/scripts/controllers/swaps/index.ts @@ -383,12 +383,11 @@ export default class SwapsController extends BaseController< const [firstQuote] = Object.values(newQuotes); // For a user to be able to swap a token, they need to have approved the MetaSwap contract to withdraw that token. - // _getERC20Allowance() returns the amount of the token they have approved for withdrawal. If that amount is greater - // than 0, it means that approval has already occurred and is not needed. Otherwise, for tokens to be swapped, a new - // call of the ERC-20 approve method is required. + // _getERC20Allowance() returns the amount of the token they have approved for withdrawal. If that amount is either + // zero or less than the soucreAmount of the swap, a new call of the ERC-20 approve method is required. approvalRequired = firstQuote.approvalNeeded && - allowance.eq(0) && + (allowance.eq(0) || allowance.lt(firstQuote.sourceAmount)) && firstQuote.aggregator !== 'wrappedNative'; if (!approvalRequired) { newQuotes = mapValues(newQuotes, (quote) => ({ From 0bf00e62156ed4a9bbf8bec3ce1206ba31c5b442 Mon Sep 17 00:00:00 2001 From: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:02:15 +0400 Subject: [PATCH 19/40] fix: SonarCloud workflow_run (#28693) 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/28693?quickstart=1) There was a typo in the name of the workflow, causing the SonarCloud workflow to not run. This PR corrects the mistake. ## **Related issues** Fixes: https://consensys.slack.com/archives/CTQAGKY5V/p1732116793123109 ## **Manual testing steps** 1. As workflows called by `workflow_run` only run on the default branch, we can only make sure that SonarCloud 100% runs after merging. I tested it on a private repository though on my GitHub account, and the workflow worked there. ## **Screenshots/Recordings** Not applicable ## **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. --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 9ca9f02e2ae5..8b12876de1cd 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -8,7 +8,7 @@ name: SonarCloud on: workflow_run: workflows: - - Run tests + - Main types: - completed From 2e2edb5269e39d9baf2f866128776fe2d4bd8dc6 Mon Sep 17 00:00:00 2001 From: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:09:48 +0100 Subject: [PATCH 20/40] test: [POM] Migrate create btc account e2e tests to TS and Page Object Model (#28437) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - Migrate create btc account e2e tests to TS and Page Object Model [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27155?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28542 ## **Manual testing steps** Check code readability, make sure tests pass. ## **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. --- test/e2e/flask/btc/common-btc.ts | 35 +-- test/e2e/flask/btc/create-btc-account.spec.ts | 234 ++++++++---------- test/e2e/helpers.js | 79 ------ .../page-objects/pages/account-list-page.ts | 109 ++++++-- .../tests/account/account-custom-name.spec.ts | 2 +- test/e2e/tests/account/add-account.spec.ts | 4 +- .../notifications/account-syncing/helpers.ts | 15 -- .../account-syncing/new-user-sync.spec.ts | 24 +- .../onboarding-with-opt-out.spec.ts | 22 +- .../sync-after-adding-account.spec.ts | 6 +- test/e2e/tests/tokens/nft/import-nft.spec.ts | 2 +- 11 files changed, 255 insertions(+), 277 deletions(-) diff --git a/test/e2e/flask/btc/common-btc.ts b/test/e2e/flask/btc/common-btc.ts index 452f9ad44f6b..05f382281e29 100644 --- a/test/e2e/flask/btc/common-btc.ts +++ b/test/e2e/flask/btc/common-btc.ts @@ -1,6 +1,6 @@ import { Mockttp } from 'mockttp'; import FixtureBuilder from '../../fixture-builder'; -import { withFixtures, unlockWallet } from '../../helpers'; +import { withFixtures } from '../../helpers'; import { DEFAULT_BTC_ACCOUNT, DEFAULT_BTC_BALANCE, @@ -11,7 +11,9 @@ import { } from '../../constants'; import { MultichainNetworks } from '../../../../shared/constants/multichain/networks'; import { Driver } from '../../webdriver/driver'; -import messages from '../../../../app/_locales/en/messages.json'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; const QUICKNODE_URL_REGEX = /^https:\/\/.*\.btc.*\.quiknode\.pro(\/|$)/u; @@ -21,27 +23,6 @@ export enum SendFlowPlaceHolders { LOADING = 'Preparing transaction', } -export async function createBtcAccount(driver: Driver) { - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.clickElement({ - text: messages.addNewBitcoinAccount.message, - tag: 'button', - }); - await driver.clickElementAndWaitToDisappear( - { - text: 'Add account', - tag: 'button', - }, - // Longer timeout than usual, this reduces the flakiness - // around Bitcoin account creation (mainly required for - // Firefox) - 5000, - ); -} - export function btcToSats(btc: number): number { // Watchout, we're not using BigNumber(s) here (but that's ok for test purposes) return btc * SATS_IN_1_BTC; @@ -231,8 +212,12 @@ export async function withBtcAccountSnap( ], }, async ({ driver, mockServer }: { driver: Driver; mockServer: Mockttp }) => { - await unlockWallet(driver); - await createBtcAccount(driver); + await loginWithBalanceValidation(driver); + // create one BTC account + await new HeaderNavbar(driver).openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addNewBtcAccount(); await test(driver, mockServer); }, ); diff --git a/test/e2e/flask/btc/create-btc-account.spec.ts b/test/e2e/flask/btc/create-btc-account.spec.ts index 1b10599bf5ca..0dfb24a8a931 100644 --- a/test/e2e/flask/btc/create-btc-account.spec.ts +++ b/test/e2e/flask/btc/create-btc-account.spec.ts @@ -1,26 +1,22 @@ import { strict as assert } from 'assert'; import { Suite } from 'mocha'; -import messages from '../../../../app/_locales/en/messages.json'; - -import { - WALLET_PASSWORD, - completeSRPRevealQuiz, - getSelectedAccountAddress, - openSRPRevealQuiz, - removeSelectedAccount, - tapAndHoldToRevealSRP, -} from '../../helpers'; -import { createBtcAccount, withBtcAccountSnap } from './common-btc'; +import { WALLET_PASSWORD } from '../../helpers'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import LoginPage from '../../page-objects/pages/login-page'; +import PrivacySettings from '../../page-objects/pages/settings/privacy-settings'; +import ResetPasswordPage from '../../page-objects/pages/reset-password-page'; +import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import { withBtcAccountSnap } from './common-btc'; describe('Create BTC Account', function (this: Suite) { it('create BTC account from the menu', async function () { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: 'Bitcoin Account', - }); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Bitcoin Account'); }, ); }); @@ -29,27 +25,23 @@ describe('Create BTC Account', function (this: Suite) { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { - await driver.delay(500); - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - - const createButton = await driver.findElement({ - text: messages.addNewBitcoinAccount.message, - tag: 'button', + // check that we have one BTC account + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Bitcoin Account'); + + // check user cannot create second BTC account + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addNewBtcAccount({ + btcAccountCreationEnabled: false, }); - assert.equal(await createButton.isEnabled(), false); - // modal will still be here - await driver.clickElement('.mm-box button[aria-label="Close"]'); - - // check the number of accounts. it should only be 2. - await driver.clickElement('[data-testid="account-menu-icon"]'); - const menuItems = await driver.findElements( - '.multichain-account-list-item', - ); - assert.equal(menuItems.length, 2); + // check the number of available accounts is 2 + await headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_numberOfAvailableAccounts(2); }, ); }); @@ -58,29 +50,22 @@ describe('Create BTC Account', function (this: Suite) { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: 'Bitcoin Account', - }); - - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]', - ); - await driver.clickElement('[data-testid="account-list-menu-remove"]'); - await driver.clickElement({ text: 'Nevermind', tag: 'button' }); - - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: 'Bitcoin Account', - }); - - // check the number of accounts. it should only be 2. - await driver.clickElement('[data-testid="account-menu-icon"]'); - const menuItems = await driver.findElements( - '.multichain-account-list-item', - ); - assert.equal(menuItems.length, 2); + // check that we have one BTC account + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Bitcoin Account'); + + // check user can cancel the removal of the BTC account + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.removeAccount('Bitcoin Account', false); + await headerNavbar.check_accountLabel('Bitcoin Account'); + + // check the number of accounts. it should be 2. + await headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_numberOfAvailableAccounts(2); }, ); }); @@ -89,22 +74,33 @@ describe('Create BTC Account', function (this: Suite) { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: 'Bitcoin Account', - }); - - const accountAddress = await getSelectedAccountAddress(driver); - await removeSelectedAccount(driver); - - // Recreate account - await createBtcAccount(driver); - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: 'Bitcoin Account', - }); + // check that we have one BTC account + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Bitcoin Account'); + + // get the address of the BTC account and remove it + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + const accountAddress = await accountListPage.getAccountAddress( + 'Bitcoin Account', + ); + await headerNavbar.openAccountMenu(); + await accountListPage.removeAccount('Bitcoin Account'); + + // Recreate account and check that the address is the same + await headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addNewBtcAccount(); + await headerNavbar.check_accountLabel('Bitcoin Account'); + + await headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + const recreatedAccountAddress = await accountListPage.getAccountAddress( + 'Bitcoin Account', + ); - const recreatedAccountAddress = await getSelectedAccountAddress(driver); assert(accountAddress === recreatedAccountAddress); }, ); @@ -114,62 +110,50 @@ describe('Create BTC Account', function (this: Suite) { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: 'Bitcoin Account', - }); - - const accountAddress = await getSelectedAccountAddress(driver); - - await openSRPRevealQuiz(driver); - await completeSRPRevealQuiz(driver); - await driver.fill('[data-testid="input-password"]', WALLET_PASSWORD); - await driver.press('[data-testid="input-password"]', driver.Key.ENTER); - await tapAndHoldToRevealSRP(driver); - const seedPhrase = await ( - await driver.findElement('[data-testid="srp_text"]') - ).getText(); - - // Reset wallet - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement({ - css: '[data-testid="global-menu-lock"]', - text: 'Lock MetaMask', - }); - - await driver.clickElement({ - text: 'Forgot password?', - tag: 'a', - }); - - await driver.pasteIntoField( - '[data-testid="import-srp__srp-word-0"]', - seedPhrase, + // check that we have one BTC account + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.check_accountLabel('Bitcoin Account'); + + await headerNavbar.openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + const accountAddress = await accountListPage.getAccountAddress( + 'Bitcoin Account', ); - await driver.fill( - '[data-testid="create-vault-password"]', - WALLET_PASSWORD, + // go to privacy settings page and get the SRP + await headerNavbar.openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToPrivacySettings(); + + const privacySettings = new PrivacySettings(driver); + await privacySettings.check_pageIsLoaded(); + await privacySettings.openRevealSrpQuiz(); + await privacySettings.completeRevealSrpQuiz(); + await privacySettings.fillPasswordToRevealSrp(WALLET_PASSWORD); + const seedPhrase = await privacySettings.getSrpInRevealSrpDialog(); + + // lock metamask and reset wallet by clicking forgot password button + await headerNavbar.lockMetaMask(); + await new LoginPage(driver).gotoResetPasswordPage(); + const resetPasswordPage = new ResetPasswordPage(driver); + await resetPasswordPage.check_pageIsLoaded(); + await resetPasswordPage.resetPassword(seedPhrase, WALLET_PASSWORD); + + // create a BTC account and check that the address is the same + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + await accountListPage.addNewBtcAccount(); + await headerNavbar.check_accountLabel('Bitcoin Account'); + + await headerNavbar.openAccountMenu(); + await accountListPage.check_pageIsLoaded(); + const recreatedAccountAddress = await accountListPage.getAccountAddress( + 'Bitcoin Account', ); - await driver.fill( - '[data-testid="create-vault-confirm-password"]', - WALLET_PASSWORD, - ); - - await driver.clickElement({ - text: 'Restore', - tag: 'button', - }); - - await createBtcAccount(driver); - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: 'Bitcoin Account', - }); - - const recreatedAccountAddress = await getSelectedAccountAddress(driver); assert(accountAddress === recreatedAccountAddress); }, ); diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index ae86720da46f..19c9aeecd6c7 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -5,7 +5,6 @@ const mockttp = require('mockttp'); const detectPort = require('detect-port'); const { difference } = require('lodash'); const createStaticServer = require('../../development/create-static-server'); -const { tEn } = require('../lib/i18n-helpers'); const { setupMocking } = require('./mock-e2e'); const { Ganache } = require('./seeder/ganache'); const FixtureServer = require('./fixture-server'); @@ -385,51 +384,6 @@ const getWindowHandles = async (driver, handlesCount) => { return { extension, dapp, popup }; }; -const openSRPRevealQuiz = async (driver) => { - // navigate settings to reveal SRP - await driver.clickElement('[data-testid="account-options-menu-button"]'); - - // fix race condition with mmi build - if (process.env.MMI) { - await driver.waitForSelector('[data-testid="global-menu-mmi-portfolio"]'); - } - - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Security & privacy', tag: 'div' }); - await driver.clickElement('[data-testid="reveal-seed-words"]'); -}; - -/** - * @deprecated Please use page object functions in `test/e2e/page-objects/pages/settings/privacy-settings.ts`. - * @param driver - */ -const completeSRPRevealQuiz = async (driver) => { - // start quiz - await driver.clickElement('[data-testid="srp-quiz-get-started"]'); - - // tap correct answer 1 - await driver.clickElement('[data-testid="srp-quiz-right-answer"]'); - - // tap Continue 1 - await driver.clickElement('[data-testid="srp-quiz-continue"]'); - - // tap correct answer 2 - await driver.clickElement('[data-testid="srp-quiz-right-answer"]'); - - // tap Continue 2 - await driver.clickElement('[data-testid="srp-quiz-continue"]'); -}; - -const tapAndHoldToRevealSRP = async (driver) => { - await driver.holdMouseDownOnElement( - { - text: tEn('holdToRevealSRP'), - tag: 'span', - }, - 3000, - ); -}; - 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'; @@ -871,34 +825,6 @@ async function initBundler(bundlerServer, ganacheServer, usePaymaster) { } } -/** - * @deprecated Please use page object functions in `pages/account-list-page`. - * @param driver - */ -async function removeSelectedAccount(driver) { - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]', - ); - await driver.clickElement('[data-testid="account-list-menu-remove"]'); - await driver.clickElement({ text: 'Remove', tag: 'button' }); -} - -/** - * @deprecated Please use page object functions in `pages/account-list-page`. - * @param driver - */ -async function getSelectedAccountAddress(driver) { - await driver.clickElement('[data-testid="account-options-menu-button"]'); - await driver.clickElement('[data-testid="account-list-menu-details"]'); - const accountAddress = await ( - await driver.findElement('[data-testid="address-copy-button-text"]') - ).getText(); - await driver.clickElement('.mm-box button[aria-label="Close"]'); - - return accountAddress; -} - /** * Rather than using the FixtureBuilder#withPreferencesController to set the setting * we need to manually set the setting because the migration #122 overrides this. @@ -970,9 +896,6 @@ module.exports = { largeDelayMs, veryLargeDelayMs, withFixtures, - openSRPRevealQuiz, - completeSRPRevealQuiz, - tapAndHoldToRevealSRP, createDownloadFolder, openDapp, openDappConnectionsPage, @@ -1004,8 +927,6 @@ module.exports = { getCleanAppState, editGasFeeForm, clickNestedButton, - removeSelectedAccount, - getSelectedAccountAddress, tempToggleSettingRedesignedConfirmations, openMenuSafe, sentryRegEx, diff --git a/test/e2e/page-objects/pages/account-list-page.ts b/test/e2e/page-objects/pages/account-list-page.ts index fb7c1232c08c..f68cdaa333a0 100644 --- a/test/e2e/page-objects/pages/account-list-page.ts +++ b/test/e2e/page-objects/pages/account-list-page.ts @@ -1,9 +1,13 @@ +import { strict as assert } from 'assert'; import { Driver } from '../../webdriver/driver'; import { largeDelayMs } from '../../helpers'; +import messages from '../../../../app/_locales/en/messages.json'; class AccountListPage { private readonly driver: Driver; + private readonly accountAddressText = '.qr-code__address-segments'; + private readonly accountListBalance = '[data-testid="second-currency-display"]'; @@ -25,6 +29,11 @@ class AccountListPage { private readonly addAccountConfirmButton = '[data-testid="submit-add-account-with-name"]'; + private readonly addBtcAccountButton = { + text: messages.addNewBitcoinAccount.message, + tag: 'button', + }; + private readonly addEthereumAccountButton = '[data-testid="multichain-account-menu-popover-add-account"]'; @@ -93,6 +102,11 @@ class AccountListPage { tag: 'div', }; + private readonly removeAccountNevermindButton = { + text: 'Nevermind', + tag: 'button', + }; + private readonly saveAccountLabelButton = '[data-testid="save-account-label-input"]'; @@ -114,15 +128,21 @@ class AccountListPage { } /** - * Adds a new account with a custom label. + * Adds a new account with an optional custom label. * - * @param customLabel - The custom label for the new account. + * @param customLabel - The custom label for the new account. If not provided, a default name will be used. */ - async addNewAccountWithCustomLabel(customLabel: string): Promise { - console.log(`Adding new account with custom label: ${customLabel}`); + async addNewAccount(customLabel?: string): Promise { + if (customLabel) { + console.log(`Adding new account with custom label: ${customLabel}`); + } else { + console.log(`Adding new account with default name`); + } await this.driver.clickElement(this.createAccountButton); await this.driver.clickElement(this.addEthereumAccountButton); - await this.driver.fill(this.accountNameInput, customLabel); + if (customLabel) { + await this.driver.fill(this.accountNameInput, customLabel); + } // needed to mitigate a race condition with the state update // there is no condition we can wait for in the UI await this.driver.delay(largeDelayMs); @@ -132,19 +152,47 @@ class AccountListPage { } /** - * Adds a new account with default next available name. + * Adds a new BTC account with an optional custom name. * + * @param options - Options for adding a new BTC account. + * @param [options.btcAccountCreationEnabled] - Indicates if the BTC account creation is expected to be enabled or disabled. Defaults to true. + * @param [options.accountName] - The custom name for the BTC account. Defaults to an empty string, which means the default name will be used. */ - async addNewAccountWithDefaultName(): Promise { - console.log(`Adding new account with next available name`); - await this.driver.clickElement(this.createAccountButton); - await this.driver.clickElement(this.addEthereumAccountButton); - // needed to mitigate a race condition with the state update - // there is no condition we can wait for in the UI - await this.driver.delay(largeDelayMs); - await this.driver.clickElementAndWaitToDisappear( - this.addAccountConfirmButton, + async addNewBtcAccount({ + btcAccountCreationEnabled = true, + accountName = '', + }: { + btcAccountCreationEnabled?: boolean; + accountName?: string; + } = {}): Promise { + console.log( + `Adding new BTC account${ + accountName ? ` with custom name: ${accountName}` : ' with default name' + }`, ); + await this.driver.clickElement(this.createAccountButton); + if (btcAccountCreationEnabled) { + await this.driver.clickElement(this.addBtcAccountButton); + // needed to mitigate a race condition with the state update + // there is no condition we can wait for in the UI + await this.driver.delay(largeDelayMs); + if (accountName) { + await this.driver.fill(this.accountNameInput, accountName); + } + await this.driver.clickElementAndWaitToDisappear( + this.addAccountConfirmButton, + // Longer timeout than usual, this reduces the flakiness + // around Bitcoin account creation (mainly required for + // Firefox) + 5000, + ); + } else { + const createButton = await this.driver.findElement( + this.addBtcAccountButton, + ); + assert.equal(await createButton.isEnabled(), false); + await this.driver.clickElement(this.closeAccountModalButton); + } } /** @@ -195,6 +243,23 @@ class AccountListPage { ); } + /** + * Get the address of the specified account. + * + * @param accountLabel - The label of the account to get the address. + */ + async getAccountAddress(accountLabel: string): Promise { + console.log(`Get account address in account list`); + await this.openAccountOptionsInAccountList(accountLabel); + await this.driver.clickElement(this.accountMenuButton); + await this.driver.waitForSelector(this.accountAddressText); + const accountAddress = await ( + await this.driver.findElement(this.accountAddressText) + ).getText(); + await this.driver.clickElement(this.closeAccountModalButton); + return accountAddress; + } + async hideAccount(): Promise { console.log(`Hide account in account list`); await this.driver.clickElement(this.hideUnhideAccountButton); @@ -284,13 +349,23 @@ class AccountListPage { * Remove the specified account from the account list. * * @param accountLabel - The label of the account to remove. + * @param confirmRemoval - Whether to confirm the removal of the account. Defaults to true. */ - async removeAccount(accountLabel: string): Promise { + async removeAccount( + accountLabel: string, + confirmRemoval: boolean = true, + ): Promise { console.log(`Remove account in account list`); await this.openAccountOptionsInAccountList(accountLabel); await this.driver.clickElement(this.removeAccountButton); await this.driver.waitForSelector(this.removeAccountMessage); - await this.driver.clickElement(this.removeAccountConfirmButton); + if (confirmRemoval) { + console.log('Confirm removal of account'); + await this.driver.clickElement(this.removeAccountConfirmButton); + } else { + console.log('Click nevermind button to cancel account removal'); + await this.driver.clickElement(this.removeAccountNevermindButton); + } } async switchToAccount(expectedLabel: string): Promise { diff --git a/test/e2e/tests/account/account-custom-name.spec.ts b/test/e2e/tests/account/account-custom-name.spec.ts index 138317b1dcf0..4c0ecbe196f9 100644 --- a/test/e2e/tests/account/account-custom-name.spec.ts +++ b/test/e2e/tests/account/account-custom-name.spec.ts @@ -32,7 +32,7 @@ describe('Account Custom Name Persistence', function (this: Suite) { // Add new account with custom label and verify new added account label await headerNavbar.openAccountMenu(); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccountWithCustomLabel(anotherAccountLabel); + await accountListPage.addNewAccount(anotherAccountLabel); await headerNavbar.check_accountLabel(anotherAccountLabel); // Switch back to the first account and verify first custom account persists diff --git a/test/e2e/tests/account/add-account.spec.ts b/test/e2e/tests/account/add-account.spec.ts index 76060611198b..8824fcb70950 100644 --- a/test/e2e/tests/account/add-account.spec.ts +++ b/test/e2e/tests/account/add-account.spec.ts @@ -33,7 +33,7 @@ describe('Add account', function () { // Create new account with default name Account 2 const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccountWithDefaultName(); + await accountListPage.addNewAccount(); await headerNavbar.check_accountLabel('Account 2'); await homePage.check_expectedBalanceIsDisplayed(); @@ -93,7 +93,7 @@ describe('Add account', function () { // Create new account with default name Account 2 const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccountWithDefaultName(); + await accountListPage.addNewAccount(); await headerNavbar.check_accountLabel('Account 2'); await homePage.check_expectedBalanceIsDisplayed(); diff --git a/test/e2e/tests/notifications/account-syncing/helpers.ts b/test/e2e/tests/notifications/account-syncing/helpers.ts index 5e2694067eed..e54a5f6f96ae 100644 --- a/test/e2e/tests/notifications/account-syncing/helpers.ts +++ b/test/e2e/tests/notifications/account-syncing/helpers.ts @@ -1,18 +1,3 @@ import { isManifestV3 } from '../../../../../shared/modules/mv3.utils'; -import { - completeSRPRevealQuiz, - openSRPRevealQuiz, - tapAndHoldToRevealSRP, -} from '../../../helpers'; -import { Driver } from '../../../webdriver/driver'; export const IS_ACCOUNT_SYNCING_ENABLED = isManifestV3; - -export const getSRP = async (driver: Driver, password: string) => { - await openSRPRevealQuiz(driver); - await completeSRPRevealQuiz(driver); - await driver.fill('[data-testid="input-password"]', password); - await driver.press('[data-testid="input-password"]', driver.Key.ENTER); - await tapAndHoldToRevealSRP(driver); - return (await driver.findElement('[data-testid="srp_text"]')).getText(); -}; diff --git a/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts b/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts index 8e2908682542..ae283c47fa8b 100644 --- a/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts @@ -12,7 +12,9 @@ import { completeCreateNewWalletOnboardingFlow, completeImportSRPOnboardingFlow, } from '../../../page-objects/flows/onboarding.flow'; -import { getSRP, IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; +import PrivacySettings from '../../../page-objects/pages/settings/privacy-settings'; +import SettingsPage from '../../../page-objects/pages/settings/settings-page'; +import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; describe('Account syncing - New User @no-mmi', function () { if (!IS_ACCOUNT_SYNCING_ENABLED) { @@ -65,12 +67,24 @@ describe('Account syncing - New User @no-mmi', function () { // Add a second account await accountListPage.openAccountOptionsMenu(); - await accountListPage.addNewAccountWithCustomLabel( - 'My Second Account', - ); + await accountListPage.addNewAccount('My Second Account'); // Set SRP to use for retreival - walletSrp = await getSRP(driver, NOTIFICATIONS_TEAM_PASSWORD); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToPrivacySettings(); + + const privacySettings = new PrivacySettings(driver); + await privacySettings.check_pageIsLoaded(); + await privacySettings.openRevealSrpQuiz(); + await privacySettings.completeRevealSrpQuiz(); + await privacySettings.fillPasswordToRevealSrp( + NOTIFICATIONS_TEAM_PASSWORD, + ); + walletSrp = await privacySettings.getSrpInRevealSrpDialog(); if (!walletSrp) { throw new Error('Wallet SRP was not set'); } diff --git a/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts b/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts index 209d3a51fdaf..3ec44e4fd07e 100644 --- a/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts @@ -18,7 +18,9 @@ import { importSRPOnboardingFlow, completeImportSRPOnboardingFlow, } from '../../../page-objects/flows/onboarding.flow'; -import { getSRP, IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; +import PrivacySettings from '../../../page-objects/pages/settings/privacy-settings'; +import SettingsPage from '../../../page-objects/pages/settings/settings-page'; +import { IS_ACCOUNT_SYNCING_ENABLED } from './helpers'; import { accountsSyncMockResponse } from './mockData'; describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { @@ -139,9 +141,23 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'Account 1', ); - await accountListPage.addNewAccountWithCustomLabel('New Account'); + await accountListPage.addNewAccount('New Account'); // Set SRP to use for retreival - walletSrp = await getSRP(driver, NOTIFICATIONS_TEAM_PASSWORD); + const headerNavbar = new HeaderNavbar(driver); + await headerNavbar.check_pageIsLoaded(); + await headerNavbar.openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToPrivacySettings(); + + const privacySettings = new PrivacySettings(driver); + await privacySettings.check_pageIsLoaded(); + await privacySettings.openRevealSrpQuiz(); + await privacySettings.completeRevealSrpQuiz(); + await privacySettings.fillPasswordToRevealSrp( + NOTIFICATIONS_TEAM_PASSWORD, + ); + walletSrp = await privacySettings.getSrpInRevealSrpDialog(); if (!walletSrp) { throw new Error('Wallet SRP was not set'); } diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts index 23a5d1eaf47b..1c1f7b3119d7 100644 --- a/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts @@ -68,9 +68,7 @@ describe('Account syncing - Add Account @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'My Second Synced Account', ); - await accountListPage.addNewAccountWithCustomLabel( - 'My third account', - ); + await accountListPage.addNewAccount('My third account'); }, ); @@ -175,7 +173,7 @@ describe('Account syncing - Add Account @no-mmi', function () { await accountListPage.check_accountDisplayedInAccountList( 'My Second Synced Account', ); - await accountListPage.addNewAccountWithDefaultName(); + await accountListPage.addNewAccount(); }, ); diff --git a/test/e2e/tests/tokens/nft/import-nft.spec.ts b/test/e2e/tests/tokens/nft/import-nft.spec.ts index 808bc26ac6e6..bc709ba463c0 100644 --- a/test/e2e/tests/tokens/nft/import-nft.spec.ts +++ b/test/e2e/tests/tokens/nft/import-nft.spec.ts @@ -65,7 +65,7 @@ describe('Import NFT', function () { await headerNavbar.openAccountMenu(); const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - await accountListPage.addNewAccountWithDefaultName(); + await accountListPage.addNewAccount(); await headerNavbar.check_accountLabel('Account 2'); await homepage.check_expectedBalanceIsDisplayed(); From eeb085fb4a2e7280ed3677e7597f970172839572 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 25 Nov 2024 18:21:52 +0100 Subject: [PATCH 21/40] fix: fix `ConnectPage` when a non-EVM account is selected (#28436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The currently selected non-EVM account was part of the requested account on during the account connection flow. The `ConnectPage` was allowing to "confirm" the request even if the UI was showing 0 account. To fix this, we no longer uses the currently selected account if this account is not EVM-compatible. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28436?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/accounts-planning/issues/670 ## **Manual testing steps** 1. `yarn start:flask` 2. Enable Bitcoin support 3. Create a Bitcoin account 4. Select this account 5. Go to: https://metamask.github.io/test-dapp/ 6. Try to connect your accounts - None accounts should be selected by default - You should not be able to "confirm" the dialog ## **Screenshots/Recordings** ### **Before** ![Screenshot 2024-11-13 at 14 57 11](https://github.com/user-attachments/assets/0b1b3de2-e968-408a-9aea-c9ea4006a1b1) If you try to connect, you will get: ![Screenshot 2024-11-13 at 14 57 44](https://github.com/user-attachments/assets/a0c8e507-7530-4de7-a73f-5414ba1a2656) ### **After** ![Screenshot 2024-11-13 at 15 01 39](https://github.com/user-attachments/assets/170f4ec3-01a9-422e-a32c-66ad81d5568e) ## **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. --- test/jest/mocks.ts | 64 ++++++++++ .../connect-page/connect-page.test.tsx | 117 +++++++++++------- .../connect-page/connect-page.tsx | 9 +- .../permissions-connect.component.js | 2 + 4 files changed, 145 insertions(+), 47 deletions(-) diff --git a/test/jest/mocks.ts b/test/jest/mocks.ts index bc7127fb2383..b0750b022e78 100644 --- a/test/jest/mocks.ts +++ b/test/jest/mocks.ts @@ -4,6 +4,7 @@ import { BtcMethod, BtcAccountType, InternalAccount, + isEvmAccountType, } from '@metamask/keyring-api'; import { KeyringTypes } from '@metamask/keyring-controller'; import { v4 as uuidv4 } from 'uuid'; @@ -14,6 +15,9 @@ import { initialState, } from '../../ui/ducks/send'; import { MetaMaskReduxState } from '../../ui/store/store'; +import mockState from '../data/mock-state.json'; + +export type MockState = typeof mockState; export const MOCK_DEFAULT_ADDRESS = '0xd5e099c71b797516c10ed0f0d895f429c2781111'; @@ -245,3 +249,63 @@ export const getSelectedInternalAccountFromMockState = ( state.metamask.internalAccounts.selectedAccount ]; }; + +export function overrideAccountsFromMockState< + MockMetaMaskState extends MockState['metamask'], +>( + state: { metamask: MockMetaMaskState }, + accounts: InternalAccount[], + selectedAccountId?: string, +): { metamask: MockMetaMaskState } { + // First, re-create the accounts mapping and the currently selected account. + const [{ id: newFirstAccountId }] = accounts; + const newSelectedAccount = selectedAccountId ?? newFirstAccountId ?? ''; + const newInternalAccounts = accounts.reduce( + ( + acc: MetaMaskReduxState['metamask']['internalAccounts']['accounts'], + account, + ) => { + acc[account.id] = account; + return acc; + }, + {}, + ); + + // Re-create the keyring mapping too, since some selectors are using their internal + // account list. + const newKeyrings: MetaMaskReduxState['metamask']['keyrings'] = []; + for (const keyring of state.metamask.keyrings) { + const newAccountsForKeyring = []; + for (const account of accounts) { + if (account.metadata.keyring.type === keyring.type) { + newAccountsForKeyring.push(account.address); + } + } + newKeyrings.push({ + type: keyring.type, + accounts: newAccountsForKeyring, + }); + } + + // Compute balances for EVM addresses: + // FIXME: Looks like there's no `balances` type in `MetaMaskReduxState`. + const newBalances: Record = {}; + for (const account of accounts) { + if (isEvmAccountType(account.type)) { + newBalances[account.address] = '0x0'; + } + } + + return { + ...state, + metamask: { + ...state.metamask, + internalAccounts: { + accounts: newInternalAccounts, + selectedAccount: newSelectedAccount, + }, + keyrings: newKeyrings, + balances: newBalances, + }, + }; +} diff --git a/ui/pages/permissions-connect/connect-page/connect-page.test.tsx b/ui/pages/permissions-connect/connect-page/connect-page.test.tsx index ef705e474ad9..86fa769c3206 100644 --- a/ui/pages/permissions-connect/connect-page/connect-page.test.tsx +++ b/ui/pages/permissions-connect/connect-page/connect-page.test.tsx @@ -7,34 +7,42 @@ import { EndowmentTypes, RestrictedMethods, } from '../../../../shared/constants/permissions'; -import { ConnectPage, ConnectPageRequest } from './connect-page'; +import { overrideAccountsFromMockState } from '../../../../test/jest/mocks'; +import { + MOCK_ACCOUNT_BIP122_P2WPKH, + MOCK_ACCOUNT_EOA, +} from '../../../../test/data/mock-accounts'; +import { ConnectPage, ConnectPageProps } from './connect-page'; + +const mockTestDappUrl = 'https://test.dapp'; const render = ( - props: { - request: ConnectPageRequest; - permissionsRequestId: string; - rejectPermissionsRequest: (id: string) => void; - approveConnection: (request: ConnectPageRequest) => void; - activeTabOrigin: string; - } = { - request: { - id: '1', - origin: 'https://test.dapp', - }, - permissionsRequestId: '1', - rejectPermissionsRequest: jest.fn(), - approveConnection: jest.fn(), - activeTabOrigin: 'https://test.dapp', - }, - state = {}, + options: { + props?: ConnectPageProps; + state?: object; + } = {}, ) => { + const { + props = { + request: { + id: '1', + origin: mockTestDappUrl, + }, + permissionsRequestId: '1', + rejectPermissionsRequest: jest.fn(), + approveConnection: jest.fn(), + activeTabOrigin: mockTestDappUrl, + }, + state, + } = options; + const store = configureStore({ ...mockState, metamask: { ...mockState.metamask, ...state, permissionHistory: { - 'https://test.dapp': { + mockTestDappUrl: { eth_accounts: { accounts: { '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': 1709225290848, @@ -44,7 +52,7 @@ const render = ( }, }, activeTab: { - origin: 'https://test.dapp', + origin: mockTestDappUrl, }, }); return renderWithProvider(, store); @@ -82,33 +90,56 @@ describe('ConnectPage', () => { it('should render with defaults from the requested permissions', () => { const { container } = render({ - request: { - id: '1', - origin: 'https://test.dapp', - permissions: { - [RestrictedMethods.eth_accounts]: { - caveats: [ - { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'], - }, - ], - }, - [EndowmentTypes.permittedChains]: { - caveats: [ - { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x1'], - }, - ], + props: { + request: { + id: '1', + origin: mockTestDappUrl, + permissions: { + [RestrictedMethods.eth_accounts]: { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'], + }, + ], + }, + [EndowmentTypes.permittedChains]: { + caveats: [ + { + type: CaveatTypes.restrictNetworkSwitching, + value: ['0x1'], + }, + ], + }, }, }, + permissionsRequestId: '1', + rejectPermissionsRequest: jest.fn(), + approveConnection: jest.fn(), + activeTabOrigin: mockTestDappUrl, }, - permissionsRequestId: '1', - rejectPermissionsRequest: jest.fn(), - approveConnection: jest.fn(), - activeTabOrigin: 'https://test.dapp', }); expect(container).toMatchSnapshot(); }); + + it('should render a disabled confirm if current account is a non-EVM account', () => { + // NOTE: We select the non-EVM account by default here! + const mockSelectedAccountId = MOCK_ACCOUNT_BIP122_P2WPKH.id; + const mockAccounts = [MOCK_ACCOUNT_EOA, MOCK_ACCOUNT_BIP122_P2WPKH]; + const mockAccountsState = overrideAccountsFromMockState( + mockState, + mockAccounts, + mockSelectedAccountId, + ); + + const { getByText } = render({ + state: mockAccountsState.metamask, + }); + const confirmButton = getByText('Connect'); + const cancelButton = getByText('Cancel'); + // The currently selected account is a Bitcoin account, the "connecting account list" would be + // empty by default and thus, we cannot confirm without explictly select an EVM account. + expect(confirmButton).toBeDisabled(); + expect(cancelButton).toBeDefined(); + }); }); diff --git a/ui/pages/permissions-connect/connect-page/connect-page.tsx b/ui/pages/permissions-connect/connect-page/connect-page.tsx index 99791a8d5333..32001a75d3a7 100644 --- a/ui/pages/permissions-connect/connect-page/connect-page.tsx +++ b/ui/pages/permissions-connect/connect-page/connect-page.tsx @@ -50,7 +50,7 @@ export type ConnectPageRequest = { >; }; -type ConnectPageProps = { +export type ConnectPageProps = { request: ConnectPageRequest; permissionsRequestId: string; rejectPermissionsRequest: (id: string) => void; @@ -124,10 +124,11 @@ export const ConnectPage: React.FC = ({ }, [accounts, internalAccounts]); const currentAccount = useSelector(getSelectedInternalAccount); + const currentAccountAddress = isEvmAccountType(currentAccount.type) + ? [currentAccount.address] + : []; // We do not support non-EVM accounts connections const defaultAccountsAddresses = - requestedAccounts.length > 0 - ? requestedAccounts - : [currentAccount?.address]; + requestedAccounts.length > 0 ? requestedAccounts : currentAccountAddress; const [selectedAccountAddresses, setSelectedAccountAddresses] = useState( defaultAccountsAddresses, ); diff --git a/ui/pages/permissions-connect/permissions-connect.component.js b/ui/pages/permissions-connect/permissions-connect.component.js index e32f85609406..6d37b46e39e2 100644 --- a/ui/pages/permissions-connect/permissions-connect.component.js +++ b/ui/pages/permissions-connect/permissions-connect.component.js @@ -41,10 +41,12 @@ function getDefaultSelectedAccounts(currentAddress, permissionsRequest) { return new Set( requestedAccounts .map((address) => address.toLowerCase()) + // We only consider EVM accounts here (used for `eth_requestAccounts` or `eth_accounts`) .filter(isEthAddress), ); } + // We only consider EVM accounts here (used for `eth_requestAccounts` or `eth_accounts`) return new Set(isEthAddress(currentAddress) ? [currentAddress] : []); } From 9c4d1b41146fae9e4b2888a1c73c35f2213a5254 Mon Sep 17 00:00:00 2001 From: Salim TOUBAL Date: Mon, 25 Nov 2024 19:12:23 +0100 Subject: [PATCH 22/40] fix: prevent non-current network tokens from being hidden incorrectly (#28674) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** fix to prevent non current network tokens from being hidden incorrecly core PR: https://github.com/MetaMask/core/pull/4967 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28674?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. add different network accros different chains 2. choose ethereum as current chain 3. try to hide token of polygon for instance ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/2637ed94-6ad1-4025-8000-963906aca187 ### **After** https://github.com/user-attachments/assets/4c5d6cbd-4af2-43c5-bcbd-879a71b9997e ## **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. --- ...ts-controllers-npm-45.1.0-d914c453f0.patch | 24 +++++++++ .../hide-token-confirmation-modal.test.js | 51 +++++++++++++++++++ yarn.lock | 4 +- 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch b/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch index ca412ba89489..5dec24d6e625 100644 --- a/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch +++ b/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch @@ -33,3 +33,27 @@ index a13403446a2376d4d905a9ef733941798da89c88..3c8229f9ea40f4c1ee760a22884e1066 /** * The list of currencies that can be supplied as the `vsCurrency` parameter to * the `/spot-prices` endpoint, in lowercase form. +diff --git a/dist/TokensController.cjs b/dist/TokensController.cjs +index 343b343b8300136756d96acac77aab8140efc95a..69d8e2ea84d6303a3af02bd95458ef3060c76f2b 100644 +--- a/dist/TokensController.cjs ++++ b/dist/TokensController.cjs +@@ -270,13 +270,16 @@ class TokensController extends base_controller_1.BaseController { + * @param networkClientId - Optional network client ID used to determine interacting chain ID. + */ + ignoreTokens(tokenAddressesToIgnore, networkClientId) { +- const { ignoredTokens, detectedTokens, tokens } = this.state; +- const ignoredTokensMap = {}; +- let newIgnoredTokens = [...ignoredTokens]; + let interactingChainId; + if (networkClientId) { + interactingChainId = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId).configuration.chainId; + } ++ const { allTokens, allDetectedTokens, allIgnoredTokens } = this.state; ++ const ignoredTokensMap = {}; ++ const ignoredTokens = allIgnoredTokens[interactingChainId ?? __classPrivateFieldGet(this, _TokensController_chainId, "f")]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAddress).call(this)] || []; ++ let newIgnoredTokens = [...ignoredTokens]; ++ const tokens = allTokens[interactingChainId ?? __classPrivateFieldGet(this, _TokensController_chainId, "f")]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAddress).call(this)] || []; ++ const detectedTokens = allDetectedTokens[interactingChainId ?? __classPrivateFieldGet(this, _TokensController_chainId, "f")]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAddress).call(this)] || []; + const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => { + const checksumAddress = (0, controller_utils_1.toChecksumHexAddress)(address); + ignoredTokensMap[address.toLowerCase()] = true; diff --git a/ui/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.test.js b/ui/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.test.js index 982a6a272943..04ba342e9c48 100644 --- a/ui/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.test.js +++ b/ui/components/app/modals/hide-token-confirmation-modal/hide-token-confirmation-modal.test.js @@ -5,6 +5,7 @@ import thunk from 'redux-thunk'; import * as actions from '../../../../store/actions'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import mockState from '../../../../../test/data/mock-state.json'; +import { mockNetworkState } from '../../../../../test/stub/networks'; import HideTokenConfirmationModal from '.'; const mockHistoryPush = jest.fn(); @@ -25,6 +26,13 @@ describe('Hide Token Confirmation Modal', () => { image: '', }; + const tokenState2 = { + address: '0xTokenAddress2', + symbol: 'TKN2', + image: '', + chainId: '0x89', + }; + const tokenModalState = { ...mockState, appState: { @@ -82,4 +90,47 @@ describe('Hide Token Confirmation Modal', () => { networkClientId: 'goerli', }); }); + + it('should hide token from another chain', () => { + const tokenModalStateWithDifferentChain = { + ...mockState, + metamask: { + ...mockState.metamask, + selectedNetworkClientId: 'bsc', + ...mockNetworkState({ chainId: '0x89', id: 'bsc' }), + }, + appState: { + ...mockState.appState, + modal: { + modalState: { + props: { + history: { + push: mockHistoryPush, + }, + token: tokenState2, + }, + }, + }, + }, + }; + + const mockStoreDifferentChain = configureMockStore([thunk])( + tokenModalStateWithDifferentChain, + ); + + const { queryByTestId } = renderWithProvider( + , + mockStoreDifferentChain, + ); + + const hideButton = queryByTestId('hide-token-confirmation__hide'); + + fireEvent.click(hideButton); + + expect(mockHideModal).toHaveBeenCalled(); + expect(actions.ignoreTokens).toHaveBeenCalledWith({ + tokensToIgnore: tokenState2.address, + networkClientId: 'bsc', + }); + }); }); diff --git a/yarn.lock b/yarn.lock index db952a4b1e70..9f8daf6ce2d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4975,7 +4975,7 @@ __metadata: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch": version: 45.1.0 - resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch::version=45.1.0&hash=86167d" + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch::version=45.1.0&hash=cfcadc" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@ethersproject/abi": "npm:^5.7.0" @@ -5008,7 +5008,7 @@ __metadata: "@metamask/keyring-controller": ^19.0.0 "@metamask/network-controller": ^22.0.0 "@metamask/preferences-controller": ^15.0.0 - checksum: 10/985ec7dffb75aaff8eea00f556157e42cd5db063cbfa94dfd4f070c5b9d98b1315f3680fa7370f4c734a1688598bbda9c44a7c33c342e1d123d6ee2edd6120fc + checksum: 10/d2f7d5bb07feceb5b972beda019f411cd073ece3ed682b21373fc6d4c06812ec10245b40c78ce6316c5fb1718278fd269b73e13d37c2ff07b5bb3ecdfd8278f7 languageName: node linkType: hard From ff635d238ce177c124e70b5f9e33a03d606e031a Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:23:03 -0800 Subject: [PATCH 23/40] fix: PortfolioView swap native token bug (#28639) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** When `PORTFOLIO_VIEW` feature flag is enabled, when swapping a native token from a different chain than the globally selected chain, the incorrect native token would be prepoulated in the `fromToken` in the swap UI. For instance, if user is on Ethereum mainnet, navigated to POL, then attempted to swap, the globally selected network would change from Ethereum mainnet to Polygon mainnet (expected), but the swaps `fromToken` would still be POL (unexpected) Changes in this PR fixes this, and prepoulates `fromToken` with the native token from the correct chain. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28639?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28534 ## **Manual testing steps** 1. `PORTFOLIO_VIEW=1 yarn webpack --watch` 2. Import wallet with at least two networks added 3. When "All Networks" is toggled, attempt to swap a native token from another network. Ensure that the token prepopulated in the swap UI is the native token from the correct chain 4. Ensure swap completes successfully. ## **Screenshots/Recordings** https://github.com/user-attachments/assets/016ffa54-9ed1-450c-9aa0-da27f0fd6caa ## **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: David Walsh --- ui/pages/asset/components/asset-page.tsx | 17 +- ui/selectors/selectors.js | 17 +- ui/selectors/selectors.test.js | 199 +++++++++++++++++++++++ 3 files changed, 223 insertions(+), 10 deletions(-) diff --git a/ui/pages/asset/components/asset-page.tsx b/ui/pages/asset/components/asset-page.tsx index 0f4529861dbc..68d7f9d18e65 100644 --- a/ui/pages/asset/components/asset-page.tsx +++ b/ui/pages/asset/components/asset-page.tsx @@ -101,11 +101,21 @@ const AssetPage = ({ const selectedAccount = useSelector(getSelectedAccount); const currency = useSelector(getCurrentCurrency); const conversionRate = useSelector(getConversionRate); - const isBridgeChain = useSelector(getIsBridgeChain); const isBuyableChain = useSelector(getIsNativeTokenBuyable); - const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual); + + const { chainId, type, symbol, name, image, decimals } = asset; + + // These need to be specific to the asset and not the current chain + const defaultSwapsToken = useSelector( + (state) => getSwapsDefaultToken(state, chainId), + isEqual, + ); + const isSwapsChain = useSelector((state) => getIsSwapsChain(state, chainId)); + const isBridgeChain = useSelector((state) => + getIsBridgeChain(state, chainId), + ); + const account = useSelector(getSelectedInternalAccount, isEqual); - const isSwapsChain = useSelector(getIsSwapsChain); const isSigningEnabled = account.methods.includes(EthMethod.SignTransaction) || account.methods.includes(EthMethod.SignUserOperation); @@ -132,7 +142,6 @@ const AssetPage = ({ const selectedAccountTokenBalancesAcrossChains = tokenBalances[selectedAccount.address]; - const { chainId, type, symbol, name, image, decimals } = asset; const isMetaMetricsEnabled = useSelector(getParticipateInMetaMetrics); const isMarketingEnabled = useSelector(getDataCollectionForMarketing); const metaMetricsId = useSelector(getMetaMetricsId); diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index b6df07bd2bd0..eea467ec0f16 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -1495,14 +1495,17 @@ export function getWeb3ShimUsageStateForOrigin(state, origin) { * objects, per the above description. * * @param {object} state - the redux state object + * @param {string} overrideChainId - the chainId to override the current chainId * @returns {SwapsEthToken} The token object representation of the currently * selected account's ETH balance, as expected by the Swaps API. */ -export function getSwapsDefaultToken(state) { +export function getSwapsDefaultToken(state, overrideChainId = null) { const selectedAccount = getSelectedAccount(state); const balance = selectedAccount?.balance; - const chainId = getCurrentChainId(state); + const currentChainId = getCurrentChainId(state); + + const chainId = overrideChainId ?? currentChainId; const defaultTokenObject = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]; return { @@ -1516,8 +1519,9 @@ export function getSwapsDefaultToken(state) { }; } -export function getIsSwapsChain(state) { - const chainId = getCurrentChainId(state); +export function getIsSwapsChain(state, overrideChainId) { + const currentChainId = getCurrentChainId(state); + const chainId = overrideChainId ?? currentChainId; const isNotDevelopment = process.env.METAMASK_ENVIRONMENT !== 'development' && process.env.METAMASK_ENVIRONMENT !== 'testing'; @@ -1526,8 +1530,9 @@ export function getIsSwapsChain(state) { : ALLOWED_DEV_SWAPS_CHAIN_IDS.includes(chainId); } -export function getIsBridgeChain(state) { - const chainId = getCurrentChainId(state); +export function getIsBridgeChain(state, overrideChainId) { + const currentChainId = getCurrentChainId(state); + const chainId = overrideChainId ?? currentChainId; return ALLOWED_BRIDGE_CHAIN_IDS.includes(chainId); } diff --git a/ui/selectors/selectors.test.js b/ui/selectors/selectors.test.js index b2c3cd894e44..c749d8ff3fe7 100644 --- a/ui/selectors/selectors.test.js +++ b/ui/selectors/selectors.test.js @@ -1978,4 +1978,203 @@ describe('#getConnectedSitesList', () => { expect(selectors.getSelectedEvmInternalAccount(state)).toBe(undefined); }); }); + + describe('getSwapsDefaultToken', () => { + it('returns the token object for the current chainId when no overrideChainId is provided', () => { + const expectedToken = { + symbol: 'ETH', + name: 'Ether', + address: '0x0000000000000000000000000000000000000000', + decimals: 18, + balance: '966987986469506564059', + string: '966.988', + iconUrl: './images/black-eth-logo.svg', + }; + + const result = selectors.getSwapsDefaultToken(mockState); + + expect(result).toStrictEqual(expectedToken); + }); + + it('returns the token object for the overridden chainId when overrideChainId is provided', () => { + const getCurrentChainIdSpy = jest.spyOn(selectors, 'getCurrentChainId'); + const expectedToken = { + symbol: 'POL', + name: 'Polygon', + address: '0x0000000000000000000000000000000000000000', + decimals: 18, + balance: '966987986469506564059', + string: '966.988', + iconUrl: './images/pol-token.svg', + }; + + const result = selectors.getSwapsDefaultToken( + mockState, + CHAIN_IDS.POLYGON, + ); + + expect(result).toStrictEqual(expectedToken); + expect(getCurrentChainIdSpy).not.toHaveBeenCalled(); // Ensure overrideChainId is used + }); + }); + + describe('getIsSwapsChain', () => { + it('returns true for an allowed chainId in production environment', () => { + process.env.METAMASK_ENVIRONMENT = 'production'; + + const state = { + ...mockState, + metamask: { + ...mockState.metamask, + selectedNetworkClientId: 'testNetworkConfigurationId', // corresponds to mainnet RPC in mockState + }, + }; + + const result = selectors.getIsSwapsChain(state); + + expect(result).toBe(true); + }); + + it('returns true for an allowed chainId in development environment', () => { + process.env.METAMASK_ENVIRONMENT = 'development'; + + const state = { + ...mockState, + metamask: { + ...mockState.metamask, + selectedNetworkClientId: 'goerli', + }, + }; + + const result = selectors.getIsSwapsChain(state); + + expect(result).toBe(true); + }); + + it('returns false for a disallowed chainId in production environment', () => { + process.env.METAMASK_ENVIRONMENT = 'production'; + + const state = { + ...mockState, + metamask: { + ...mockState.metamask, + selectedNetworkClientId: 'fooChain', // corresponds to mainnet RPC in mockState + networkConfigurationsByChainId: { + '0x8080': { + chainId: '0x8080', + name: 'Custom Mainnet RPC', + nativeCurrency: 'ETH', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + type: 'custom', + url: 'https://testrpc.com', + networkClientId: 'fooChain', + }, + ], + }, + }, + }, + }; + + const result = selectors.getIsSwapsChain(state); + + expect(result).toBe(false); + }); + + it('returns false for a disallowed chainId in development environment', () => { + process.env.METAMASK_ENVIRONMENT = 'development'; + + const state = { + ...mockState, + metamask: { + ...mockState.metamask, + selectedNetworkClientId: 'fooChain', // corresponds to mainnet RPC in mockState + networkConfigurationsByChainId: { + '0x8080': { + chainId: '0x8080', + name: 'Custom Mainnet RPC', + nativeCurrency: 'ETH', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + type: 'custom', + url: 'https://testrpc.com', + networkClientId: 'fooChain', + }, + ], + }, + }, + }, + }; + + const result = selectors.getIsSwapsChain(state); + + expect(result).toBe(false); + }); + + it('respects the overrideChainId parameter', () => { + process.env.METAMASK_ENVIRONMENT = 'production'; + + const getCurrentChainIdSpy = jest.spyOn(selectors, 'getCurrentChainId'); + + const result = selectors.getIsSwapsChain(mockState, '0x89'); + expect(result).toBe(true); + expect(getCurrentChainIdSpy).not.toHaveBeenCalled(); // Ensure overrideChainId is used + }); + }); + + describe('getIsBridgeChain', () => { + it('returns true for an allowed bridge chainId', () => { + const state = { + ...mockState, + metamask: { + ...mockState.metamask, + selectedNetworkClientId: 'testNetworkConfigurationId', // corresponds to mainnet RPC in mockState + }, + }; + + const result = selectors.getIsBridgeChain(state); + + expect(result).toBe(true); + }); + + it('returns false for a disallowed bridge chainId', () => { + const state = { + ...mockState, + metamask: { + ...mockState.metamask, + selectedNetworkClientId: 'fooChain', // corresponds to mainnet RPC in mockState + networkConfigurationsByChainId: { + '0x8080': { + chainId: '0x8080', + name: 'Custom Mainnet RPC', + nativeCurrency: 'ETH', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + type: 'custom', + url: 'https://testrpc.com', + networkClientId: 'fooChain', + }, + ], + }, + }, + }, + }; + + const result = selectors.getIsBridgeChain(state); + + expect(result).toBe(false); + }); + + it('respects the overrideChainId parameter', () => { + const getCurrentChainIdSpy = jest.spyOn(selectors, 'getCurrentChainId'); + + const result = selectors.getIsBridgeChain(mockState, '0x89'); + + expect(result).toBe(true); + expect(getCurrentChainIdSpy).not.toHaveBeenCalled(); // Ensure overrideChainId is used + }); + }); }); From 03028487ee498e65cabc231bd30f73d69592aaed Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Tue, 26 Nov 2024 10:40:50 +0100 Subject: [PATCH 24/40] fix: add e2e for portfolio view polling (#28682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** PR adds an e2e test to check polling activity [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28682?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask 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. --- privacy-snapshot.json | 2 + test/e2e/tests/privacy/polling.spec.ts | 383 +++++++++++++++++++++++++ 2 files changed, 385 insertions(+) create mode 100644 test/e2e/tests/privacy/polling.spec.ts diff --git a/privacy-snapshot.json b/privacy-snapshot.json index f5cf068b8728..36249b132bca 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -29,6 +29,8 @@ "github.com", "goerli.infura.io", "lattice.gridplus.io", + "linea-mainnet.infura.io", + "linea-sepolia.infura.io", "localhost:8000", "localhost:8545", "mainnet.infura.io", diff --git a/test/e2e/tests/privacy/polling.spec.ts b/test/e2e/tests/privacy/polling.spec.ts new file mode 100644 index 000000000000..b63e1bce6191 --- /dev/null +++ b/test/e2e/tests/privacy/polling.spec.ts @@ -0,0 +1,383 @@ +import { strict as assert } from 'assert'; +import { JsonRpcRequest } from '@metamask/utils'; +import { MockedEndpoint } from 'mockttp'; +import { expect } from '@playwright/test'; +import FixtureBuilder from '../../fixture-builder'; +import { defaultGanacheOptions, withFixtures } from '../../helpers'; +import { Mockttp } from '../../mock-e2e'; +import HomePage from '../../page-objects/pages/homepage'; +import { loginWithoutBalanceValidation } from '../../page-objects/flows/login.flow'; + +const infuraMainnetUrl = + 'https://mainnet.infura.io/v3/00000000000000000000000000000000'; +const infuraSepoliaUrl = + 'https://sepolia.infura.io/v3/00000000000000000000000000000000'; +const infuraLineaMainnetUrl = + 'https://linea-mainnet.infura.io/v3/00000000000000000000000000000000'; +const infuraLineaSepoliaUrl = + 'https://linea-sepolia.infura.io/v3/00000000000000000000000000000000'; + +const ethGetBlockByNumberResult = { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: 367912711400466, + result: { + hash: '0x8f1697a1dfd439404fccc9ea370ab8ca4e1bb3465a6b74e5bf59891b909c5b86', + parentHash: + '0xc745f42de8dcb553511e5953b00220d2872c889261f606bbc6940600da3e24ad', + sha3Uncles: + '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + miner: '0x0000000000000000000000000000000000000000', + stateRoot: + '0x3e6f4a18a3d430fcb3748c89a32c98b7822c26ece58a28010c502af0247a5a05', + transactionsRoot: + '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + receiptsRoot: + '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + logsBloom: + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + difficulty: '0x1', + number: '0xd', + gasLimit: '0x1c9c380', + gasUsed: '0x0', + timestamp: '0x67409c7e', + extraData: '0x', + mixHash: + '0x0000000000000000000000000000000000000000000000000000000000000000', + nonce: '0x0000000000000000', + totalDifficulty: '0xe', + size: '0x1fd', + transactions: [], + uncles: [], + }, + }, +}; + +async function mockInfura(mockServer: Mockttp): Promise { + const blockNumber = { value: 0 }; + return [ + // Mocks for mainnet + await mockServer + .forPost(infuraMainnetUrl) + .withJsonBodyIncluding({ method: 'net_version' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '6327576363628226', + result: '0x1', + }, + })), + await mockServer + .forPost(infuraMainnetUrl) + .withBodyIncluding('eth_blockNumber') + .thenCallback(() => { + blockNumber.value += 1; + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: 8723760595506777, + result: blockNumber.value.toString(16), + }, + }; + }), + await mockServer + .forPost(infuraMainnetUrl) + .withBodyIncluding('eth_getBlockByNumber') + .thenCallback(() => { + return ethGetBlockByNumberResult; + }), + await mockServer + .forPost(infuraMainnetUrl) + .withBodyIncluding('eth_call') + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '3aca99b4-92a1-4ad2-be3a-ae9fdd76fdaa', + result: + '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000004e2adedda15fd6', + }, + }; + }), + // Mocks for linea mainnet + await mockServer + .forPost(infuraLineaMainnetUrl) + .withJsonBodyIncluding({ method: 'net_version' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '6327576363628226', + result: '0x1', + }, + })), + await mockServer + .forPost(infuraLineaMainnetUrl) + .withBodyIncluding('eth_blockNumber') + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: 8794509830454968, + result: blockNumber.value.toString(16), + }, + }; + }), + await mockServer + .forPost(infuraLineaMainnetUrl) + .withBodyIncluding('eth_getBlockByNumber') + .thenCallback(() => { + return ethGetBlockByNumberResult; + }), + await mockServer + .forPost(infuraLineaMainnetUrl) + .withBodyIncluding('eth_call') + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '3aca99b4-92a1-4ad2-be3a-ae9fdd76fdaa', + result: + '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000004e2adedda15fd6', + }, + }; + }), + // Mocks for Sepolia + await mockServer + .forPost(infuraSepoliaUrl) + .withJsonBodyIncluding({ method: 'net_version' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '6327576363628226', + result: '0x1', + }, + })), + await mockServer + .forPost(infuraSepoliaUrl) + .withBodyIncluding('eth_blockNumber') + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: 8794509830454968, + result: blockNumber.value.toString(16), + }, + }; + }), + await mockServer + .forPost(infuraSepoliaUrl) + .withBodyIncluding('eth_getBlockByNumber') + .thenCallback(() => { + return ethGetBlockByNumberResult; + }), + await mockServer + .forPost(infuraSepoliaUrl) + .withJsonBodyIncluding({ method: 'eth_getBalance' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '367912711400467', + result: '0x15af1d78b58c40000', + }, + })), + // Mocks for Linea Sepolia + await mockServer + .forPost(infuraLineaSepoliaUrl) + .withJsonBodyIncluding({ method: 'net_version' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '6327576363628226', + result: '0x1', + }, + })), + await mockServer + .forPost(infuraLineaSepoliaUrl) + .withBodyIncluding('eth_blockNumber') + .thenCallback(() => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: 8794509830454968, + result: blockNumber.value.toString(16), + }, + }; + }), + await mockServer + .forPost(infuraLineaSepoliaUrl) + .withBodyIncluding('eth_getBlockByNumber') + .thenCallback(() => { + return ethGetBlockByNumberResult; + }), + await mockServer + .forPost(infuraLineaSepoliaUrl) + .withJsonBodyIncluding({ method: 'eth_getBalance' }) + .thenCallback(() => ({ + statusCode: 200, + json: { + jsonrpc: '2.0', + id: '367912711400467', + result: '0x15af1d78b58c40000', + }, + })), + ]; +} +const DELAY_UNTIL_NEXT_POLL = 20000; +async function getAllInfuraJsonRpcRequests( + mockedEndpoint: MockedEndpoint[], +): Promise { + const allInfuraJsonRpcRequests: JsonRpcRequest[] = []; + let seenRequests; + let seenProviderRequests; + + for (const m of mockedEndpoint) { + seenRequests = await m.getSeenRequests(); + seenProviderRequests = seenRequests.filter((request) => + request.url.match('infura'), + ); + + for (const r of seenProviderRequests) { + const json = (await r.body.getJson()) as JsonRpcRequest | undefined; + if (json !== undefined) { + allInfuraJsonRpcRequests.push(json); + } + } + } + + return allInfuraJsonRpcRequests; +} +describe('Account Tracker API polling', function () { + it('should make the expected RPC calls to infura', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPreferencesControllerShowNativeTokenAsMainBalanceDisabled() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: mockInfura, + }, + async ({ driver, mockedEndpoint }) => { + await loginWithoutBalanceValidation(driver); + const homepage = new HomePage(driver); + await homepage.check_pageIsLoaded(); + // Want to wait long enough to pull requests relevant to a single loop cycle + await driver.delay(DELAY_UNTIL_NEXT_POLL); + const infuraJsonRpcRequests = await getAllInfuraJsonRpcRequests( + mockedEndpoint, + ); + + // TODO: expecting the length of infuraJsonRpcRequests would be more accurate + if (process.env.PORTFOLIO_VIEW) { + const ethCallInfuraRequests = infuraJsonRpcRequests.filter( + (obj) => + obj.method === 'eth_call' && + (obj.params as unknown[])?.[1] === '3', + ); + + const ethGetBalanceInfuraRequests = infuraJsonRpcRequests.filter( + (obj) => + obj.method === 'eth_getBalance' && + (obj.params as unknown[])?.[1] === '3', + ); + + // We will call eth_getBalance for Sepolia and Linea Sepolia because multicall is not available for them + expect(ethGetBalanceInfuraRequests.length).toEqual(2); + // We will call eth_call for linea mainnet and mainnet + expect(ethCallInfuraRequests.length).toEqual(2); + } else { + expect( + infuraJsonRpcRequests.some( + (obj) => obj.method === 'eth_blockNumber', + ), + ).toBeTruthy(); + expect( + infuraJsonRpcRequests.some( + (obj) => obj.method === 'eth_getBlockByNumber', + ), + ).toBeTruthy(); + expect( + infuraJsonRpcRequests.some((obj) => obj.method === 'eth_call'), + ).toBeTruthy(); + } + }, + ); + }); +}); + +describe('Token Detection', function () { + async function mockAccountApiForPortfolioView(mockServer: Mockttp) { + return [ + await mockServer + .forGet( + 'https://accounts.api.cx.metamask.io/v2/accounts/0x5cfe73b6021e818b776b421b1c4db2474086a7e1/balances', + ) + .withQuery({ + networks: '1,59144', + }) + .thenCallback(() => ({ + statusCode: 200, + json: { + count: 0, + balances: [ + { + object: 'token', + address: '0x0000000000000000000000000000000000000000', + symbol: 'ETH', + name: 'Ether', + type: 'native', + timestamp: '2015-07-30T03:26:13.000Z', + decimals: 18, + chainId: 1, + balance: '20', + }, + ], + unprocessedNetworks: [], + }, + })), + ]; + } + it('should make calls to account api as expected', async function () { + if (process.env.PORTFOLIO_VIEW) { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withPreferencesControllerShowNativeTokenAsMainBalanceDisabled() + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: mockAccountApiForPortfolioView, + }, + async ({ driver, mockedEndpoint: mockedEndpoints }) => { + await loginWithoutBalanceValidation(driver); + const homepage = new HomePage(driver); + await homepage.check_pageIsLoaded(); + await driver.delay(DELAY_UNTIL_NEXT_POLL); + + for (const single of mockedEndpoints) { + const requests = await single.getSeenRequests(); + assert.equal( + requests.length, + 1, + `${single} should make requests after onboarding`, + ); + } + }, + ); + } + }); +}); From 6830a39762eee1854f0fd86b0c13d31872fa556e Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Tue, 26 Nov 2024 15:16:24 +0530 Subject: [PATCH 25/40] feat: Changing title for permit requests (#28537) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Change title and description for permit pages. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3633 ## **Manual testing steps** 1. Enable permit signature decoding locally 2. Go to test dapp 3. Check title and description of permit pages ## **Screenshots/Recordings** Screenshot 2024-11-19 at 3 46 02 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. --------- Co-authored-by: MetaMask Bot --- .../signatures/nft-permit.spec.ts | 4 +-- .../confirmations/signatures/permit.test.tsx | 4 +-- .../components/confirm/title/title.test.tsx | 35 ------------------- .../components/confirm/title/title.tsx | 22 ------------ .../__snapshots__/confirm.test.tsx.snap | 12 +++---- 5 files changed, 10 insertions(+), 67 deletions(-) diff --git a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts index 383a3bd6b924..4aeda07a3758 100644 --- a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts @@ -126,9 +126,9 @@ async function assertInfoValues(driver: Driver) { text: '0x581c3...45947', }); - const title = driver.findElement({ text: 'Withdrawal request' }); + const title = driver.findElement({ text: 'Signature request' }); const description = driver.findElement({ - text: 'This site wants permission to withdraw your NFTs', + text: 'Review request details before you confirm.', }); const primaryType = driver.findElement({ text: 'Permit' }); const spender = driver.findElement({ diff --git a/test/integration/confirmations/signatures/permit.test.tsx b/test/integration/confirmations/signatures/permit.test.tsx index 7af3be743f5f..ba51deb7336c 100644 --- a/test/integration/confirmations/signatures/permit.test.tsx +++ b/test/integration/confirmations/signatures/permit.test.tsx @@ -191,9 +191,9 @@ describe('Permit Confirmation', () => { }); await waitFor(() => { - expect(screen.getByText('Spending cap request')).toBeInTheDocument(); + expect(screen.getByText('Signature request')).toBeInTheDocument(); expect( - screen.getByText('This site wants permission to spend your tokens.'), + screen.getByText('Review request details before you confirm.'), ).toBeInTheDocument(); }); }); diff --git a/ui/pages/confirmations/components/confirm/title/title.test.tsx b/ui/pages/confirmations/components/confirm/title/title.test.tsx index 3d4d6672940d..b20b67b05c97 100644 --- a/ui/pages/confirmations/components/confirm/title/title.test.tsx +++ b/ui/pages/confirmations/components/confirm/title/title.test.tsx @@ -8,13 +8,8 @@ import { getMockPersonalSignConfirmStateForRequest, getMockSetApprovalForAllConfirmState, getMockTypedSignConfirmState, - getMockTypedSignConfirmStateForRequest, } from '../../../../../../test/data/confirmations/helper'; import { unapprovedPersonalSignMsg } from '../../../../../../test/data/confirmations/personal_sign'; -import { - permitNFTSignatureMsg, - permitSignatureMsg, -} from '../../../../../../test/data/confirmations/typed_sign'; import { renderWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers'; import { tEn } from '../../../../../../test/lib/i18n-helpers'; import { @@ -59,36 +54,6 @@ describe('ConfirmTitle', () => { ).toBeInTheDocument(); }); - it('should render the title and description for a permit signature', () => { - const mockStore = configureMockStore([])( - getMockTypedSignConfirmStateForRequest(permitSignatureMsg), - ); - const { getByText } = renderWithConfirmContextProvider( - , - mockStore, - ); - - expect(getByText('Spending cap request')).toBeInTheDocument(); - expect( - getByText('This site wants permission to spend your tokens.'), - ).toBeInTheDocument(); - }); - - it('should render the title and description for a NFT permit signature', () => { - const mockStore = configureMockStore([])( - getMockTypedSignConfirmStateForRequest(permitNFTSignatureMsg), - ); - const { getByText } = renderWithConfirmContextProvider( - , - mockStore, - ); - - expect(getByText('Withdrawal request')).toBeInTheDocument(); - expect( - getByText('This site wants permission to withdraw your NFTs'), - ).toBeInTheDocument(); - }); - it('should render the title and description for typed signature', () => { const mockStore = configureMockStore([])(getMockTypedSignConfirmState()); const { getByText } = renderWithConfirmContextProvider( diff --git a/ui/pages/confirmations/components/confirm/title/title.tsx b/ui/pages/confirmations/components/confirm/title/title.tsx index a926c0f6b482..5fa3cc5b96f9 100644 --- a/ui/pages/confirmations/components/confirm/title/title.tsx +++ b/ui/pages/confirmations/components/confirm/title/title.tsx @@ -4,7 +4,6 @@ import { } from '@metamask/transaction-controller'; import React, { memo, useMemo } from 'react'; -import { TokenStandard } from '../../../../../../shared/constants/transaction'; import GeneralAlert from '../../../../../components/app/alert-system/general-alert/general-alert'; import { Box, Text } from '../../../../../components/component-library'; import { @@ -14,7 +13,6 @@ import { } from '../../../../../helpers/constants/design-system'; import useAlerts from '../../../../../hooks/useAlerts'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; -import { TypedSignSignaturePrimaryTypes } from '../../../constants'; import { useConfirmContext } from '../../../context/confirm'; import { Confirmation, SignatureRequestType } from '../../../types/confirm'; import { isSIWESignatureRequest } from '../../../utils'; @@ -61,8 +59,6 @@ const getTitle = ( customSpendingCap?: string, isRevokeSetApprovalForAll?: boolean, pending?: boolean, - primaryType?: keyof typeof TypedSignSignaturePrimaryTypes, - tokenStandard?: string, ) => { if (pending) { return ''; @@ -79,12 +75,6 @@ const getTitle = ( } return t('confirmTitleSignature'); case TransactionType.signTypedData: - if (primaryType === TypedSignSignaturePrimaryTypes.PERMIT) { - if (tokenStandard === TokenStandard.ERC721) { - return t('setApprovalForAllRedesignedTitle'); - } - return t('confirmTitlePermitTokens'); - } return t('confirmTitleSignature'); case TransactionType.tokenMethodApprove: if (isNFT) { @@ -113,8 +103,6 @@ const getDescription = ( customSpendingCap?: string, isRevokeSetApprovalForAll?: boolean, pending?: boolean, - primaryType?: keyof typeof TypedSignSignaturePrimaryTypes, - tokenStandard?: string, ) => { if (pending) { return ''; @@ -131,12 +119,6 @@ const getDescription = ( } return t('confirmTitleDescSign'); case TransactionType.signTypedData: - if (primaryType === TypedSignSignaturePrimaryTypes.PERMIT) { - if (tokenStandard === TokenStandard.ERC721) { - return t('confirmTitleDescApproveTransaction'); - } - return t('confirmTitleDescPermitSignature'); - } return t('confirmTitleDescSign'); case TransactionType.tokenMethodApprove: if (isNFT) { @@ -195,8 +177,6 @@ const ConfirmTitle: React.FC = memo(() => { customSpendingCap, isRevokeSetApprovalForAll, spendingCapPending || revokePending, - primaryType, - tokenStandard, ), [ currentConfirmation, @@ -219,8 +199,6 @@ const ConfirmTitle: React.FC = memo(() => { customSpendingCap, isRevokeSetApprovalForAll, spendingCapPending || revokePending, - primaryType, - tokenStandard, ), [ currentConfirmation, diff --git a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap index 9f718a4b8a03..1d504025a44d 100644 --- a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap +++ b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap @@ -457,12 +457,12 @@ exports[`Confirm should match snapshot for signature - typed sign - V4 - PermitB

- Spending cap request + Signature request

- This site wants permission to spend your tokens. + Review request details before you confirm.

- Spending cap request + Signature request