From 4b6c97e72001de29abb73e793cf22d33488c3be2 Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Fri, 1 Nov 2024 04:33:55 +0000 Subject: [PATCH 01/32] Version v12.7.0 --- CHANGELOG.md | 272 ++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 272 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af63a4ed61d3..51e35443af23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,275 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.7.0] +### Uncategorized +- chore: Master sync ([#28222](https://github.com/MetaMask/metamask-extension/pull/28222)) +- Merge origin/develop into master-sync +- Version v12.6.0 ([#27996](https://github.com/MetaMask/metamask-extension/pull/27996)) +- refactor: move `getSelectedInternalAccount` from `selectors.js` to `accounts.ts` ([#27644](https://github.com/MetaMask/metamask-extension/pull/27644)) +- fix: cherry-pick: Prevent coercing small spending caps to zero (#28179) ([#28179](https://github.com/MetaMask/metamask-extension/pull/28179)) +- [cherry pick] Fix left aligned fullscreen (#28218) ([#28218](https://github.com/MetaMask/metamask-extension/pull/28218)) +- v12.6.0 changelog lint fix ([#28228](https://github.com/MetaMask/metamask-extension/pull/28228)) +- feat: Improve provider method metrics for add/switch chain ([#28214](https://github.com/MetaMask/metamask-extension/pull/28214)) +- V12.6.0 Changelog ([#28166](https://github.com/MetaMask/metamask-extension/pull/28166)) +- Merge master to v12.6.0 after the merge of 12.5.1 to master ([#28217](https://github.com/MetaMask/metamask-extension/pull/28217)) +- fix: Fix left-aligned fullscreen UI ([#28218](https://github.com/MetaMask/metamask-extension/pull/28218)) +- test: add ui render for debug ui integration tests ([#27621](https://github.com/MetaMask/metamask-extension/pull/27621)) +- Merge branch 'Version-v12.6.0' into v12.6.0-merge-master-12.5.1 +- chore: Cherry pick data deletion into v12.6.0 ([#28223](https://github.com/MetaMask/metamask-extension/pull/28223)) +- feat: poll native currency prices across chains ([#28196](https://github.com/MetaMask/metamask-extension/pull/28196)) +- test: Fix data deletion e2e tests ([#28221](https://github.com/MetaMask/metamask-extension/pull/28221)) +- Update LavaMoat policies +- Merge remote-tracking branch 'origin/master' into v12.6.0-merge-master-12.5.1 +- fix (cherry-pick): incorrect standard swap gas fee estimation (#28127) ([#28127](https://github.com/MetaMask/metamask-extension/pull/28127)) +- [cherry pick] Fix bugs related to queued requests ([#28197](https://github.com/MetaMask/metamask-extension/pull/28197)) +- feat (cherry-pick): added test network as selected network if globally selected for… ([#28139](https://github.com/MetaMask/metamask-extension/pull/28139)) +- chore(cherry-pick): update @metamask/bitcoin-wallet-snap to 0.8.2 (#28135) ([#28135](https://github.com/MetaMask/metamask-extension/pull/28135)) +- feat: Bump `QueuedRequestController` from `^2.0.0` to `^7.0.0`  ([#28090](https://github.com/MetaMask/metamask-extension/pull/28090)) +- chore: Add a new transaction event prop ([#28153](https://github.com/MetaMask/metamask-extension/pull/28153)) +- feat: Copy updates to satisfy UK regulation requirements ([#28157](https://github.com/MetaMask/metamask-extension/pull/28157)) +- feat: Token Network Filter UI [Extension] ([#27884](https://github.com/MetaMask/metamask-extension/pull/27884)) +- fix: flaky test `BTC Account - Overview has balance` ([#28181](https://github.com/MetaMask/metamask-extension/pull/28181)) +- feat: Native asset send ([#27979](https://github.com/MetaMask/metamask-extension/pull/27979)) +- chore: poll for bridge quotes ([#28029](https://github.com/MetaMask/metamask-extension/pull/28029)) +- fix: Prevent coercing small spending caps to zero ([#28179](https://github.com/MetaMask/metamask-extension/pull/28179)) +- fix: Fix #28097 - Prevent redirect after adding network in Onboarding Settings ([#28165](https://github.com/MetaMask/metamask-extension/pull/28165)) +- chore (cherry-pick): ignore warning for ethereumjs-wallet (#28145) ([#28145](https://github.com/MetaMask/metamask-extension/pull/28145)) +- feat: add privacy mode ([#28021](https://github.com/MetaMask/metamask-extension/pull/28021)) +- chore: update confirmations code ownership ([#27862](https://github.com/MetaMask/metamask-extension/pull/27862)) +- feat(snaps): Add `useDisplayName` hook ([#27868](https://github.com/MetaMask/metamask-extension/pull/27868)) +- chore: upgrade signature controller to remove global network ([#28063](https://github.com/MetaMask/metamask-extension/pull/28063)) +- feat: enable security alerts api ([#28040](https://github.com/MetaMask/metamask-extension/pull/28040)) +- feat: Add re-simulation logic ([#28104](https://github.com/MetaMask/metamask-extension/pull/28104)) +- chore: update bridge quote request on input change ([#28028](https://github.com/MetaMask/metamask-extension/pull/28028)) +- fix: Updated network message on Review Permission and Connections page ([#28126](https://github.com/MetaMask/metamask-extension/pull/28126)) +- chore: bump asset controllers to 39 + polling API ([#28025](https://github.com/MetaMask/metamask-extension/pull/28025)) +- fix: incorrect standard swap gas fee estimation ([#28127](https://github.com/MetaMask/metamask-extension/pull/28127)) +- feat: Capture 3 existing properties within non-anonymous transaction … ([#28144](https://github.com/MetaMask/metamask-extension/pull/28144)) +- refactor: remove global network usage from transaction simulation ([#27895](https://github.com/MetaMask/metamask-extension/pull/27895)) +- test(ramps): fixes btc native token test ([#27601](https://github.com/MetaMask/metamask-extension/pull/27601)) +- fix: Reduce gas limit fallback from 95% to 35% of the block gas limit on failed gas limit estimations ([#27954](https://github.com/MetaMask/metamask-extension/pull/27954)) +- refactor: clean up profile sync hooks ([#28132](https://github.com/MetaMask/metamask-extension/pull/28132)) +- chore: ignore warning for ethereumjs-wallet ([#28145](https://github.com/MetaMask/metamask-extension/pull/28145)) +- test: [Snaps E2E] Unified methods and clean up snaps e2e tests ([#27684](https://github.com/MetaMask/metamask-extension/pull/27684)) +- feat: added test network as selected network if globally selected for connection Request ([#27980](https://github.com/MetaMask/metamask-extension/pull/27980)) +- chore: update @metamask/bitcoin-wallet-snap to 0.8.2 ([#28135](https://github.com/MetaMask/metamask-extension/pull/28135)) +- chore: small storybook and docs updates to SensitiveText component ([#28089](https://github.com/MetaMask/metamask-extension/pull/28089)) +- feat(NOTIFY-1260): enable account syncing ([#28120](https://github.com/MetaMask/metamask-extension/pull/28120)) +- chore: bridge-api fetchBridgeQuotes util ([#28027](https://github.com/MetaMask/metamask-extension/pull/28027)) +- feat: Cherry-pick: Please view the attached issue ([#28133](https://github.com/MetaMask/metamask-extension/pull/28133)) +- feat: update phishing controller version ([#28131](https://github.com/MetaMask/metamask-extension/pull/28131)) +- fix: broken not existing type file import ([#28055](https://github.com/MetaMask/metamask-extension/pull/28055)) +- test: blockaid update version and reenable specs ([#28121](https://github.com/MetaMask/metamask-extension/pull/28121)) +- fix: Reduce usage of scientific notation ([#27992](https://github.com/MetaMask/metamask-extension/pull/27992)) +- test: [POM] Migrate onboarding infura call privacy e2e tests ([#28079](https://github.com/MetaMask/metamask-extension/pull/28079)) +- feat: share the same user storage mock instance in tests ([#28119](https://github.com/MetaMask/metamask-extension/pull/28119)) +- chore: Using button icon component for clikable icons ([#28082](https://github.com/MetaMask/metamask-extension/pull/28082)) +- feat: convert MetaMetricsController to typescript ([#28072](https://github.com/MetaMask/metamask-extension/pull/28072)) +- feat: improved way to trigger mmi e2e tests ([#27932](https://github.com/MetaMask/metamask-extension/pull/27932)) +- test: allow more simple findElement by data-testid ([#28065](https://github.com/MetaMask/metamask-extension/pull/28065)) +- fix: json-rpc-middleware-stream@^5 -> @metamask/json-rpc-middleware-stream@^8 ([#28060](https://github.com/MetaMask/metamask-extension/pull/28060)) +- fix(devDeps): babel@7.23.2->7.25.9 ([#28068](https://github.com/MetaMask/metamask-extension/pull/28068)) +- feat: better storybook stories for the notification pages ([#27861](https://github.com/MetaMask/metamask-extension/pull/27861)) +- fix: update storybook to support NFT images ([#28105](https://github.com/MetaMask/metamask-extension/pull/28105)) +- fix: Cherry-pick: Fix c2 detection bypass by supporting all network requests types ([#28087](https://github.com/MetaMask/metamask-extension/pull/28087)) +- fix: broken test `Vault Decryptor Page is able to decrypt the vault uploading the log file in the vault-decryptor webapp` ([#28098](https://github.com/MetaMask/metamask-extension/pull/28098)) +- test: Complete missing step for add a contact to the address book in existing E2E test ([#27959](https://github.com/MetaMask/metamask-extension/pull/27959)) +- feat(3419): sensitive text component ([#28056](https://github.com/MetaMask/metamask-extension/pull/28056)) +- test: Added e2e for switch network ([#27967](https://github.com/MetaMask/metamask-extension/pull/27967)) +- fix: cherry-pick: Fall back to token list for the token symbol (#28003) ([#28003](https://github.com/MetaMask/metamask-extension/pull/28003)) +- fix: Cherry-pick Support dynamic native token name on gas component (#28048) ([#28048](https://github.com/MetaMask/metamask-extension/pull/28048)) +- fix: cherry-pick: Gas changes for low Max base fee and Priority fee (#28037) ([#28037](https://github.com/MetaMask/metamask-extension/pull/28037)) +- fix: c2 bypass ([#28057](https://github.com/MetaMask/metamask-extension/pull/28057)) +- feat: design changes in signature paged message section ([#28038](https://github.com/MetaMask/metamask-extension/pull/28038)) +- test: NOTIFY-1256 - Extending E2E tests for Account Sync ([#28067](https://github.com/MetaMask/metamask-extension/pull/28067)) +- feat: NFT token transfer ([#27955](https://github.com/MetaMask/metamask-extension/pull/27955)) +- test: notifications integration tests ([#28022](https://github.com/MetaMask/metamask-extension/pull/28022)) +- fix: disable notifications when basic functionality off ([#28045](https://github.com/MetaMask/metamask-extension/pull/28045)) +- chore: update stories for name component ([#28049](https://github.com/MetaMask/metamask-extension/pull/28049)) +- fix: flaky anti-pattern getText + assert 3 ([#28062](https://github.com/MetaMask/metamask-extension/pull/28062)) +- feat: add support for external links in feature announcements ([#26491](https://github.com/MetaMask/metamask-extension/pull/26491)) +- test: [POM] Create onboarding related page object modal base pages and migrate e2e tests ([#28036](https://github.com/MetaMask/metamask-extension/pull/28036)) +- docs: update debugging sentry step 3 ([#28034](https://github.com/MetaMask/metamask-extension/pull/28034)) +- fix: Support dynamic native token name on gas component ([#28048](https://github.com/MetaMask/metamask-extension/pull/28048)) +- fix: Fall back to token list for the token symbol ([#28003](https://github.com/MetaMask/metamask-extension/pull/28003)) +- fix: flaky anti-pattern getText + assert 2 ([#28043](https://github.com/MetaMask/metamask-extension/pull/28043)) +- feat: :sparkles: show a notification item in the settings page ([#26843](https://github.com/MetaMask/metamask-extension/pull/26843)) +- fix: Fix limited visibility of decrypt message ([#27622](https://github.com/MetaMask/metamask-extension/pull/27622)) +- fix(deps): @metamask/eth-json-rpc-filters@^8.0.0->^9.0.0 ([#27956](https://github.com/MetaMask/metamask-extension/pull/27956)) +- chore: Bump gridplus-sdk to 2.7.1 ([#28008](https://github.com/MetaMask/metamask-extension/pull/28008)) +- fix: adjust spacing of quote rate in swaps ([#28051](https://github.com/MetaMask/metamask-extension/pull/28051)) +- feat: new phishing warning UI with metrics ([#27942](https://github.com/MetaMask/metamask-extension/pull/27942)) +- fix(deps): @keystonehq/metamask-airgapped-keyring@^0.13.1->^0.14.1 ([#27952](https://github.com/MetaMask/metamask-extension/pull/27952)) +- fix: Gas changes for low Max base fee and Priority fee ([#28037](https://github.com/MetaMask/metamask-extension/pull/28037)) +- fix: adjust spacing of quote rate in swaps ([#28016](https://github.com/MetaMask/metamask-extension/pull/28016)) +- feat: enable preview token ([#27809](https://github.com/MetaMask/metamask-extension/pull/27809)) +- refactor: use `reselect`'s `createSelector` instead of going through `@redux/toolkit`, as the import names collide when trying to merge files. ([#27643](https://github.com/MetaMask/metamask-extension/pull/27643)) +- fix: storybook `getManifest` issue ([#28010](https://github.com/MetaMask/metamask-extension/pull/28010)) +- feat: bump @metamask/notification-services-controller from 0.7.0 to 0.11.0 ([#28017](https://github.com/MetaMask/metamask-extension/pull/28017)) +- refactor: remove global network usage from petnames ([#27946](https://github.com/MetaMask/metamask-extension/pull/27946)) +- chore: updated package ([#28002](https://github.com/MetaMask/metamask-extension/pull/28002)) +- feat(NOTIFY-1245): add account syncing E2E helpers & basic tests ([#28005](https://github.com/MetaMask/metamask-extension/pull/28005)) +- fix: Fix stream re-initialization ([#28024](https://github.com/MetaMask/metamask-extension/pull/28024)) +- refactor: routes.component.js and creation of ToastMaster ([#27735](https://github.com/MetaMask/metamask-extension/pull/27735)) +- fix: @metamask/eth-json-rpc-filters@^7.0.0->^8.0.0 ([#27917](https://github.com/MetaMask/metamask-extension/pull/27917)) +- refactor: remove relative imports to `selectors/index.js` from other selectors files ([#27642](https://github.com/MetaMask/metamask-extension/pull/27642)) +- refactor: remove circular dependency between `ui/ducks/custom-gas.js` and `ui/selectors/index.js` ([#27640](https://github.com/MetaMask/metamask-extension/pull/27640)) +- fix: Allow users to remove linea from networks list ([#27512](https://github.com/MetaMask/metamask-extension/pull/27512)) +- fix: prevent scrolling to account list item on send page ([#27934](https://github.com/MetaMask/metamask-extension/pull/27934)) +- feat: add ape token mainnet ([#27974](https://github.com/MetaMask/metamask-extension/pull/27974)) +- feat: removed feature flag for confirmations screen ([#27877](https://github.com/MetaMask/metamask-extension/pull/27877)) +- test: update notification date tests to be timezone agnostic ([#27925](https://github.com/MetaMask/metamask-extension/pull/27925)) +- fix: updated event name for site cell component ([#27981](https://github.com/MetaMask/metamask-extension/pull/27981)) +- fix(snaps): Adjust alignment of custom UI links ([#27957](https://github.com/MetaMask/metamask-extension/pull/27957)) +- fix(deps): gridplus-sdk@2.5.1->~2.6.0 ([#27973](https://github.com/MetaMask/metamask-extension/pull/27973)) +- Version v12.6.0 +- ci: reduced Sentry frequency on CircleCI develop ([#27912](https://github.com/MetaMask/metamask-extension/pull/27912)) +- chore:Master sync ([#27935](https://github.com/MetaMask/metamask-extension/pull/27935)) +- Merge origin/develop into master-sync +- test: Completing missing step for import ERC1155 token origin dapp in existing E2E test ([#27680](https://github.com/MetaMask/metamask-extension/pull/27680)) +- feat(metametrics): use specific `account_hardware_type` for OneKey devices ([#27296](https://github.com/MetaMask/metamask-extension/pull/27296)) +- feat: add migration 131 ([#27364](https://github.com/MetaMask/metamask-extension/pull/27364)) +- chore: Disable account syncing in prod ([#27943](https://github.com/MetaMask/metamask-extension/pull/27943)) +- test: Remove delays from onboarding tests ([#27961](https://github.com/MetaMask/metamask-extension/pull/27961)) +- perf: Create custom trace to measure performance of opening the account list ([#27907](https://github.com/MetaMask/metamask-extension/pull/27907)) +- fix: flaky test `Confirmation Redesign ERC721 Approve Component Submit an Approve transaction @no-mmi Sends a type 2 transaction (EIP1559)` ([#27928](https://github.com/MetaMask/metamask-extension/pull/27928)) +- fix: lint-lockfile flaky job by changing resources from medium to medium-plus ([#27950](https://github.com/MetaMask/metamask-extension/pull/27950)) +- feat: add “Incomplete Asset Displayed” metric & fix: should only set default decimals if ERC20 ([#27494](https://github.com/MetaMask/metamask-extension/pull/27494)) +- feat: Convert AppStateController to typescript ([#27572](https://github.com/MetaMask/metamask-extension/pull/27572)) +- chore(deps): upgrade from json-rpc-engine to @metamask/json-rpc-engine ([#22875](https://github.com/MetaMask/metamask-extension/pull/22875)) +- chore: bump signature controller to remove message managers ([#27787](https://github.com/MetaMask/metamask-extension/pull/27787)) +- chore: add testing-library/dom dependency ([#27493](https://github.com/MetaMask/metamask-extension/pull/27493)) +- test: [POM] Migrate contract interaction with snap account e2e tests to page object modal ([#27924](https://github.com/MetaMask/metamask-extension/pull/27924)) +- fix: bump message signing snap to support portfolio automatic connections ([#27936](https://github.com/MetaMask/metamask-extension/pull/27936)) +- fix: bump `@metamask/ppom-validator` from `0.34.0` to `0.35.1` ([#27939](https://github.com/MetaMask/metamask-extension/pull/27939)) +- feat: convert AlertController to typescript ([#27764](https://github.com/MetaMask/metamask-extension/pull/27764)) +- fix: flaky test `Vault Decryptor Page is able to decrypt the vault pasting the text in the vault-decryptor webapp` ([#27921](https://github.com/MetaMask/metamask-extension/pull/27921)) +- fix: flaky tests `Add existing token using search renders the balance for the chosen token` ([#27853](https://github.com/MetaMask/metamask-extension/pull/27853)) +- feat(logging): add extension request logging and retrieval ([#27655](https://github.com/MetaMask/metamask-extension/pull/27655)) +- test: Update test-dapp to verison 8.7.0 ([#27816](https://github.com/MetaMask/metamask-extension/pull/27816)) +- fix: fall back to bundled chainlist ([#23392](https://github.com/MetaMask/metamask-extension/pull/23392)) +- fix: SonarCloud for forks ([#27700](https://github.com/MetaMask/metamask-extension/pull/27700)) +- fix(deps): update from eth-rpc-errors to @metamask/rpc-errors (cause edition) ([#24496](https://github.com/MetaMask/metamask-extension/pull/24496)) +- fix: swapQuotesError as a property in the reported metric ([#27712](https://github.com/MetaMask/metamask-extension/pull/27712)) +- chore: Bump Snaps packages ([#27376](https://github.com/MetaMask/metamask-extension/pull/27376)) +- chore: update @metamask/bitcoin-wallet-snap to 0.7.0 ([#27730](https://github.com/MetaMask/metamask-extension/pull/27730)) +- fix: Onboarding: Code style nits ([#27767](https://github.com/MetaMask/metamask-extension/pull/27767)) +- feat: use asset pickers with network dropdown in cross-chain swaps page ([#27522](https://github.com/MetaMask/metamask-extension/pull/27522)) +- test: set ENABLE_MV3 automatically ([#27748](https://github.com/MetaMask/metamask-extension/pull/27748)) +- fix: Contract Interaction - cannot read the property `text_signature` ([#27686](https://github.com/MetaMask/metamask-extension/pull/27686)) +- feat: Use requested permissions as default selected values for AmonHenV2 connection flow with case insensitive address comparison ([#27517](https://github.com/MetaMask/metamask-extension/pull/27517)) +- test: [POM] Migrate signature with snap account e2e tests to page object modal ([#27829](https://github.com/MetaMask/metamask-extension/pull/27829)) +- fix: flaky test `ERC1155 NFTs testdapp interaction should batch transfers ERC1155 token` ([#27897](https://github.com/MetaMask/metamask-extension/pull/27897)) +- chore: Master sync following v12.4.1 ([#27793](https://github.com/MetaMask/metamask-extension/pull/27793)) +- fix: flaky test `Permissions sets permissions and connect to Dapp` ([#27888](https://github.com/MetaMask/metamask-extension/pull/27888)) +- fix: flaky test `ERC721 NFTs testdapp interaction should prompt users to add their NFTs to their wallet (all at once)` ([#27889](https://github.com/MetaMask/metamask-extension/pull/27889)) +- fix: flaky test `Wallet Revoke Permissions should revoke eth_accounts permissions via test dapp` ([#27894](https://github.com/MetaMask/metamask-extension/pull/27894)) +- fix: flaky test `Snap Account Signatures and Disconnects can connect to the Test Dapp, then #signTypedDataV3, disconnect then connect, then #signTypedDataV4 (async flow approve)` ([#27887](https://github.com/MetaMask/metamask-extension/pull/27887)) +- test(mock-e2e): add private domains logic for the privacy report ([#27844](https://github.com/MetaMask/metamask-extension/pull/27844)) +- fix: SENTRY_DSN_FAKE problem ([#27881](https://github.com/MetaMask/metamask-extension/pull/27881)) +- chore: remove unused swaps code ([#27679](https://github.com/MetaMask/metamask-extension/pull/27679)) +- test(TXL-308): initial e2e for stx using swaps ([#27215](https://github.com/MetaMask/metamask-extension/pull/27215)) +- feat: upgrade assets-controllers to v38.3.0 ([#27755](https://github.com/MetaMask/metamask-extension/pull/27755)) +- fix: phishing test to not check c2 domains ([#27846](https://github.com/MetaMask/metamask-extension/pull/27846)) +- feat: use messenger in AccountTracker to get Preferences state ([#27711](https://github.com/MetaMask/metamask-extension/pull/27711)) +- fix: "Update Network: should update added rpc url for exis..." flaky tests ([#27437](https://github.com/MetaMask/metamask-extension/pull/27437)) +- fix: flaky test `Add account should not affect public address when using secret recovery phrase to recover account with non-zero balance @no-mmi` ([#27834](https://github.com/MetaMask/metamask-extension/pull/27834)) +- fix: flaky test `MultiRpc: should select rpc from settings @no-mmi` ([#27858](https://github.com/MetaMask/metamask-extension/pull/27858)) +- perf: include custom traces in benchmark results ([#27701](https://github.com/MetaMask/metamask-extension/pull/27701)) +- chore: Add react-beautiful-dnd to deprecated packages list ([#27856](https://github.com/MetaMask/metamask-extension/pull/27856)) +- feat: Create a quality gate for typescript coverage ([#27717](https://github.com/MetaMask/metamask-extension/pull/27717)) +- feat: preferences controller to base controller v2 ([#27398](https://github.com/MetaMask/metamask-extension/pull/27398)) +- revert: use networkClientId to resolve chainId in PPOM Middleware ([#27570](https://github.com/MetaMask/metamask-extension/pull/27570)) +- feat: Added metrics for edit networks and accounts ([#27820](https://github.com/MetaMask/metamask-extension/pull/27820)) +- ci: Revert minimum E2E timeout to 20 minutes ([#27827](https://github.com/MetaMask/metamask-extension/pull/27827)) +- fix: disable balance checker for Sepolia in account tracker ([#27763](https://github.com/MetaMask/metamask-extension/pull/27763)) +- ci: Improve validation for `sentry:publish` script ([#26580](https://github.com/MetaMask/metamask-extension/pull/26580)) +- test: Fix Vault Decryptor Page e2e test on develop branch ([#27794](https://github.com/MetaMask/metamask-extension/pull/27794)) +- chore: remove old token details page ([#27774](https://github.com/MetaMask/metamask-extension/pull/27774)) +- chore: remove token list display component ([#27772](https://github.com/MetaMask/metamask-extension/pull/27772)) +- test: [POM] Migrate transaction with snap account e2e tests to page object modal ([#27760](https://github.com/MetaMask/metamask-extension/pull/27760)) +- Merge origin/develop into master-sync +- test: Onboarding: Fix vault-decryption-chrome.spec.js ([#27779](https://github.com/MetaMask/metamask-extension/pull/27779)) +- docs: remove outdated Medium link, update "Twitter" to "X" ([#26692](https://github.com/MetaMask/metamask-extension/pull/26692)) +- fix(btc): fix jazzicons generations ([#27662](https://github.com/MetaMask/metamask-extension/pull/27662)) +- feat: upgrade assets-controllers to v38.2.0 ([#27629](https://github.com/MetaMask/metamask-extension/pull/27629)) +- ci: followup to CircleCI Sentry reporting ([#27548](https://github.com/MetaMask/metamask-extension/pull/27548)) +- chore: Master sync ([#27729](https://github.com/MetaMask/metamask-extension/pull/27729)) +- fix(multichain): fix getMultichainCurrentCurrency selector ([#27726](https://github.com/MetaMask/metamask-extension/pull/27726)) +- Merge origin/develop into master-sync +- test: [POM] Migrate create snap account e2e tests to page object modal ([#27697](https://github.com/MetaMask/metamask-extension/pull/27697)) +- fix(btc): fetch btc balance right after account creation ([#27628](https://github.com/MetaMask/metamask-extension/pull/27628)) +- fix: UI startup with no Sentry DSN ([#27714](https://github.com/MetaMask/metamask-extension/pull/27714)) +- ci: make git-diff-develop work for PRs from foreign repos ([#27268](https://github.com/MetaMask/metamask-extension/pull/27268)) +- test: Convert json-rpc e2e tests to TypeScript ([#27659](https://github.com/MetaMask/metamask-extension/pull/27659)) +- perf: add tags to UI startup trace ([#27550](https://github.com/MetaMask/metamask-extension/pull/27550)) +- fix: Disable redirecting Extension users using beta & flask build and dev env to the existing offboarding page ([#27226](https://github.com/MetaMask/metamask-extension/pull/27226)) +- feat(NOTIFY-1193): add profile sync dev menu ([#27666](https://github.com/MetaMask/metamask-extension/pull/27666)) +- refactor: Typescript conversion of log-web3-shim-usage.js ([#23732](https://github.com/MetaMask/metamask-extension/pull/23732)) +- test: removing race condition for asserting inner values (PR-#2) ([#27664](https://github.com/MetaMask/metamask-extension/pull/27664)) +- fix(btc): fix address validation ([#27690](https://github.com/MetaMask/metamask-extension/pull/27690)) +- chore: Update coverage.json ([#27696](https://github.com/MetaMask/metamask-extension/pull/27696)) +- fix: test coverage quality gate ([#27691](https://github.com/MetaMask/metamask-extension/pull/27691)) +- refactor: routes constants ([#27078](https://github.com/MetaMask/metamask-extension/pull/27078)) +- fix: Test coverage quality gate ([#27581](https://github.com/MetaMask/metamask-extension/pull/27581)) +- build: add lottie-web dependency to extension ([#27632](https://github.com/MetaMask/metamask-extension/pull/27632)) +- fix(btc): do not show percentage for tokens ([#27637](https://github.com/MetaMask/metamask-extension/pull/27637)) +- test: removing race condition for asserting inner values (PR-#1) ([#27606](https://github.com/MetaMask/metamask-extension/pull/27606)) +- test: [POM] Migrate Snap Simple Keyring page and Snap List page to page object modal ([#27327](https://github.com/MetaMask/metamask-extension/pull/27327)) +- fix: fix sentry reading undefined ([#27584](https://github.com/MetaMask/metamask-extension/pull/27584)) +- fix: fix sentry reading null ([#27582](https://github.com/MetaMask/metamask-extension/pull/27582)) +- fix(btc): disable balanceIsCached flag ([#27636](https://github.com/MetaMask/metamask-extension/pull/27636)) +- chore: update accounts related packages ([#27284](https://github.com/MetaMask/metamask-extension/pull/27284)) +- chore: set bridge src network, tokens and top assets ([#26214](https://github.com/MetaMask/metamask-extension/pull/26214)) +- test: [Snaps E2E] add delay to installed snaps test to reduce flaking ([#27521](https://github.com/MetaMask/metamask-extension/pull/27521)) +- chore: set bridge dest network, tokens and top assets ([#26213](https://github.com/MetaMask/metamask-extension/pull/26213)) +- feat: Migrate AccountTrackerController to BaseController v2 ([#27258](https://github.com/MetaMask/metamask-extension/pull/27258)) +- fix: disable transaction data decode if deployment ([#27586](https://github.com/MetaMask/metamask-extension/pull/27586)) +- fix: revert jest collect coverage patterns ([#27583](https://github.com/MetaMask/metamask-extension/pull/27583)) +- fix: add amount row for contract deployment ([#27594](https://github.com/MetaMask/metamask-extension/pull/27594)) +- fix: "Dapp viewed Event @no-mmi is sent when refreshing da..." flaky test ([#27381](https://github.com/MetaMask/metamask-extension/pull/27381)) +- chore: fix deps audit ([#27620](https://github.com/MetaMask/metamask-extension/pull/27620)) +- fix: Recreate offscreen document if it already exists ([#27596](https://github.com/MetaMask/metamask-extension/pull/27596)) +- fix: flaky test `Block Explorer links to the token tracker in the explorer` ([#27599](https://github.com/MetaMask/metamask-extension/pull/27599)) +- fix: flaky test `Import flow allows importing multiple tokens from search` ([#27567](https://github.com/MetaMask/metamask-extension/pull/27567)) +- fix: flaky test `Address Book Edit entry in address book` due to race condition with mmi menu ([#27557](https://github.com/MetaMask/metamask-extension/pull/27557)) +- refactor: Typescript conversion of get-provider-state.js ([#23635](https://github.com/MetaMask/metamask-extension/pull/23635)) +- chore: Use "gas_included" event prop ([#27559](https://github.com/MetaMask/metamask-extension/pull/27559)) +- fix: mock locale in unit test ([#27574](https://github.com/MetaMask/metamask-extension/pull/27574)) +- feat: codefence Account Watcher for flask ([#27543](https://github.com/MetaMask/metamask-extension/pull/27543)) +- chore: start upgrade to React Router v6 ([#27185](https://github.com/MetaMask/metamask-extension/pull/27185)) +- fix: AmonHenV2 connection flow incremental permitted chain approval and account address case comparison ([#27518](https://github.com/MetaMask/metamask-extension/pull/27518)) +- fix: flaky test `Backup and Restore should backup the account settings` ([#27565](https://github.com/MetaMask/metamask-extension/pull/27565)) +- feat: Add redesign integration tests ([#27259](https://github.com/MetaMask/metamask-extension/pull/27259)) +- fix: flaky test `4byte setting does not try to get contract method name from 4byte when the setting is off` ([#27560](https://github.com/MetaMask/metamask-extension/pull/27560)) +- feat: add merge queue ([#26871](https://github.com/MetaMask/metamask-extension/pull/26871)) +- feat: remove squiggle animation from swaps smart transactions ([#27264](https://github.com/MetaMask/metamask-extension/pull/27264)) +- fix(snaps): Fix custom UI buttons submitting forms ([#27531](https://github.com/MetaMask/metamask-extension/pull/27531)) +- chore: Master sync following v12.3.1 ([#27538](https://github.com/MetaMask/metamask-extension/pull/27538)) +- Merge origin/develop into master-sync +- fix(NOTIFY-1171): account syncing performance and bug fixes ([#27529](https://github.com/MetaMask/metamask-extension/pull/27529)) +- fix: genUnapprovedApproveConfirmation import path ([#27530](https://github.com/MetaMask/metamask-extension/pull/27530)) +- feat: convert account tracker to typescript ([#27231](https://github.com/MetaMask/metamask-extension/pull/27231)) +- fix: Fix snaps permission connection for `CHAIN_PERMISSIONS` feature flag ([#27459](https://github.com/MetaMask/metamask-extension/pull/27459)) +- fix: flaky test `Navigation Signature - Different signature types initiates multiple signatures and rejects all` ([#27481](https://github.com/MetaMask/metamask-extension/pull/27481)) +- feat: Double Sentry performance trace sample rate ([#27468](https://github.com/MetaMask/metamask-extension/pull/27468)) +- ci: Expand github bot policy update comment to be more actionable ([#27242](https://github.com/MetaMask/metamask-extension/pull/27242)) +- chore: Add `useLedgerConnection` unit tests ([#27358](https://github.com/MetaMask/metamask-extension/pull/27358)) +- ci: Sentry reporting only on develop branch, with Git message overrides ([#27412](https://github.com/MetaMask/metamask-extension/pull/27412)) +- test: Fix flaky permit test ([#27450](https://github.com/MetaMask/metamask-extension/pull/27450)) +- fix: removed closeMenu for ConnectedAccountsMenu ([#27460](https://github.com/MetaMask/metamask-extension/pull/27460)) +- chore: set bridge selected tokens and amount ([#26212](https://github.com/MetaMask/metamask-extension/pull/26212)) +- fix: flaky test `Add account should not affect public address when using secret recovery phrase to recover account with non-zero balance @no-mmi`aded ([#27420](https://github.com/MetaMask/metamask-extension/pull/27420)) +- fix: flaky test `Responsive UI Send Transaction from responsive window` ([#27417](https://github.com/MetaMask/metamask-extension/pull/27417)) +- fix: flaky test `Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed` ([#27352](https://github.com/MetaMask/metamask-extension/pull/27352)) +- fix: "Warning: Invalid argument supplied to oneOfType" ([#27267](https://github.com/MetaMask/metamask-extension/pull/27267)) +- chore: bump profile-sync-controller to 0.9.3 ([#27415](https://github.com/MetaMask/metamask-extension/pull/27415)) +- fix: Remove duplication ([#27421](https://github.com/MetaMask/metamask-extension/pull/27421)) +- fix: Confirm Page test failing in CI/CD ([#27423](https://github.com/MetaMask/metamask-extension/pull/27423)) +- feat: Add performance metrics for signature requests ([#26967](https://github.com/MetaMask/metamask-extension/pull/26967)) +- fix(NOTIFY-1166): rename account sync event names ([#27413](https://github.com/MetaMask/metamask-extension/pull/27413)) + ## [12.6.0] ### Added - Added the APE network icon ([#27841](https://github.com/MetaMask/metamask-extension/pull/27841)) @@ -5303,7 +5572,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.6.0...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.7.0...HEAD +[12.7.0]: https://github.com/MetaMask/metamask-extension/compare/v12.6.0...v12.7.0 [12.6.0]: https://github.com/MetaMask/metamask-extension/compare/v12.5.1...v12.6.0 [12.5.1]: https://github.com/MetaMask/metamask-extension/compare/v12.5.0...v12.5.1 [12.5.0]: https://github.com/MetaMask/metamask-extension/compare/v12.4.2...v12.5.0 diff --git a/package.json b/package.json index 4b30ab0948c8..496f943590d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "12.6.0", + "version": "12.7.0", "private": true, "repository": { "type": "git", From 7798ef4c536d30ea2980831183c512d1bf2c9f87 Mon Sep 17 00:00:00 2001 From: Marina Boboc <120041701+benjisclowder@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:19:34 +0200 Subject: [PATCH 02/32] chore: Modify changelog title to fix ci job (#28237) ## **Description** Changing description of changelog entries from "Uncategorized" to "Fixed" to avoid CI job fail. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28237?quickstart=1) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51e35443af23..be2d49f0631f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [12.7.0] -### Uncategorized +### Fixed - chore: Master sync ([#28222](https://github.com/MetaMask/metamask-extension/pull/28222)) - Merge origin/develop into master-sync - Version v12.6.0 ([#27996](https://github.com/MetaMask/metamask-extension/pull/27996)) From 18f97b80376dccef42a30c2091f4a9e703e3a137 Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:03:10 +0100 Subject: [PATCH 03/32] cherry pick: fix: smart transactions in redesigned confirmations (#28353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry pick of: https://github.com/MetaMask/metamask-extension/pull/28273 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28273?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. Install fresh extension. 2. Create transaction using redesigned confirmation. 3. Ensure smart transaction is performed. ## **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. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28353?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. Co-authored-by: Matthew Walsh --- test/data/confirmations/helper.ts | 1 + .../components/confirm/info/info.tsx | 4 + .../useSmartTransactionFeatureFlags.test.ts | 119 ++++++++++++++++++ .../hooks/useSmartTransactionFeatureFlags.ts | 53 ++++++++ 4 files changed, 177 insertions(+) create mode 100644 ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.test.ts create mode 100644 ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.ts diff --git a/test/data/confirmations/helper.ts b/test/data/confirmations/helper.ts index 6669c043d0ea..b8bd8a634588 100644 --- a/test/data/confirmations/helper.ts +++ b/test/data/confirmations/helper.ts @@ -133,6 +133,7 @@ export const getMockConfirmState = (args: RootState = { metamask: {} }) => ({ ...args.metamask, preferences: { ...mockState.metamask.preferences, + ...(args.metamask?.preferences as Record), redesignedTransactionsEnabled: true, redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, diff --git a/ui/pages/confirmations/components/confirm/info/info.tsx b/ui/pages/confirmations/components/confirm/info/info.tsx index f283cbfc2e61..642d1d70c017 100644 --- a/ui/pages/confirmations/components/confirm/info/info.tsx +++ b/ui/pages/confirmations/components/confirm/info/info.tsx @@ -2,6 +2,7 @@ import { TransactionType } from '@metamask/transaction-controller'; import React, { useMemo } from 'react'; import { useConfirmContext } from '../../../context/confirm'; import { SignatureRequestType } from '../../../types/confirm'; +import { useSmartTransactionFeatureFlags } from '../../../hooks/useSmartTransactionFeatureFlags'; import ApproveInfo from './approve/approve'; import BaseTransactionInfo from './base-transaction-info/base-transaction-info'; import NativeTransferInfo from './native-transfer/native-transfer'; @@ -15,6 +16,9 @@ import TypedSignInfo from './typed-sign/typed-sign'; const Info = () => { const { currentConfirmation } = useConfirmContext(); + // TODO: Create TransactionInfo and SignatureInfo components. + useSmartTransactionFeatureFlags(); + const ConfirmationInfoComponentMap = useMemo( () => ({ [TransactionType.contractInteraction]: () => BaseTransactionInfo, diff --git a/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.test.ts b/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.test.ts new file mode 100644 index 000000000000..708a78b5b1eb --- /dev/null +++ b/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.test.ts @@ -0,0 +1,119 @@ +import { act } from 'react-dom/test-utils'; +import { useDispatch } from 'react-redux'; +import { CHAIN_IDS, TransactionMeta } from '@metamask/transaction-controller'; +import { Hex } from '@metamask/utils'; +import { + fetchSmartTransactionsLiveness, + setSwapsFeatureFlags, +} from '../../../store/actions'; +import { renderHookWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers'; +import { genUnapprovedContractInteractionConfirmation } from '../../../../test/data/confirmations/contract-interaction'; +import { getMockConfirmStateForTransaction } from '../../../../test/data/confirmations/helper'; +import { mockNetworkState } from '../../../../test/stub/networks'; +import { fetchSwapsFeatureFlags } from '../../swaps/swaps.util'; +import { useSmartTransactionFeatureFlags } from './useSmartTransactionFeatureFlags'; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), +})); + +jest.mock('../../../store/actions', () => ({ + ...jest.requireActual('../../../store/actions'), + setSwapsFeatureFlags: jest.fn(), + fetchSmartTransactionsLiveness: jest.fn(), +})); + +jest.mock('../../swaps/swaps.util', () => ({ + ...jest.requireActual('../../swaps/swaps.util'), + fetchSwapsFeatureFlags: jest.fn(), +})); + +async function runHook({ + smartTransactionsOptInStatus, + chainId, + confirmation, +}: { + smartTransactionsOptInStatus: boolean; + chainId: Hex; + confirmation?: Partial; +}) { + const transaction = + (confirmation as TransactionMeta) ?? + genUnapprovedContractInteractionConfirmation({ + chainId, + }); + + const state = getMockConfirmStateForTransaction(transaction, { + metamask: { + ...mockNetworkState({ chainId, id: 'Test' }), + selectedNetworkClientId: 'Test', + preferences: { + smartTransactionsOptInStatus, + }, + }, + }); + + renderHookWithConfirmContextProvider( + () => useSmartTransactionFeatureFlags(), + state, + ); + + await act(async () => { + // Intentionally empty + }); +} + +describe('useSmartTransactionFeatureFlags', () => { + const setSwapsFeatureFlagsMock = jest.mocked(setSwapsFeatureFlags); + const fetchSwapsFeatureFlagsMock = jest.mocked(fetchSwapsFeatureFlags); + const fetchSmartTransactionsLivenessMock = jest.mocked( + fetchSmartTransactionsLiveness, + ); + const useDispatchMock = jest.mocked(useDispatch); + + beforeEach(() => { + jest.resetAllMocks(); + useDispatchMock.mockReturnValue(jest.fn()); + fetchSwapsFeatureFlagsMock.mockResolvedValue({}); + fetchSmartTransactionsLivenessMock.mockReturnValue(() => Promise.resolve()); + }); + + it('updates feature flags', async () => { + await runHook({ + smartTransactionsOptInStatus: true, + chainId: CHAIN_IDS.MAINNET, + }); + + expect(setSwapsFeatureFlagsMock).toHaveBeenCalledTimes(1); + expect(setSwapsFeatureFlagsMock).toHaveBeenCalledWith({}); + }); + + it('does not update feature flags if smart transactions disabled', async () => { + await runHook({ + smartTransactionsOptInStatus: false, + chainId: CHAIN_IDS.MAINNET, + }); + + expect(setSwapsFeatureFlagsMock).not.toHaveBeenCalled(); + }); + + it('does not update feature flags if chain not supported', async () => { + await runHook({ + smartTransactionsOptInStatus: true, + chainId: CHAIN_IDS.ARBITRUM, + }); + + expect(setSwapsFeatureFlagsMock).not.toHaveBeenCalled(); + }); + + it('does not update feature flags if confirmation is not transaction', async () => { + await runHook({ + smartTransactionsOptInStatus: true, + chainId: CHAIN_IDS.MAINNET, + confirmation: {}, + }); + + expect(setSwapsFeatureFlagsMock).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.ts b/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.ts new file mode 100644 index 000000000000..099327eb211f --- /dev/null +++ b/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.ts @@ -0,0 +1,53 @@ +import { useDispatch, useSelector } from 'react-redux'; +import { useEffect } from 'react'; +import { TransactionMeta } from '@metamask/transaction-controller'; +import log from 'loglevel'; +import { + getCurrentChainSupportsSmartTransactions, + getSmartTransactionsPreferenceEnabled, +} from '../../../../shared/modules/selectors'; +import { fetchSwapsFeatureFlags } from '../../swaps/swaps.util'; +import { + fetchSmartTransactionsLiveness, + setSwapsFeatureFlags, +} from '../../../store/actions'; +import { useConfirmContext } from '../context/confirm'; + +export function useSmartTransactionFeatureFlags() { + const dispatch = useDispatch(); + const { currentConfirmation } = useConfirmContext(); + const { id: transactionId, txParams } = currentConfirmation ?? {}; + const isTransaction = Boolean(txParams); + + const smartTransactionsPreferenceEnabled = useSelector( + getSmartTransactionsPreferenceEnabled, + ); + + const currentChainSupportsSmartTransactions = useSelector( + getCurrentChainSupportsSmartTransactions, + ); + + useEffect(() => { + if ( + !isTransaction || + !transactionId || + !smartTransactionsPreferenceEnabled || + !currentChainSupportsSmartTransactions + ) { + return; + } + + Promise.all([fetchSwapsFeatureFlags(), fetchSmartTransactionsLiveness()()]) + .then(([swapsFeatureFlags]) => { + dispatch(setSwapsFeatureFlags(swapsFeatureFlags)); + }) + .catch((error) => { + log.debug('Error updating smart transaction feature flags', error); + }); + }, [ + isTransaction, + transactionId, + smartTransactionsPreferenceEnabled, + currentChainSupportsSmartTransactions, + ]); +} From 4c3a35536b63e0125e53130255dfe210faa590b9 Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:15:49 +0100 Subject: [PATCH 04/32] cherry pick: chore: add the gas_included prop into Quotes Received event (#28351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry pick of: https://github.com/MetaMask/metamask-extension/pull/28295 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28295?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to Swaps on Ethereum mainnet 2. Fill in the form with max ETH -> ERC20 3. You will see in a network request that the new prop is there for the Quotes Requested event ## **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. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28351?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/ducks/swaps/swaps.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/ducks/swaps/swaps.js b/ui/ducks/swaps/swaps.js index 8dd7336d7a62..d23c0ce69381 100644 --- a/ui/ducks/swaps/swaps.js +++ b/ui/ducks/swaps/swaps.js @@ -858,6 +858,7 @@ export const fetchQuotesAndSetQuoteState = ( stx_enabled: smartTransactionsEnabled, current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: getSmartTransactionsOptInStatusForMetrics(state), + gas_included: newSelectedQuote.isGasIncludedTrade, anonymizedData: true, }, }); From 8219df57a9ec2493211c03fac596fe56a68316b2 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:16:12 +0700 Subject: [PATCH 05/32] Cherrypick v12.7.0 feat: Enable simulation metrics for redesign transactions (#28324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry-picks https://github.com/MetaMask/metamask-extension/pull/28280 into V12.7.0 Discovered this missing behavior while working on https://github.com/MetaMask/metamask-extension/pull/28314. The feat was unexpectedly missing [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28324?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28292 ## **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. --- .../confirm/info/base-transaction-info/base-transaction-info.tsx | 1 + .../components/confirm/info/native-transfer/native-transfer.tsx | 1 + .../confirm/info/nft-token-transfer/nft-token-transfer.tsx | 1 + .../components/confirm/info/token-transfer/token-transfer.tsx | 1 + 4 files changed, 4 insertions(+) diff --git a/ui/pages/confirmations/components/confirm/info/base-transaction-info/base-transaction-info.tsx b/ui/pages/confirmations/components/confirm/info/base-transaction-info/base-transaction-info.tsx index 08329536b524..23629ee5096c 100644 --- a/ui/pages/confirmations/components/confirm/info/base-transaction-info/base-transaction-info.tsx +++ b/ui/pages/confirmations/components/confirm/info/base-transaction-info/base-transaction-info.tsx @@ -22,6 +22,7 @@ const BaseTransactionInfo = () => { diff --git a/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx b/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx index a2dd3ceaaa05..c098936989dd 100644 --- a/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx +++ b/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx @@ -24,6 +24,7 @@ const NativeTransferInfo = () => { )} diff --git a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx index b5d579994ef9..113920a6aec2 100644 --- a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx +++ b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx @@ -24,6 +24,7 @@ const NFTTokenTransferInfo = () => { )} diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx index df1136ec5213..50e9d85936f0 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx @@ -24,6 +24,7 @@ const TokenTransferInfo = () => { )} From 3b597d9819122a4a18b9d959fd86be6f66ef4a4c Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:18:55 +0700 Subject: [PATCH 06/32] Cherrypick v12.7.0 feat: Add simulation metrics to "Transaction Submitted" and "Transaction Finalized" events (#28314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry-picks https://github.com/MetaMask/metamask-extension/pull/28240 into v12.7.0. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3507 ## **Manual testing steps** 1. Turn on "Participate in MetaMetrics" setting 2. Confirm or Reject a Send transaction (old transaction, not redesign transaction) 3. Observe simulation props are added to Transaction Submitted and Transaction Finalized events ## **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/controllers/metametrics.test.ts | 200 +++++++++++++++++- app/scripts/controllers/metametrics.ts | 70 +++++- app/scripts/lib/transaction/metrics.ts | 24 +-- shared/constants/metametrics.ts | 7 + .../hooks/useTransactionEventFragment.js | 1 + 5 files changed, 280 insertions(+), 22 deletions(-) diff --git a/app/scripts/controllers/metametrics.test.ts b/app/scripts/controllers/metametrics.test.ts index c0a544073614..4b2a1f09a562 100644 --- a/app/scripts/controllers/metametrics.test.ts +++ b/app/scripts/controllers/metametrics.test.ts @@ -10,6 +10,7 @@ import { import { InternalAccount } from '@metamask/keyring-api'; import { Browser } from 'webextension-polyfill'; import { Hex } from '@metamask/utils'; +import { merge } from 'lodash'; import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'; import { createSegmentMock } from '../lib/segment'; import { @@ -93,8 +94,19 @@ const DEFAULT_PAGE_PROPERTIES = { ...DEFAULT_SHARED_PROPERTIES, }; -const SAMPLE_PERSISTED_EVENT = { - id: 'testid', +const SAMPLE_TX_SUBMITTED_PARTIAL_FRAGMENT = { + id: 'transaction-submitted-0000', + canDeleteIfAbandoned: true, + category: 'Unit Test', + successEvent: 'Transaction Finalized', + persist: true, + properties: { + simulation_response: 'no_balance_change', + test_stored_prop: 1, + }, +}; + +const SAMPLE_PERSISTED_EVENT_NO_ID = { persist: true, category: 'Unit Test', successEvent: 'sample persisted event success', @@ -104,6 +116,11 @@ const SAMPLE_PERSISTED_EVENT = { }, }; +const SAMPLE_PERSISTED_EVENT = { + id: 'testid', + ...SAMPLE_PERSISTED_EVENT_NO_ID, +}; + const SAMPLE_NON_PERSISTED_EVENT = { id: 'testid2', persist: false, @@ -255,6 +272,185 @@ describe('MetaMetricsController', function () { }); }); + describe('createEventFragment', function () { + it('should throw an error if the param is missing successEvent or category', async function () { + const metaMetricsController = getMetaMetricsController(); + + await expect(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error because we are testing the error case + metaMetricsController.createEventFragment({ event: 'test' }); + }).toThrow(/Must specify success event and category\./u); + + await expect(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error because we are testing the error case + metaMetricsController.createEventFragment({ category: 'test' }); + }).toThrow(/Must specify success event and category\./u); + }); + + it('should update fragments state with new fragment', function () { + jest.useFakeTimers().setSystemTime(1730798301422); + + const metaMetricsController = getMetaMetricsController(); + const mockNewId = 'testid3'; + + metaMetricsController.createEventFragment({ + ...SAMPLE_PERSISTED_EVENT_NO_ID, + uniqueIdentifier: mockNewId, + }); + + const resultFragment = metaMetricsController.state.fragments[mockNewId]; + + expect(resultFragment).toStrictEqual({ + ...SAMPLE_PERSISTED_EVENT_NO_ID, + id: mockNewId, + uniqueIdentifier: mockNewId, + lastUpdated: 1730798301422, + }); + + jest.useRealTimers(); + }); + + it('should track the initial event if provided', function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + }); + const spy = jest.spyOn(segmentMock, 'track'); + const mockInitialEventName = 'Test Initial Event'; + + metaMetricsController.createEventFragment({ + ...SAMPLE_PERSISTED_EVENT_NO_ID, + initialEvent: mockInitialEventName, + }); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should not call track if no initialEvent was provided', function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + }); + const spy = jest.spyOn(segmentMock, 'track'); + + metaMetricsController.createEventFragment({ + ...SAMPLE_PERSISTED_EVENT_NO_ID, + }); + + expect(spy).toHaveBeenCalledTimes(0); + }); + + describe('when intialEvent is "Transaction Submitted" and a fragment exists before createEventFragment is called', function () { + it('should update existing fragment state with new fragment props', function () { + jest.useFakeTimers().setSystemTime(1730798302222); + + const metaMetricsController = getMetaMetricsController(); + const { id } = SAMPLE_TX_SUBMITTED_PARTIAL_FRAGMENT; + + metaMetricsController.updateEventFragment( + SAMPLE_TX_SUBMITTED_PARTIAL_FRAGMENT.id, + { + ...SAMPLE_TX_SUBMITTED_PARTIAL_FRAGMENT, + }, + ); + metaMetricsController.createEventFragment({ + ...SAMPLE_PERSISTED_EVENT_NO_ID, + initialEvent: 'Transaction Submitted', + uniqueIdentifier: id, + }); + + const resultFragment = metaMetricsController.state.fragments[id]; + const expectedFragment = merge( + SAMPLE_TX_SUBMITTED_PARTIAL_FRAGMENT, + SAMPLE_PERSISTED_EVENT_NO_ID, + { + canDeleteIfAbandoned: false, + id, + initialEvent: 'Transaction Submitted', + uniqueIdentifier: id, + lastUpdated: 1730798302222, + }, + ); + + expect(resultFragment).toStrictEqual(expectedFragment); + + jest.useRealTimers(); + }); + }); + }); + + describe('updateEventFragment', function () { + beforeEach(function () { + jest.useFakeTimers().setSystemTime(1730798303333); + }); + afterEach(function () { + jest.useRealTimers(); + }); + + it('updates fragment with additional provided props', async function () { + const metaMetricsController = getMetaMetricsController(); + const MOCK_PROPS_TO_UPDATE = { + properties: { + test: 1, + }, + }; + + metaMetricsController.updateEventFragment( + SAMPLE_PERSISTED_EVENT.id, + MOCK_PROPS_TO_UPDATE, + ); + + const resultFragment = + metaMetricsController.state.fragments[SAMPLE_PERSISTED_EVENT.id]; + const expectedPartialFragment = { + ...SAMPLE_PERSISTED_EVENT, + ...MOCK_PROPS_TO_UPDATE, + lastUpdated: 1730798303333, + }; + expect(resultFragment).toStrictEqual(expectedPartialFragment); + }); + + it('throws error when no existing fragment exists', async function () { + const metaMetricsController = getMetaMetricsController(); + + const MOCK_NONEXISTING_ID = 'test-nonexistingid'; + + await expect(() => { + metaMetricsController.updateEventFragment(MOCK_NONEXISTING_ID, { + properties: { test: 1 }, + }); + }).toThrow(/Event fragment with id test-nonexistingid does not exist\./u); + }); + + describe('when id includes "transaction-submitted"', function () { + it('creates and stores new fragment props with canDeleteIfAbandoned set to true', function () { + const metaMetricsController = getMetaMetricsController(); + const MOCK_ID = 'transaction-submitted-1111'; + const MOCK_PROPS_TO_UPDATE = { + properties: { + test: 1, + }, + }; + + metaMetricsController.updateEventFragment( + MOCK_ID, + MOCK_PROPS_TO_UPDATE, + ); + + const resultFragment = metaMetricsController.state.fragments[MOCK_ID]; + const expectedPartialFragment = { + ...MOCK_PROPS_TO_UPDATE, + category: 'Transactions', + canDeleteIfAbandoned: true, + id: MOCK_ID, + lastUpdated: 1730798303333, + successEvent: 'Transaction Finalized', + }; + expect(resultFragment).toStrictEqual(expectedPartialFragment); + }); + }); + }); + describe('generateMetaMetricsId', function () { it('should generate an 0x prefixed hex string', function () { const metaMetricsController = getMetaMetricsController(); diff --git a/app/scripts/controllers/metametrics.ts b/app/scripts/controllers/metametrics.ts index 915fcbf53250..ded99dd917f4 100644 --- a/app/scripts/controllers/metametrics.ts +++ b/app/scripts/controllers/metametrics.ts @@ -32,6 +32,7 @@ import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'; import { METAMETRICS_ANONYMOUS_ID, METAMETRICS_BACKGROUND_PAGE_OBJECT, + MetaMetricsEventCategory, MetaMetricsEventName, MetaMetricsEventFragment, MetaMetricsUserTrait, @@ -312,7 +313,7 @@ export default class MetaMetricsController { // fragments that are not marked as persistent will be purged and the // failure event will be emitted. Object.values(abandonedFragments).forEach((fragment) => { - this.finalizeEventFragment(fragment.id, { abandoned: true }); + this.processAbandonedFragment(fragment); }); // Code below submits any pending segmentApiCalls to Segment if/when the controller is re-instantiated @@ -368,7 +369,7 @@ export default class MetaMetricsController { fragment.lastUpdated && Date.now() - fragment.lastUpdated / 1000 > fragment.timeout ) { - this.finalizeEventFragment(fragment.id, { abandoned: true }); + this.processAbandonedFragment(fragment); } }); } @@ -414,10 +415,30 @@ export default class MetaMetricsController { ...options, lastUpdated: Date.now(), }; + + /** + * HACK: "transaction-submitted-" fragment hack + * A "transaction-submitted-" fragment may exist following the "Transaction Added" + * event to persist accumulated event fragment props to the "Transaction Submitted" event + * which fires after a user confirms a transaction. Rejecting a confirmation does not fire the + * "Transaction Submitted" event. In this case, these abandoned fragments will be deleted + * instead of finalized with canDeleteIfAbandoned set to true. + */ + const hasExistingSubmittedFragment = + options.initialEvent === TransactionMetaMetricsEvent.submitted && + fragments[id]; + + const additionalFragmentProps = hasExistingSubmittedFragment + ? { + ...fragments[id], + canDeleteIfAbandoned: false, + } + : {}; + this.store.updateState({ fragments: { ...fragments, - [id]: fragment, + [id]: merge(additionalFragmentProps, fragment), }, }); @@ -455,6 +476,19 @@ export default class MetaMetricsController { return fragment; } + /** + * Deletes to finalizes event fragment based on the canDeleteIfAbandoned property. + * + * @param fragment + */ + processAbandonedFragment(fragment: MetaMetricsEventFragment): void { + if (fragment.canDeleteIfAbandoned) { + this.deleteEventFragment(fragment.id); + } else { + this.finalizeEventFragment(fragment.id, { abandoned: true }); + } + } + /** * Updates an event fragment in state * @@ -469,7 +503,22 @@ export default class MetaMetricsController { const fragment = fragments[id]; - if (!fragment) { + /** + * HACK: "transaction-submitted-" fragment hack + * Creates a "transaction-submitted-" fragment if it does not exist to persist + * accumulated event metrics. In the case it is unused, the abandoned fragment will + * eventually be deleted with canDeleteIfAbandoned set to true. + */ + const createIfNotFound = !fragment && id.includes('transaction-submitted-'); + + if (createIfNotFound) { + fragments[id] = { + canDeleteIfAbandoned: true, + category: MetaMetricsEventCategory.Transactions, + successEvent: TransactionMetaMetricsEvent.finalized, + id, + }; + } else if (!fragment) { throw new Error(`Event fragment with id ${id} does not exist.`); } @@ -484,6 +533,19 @@ export default class MetaMetricsController { }); } + /** + * Deletes an event fragment from state + * + * @param id - The fragment id to delete + */ + deleteEventFragment(id: string): void { + const { fragments } = this.store.getState(); + + if (fragments[id]) { + delete fragments[id]; + } + } + /** * Finalizes a fragment, tracking either a success event or failure Event * and then removes the fragment from state. diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index b33be10c8df4..1e0e26dc1c20 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -528,7 +528,12 @@ function createTransactionEventFragment({ transactionMetricsRequest.getEventFragmentById, eventName, transactionMeta, - ) + ) && + /** + * HACK: "transaction-submitted-" fragment hack + * can continue to createEventFragment if "transaction-submitted-" submitted fragment exists + */ + eventName !== TransactionMetaMetricsEvent.submitted ) { return; } @@ -642,25 +647,14 @@ function updateTransactionEventFragment({ switch (eventName) { case TransactionMetaMetricsEvent.approved: - transactionMetricsRequest.updateEventFragment(uniqueId, { - properties: payload.properties, - sensitiveProperties: payload.sensitiveProperties, - }); - break; - case TransactionMetaMetricsEvent.rejected: - transactionMetricsRequest.updateEventFragment(uniqueId, { - properties: payload.properties, - sensitiveProperties: payload.sensitiveProperties, - }); - break; - case TransactionMetaMetricsEvent.finalized: transactionMetricsRequest.updateEventFragment(uniqueId, { properties: payload.properties, sensitiveProperties: payload.sensitiveProperties, }); break; + default: break; } @@ -679,6 +673,7 @@ function finalizeTransactionEventFragment({ switch (eventName) { case TransactionMetaMetricsEvent.approved: + case TransactionMetaMetricsEvent.finalized: transactionMetricsRequest.finalizeEventFragment(uniqueId); break; @@ -688,9 +683,6 @@ function finalizeTransactionEventFragment({ }); break; - case TransactionMetaMetricsEvent.finalized: - transactionMetricsRequest.finalizeEventFragment(uniqueId); - break; default: break; } diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 2d6a87b7d1c6..615c562e21ad 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -247,6 +247,13 @@ export type MetaMetricsEventFragment = { * The event name. */ event?: string; + + /** + * HACK: "transaction-submitted-" fragment hack + * If this is true and the fragment is found as an abandoned fragment, + * then delete the fragment instead of finalizing it. + */ + canDeleteIfAbandoned?: boolean; }; /** diff --git a/ui/pages/confirmations/hooks/useTransactionEventFragment.js b/ui/pages/confirmations/hooks/useTransactionEventFragment.js index b28b1fd02a54..c69cbb8fc80f 100644 --- a/ui/pages/confirmations/hooks/useTransactionEventFragment.js +++ b/ui/pages/confirmations/hooks/useTransactionEventFragment.js @@ -31,6 +31,7 @@ export const useTransactionEventFragment = () => { await createTransactionEventFragment(transactionId); } updateEventFragment(`transaction-added-${transactionId}`, params); + updateEventFragment(`transaction-submitted-${transactionId}`, params); }, [fragmentExists, gasTransactionId], ); From 7c12d69becacca0bbf46c249527f02a26e7be4e7 Mon Sep 17 00:00:00 2001 From: Prithpal Sooriya Date: Thu, 7 Nov 2024 17:23:41 +0000 Subject: [PATCH 07/32] fix (Cherrypick v12.7.0 ): disable account syncing (#28361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherrypick of https://github.com/MetaMask/metamask-extension/pull/28359 (https://github.com/MetaMask/metamask-extension/commit/afa736569ce1bfbd5292dc28b9694ae13c7d8941) This disables account syncing in v12.7.0 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28361?quickstart=1) ## **Related issues** Fixes: ## **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. Co-authored-by: Mathieu Artu --- app/scripts/metamask-controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 55f5e881de4a..830b66d38a06 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -156,6 +156,7 @@ import { NotificationServicesPushController, NotificationServicesController, } from '@metamask/notification-services-controller'; +import { isProduction } from '../../shared/modules/environment'; import { methodsRequiringNetworkSwitch, methodsThatCanSwitchNetworkWithoutApproval, @@ -1568,7 +1569,7 @@ export default class MetamaskController extends EventEmitter { }, }, env: { - isAccountSyncingEnabled: isManifestV3, + isAccountSyncingEnabled: !isProduction() && isManifestV3, }, messenger: this.controllerMessenger.getRestricted({ name: 'UserStorageController', From a40cb29f1be845e66796e24a38b004c96cfe7184 Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:37:19 -0800 Subject: [PATCH 08/32] fix: Bug 28347 - Privacy mode tweaks (#28367) (#28372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry pick of https://github.com/MetaMask/metamask-extension/commit/82fdd64f5787ac4da20d2da6ddcd55787d136163 Original PR: https://github.com/MetaMask/metamask-extension/pull/28367 Privacy Mode should only effect PortfolioView and main account picker popover. It should not impact other areas of the App like Send/Swap/Gas because the toggle only exists on PortfolioView. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28367?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28347 ## **Manual testing steps** You can toggle privacyMode with eyeball on main PortfolioView Should respect privacyMode: 1. Go to PortfolioView, toggling eyeball should show/hide balances for tokens as well as main balance 2. Go to AccountPicker from main Portfolio View, balances should hide/show 3. Go to AccountPicker from asset detail view, balances should hide/show Should _not_ respect privacyMode: 1. Go to AssetDetails, token balance should show on main page 2. Should not be respected on send/swap/gas screens 3. Balances should not be impacted elsewhere in the app. Please try to verify this while reviewing. ## **Screenshots/Recordings** https://github.com/user-attachments/assets/695fa68a-c9bb-4871-b03c-8c41c88b1344 ## **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. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28372?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/components/app/assets/token-cell/token-cell.tsx | 5 +++-- ui/components/app/assets/token-list/token-list.tsx | 4 +++- .../user-preferenced-currency-display.component.d.ts | 1 + .../user-preferenced-currency-display.component.js | 3 +++ ui/components/app/wallet-overview/coin-overview.tsx | 6 +++--- .../multichain/account-list-item/account-list-item.js | 7 +++++++ .../multichain/account-list-menu/account-list-menu.tsx | 3 +++ .../ui/currency-display/currency-display.component.js | 5 ++--- ui/pages/routes/routes.component.js | 7 ++++++- ui/pages/routes/routes.container.js | 3 ++- 10 files changed, 33 insertions(+), 11 deletions(-) diff --git a/ui/components/app/assets/token-cell/token-cell.tsx b/ui/components/app/assets/token-cell/token-cell.tsx index 3a042de1ebb8..31bb388aa65b 100644 --- a/ui/components/app/assets/token-cell/token-cell.tsx +++ b/ui/components/app/assets/token-cell/token-cell.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { getTokenList, getPreferences } from '../../../../selectors'; +import { getTokenList } from '../../../../selectors'; import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount'; import { TokenListItem } from '../../../multichain'; import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-utils'; @@ -12,6 +12,7 @@ type TokenCellProps = { symbol: string; string?: string; image: string; + privacyMode?: boolean; onClick?: (arg: string) => void; }; @@ -20,10 +21,10 @@ export default function TokenCell({ image, symbol, string, + privacyMode = false, onClick, }: TokenCellProps) { const tokenList = useSelector(getTokenList); - const { privacyMode } = useSelector(getPreferences); const tokenData = Object.values(tokenList).find( (token) => isEqualCaseInsensitive(token.symbol, symbol) && diff --git a/ui/components/app/assets/token-list/token-list.tsx b/ui/components/app/assets/token-list/token-list.tsx index 11190c68f267..f0b17d686026 100644 --- a/ui/components/app/assets/token-list/token-list.tsx +++ b/ui/components/app/assets/token-list/token-list.tsx @@ -30,7 +30,8 @@ export default function TokenList({ nativeToken, }: TokenListProps) { const t = useI18nContext(); - const { tokenSortConfig, tokenNetworkFilter } = useSelector(getPreferences); + const { tokenSortConfig, tokenNetworkFilter, privacyMode } = + useSelector(getPreferences); const selectedAccount = useSelector(getSelectedAccount); const conversionRate = useSelector(getConversionRate); const nativeTokenWithBalance = useNativeTokenBalance(); @@ -88,6 +89,7 @@ export default function TokenList({ ); diff --git a/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.d.ts b/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.d.ts index 4db61d568f4a..779309858a18 100644 --- a/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.d.ts +++ b/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.d.ts @@ -16,6 +16,7 @@ export type UserPrefrencedCurrencyDisplayProps = OverridingUnion< showCurrencySuffix?: boolean; shouldCheckShowNativeToken?: boolean; isAggregatedFiatOverviewBalance?: boolean; + privacyMode?: boolean; } >; diff --git a/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js b/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js index 613b731d0a16..a466f7813672 100644 --- a/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js +++ b/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js @@ -28,6 +28,7 @@ export default function UserPreferencedCurrencyDisplay({ showNative, showCurrencySuffix, shouldCheckShowNativeToken, + privacyMode = false, ...restProps }) { // NOTE: When displaying currencies, we need the actual account to detect whether we're in a @@ -83,6 +84,7 @@ export default function UserPreferencedCurrencyDisplay({ numberOfDecimals={numberOfDecimals} prefixComponent={prefixComponent} suffix={showCurrencySuffix && !showEthLogo && currency} + privacyMode={privacyMode} /> ); } @@ -126,6 +128,7 @@ const UserPreferencedCurrencyDisplayPropTypes = { textProps: PropTypes.object, suffixProps: PropTypes.object, shouldCheckShowNativeToken: PropTypes.bool, + privacyMode: PropTypes.bool, }; UserPreferencedCurrencyDisplay.propTypes = diff --git a/ui/components/app/wallet-overview/coin-overview.tsx b/ui/components/app/wallet-overview/coin-overview.tsx index 9f267c96a53d..93d9e1061428 100644 --- a/ui/components/app/wallet-overview/coin-overview.tsx +++ b/ui/components/app/wallet-overview/coin-overview.tsx @@ -132,7 +132,8 @@ export const CoinOverview = ({ const shouldShowPopover = useSelector(getShouldShowAggregatedBalancePopover); const isTestnet = useSelector(getIsTestnet); - const { showFiatInTestnets, privacyMode } = useSelector(getPreferences); + const { showFiatInTestnets, privacyMode, showNativeTokenAsMainBalance } = + useSelector(getPreferences); const selectedAccount = useSelector(getSelectedAccount); const shouldHideZeroBalanceTokens = useSelector( @@ -143,8 +144,6 @@ export const CoinOverview = ({ shouldHideZeroBalanceTokens, ); - const { showNativeTokenAsMainBalance } = useSelector(getPreferences); - const isEvm = useSelector(getMultichainIsEvm); const isNotAggregatedFiatBalance = showNativeTokenAsMainBalance || isTestnet || !isEvm; @@ -281,6 +280,7 @@ export const CoinOverview = ({ isAggregatedFiatOverviewBalance={ !showNativeTokenAsMainBalance && !isTestnet } + privacyMode={privacyMode} /> { const t = useI18nContext(); const [accountOptionsMenuOpen, setAccountOptionsMenuOpen] = useState(false); @@ -313,6 +314,7 @@ const AccountListItem = ({ type={PRIMARY} showFiat={showFiat} data-testid="first-currency-display" + privacyMode={privacyMode} /> @@ -360,6 +362,7 @@ const AccountListItem = ({ type={SECONDARY} showNative data-testid="second-currency-display" + privacyMode={privacyMode} /> @@ -507,6 +510,10 @@ AccountListItem.propTypes = { * Determines if list item should be scrolled to when selected */ shouldScrollToWhenSelected: PropTypes.bool, + /** + * Determines if list balance should be obfuscated + */ + privacyMode: PropTypes.bool, }; AccountListItem.displayName = 'AccountListItem'; 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 eff0d3cb8868..cfb49d246ca6 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.tsx +++ b/ui/components/multichain/account-list-menu/account-list-menu.tsx @@ -188,6 +188,7 @@ export const mergeAccounts = ( type AccountListMenuProps = { onClose: () => void; + privacyMode?: boolean; showAccountCreation?: boolean; accountListItemProps?: object; allowedAccountTypes?: KeyringAccountType[]; @@ -195,6 +196,7 @@ type AccountListMenuProps = { export const AccountListMenu = ({ onClose, + privacyMode = false, showAccountCreation = true, accountListItemProps, allowedAccountTypes = [ @@ -644,6 +646,7 @@ export const AccountListMenu = ({ isHidden={Boolean(account.hidden)} currentTabOrigin={currentTabOrigin} isActive={Boolean(account.active)} + privacyMode={privacyMode} {...accountListItemProps} /> diff --git a/ui/components/ui/currency-display/currency-display.component.js b/ui/components/ui/currency-display/currency-display.component.js index a0bb114409f6..7e2569ffaee3 100644 --- a/ui/components/ui/currency-display/currency-display.component.js +++ b/ui/components/ui/currency-display/currency-display.component.js @@ -1,10 +1,8 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'; import { EtherDenomination } from '../../../../shared/constants/common'; -import { getPreferences } from '../../../selectors'; import { SensitiveText, Box } from '../../component-library'; import { AlignItems, @@ -35,9 +33,9 @@ export default function CurrencyDisplay({ textProps = {}, suffixProps = {}, isAggregatedFiatOverviewBalance = false, + privacyMode = false, ...props }) { - const { privacyMode } = useSelector(getPreferences); const [title, parts] = useCurrencyDisplay(value, { account, displayValue, @@ -125,6 +123,7 @@ const CurrencyDisplayPropTypes = { textProps: PropTypes.object, suffixProps: PropTypes.object, isAggregatedFiatOverviewBalance: PropTypes.bool, + privacyMode: PropTypes.bool, }; CurrencyDisplay.propTypes = CurrencyDisplayPropTypes; diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index e26e17be9e23..83e707c30f85 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -138,6 +138,7 @@ export default class Routes extends Component { history: PropTypes.object, location: PropTypes.object, autoLockTimeLimit: PropTypes.number, + privacyMode: PropTypes.bool, pageChanged: PropTypes.func.isRequired, browserEnvironmentOs: PropTypes.string, browserEnvironmentBrowser: PropTypes.string, @@ -417,6 +418,7 @@ export default class Routes extends Component { switchedNetworkDetails, clearSwitchedNetworkDetails, clearEditedNetwork, + privacyMode, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) isShowKeyringSnapRemovalResultModal, hideShowKeyringSnapRemovalResultModal, @@ -494,7 +496,10 @@ export default class Routes extends Component { ///: END:ONLY_INCLUDE_IF } {isAccountMenuOpen ? ( - toggleAccountMenu()} /> + toggleAccountMenu()} + privacyMode={privacyMode} + /> ) : null} {isNetworkMenuOpen ? ( Date: Fri, 8 Nov 2024 12:21:44 +0000 Subject: [PATCH 09/32] fix (cherry-pick): gas limit estimation (#28327) (#28378) --- package.json | 2 +- test/e2e/flask/user-operations.spec.ts | 6 +++-- .../tests/transaction/edit-gas-fee.spec.js | 16 +++++------ .../transaction/multiple-transactions.spec.js | 27 ++++++++----------- yarn.lock | 12 ++++----- 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 496f943590d3..e2d978afba1d 100644 --- a/package.json +++ b/package.json @@ -346,7 +346,7 @@ "@metamask/snaps-rpc-methods": "^11.5.0", "@metamask/snaps-sdk": "^6.9.0", "@metamask/snaps-utils": "^8.4.1", - "@metamask/transaction-controller": "^38.1.0", + "@metamask/transaction-controller": "^38.3.0", "@metamask/user-operation-controller": "^13.0.0", "@metamask/utils": "^9.3.0", "@ngraveio/bc-ur": "^1.1.12", diff --git a/test/e2e/flask/user-operations.spec.ts b/test/e2e/flask/user-operations.spec.ts index be7141444c97..7512e0b563c9 100644 --- a/test/e2e/flask/user-operations.spec.ts +++ b/test/e2e/flask/user-operations.spec.ts @@ -256,7 +256,8 @@ describe('User Operations', function () { from: ERC_4337_ACCOUNT, to: GANACHE_ACCOUNT, value: convertETHToHexGwei(1), - data: '0x', + maxFeePerGas: '0x0', + maxPriorityFeePerGas: '0x0', }); await confirmTransaction(driver); @@ -294,7 +295,8 @@ describe('User Operations', function () { from: ERC_4337_ACCOUNT, to: GANACHE_ACCOUNT, value: convertETHToHexGwei(1), - data: '0x', + maxFeePerGas: '0x0', + maxPriorityFeePerGas: '0x0', }); await confirmTransaction(driver); diff --git a/test/e2e/tests/transaction/edit-gas-fee.spec.js b/test/e2e/tests/transaction/edit-gas-fee.spec.js index 85ae4da3a31f..918831f8f3ad 100644 --- a/test/e2e/tests/transaction/edit-gas-fee.spec.js +++ b/test/e2e/tests/transaction/edit-gas-fee.spec.js @@ -1,11 +1,11 @@ const { strict: assert } = require('assert'); const { createInternalTransaction, + createDappTransaction, } = require('../../page-objects/flows/transaction'); const { withFixtures, - openDapp, unlockWallet, generateGanacheOptions, WINDOW_TITLES, @@ -172,11 +172,9 @@ describe('Editing Confirm Transaction', function () { // login to extension await unlockWallet(driver); - // open dapp and connect - await openDapp(driver); - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', + await createDappTransaction(driver, { + maxFeePerGas: '0x2000000000', + maxPriorityFeePerGas: '0x1000000000', }); // check transaction in extension popup @@ -198,12 +196,12 @@ describe('Editing Confirm Transaction', function () { '.currency-display-component__text', ); const transactionAmount = transactionAmounts[0]; - assert.equal(await transactionAmount.getText(), '0'); + assert.equal(await transactionAmount.getText(), '0.001'); // has correct updated value on the confirm screen the transaction await driver.waitForSelector({ css: '.currency-display-component__text', - text: '0.00021', + text: '0.00185144', }); // confirms the transaction @@ -227,7 +225,7 @@ describe('Editing Confirm Transaction', function () { '[data-testid="transaction-list-item-primary-currency"]', ); assert.equal(txValues.length, 1); - assert.ok(/-0\s*ETH/u.test(await txValues[0].getText())); + assert.ok(/-0.001\s*ETH/u.test(await txValues[0].getText())); }, ); }); diff --git a/test/e2e/tests/transaction/multiple-transactions.spec.js b/test/e2e/tests/transaction/multiple-transactions.spec.js index 8f1318c31e3b..4d913cb07edb 100644 --- a/test/e2e/tests/transaction/multiple-transactions.spec.js +++ b/test/e2e/tests/transaction/multiple-transactions.spec.js @@ -26,19 +26,13 @@ describe('Multiple transactions', function () { // initiates a transaction from the dapp await openDapp(driver); // creates first transaction - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', - }); + await createDappTransaction(driver); await driver.waitUntilXWindowHandles(3); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); // creates second transaction - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', - }); + await createDappTransaction(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); // confirms second transaction @@ -94,19 +88,13 @@ describe('Multiple transactions', function () { // initiates a transaction from the dapp await openDapp(driver); // creates first transaction - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', - }); + await createDappTransaction(driver); await driver.waitUntilXWindowHandles(3); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); // creates second transaction - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', - }); + await createDappTransaction(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); // rejects second transaction @@ -141,3 +129,10 @@ describe('Multiple transactions', function () { ); }); }); + +async function createDappTransaction(driver) { + await driver.clickElement({ + text: 'Send EIP 1559 Without Gas', + tag: 'button', + }); +} diff --git a/yarn.lock b/yarn.lock index 7890bcaa7b3f..3d50309eefec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6429,9 +6429,9 @@ __metadata: languageName: node linkType: hard -"@metamask/transaction-controller@npm:^38.1.0": - version: 38.1.0 - resolution: "@metamask/transaction-controller@npm:38.1.0" +"@metamask/transaction-controller@npm:^38.3.0": + version: 38.3.0 + resolution: "@metamask/transaction-controller@npm:38.3.0" dependencies: "@ethereumjs/common": "npm:^3.2.0" "@ethereumjs/tx": "npm:^4.2.0" @@ -6440,7 +6440,7 @@ __metadata: "@ethersproject/contracts": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.0" "@metamask/base-controller": "npm:^7.0.2" - "@metamask/controller-utils": "npm:^11.4.1" + "@metamask/controller-utils": "npm:^11.4.2" "@metamask/eth-query": "npm:^4.0.0" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/nonce-tracker": "npm:^6.0.0" @@ -6458,7 +6458,7 @@ __metadata: "@metamask/approval-controller": ^7.0.0 "@metamask/gas-fee-controller": ^22.0.0 "@metamask/network-controller": ^22.0.0 - checksum: 10/c1bdca52bbbce42a76ec9c640197534ec6c223b0f5d5815acfa53490dc1175850ea9aeeb6ae3c5ec34218f0bdbbbeb3e8731e2552aa9411e3ed7798a5dea8ab5 + checksum: 10/f4e8e3a1a31e3e62b0d1a59bbe15ebfa4dc3e4cf077fb95c1815c00661c60ef4676046c49f57eab9749cd31d3e55ac3fed7bc247e3f5a3d459f2dcb03998633d languageName: node linkType: hard @@ -26470,7 +26470,7 @@ __metadata: "@metamask/snaps-utils": "npm:^8.4.1" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:8.7.0" - "@metamask/transaction-controller": "npm:^38.1.0" + "@metamask/transaction-controller": "npm:^38.3.0" "@metamask/user-operation-controller": "npm:^13.0.0" "@metamask/utils": "npm:^9.3.0" "@ngraveio/bc-ur": "npm:^1.1.12" From ae779d0507dc99909b67f5a8e59282c5dcec604f Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Fri, 8 Nov 2024 09:19:35 -0600 Subject: [PATCH 10/32] cherry-pick: bump `@metamask/queued-request-controller` with patch fix (#28355) (#28371) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Bumps version of QueuedRequestController, with a patch that fixes an issue where `QueuedRequestController.state.queuedRequestCount` is not updated after flushing requests for an origin ## References - https://github.com/MetaMask/core/pull/4899 - https://github.com/MetaMask/core/pull/4846 - https://github.com/MetaMask/metamask-extension/pull/28090 ## Fixes Fixes #28358 [Slack discussion in v12.7.0 RC Thread](https://consensys.slack.com/archives/C029JG63136/p1730918073046389?thread_ts=1729246801.516029&cid=C029JG63136) ## Before https://drive.google.com/file/d/1ujdQgVLlT8KlwRwO-Cc3XvRHPrkpxIg_/view?usp=drive_link ## After https://github.com/user-attachments/assets/e77928e5-165b-441a-b4da-0e10471c0529 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28355?quickstart=1) ## **Manual testing steps** On a dapp permissioned for chain A and B, on chain A, queue up one send transaction, then use wallet_switchEthereumChain to switch to chain B, then queue up several more send transactions. Reject/approve the first transaction. Afterwards, you should see chain B as the active chain for the dapp, and all subsequent approvals cleared/rejected automatically. - [ ] 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. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28371?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. --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index e2d978afba1d..26f02cebced7 100644 --- a/package.json +++ b/package.json @@ -333,7 +333,7 @@ "@metamask/preinstalled-example-snap": "^0.2.0", "@metamask/profile-sync-controller": "^0.9.7", "@metamask/providers": "^14.0.2", - "@metamask/queued-request-controller": "^7.0.0", + "@metamask/queued-request-controller": "^7.0.1", "@metamask/rate-limit-controller": "^6.0.0", "@metamask/rpc-errors": "^7.0.0", "@metamask/safe-event-emitter": "^3.1.1", diff --git a/yarn.lock b/yarn.lock index 3d50309eefec..537017bab49f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6052,12 +6052,12 @@ __metadata: languageName: node linkType: hard -"@metamask/queued-request-controller@npm:^7.0.0": - version: 7.0.0 - resolution: "@metamask/queued-request-controller@npm:7.0.0" +"@metamask/queued-request-controller@npm:^7.0.1": + version: 7.0.1 + resolution: "@metamask/queued-request-controller@npm:7.0.1" dependencies: "@metamask/base-controller": "npm:^7.0.2" - "@metamask/controller-utils": "npm:^11.4.1" + "@metamask/controller-utils": "npm:^11.4.2" "@metamask/json-rpc-engine": "npm:^10.0.1" "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/swappable-obj-proxy": "npm:^2.2.0" @@ -6065,7 +6065,7 @@ __metadata: peerDependencies: "@metamask/network-controller": ^22.0.0 "@metamask/selected-network-controller": ^19.0.0 - checksum: 10/69118c11e3faecdbec7c9f02f4ecec4734ce0950115bfac0cdd4338309898690ae3187bcef1cc4f75f54c5c02eff07d80286d3ef29088a665039c13cb50bef88 + checksum: 10/e5b16b3dc2fa0dcf74a81b5046abb65bc05da3802ee891b5a59a80b980301c790cf949d72adba00ead6f5b3d2eaac40694308297f7dc08eb5e5f05b5a68bbf57 languageName: node linkType: hard @@ -26455,7 +26455,7 @@ __metadata: "@metamask/preinstalled-example-snap": "npm:^0.2.0" "@metamask/profile-sync-controller": "npm:^0.9.7" "@metamask/providers": "npm:^14.0.2" - "@metamask/queued-request-controller": "npm:^7.0.0" + "@metamask/queued-request-controller": "npm:^7.0.1" "@metamask/rate-limit-controller": "npm:^6.0.0" "@metamask/rpc-errors": "npm:^7.0.0" "@metamask/safe-event-emitter": "npm:^3.1.1" From 91a4ec43c0ff68fa53c192ec6384d307bbb40c5d Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Mon, 11 Nov 2024 12:56:57 +0000 Subject: [PATCH 11/32] Version v12.6.1 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af63a4ed61d3..2dfa2c828f63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.6.1] + ## [12.6.0] ### Added - Added the APE network icon ([#27841](https://github.com/MetaMask/metamask-extension/pull/27841)) @@ -5303,7 +5305,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.6.0...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.6.1...HEAD +[12.6.1]: https://github.com/MetaMask/metamask-extension/compare/v12.6.0...v12.6.1 [12.6.0]: https://github.com/MetaMask/metamask-extension/compare/v12.5.1...v12.6.0 [12.5.1]: https://github.com/MetaMask/metamask-extension/compare/v12.5.0...v12.5.1 [12.5.0]: https://github.com/MetaMask/metamask-extension/compare/v12.4.2...v12.5.0 diff --git a/package.json b/package.json index 1a217ec7bd9a..06d64c291fec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "12.6.0", + "version": "12.6.1", "private": true, "repository": { "type": "git", From 54588f960d3bb37bb31687f4b5c81ee7b55c80e6 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Mon, 11 Nov 2024 12:57:39 +0000 Subject: [PATCH 12/32] fix: cherry-pick: Return to send page with different asset types (#28384) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry-pick: https://github.com/MetaMask/metamask-extension/pull/28382 ## **Description** This bug was reproducible by opening a new wallet initiated send confirmation with a Native token ("simple send") from the extension full screen view, and then triggering a dApp initiated confirmation, and trying to return back to the send flow stepper. The bug was provoked due to having the `editExistingTransaction` action dispatched on back button click hardcoded for asset of type token. The fix involves dynamically determining the asset type. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28382?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28316 ## **Manual testing steps** See above or check video on the bug report 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. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28384?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. --- .../header/wallet-initiated-header.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/header/wallet-initiated-header.tsx b/ui/pages/confirmations/components/confirm/header/wallet-initiated-header.tsx index ffc8e7549faf..523e0206167d 100644 --- a/ui/pages/confirmations/components/confirm/header/wallet-initiated-header.tsx +++ b/ui/pages/confirmations/components/confirm/header/wallet-initiated-header.tsx @@ -1,4 +1,7 @@ -import { TransactionMeta } from '@metamask/transaction-controller'; +import { + TransactionMeta, + TransactionType, +} from '@metamask/transaction-controller'; import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -38,7 +41,26 @@ export const WalletInitiatedHeader = () => { const handleBackButtonClick = useCallback(async () => { const { id } = currentConfirmation; - await dispatch(editExistingTransaction(AssetType.token, id.toString())); + const isNativeSend = + currentConfirmation.type === TransactionType.simpleSend; + const isERC20TokenSend = + currentConfirmation.type === TransactionType.tokenMethodTransfer; + const isNFTTokenSend = + currentConfirmation.type === TransactionType.tokenMethodTransferFrom || + currentConfirmation.type === TransactionType.tokenMethodSafeTransferFrom; + + let assetType: AssetType; + if (isNativeSend) { + assetType = AssetType.native; + } else if (isERC20TokenSend) { + assetType = AssetType.token; + } else if (isNFTTokenSend) { + assetType = AssetType.NFT; + } else { + assetType = AssetType.unknown; + } + + await dispatch(editExistingTransaction(assetType, id.toString())); dispatch(clearConfirmTransaction()); dispatch(showSendTokenPage()); From d876ccb942d20e3a0db09f46b20b8397ff7fed8c Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Mon, 11 Nov 2024 13:58:33 +0100 Subject: [PATCH 13/32] fix(snaps): Patch `@metamask/snaps-utils` (#28377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR patches the `@metamask/snaps-utils` package after https://github.com/MetaMask/snaps/pull/2876. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28377?quickstart=1) ## **Related issues** ## **Manual testing steps** ## **Screenshots/Recordings** ## **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. --- ...ask-snaps-utils-npm-8.4.1-90481bac4b.patch | 26 +++++++++++++++ package.json | 9 +++-- yarn.lock | 33 ++++++++----------- 3 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 .yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch diff --git a/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch b/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch new file mode 100644 index 000000000000..1c0aa8a99b3a --- /dev/null +++ b/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch @@ -0,0 +1,26 @@ +diff --git a/dist/json-rpc.cjs b/dist/json-rpc.cjs +index 6061f7b8b42f0521b0718d616e5a12a1a7520068..11d0233a7bd4b610a99da6a3d105840e88e108e6 100644 +--- a/dist/json-rpc.cjs ++++ b/dist/json-rpc.cjs +@@ -68,7 +68,7 @@ function createOriginRegExp(matcher) { + const escaped = matcher.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'); + // Support wildcards + const regex = escaped.replace(/\\\*/gu, '.*'); +- return RegExp(`${regex}$`, 'u'); ++ return RegExp(`^${regex}$`, 'u'); + } + /** + * Check whether an origin is allowed or not using a matcher string. +diff --git a/dist/json-rpc.mjs b/dist/json-rpc.mjs +index bfa1c2dbbed46a2221ef708afdb97b15db84bc1b..81bc2150cf5d6a9bdabe8d43b04352b299bc1c4d 100644 +--- a/dist/json-rpc.mjs ++++ b/dist/json-rpc.mjs +@@ -63,7 +63,7 @@ function createOriginRegExp(matcher) { + const escaped = matcher.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'); + // Support wildcards + const regex = escaped.replace(/\\\*/gu, '.*'); +- return RegExp(`${regex}$`, 'u'); ++ return RegExp(`^${regex}$`, 'u'); + } + /** + * Check whether an origin is allowed or not using a matcher string. diff --git a/package.json b/package.json index 26f02cebced7..f7900e85d536 100644 --- a/package.json +++ b/package.json @@ -250,7 +250,12 @@ "@metamask/network-controller@npm:^17.0.0": "patch:@metamask/network-controller@npm%3A21.0.0#~/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch", "@metamask/network-controller@npm:^19.0.0": "patch:@metamask/network-controller@npm%3A21.0.0#~/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch", "@metamask/network-controller@npm:^20.0.0": "patch:@metamask/network-controller@npm%3A21.0.0#~/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch", - "path-to-regexp": "1.9.0" + "path-to-regexp": "1.9.0", + "@metamask/snaps-utils@npm:^8.4.1": "patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch", + "@metamask/snaps-utils@npm:^8.3.0": "patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch", + "@metamask/snaps-utils@npm:^8.1.1": "patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch", + "@metamask/snaps-utils@npm:^8.4.0": "patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch", + "@metamask/snaps-utils@npm:^7.4.0": "patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch" }, "dependencies": { "@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch", @@ -345,7 +350,7 @@ "@metamask/snaps-execution-environments": "^6.9.1", "@metamask/snaps-rpc-methods": "^11.5.0", "@metamask/snaps-sdk": "^6.9.0", - "@metamask/snaps-utils": "^8.4.1", + "@metamask/snaps-utils": "patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch", "@metamask/transaction-controller": "^38.3.0", "@metamask/user-operation-controller": "^13.0.0", "@metamask/utils": "^9.3.0", diff --git a/yarn.lock b/yarn.lock index 537017bab49f..a605a407bc81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6152,13 +6152,6 @@ __metadata: languageName: node linkType: hard -"@metamask/slip44@npm:^3.1.0": - version: 3.1.0 - resolution: "@metamask/slip44@npm:3.1.0" - checksum: 10/83f902c455468f1ec252d0554cd4ebf8da1fc9a27ec7199b81e265e5e8710fad86eaa71d86f24500f9db6626007ad71b1380b239e2104e7e558a061393b066fa - languageName: node - linkType: hard - "@metamask/slip44@npm:^4.0.0": version: 4.0.0 resolution: "@metamask/slip44@npm:4.0.0" @@ -6286,9 +6279,9 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils@npm:^7.4.0": - version: 7.8.1 - resolution: "@metamask/snaps-utils@npm:7.8.1" +"@metamask/snaps-utils@npm:8.4.1": + version: 8.4.1 + resolution: "@metamask/snaps-utils@npm:8.4.1" dependencies: "@babel/core": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" @@ -6296,30 +6289,30 @@ __metadata: "@metamask/key-tree": "npm:^9.1.2" "@metamask/permission-controller": "npm:^11.0.0" "@metamask/rpc-errors": "npm:^6.3.1" - "@metamask/slip44": "npm:^3.1.0" + "@metamask/slip44": "npm:^4.0.0" "@metamask/snaps-registry": "npm:^3.2.1" - "@metamask/snaps-sdk": "npm:^6.1.0" + "@metamask/snaps-sdk": "npm:^6.9.0" "@metamask/superstruct": "npm:^3.1.0" - "@metamask/utils": "npm:^9.1.0" + "@metamask/utils": "npm:^9.2.1" "@noble/hashes": "npm:^1.3.1" "@scure/base": "npm:^1.1.1" chalk: "npm:^4.1.2" cron-parser: "npm:^4.5.0" fast-deep-equal: "npm:^3.1.3" fast-json-stable-stringify: "npm:^2.1.0" - fast-xml-parser: "npm:^4.3.4" + fast-xml-parser: "npm:^4.4.1" marked: "npm:^12.0.1" rfdc: "npm:^1.3.0" semver: "npm:^7.5.4" ses: "npm:^1.1.0" validate-npm-package-name: "npm:^5.0.0" - checksum: 10/572108aafbad970910ffb3605cf9eb4675ede0d69ff2bd37515da7f071de2065a55c73d6dc44dbe70bbd9c3ff0dfe29d40fd16badd925a4b8504db293265ca2f + checksum: 10/c68a2fe69dc835c2b996d621fd4698435475d419a85aa557aa000aae0ab7ebb68d2a52f0b28bbab94fff895ece9a94077e3910a21b16d904cff3b9419ca575b6 languageName: node linkType: hard -"@metamask/snaps-utils@npm:^8.1.1, @metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.4.0, @metamask/snaps-utils@npm:^8.4.1": +"@metamask/snaps-utils@patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch": version: 8.4.1 - resolution: "@metamask/snaps-utils@npm:8.4.1" + resolution: "@metamask/snaps-utils@patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch::version=8.4.1&hash=421c26" dependencies: "@babel/core": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" @@ -6344,7 +6337,7 @@ __metadata: semver: "npm:^7.5.4" ses: "npm:^1.1.0" validate-npm-package-name: "npm:^5.0.0" - checksum: 10/c68a2fe69dc835c2b996d621fd4698435475d419a85aa557aa000aae0ab7ebb68d2a52f0b28bbab94fff895ece9a94077e3910a21b16d904cff3b9419ca575b6 + checksum: 10/cc1e10898c81361283aef46d48197177346f17febd8b005d486a9988c6a636ddf5369f7ccc8c22466091daf60fe8e1a8f6fc5790229efc15e8b4449f33a4d214 languageName: node linkType: hard @@ -19333,7 +19326,7 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.3.4, fast-xml-parser@npm:^4.4.1": +"fast-xml-parser@npm:^4.4.1": version: 4.4.1 resolution: "fast-xml-parser@npm:4.4.1" dependencies: @@ -26467,7 +26460,7 @@ __metadata: "@metamask/snaps-execution-environments": "npm:^6.9.1" "@metamask/snaps-rpc-methods": "npm:^11.5.0" "@metamask/snaps-sdk": "npm:^6.9.0" - "@metamask/snaps-utils": "npm:^8.4.1" + "@metamask/snaps-utils": "patch:@metamask/snaps-utils@npm%3A8.4.1#~/.yarn/patches/@metamask-snaps-utils-npm-8.4.1-90481bac4b.patch" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:8.7.0" "@metamask/transaction-controller": "npm:^38.3.0" From f2865d11709a430628062f5e40b71c1ae30df5aa Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Mon, 11 Nov 2024 13:39:58 +0000 Subject: [PATCH 14/32] fix (cherry-pick): gas limit estimation (#28327) (#28405) --- package.json | 2 +- test/e2e/flask/user-operations.spec.ts | 6 +++-- .../tests/transaction/edit-gas-fee.spec.js | 16 +++++------ .../transaction/multiple-transactions.spec.js | 27 ++++++++----------- yarn.lock | 14 +++++----- 5 files changed, 30 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 06d64c291fec..594647671c7c 100644 --- a/package.json +++ b/package.json @@ -362,7 +362,7 @@ "@metamask/snaps-rpc-methods": "^11.5.0", "@metamask/snaps-sdk": "^6.9.0", "@metamask/snaps-utils": "^8.4.1", - "@metamask/transaction-controller": "^38.1.0", + "@metamask/transaction-controller": "^38.3.0", "@metamask/user-operation-controller": "^13.0.0", "@metamask/utils": "^9.3.0", "@ngraveio/bc-ur": "^1.1.12", diff --git a/test/e2e/flask/user-operations.spec.ts b/test/e2e/flask/user-operations.spec.ts index be7141444c97..7512e0b563c9 100644 --- a/test/e2e/flask/user-operations.spec.ts +++ b/test/e2e/flask/user-operations.spec.ts @@ -256,7 +256,8 @@ describe('User Operations', function () { from: ERC_4337_ACCOUNT, to: GANACHE_ACCOUNT, value: convertETHToHexGwei(1), - data: '0x', + maxFeePerGas: '0x0', + maxPriorityFeePerGas: '0x0', }); await confirmTransaction(driver); @@ -294,7 +295,8 @@ describe('User Operations', function () { from: ERC_4337_ACCOUNT, to: GANACHE_ACCOUNT, value: convertETHToHexGwei(1), - data: '0x', + maxFeePerGas: '0x0', + maxPriorityFeePerGas: '0x0', }); await confirmTransaction(driver); diff --git a/test/e2e/tests/transaction/edit-gas-fee.spec.js b/test/e2e/tests/transaction/edit-gas-fee.spec.js index 85ae4da3a31f..918831f8f3ad 100644 --- a/test/e2e/tests/transaction/edit-gas-fee.spec.js +++ b/test/e2e/tests/transaction/edit-gas-fee.spec.js @@ -1,11 +1,11 @@ const { strict: assert } = require('assert'); const { createInternalTransaction, + createDappTransaction, } = require('../../page-objects/flows/transaction'); const { withFixtures, - openDapp, unlockWallet, generateGanacheOptions, WINDOW_TITLES, @@ -172,11 +172,9 @@ describe('Editing Confirm Transaction', function () { // login to extension await unlockWallet(driver); - // open dapp and connect - await openDapp(driver); - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', + await createDappTransaction(driver, { + maxFeePerGas: '0x2000000000', + maxPriorityFeePerGas: '0x1000000000', }); // check transaction in extension popup @@ -198,12 +196,12 @@ describe('Editing Confirm Transaction', function () { '.currency-display-component__text', ); const transactionAmount = transactionAmounts[0]; - assert.equal(await transactionAmount.getText(), '0'); + assert.equal(await transactionAmount.getText(), '0.001'); // has correct updated value on the confirm screen the transaction await driver.waitForSelector({ css: '.currency-display-component__text', - text: '0.00021', + text: '0.00185144', }); // confirms the transaction @@ -227,7 +225,7 @@ describe('Editing Confirm Transaction', function () { '[data-testid="transaction-list-item-primary-currency"]', ); assert.equal(txValues.length, 1); - assert.ok(/-0\s*ETH/u.test(await txValues[0].getText())); + assert.ok(/-0.001\s*ETH/u.test(await txValues[0].getText())); }, ); }); diff --git a/test/e2e/tests/transaction/multiple-transactions.spec.js b/test/e2e/tests/transaction/multiple-transactions.spec.js index 8f1318c31e3b..4d913cb07edb 100644 --- a/test/e2e/tests/transaction/multiple-transactions.spec.js +++ b/test/e2e/tests/transaction/multiple-transactions.spec.js @@ -26,19 +26,13 @@ describe('Multiple transactions', function () { // initiates a transaction from the dapp await openDapp(driver); // creates first transaction - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', - }); + await createDappTransaction(driver); await driver.waitUntilXWindowHandles(3); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); // creates second transaction - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', - }); + await createDappTransaction(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); // confirms second transaction @@ -94,19 +88,13 @@ describe('Multiple transactions', function () { // initiates a transaction from the dapp await openDapp(driver); // creates first transaction - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', - }); + await createDappTransaction(driver); await driver.waitUntilXWindowHandles(3); await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); // creates second transaction - await driver.clickElement({ - text: 'Send EIP 1559 Transaction', - tag: 'button', - }); + await createDappTransaction(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); // rejects second transaction @@ -141,3 +129,10 @@ describe('Multiple transactions', function () { ); }); }); + +async function createDappTransaction(driver) { + await driver.clickElement({ + text: 'Send EIP 1559 Without Gas', + tag: 'button', + }); +} diff --git a/yarn.lock b/yarn.lock index fe2503ddf619..3b02bd62f8b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5015,7 +5015,7 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@npm:^11.0.0, @metamask/controller-utils@npm:^11.0.2, @metamask/controller-utils@npm:^11.1.0, @metamask/controller-utils@npm:^11.2.0, @metamask/controller-utils@npm:^11.3.0, @metamask/controller-utils@npm:^11.4.0, @metamask/controller-utils@npm:^11.4.1": +"@metamask/controller-utils@npm:^11.0.0, @metamask/controller-utils@npm:^11.0.2, @metamask/controller-utils@npm:^11.1.0, @metamask/controller-utils@npm:^11.2.0, @metamask/controller-utils@npm:^11.3.0, @metamask/controller-utils@npm:^11.4.0, @metamask/controller-utils@npm:^11.4.1, @metamask/controller-utils@npm:^11.4.2": version: 11.4.2 resolution: "@metamask/controller-utils@npm:11.4.2" dependencies: @@ -6521,9 +6521,9 @@ __metadata: languageName: node linkType: hard -"@metamask/transaction-controller@npm:^38.1.0": - version: 38.1.0 - resolution: "@metamask/transaction-controller@npm:38.1.0" +"@metamask/transaction-controller@npm:^38.3.0": + version: 38.3.0 + resolution: "@metamask/transaction-controller@npm:38.3.0" dependencies: "@ethereumjs/common": "npm:^3.2.0" "@ethereumjs/tx": "npm:^4.2.0" @@ -6532,7 +6532,7 @@ __metadata: "@ethersproject/contracts": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.0" "@metamask/base-controller": "npm:^7.0.2" - "@metamask/controller-utils": "npm:^11.4.1" + "@metamask/controller-utils": "npm:^11.4.2" "@metamask/eth-query": "npm:^4.0.0" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/nonce-tracker": "npm:^6.0.0" @@ -6550,7 +6550,7 @@ __metadata: "@metamask/approval-controller": ^7.0.0 "@metamask/gas-fee-controller": ^22.0.0 "@metamask/network-controller": ^22.0.0 - checksum: 10/c1bdca52bbbce42a76ec9c640197534ec6c223b0f5d5815acfa53490dc1175850ea9aeeb6ae3c5ec34218f0bdbbbeb3e8731e2552aa9411e3ed7798a5dea8ab5 + checksum: 10/f4e8e3a1a31e3e62b0d1a59bbe15ebfa4dc3e4cf077fb95c1815c00661c60ef4676046c49f57eab9749cd31d3e55ac3fed7bc247e3f5a3d459f2dcb03998633d languageName: node linkType: hard @@ -26183,7 +26183,7 @@ __metadata: "@metamask/snaps-utils": "npm:^8.4.1" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:8.7.0" - "@metamask/transaction-controller": "npm:^38.1.0" + "@metamask/transaction-controller": "npm:^38.3.0" "@metamask/user-operation-controller": "npm:^13.0.0" "@metamask/utils": "npm:^9.3.0" "@ngraveio/bc-ur": "npm:^1.1.12" From bf078adbc20027b9efa573bf0a48e6e9a85a671a Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Mon, 11 Nov 2024 14:43:25 +0000 Subject: [PATCH 15/32] chore: update changelog (#28410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Add gas limit fix to changelog. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28410?quickstart=1) ## **Related issues** ## **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. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dfa2c828f63..557e5f290d01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [12.6.1] +### Fixed +- Fixed gas limit estimation on Base and BNB chains ([#28327](https://github.com/MetaMask/metamask-extension/pull/28327)) ## [12.6.0] ### Added From f042d840c5cd477496a1c90c8e2dc600907f9c8c Mon Sep 17 00:00:00 2001 From: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:36:10 +0000 Subject: [PATCH 16/32] fix (cherry-pick): get `supportedChains` to avoid blocking the confirmation process (#28422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry-pick of #28313 for release `12.7.0`. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28422?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28255 https://github.com/MetaMask/metamask-extension/issues/28257 ## **Manual testing steps** 1. Use a proxy to intercept requests 2. Start a Send flow 3. Intercept the request to `security-alerts.api.cx.metamask.io` 4. See send flow cannot be initiated until we have a response to this request ## **Screenshots/Recordings** ### **Before** ### **After** [supported-chains-.webm](https://github.com/user-attachments/assets/4e9e495a-10f3-4bb1-8d05-8045a735b655) [unsupported-chains.webm](https://github.com/user-attachments/assets/e5767bc1-2eab-44bd-83c3-777d34c23ff6) ## **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/lib/ppom/ppom-middleware.test.ts | 78 +---------- app/scripts/lib/ppom/ppom-middleware.ts | 30 ++--- app/scripts/lib/ppom/ppom-util.test.ts | 121 ++++++++++++------ app/scripts/lib/ppom/ppom-util.ts | 49 +++++-- app/scripts/lib/ppom/types.ts | 6 + app/scripts/lib/transaction/util.test.ts | 58 +-------- app/scripts/lib/transaction/util.ts | 35 ++--- shared/constants/security-provider.ts | 15 ++- .../signatures/malicious-signatures.spec.ts | 8 ++ .../signatures/signature-helpers.ts | 68 ++++++++-- .../confirmations/signatures/siwe.spec.ts | 8 ++ .../tests/metrics/signature-approved.spec.js | 88 +++++-------- 12 files changed, 271 insertions(+), 293 deletions(-) diff --git a/app/scripts/lib/ppom/ppom-middleware.test.ts b/app/scripts/lib/ppom/ppom-middleware.test.ts index 8977c00aa3d7..16317fef0380 100644 --- a/app/scripts/lib/ppom/ppom-middleware.test.ts +++ b/app/scripts/lib/ppom/ppom-middleware.test.ts @@ -7,7 +7,6 @@ import { BlockaidReason, BlockaidResultType, } from '../../../../shared/constants/security-provider'; -import { flushPromises } from '../../../../test/lib/timer-helpers'; import { mockNetworkState } from '../../../../test/stub/networks'; import { createPPOMMiddleware, PPOMMiddlewareRequest } from './ppom-middleware'; import { @@ -105,7 +104,6 @@ const createMiddleware = ( }; describe('PPOMMiddleware', () => { - const validateRequestWithPPOMMock = jest.mocked(validateRequestWithPPOM); const generateSecurityAlertIdMock = jest.mocked(generateSecurityAlertId); const handlePPOMErrorMock = jest.mocked(handlePPOMError); const isChainSupportedMock = jest.mocked(isChainSupported); @@ -114,7 +112,6 @@ describe('PPOMMiddleware', () => { beforeEach(() => { jest.resetAllMocks(); - validateRequestWithPPOMMock.mockResolvedValue(SECURITY_ALERT_RESPONSE_MOCK); generateSecurityAlertIdMock.mockReturnValue(SECURITY_ALERT_ID_MOCK); handlePPOMErrorMock.mockReturnValue(SECURITY_ALERT_RESPONSE_MOCK); isChainSupportedMock.mockResolvedValue(true); @@ -129,38 +126,13 @@ describe('PPOMMiddleware', () => { }; }); - it('updates alert response after validating request', async () => { + it('adds checking chain response to confirmation requests while validation is in progress', async () => { const updateSecurityAlertResponse = jest.fn(); const middlewareFunction = createMiddleware({ updateSecurityAlertResponse, }); - const req = { - ...REQUEST_MOCK, - method: 'eth_sendTransaction', - securityAlertResponse: undefined, - }; - - await middlewareFunction( - req, - { ...JsonRpcResponseStruct.TYPE }, - () => undefined, - ); - - await flushPromises(); - - expect(updateSecurityAlertResponse).toHaveBeenCalledTimes(1); - expect(updateSecurityAlertResponse).toHaveBeenCalledWith( - req.method, - SECURITY_ALERT_ID_MOCK, - SECURITY_ALERT_RESPONSE_MOCK, - ); - }); - - it('adds loading response to confirmation requests while validation is in progress', async () => { - const middlewareFunction = createMiddleware(); - const req: PPOMMiddlewareRequest<(string | { to: string })[]> = { ...REQUEST_MOCK, method: 'eth_sendTransaction', @@ -173,7 +145,9 @@ describe('PPOMMiddleware', () => { () => undefined, ); - expect(req.securityAlertResponse?.reason).toBe(BlockaidReason.inProgress); + expect(req.securityAlertResponse?.reason).toBe( + BlockaidReason.checkingChain, + ); expect(req.securityAlertResponse?.result_type).toBe( BlockaidResultType.Loading, ); @@ -197,50 +171,6 @@ describe('PPOMMiddleware', () => { expect(validateRequestWithPPOM).not.toHaveBeenCalled(); }); - it('does not do validation if unable to get the chainId from the network provider config', async () => { - isChainSupportedMock.mockResolvedValue(false); - const middlewareFunction = createMiddleware({ - chainId: null, - }); - - const req = { - ...REQUEST_MOCK, - method: 'eth_sendTransaction', - securityAlertResponse: undefined, - }; - - await middlewareFunction( - req, - { ...JsonRpcResponseStruct.TYPE }, - () => undefined, - ); - - expect(req.securityAlertResponse).toBeUndefined(); - expect(validateRequestWithPPOM).not.toHaveBeenCalled(); - }); - - it('does not do validation if user is not on a supported network', async () => { - isChainSupportedMock.mockResolvedValue(false); - const middlewareFunction = createMiddleware({ - chainId: '0x2', - }); - - const req = { - ...REQUEST_MOCK, - method: 'eth_sendTransaction', - securityAlertResponse: undefined, - }; - - await middlewareFunction( - req, - { ...JsonRpcResponseStruct.TYPE }, - () => undefined, - ); - - expect(req.securityAlertResponse).toBeUndefined(); - expect(validateRequestWithPPOM).not.toHaveBeenCalled(); - }); - it('does not do validation when request is not for confirmation method', async () => { const middlewareFunction = createMiddleware(); diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index ebfdbe3f04d7..44c7a8a965c4 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -13,17 +13,16 @@ import { MESSAGE_TYPE } from '../../../../shared/constants/app'; import { SIGNING_METHODS } from '../../../../shared/constants/transaction'; import { PreferencesController } from '../../controllers/preferences-controller'; import { AppStateController } from '../../controllers/app-state-controller'; -import { LOADING_SECURITY_ALERT_RESPONSE } from '../../../../shared/constants/security-provider'; +import { SECURITY_ALERT_RESPONSE_CHECKING_CHAIN } from '../../../../shared/constants/security-provider'; // eslint-disable-next-line import/no-restricted-paths import { getProviderConfig } from '../../../../ui/ducks/metamask/metamask'; import { trace, TraceContext, TraceName } from '../../../../shared/lib/trace'; import { generateSecurityAlertId, handlePPOMError, - isChainSupported, validateRequestWithPPOM, } from './ppom-util'; -import { SecurityAlertResponse } from './types'; +import { SecurityAlertResponse, UpdateSecurityAlertResponse } from './types'; const CONFIRMATION_METHODS = Object.freeze([ 'eth_sendRawTransaction', @@ -64,11 +63,7 @@ export function createPPOMMiddleware< networkController: NetworkController, appStateController: AppStateController, accountsController: AccountsController, - updateSecurityAlertResponse: ( - method: string, - signatureAlertId: string, - securityAlertResponse: SecurityAlertResponse, - ) => void, + updateSecurityAlertResponse: UpdateSecurityAlertResponse, ) { return async ( req: PPOMMiddlewareRequest, @@ -88,9 +83,7 @@ export function createPPOMMiddleware< if ( !securityAlertsEnabled || - !CONFIRMATION_METHODS.includes(req.method) || - // Do not move this call above this check because it will result in unnecessary calls - !(await isChainSupported(chainId)) + !CONFIRMATION_METHODS.includes(req.method) ) { return; } @@ -122,27 +115,22 @@ export function createPPOMMiddleware< request: req, securityAlertId, chainId, - }).then((securityAlertResponse) => { - updateSecurityAlertResponse( - req.method, - securityAlertId, - securityAlertResponse, - ); + updateSecurityAlertResponse, }), ); - const loadingSecurityAlertResponse: SecurityAlertResponse = { - ...LOADING_SECURITY_ALERT_RESPONSE, + const securityAlertResponseCheckingChain: SecurityAlertResponse = { + ...SECURITY_ALERT_RESPONSE_CHECKING_CHAIN, securityAlertId, }; if (SIGNING_METHODS.includes(req.method)) { appStateController.addSignatureSecurityAlertResponse( - loadingSecurityAlertResponse, + securityAlertResponseCheckingChain, ); } - req.securityAlertResponse = loadingSecurityAlertResponse; + req.securityAlertResponse = securityAlertResponseCheckingChain; } catch (error) { req.securityAlertResponse = handlePPOMError( error, diff --git a/app/scripts/lib/ppom/ppom-util.test.ts b/app/scripts/lib/ppom/ppom-util.test.ts index ea62c3b88533..8acb6dd9788c 100644 --- a/app/scripts/lib/ppom/ppom-util.test.ts +++ b/app/scripts/lib/ppom/ppom-util.test.ts @@ -10,9 +10,12 @@ import { SignatureController, SignatureRequest, } from '@metamask/signature-controller'; +import { Hex } from '@metamask/utils'; import { BlockaidReason, BlockaidResultType, + LOADING_SECURITY_ALERT_RESPONSE, + SECURITY_ALERT_RESPONSE_CHAIN_NOT_SUPPORTED, SecurityAlertSource, } from '../../../../shared/constants/security-provider'; import { AppStateController } from '../../controllers/app-state-controller'; @@ -32,7 +35,7 @@ jest.mock('@metamask/transaction-controller', () => ({ const SECURITY_ALERT_ID_MOCK = '1234-5678'; const TRANSACTION_ID_MOCK = '123'; -const CHAIN_ID_MOCK = '0x1'; +const CHAIN_ID_MOCK = '0x1' as Hex; const REQUEST_MOCK = { method: 'eth_signTypedData_v4', @@ -45,6 +48,7 @@ const SECURITY_ALERT_RESPONSE_MOCK: SecurityAlertResponse = { result_type: 'success', reason: 'success', source: SecurityAlertSource.Local, + securityAlertId: SECURITY_ALERT_ID_MOCK, }; const TRANSACTION_PARAMS_MOCK_1: TransactionParams = { @@ -110,6 +114,15 @@ describe('PPOM Utils', () => { ); let isSecurityAlertsEnabledMock: jest.SpyInstance; + const updateSecurityAlertResponseMock = jest.fn(); + + const validateRequestWithPPOMOptionsBase = { + request: REQUEST_MOCK, + securityAlertId: SECURITY_ALERT_ID_MOCK, + chainId: CHAIN_ID_MOCK, + updateSecurityAlertResponse: updateSecurityAlertResponseMock, + }; + beforeEach(() => { jest.resetAllMocks(); jest.spyOn(console, 'error').mockImplementation(() => undefined); @@ -119,7 +132,7 @@ describe('PPOM Utils', () => { }); describe('validateRequestWithPPOM', () => { - it('returns response from validation with PPOM instance via controller', async () => { + it('updates response from validation with PPOM instance via controller', async () => { const ppom = createPPOMMock(); const ppomController = createPPOMControllerMock(); @@ -129,23 +142,39 @@ describe('PPOM Utils', () => { (callback) => callback(ppom as any) as any, ); - const response = await validateRequestWithPPOM({ + await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, ppomController, - request: REQUEST_MOCK, - securityAlertId: SECURITY_ALERT_ID_MOCK, - chainId: CHAIN_ID_MOCK, - }); - - expect(response).toStrictEqual({ - ...SECURITY_ALERT_RESPONSE_MOCK, - securityAlertId: SECURITY_ALERT_ID_MOCK, }); + expect(updateSecurityAlertResponseMock).toHaveBeenCalledWith( + REQUEST_MOCK.method, + SECURITY_ALERT_ID_MOCK, + { + ...SECURITY_ALERT_RESPONSE_MOCK, + securityAlertId: SECURITY_ALERT_ID_MOCK, + }, + ); expect(ppom.validateJsonRpc).toHaveBeenCalledTimes(1); expect(ppom.validateJsonRpc).toHaveBeenCalledWith(REQUEST_MOCK); }); - it('returns error response if validation with PPOM instance throws', async () => { + it('updates securityAlertResponse with loading state', async () => { + const ppomController = createPPOMControllerMock(); + + await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, + ppomController, + }); + + expect(updateSecurityAlertResponseMock).toHaveBeenCalledWith( + REQUEST_MOCK.method, + SECURITY_ALERT_ID_MOCK, + LOADING_SECURITY_ALERT_RESPONSE, + ); + }); + + it('updates error response if validation with PPOM instance throws', async () => { const ppom = createPPOMMock(); const ppomController = createPPOMControllerMock(); @@ -157,37 +186,41 @@ describe('PPOM Utils', () => { callback(ppom as any) as any, ); - const response = await validateRequestWithPPOM({ + await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, ppomController, - request: REQUEST_MOCK, - securityAlertId: SECURITY_ALERT_ID_MOCK, - chainId: CHAIN_ID_MOCK, }); - expect(response).toStrictEqual({ - result_type: BlockaidResultType.Errored, - reason: BlockaidReason.errored, - description: 'Test Error: Test error message', - }); + expect(updateSecurityAlertResponseMock).toHaveBeenCalledWith( + validateRequestWithPPOMOptionsBase.request.method, + SECURITY_ALERT_ID_MOCK, + { + result_type: BlockaidResultType.Errored, + reason: BlockaidReason.errored, + description: 'Test Error: Test error message', + }, + ); }); - it('returns error response if controller throws', async () => { + it('updates error response if controller throws', async () => { const ppomController = createPPOMControllerMock(); ppomController.usePPOM.mockRejectedValue(createErrorMock()); - const response = await validateRequestWithPPOM({ + await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, ppomController, - request: REQUEST_MOCK, - securityAlertId: SECURITY_ALERT_ID_MOCK, - chainId: CHAIN_ID_MOCK, }); - expect(response).toStrictEqual({ - result_type: BlockaidResultType.Errored, - reason: BlockaidReason.errored, - description: 'Test Error: Test error message', - }); + expect(updateSecurityAlertResponseMock).toHaveBeenCalledWith( + validateRequestWithPPOMOptionsBase.request.method, + SECURITY_ALERT_ID_MOCK, + { + result_type: BlockaidResultType.Errored, + reason: BlockaidReason.errored, + description: 'Test Error: Test error message', + }, + ); }); it('normalizes request if method is eth_sendTransaction', async () => { @@ -209,10 +242,9 @@ describe('PPOM Utils', () => { }; await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, ppomController, request, - securityAlertId: SECURITY_ALERT_ID_MOCK, - chainId: CHAIN_ID_MOCK, }); expect(ppom.validateJsonRpc).toHaveBeenCalledTimes(1); @@ -226,6 +258,23 @@ describe('PPOM Utils', () => { TRANSACTION_PARAMS_MOCK_1, ); }); + + it('updates response indicating chain is not supported', async () => { + const ppomController = {} as PPOMController; + const CHAIN_ID_UNSUPPORTED_MOCK = '0x2'; + + await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, + ppomController, + chainId: CHAIN_ID_UNSUPPORTED_MOCK, + }); + + expect(updateSecurityAlertResponseMock).toHaveBeenCalledWith( + validateRequestWithPPOMOptionsBase.request.method, + SECURITY_ALERT_ID_MOCK, + SECURITY_ALERT_RESPONSE_CHAIN_NOT_SUPPORTED, + ); + }); }); describe('generateSecurityAlertId', () => { @@ -318,10 +367,9 @@ describe('PPOM Utils', () => { const ppomController = createPPOMControllerMock(); await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, ppomController, request, - securityAlertId: SECURITY_ALERT_ID_MOCK, - chainId: CHAIN_ID_MOCK, }); expect(ppomController.usePPOM).not.toHaveBeenCalled(); @@ -345,10 +393,9 @@ describe('PPOM Utils', () => { .mockRejectedValue(new Error('Test Error')); await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, ppomController, request, - securityAlertId: SECURITY_ALERT_ID_MOCK, - chainId: CHAIN_ID_MOCK, }); expect(ppomController.usePPOM).toHaveBeenCalledTimes(1); diff --git a/app/scripts/lib/ppom/ppom-util.ts b/app/scripts/lib/ppom/ppom-util.ts index 7662c364b651..fa1172ba01eb 100644 --- a/app/scripts/lib/ppom/ppom-util.ts +++ b/app/scripts/lib/ppom/ppom-util.ts @@ -11,12 +11,14 @@ import { SignatureController } from '@metamask/signature-controller'; import { BlockaidReason, BlockaidResultType, + LOADING_SECURITY_ALERT_RESPONSE, + SECURITY_ALERT_RESPONSE_CHAIN_NOT_SUPPORTED, SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS, SecurityAlertSource, } from '../../../../shared/constants/security-provider'; import { SIGNING_METHODS } from '../../../../shared/constants/transaction'; import { AppStateController } from '../../controllers/app-state-controller'; -import { SecurityAlertResponse } from './types'; +import { SecurityAlertResponse, UpdateSecurityAlertResponse } from './types'; import { getSecurityAlertsAPISupportedChainIds, isSecurityAlertsAPIEnabled, @@ -37,18 +39,36 @@ type PPOMRequest = Omit & { method: typeof METHOD_SEND_TRANSACTION; params: [TransactionParams]; }; + export async function validateRequestWithPPOM({ ppomController, request, securityAlertId, chainId, + updateSecurityAlertResponse: updateSecurityResponse, }: { ppomController: PPOMController; request: JsonRpcRequest; securityAlertId: string; chainId: Hex; -}): Promise { + updateSecurityAlertResponse: UpdateSecurityAlertResponse; +}) { try { + if (!(await isChainSupported(chainId))) { + await updateSecurityResponse( + request.method, + securityAlertId, + SECURITY_ALERT_RESPONSE_CHAIN_NOT_SUPPORTED, + ); + return; + } + + await updateSecurityResponse( + request.method, + securityAlertId, + LOADING_SECURITY_ALERT_RESPONSE, + ); + const normalizedRequest = normalizePPOMRequest(request); const ppomResponse = isSecurityAlertsAPIEnabled() @@ -58,13 +78,13 @@ export async function validateRequestWithPPOM({ normalizedRequest, chainId, ); - - return { - ...ppomResponse, - securityAlertId, - }; + await updateSecurityResponse(request.method, securityAlertId, ppomResponse); } catch (error: unknown) { - return handlePPOMError(error, 'Error validating JSON RPC using PPOM: '); + await updateSecurityResponse( + request.method, + securityAlertId, + handlePPOMError(error, 'Error validating JSON RPC using PPOM: '), + ); } } @@ -97,12 +117,15 @@ export async function updateSecurityAlertResponse({ ); if (isSignatureRequest) { - appStateController.addSignatureSecurityAlertResponse(securityAlertResponse); + appStateController.addSignatureSecurityAlertResponse({ + ...securityAlertResponse, + securityAlertId, + }); } else { - transactionController.updateSecurityAlertResponse( - confirmation.id, - securityAlertResponse, - ); + transactionController.updateSecurityAlertResponse(confirmation.id, { + ...securityAlertResponse, + securityAlertId, + } as SecurityAlertResponse); } } diff --git a/app/scripts/lib/ppom/types.ts b/app/scripts/lib/ppom/types.ts index 6188d644aa12..57dd4a9e533d 100644 --- a/app/scripts/lib/ppom/types.ts +++ b/app/scripts/lib/ppom/types.ts @@ -10,3 +10,9 @@ export type SecurityAlertResponse = { securityAlertId?: string; source?: SecurityAlertSource; }; + +export type UpdateSecurityAlertResponse = ( + method: string, + securityAlertId: string, + securityAlertResponse: SecurityAlertResponse, +) => Promise; diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index 16077e0f08ed..fbbee025381b 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -16,7 +16,6 @@ import { BlockaidReason, BlockaidResultType, } from '../../../../shared/constants/security-provider'; -import { SecurityAlertResponse } from '../ppom/types'; import { flushPromises } from '../../../../test/lib/timer-helpers'; import { createMockInternalAccount } from '../../../../test/jest/mocks'; import { @@ -79,11 +78,6 @@ const TRANSACTION_REQUEST_MOCK: AddTransactionRequest = { internalAccounts: [], } as unknown as AddTransactionRequest; -const SECURITY_ALERT_RESPONSE_MOCK: SecurityAlertResponse = { - result_type: BlockaidResultType.Malicious, - reason: BlockaidReason.maliciousDomain, -}; - function createTransactionControllerMock() { return { addTransaction: jest.fn(), @@ -406,10 +400,6 @@ describe('Transaction Utils', () => { describe('validates using security provider', () => { it('adds loading response to request options', async () => { - validateRequestWithPPOMMock.mockResolvedValue( - SECURITY_ALERT_RESPONSE_MOCK, - ); - await addTransaction({ ...request, securityAlertsEnabled: true, @@ -425,36 +415,13 @@ describe('Transaction Utils', () => { ).toHaveBeenCalledWith(TRANSACTION_PARAMS_MOCK, { ...TRANSACTION_OPTIONS_MOCK, securityAlertResponse: { - reason: BlockaidReason.inProgress, + reason: BlockaidReason.checkingChain, result_type: BlockaidResultType.Loading, securityAlertId: SECURITY_ALERT_ID_MOCK, }, }); }); - it('updates response after validation', async () => { - validateRequestWithPPOMMock.mockResolvedValue( - SECURITY_ALERT_RESPONSE_MOCK, - ); - - await addTransaction({ - ...request, - securityAlertsEnabled: true, - chainId: '0x1', - }); - - await flushPromises(); - - expect(request.updateSecurityAlertResponse).toHaveBeenCalledTimes(1); - expect(request.updateSecurityAlertResponse).toHaveBeenCalledWith( - 'eth_sendTransaction', - SECURITY_ALERT_ID_MOCK, - SECURITY_ALERT_RESPONSE_MOCK, - ); - - expect(validateRequestWithPPOMMock).toHaveBeenCalledTimes(1); - }); - it('unless blockaid is disabled', async () => { await addTransaction({ ...request, @@ -505,29 +472,6 @@ describe('Transaction Utils', () => { expect(validateRequestWithPPOMMock).toHaveBeenCalledTimes(0); }); - it('unless chain is not supported', async () => { - isChainSupportedMock.mockResolvedValue(false); - - await addTransaction({ - ...request, - securityAlertsEnabled: true, - chainId: '0xF', - }); - - expect( - request.transactionController.addTransaction, - ).toHaveBeenCalledTimes(1); - - expect( - request.transactionController.addTransaction, - ).toHaveBeenCalledWith( - TRANSACTION_PARAMS_MOCK, - TRANSACTION_OPTIONS_MOCK, - ); - - expect(validateRequestWithPPOMMock).toHaveBeenCalledTimes(0); - }); - it('unless transaction type is swap', async () => { const swapRequest = { ...request }; swapRequest.transactionOptions.type = TransactionType.swap; diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index 8b71e33119f8..0bbf93afd8a8 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -16,12 +16,14 @@ import { PPOMController } from '@metamask/ppom-validator'; import { generateSecurityAlertId, handlePPOMError, - isChainSupported, validateRequestWithPPOM, } from '../ppom/ppom-util'; -import { SecurityAlertResponse } from '../ppom/types'; import { - LOADING_SECURITY_ALERT_RESPONSE, + SecurityAlertResponse, + UpdateSecurityAlertResponse, +} from '../ppom/types'; +import { + SECURITY_ALERT_RESPONSE_CHECKING_CHAIN, SECURITY_PROVIDER_EXCLUDED_TRANSACTION_TYPES, } from '../../../../shared/constants/security-provider'; import { endTrace, TraceName } from '../../../../shared/lib/trace'; @@ -38,11 +40,7 @@ type BaseAddTransactionRequest = { selectedAccount: InternalAccount; transactionParams: TransactionParams; transactionController: TransactionController; - updateSecurityAlertResponse: ( - method: string, - securityAlertId: string, - securityAlertResponse: SecurityAlertResponse, - ) => void; + updateSecurityAlertResponse: UpdateSecurityAlertResponse; userOperationController: UserOperationController; internalAccounts: InternalAccount[]; }; @@ -239,18 +237,12 @@ async function validateSecurity(request: AddTransactionRequest) { const { type } = transactionOptions; - const isCurrentChainSupported = await isChainSupported(chainId); - const typeIsExcludedFromPPOM = SECURITY_PROVIDER_EXCLUDED_TRANSACTION_TYPES.includes( type as TransactionType, ); - if ( - !securityAlertsEnabled || - !isCurrentChainSupported || - typeIsExcludedFromPPOM - ) { + if (!securityAlertsEnabled || typeIsExcludedFromPPOM) { return; } @@ -290,21 +282,16 @@ async function validateSecurity(request: AddTransactionRequest) { request: ppomRequest, securityAlertId, chainId, - }).then((securityAlertResponse) => { - updateSecurityAlertResponse( - ppomRequest.method, - securityAlertId, - securityAlertResponse, - ); + updateSecurityAlertResponse, }); - const loadingSecurityAlertResponse: SecurityAlertResponse = { - ...LOADING_SECURITY_ALERT_RESPONSE, + const securityAlertResponseCheckingChain: SecurityAlertResponse = { + ...SECURITY_ALERT_RESPONSE_CHECKING_CHAIN, securityAlertId, }; request.transactionOptions.securityAlertResponse = - loadingSecurityAlertResponse; + securityAlertResponseCheckingChain; } catch (error) { handlePPOMError(error, 'Error validating JSON RPC using PPOM: '); } diff --git a/shared/constants/security-provider.ts b/shared/constants/security-provider.ts index e6fff53ee28a..2864fe0339a9 100644 --- a/shared/constants/security-provider.ts +++ b/shared/constants/security-provider.ts @@ -32,7 +32,7 @@ export enum BlockaidReason { approvalFarming = 'approval_farming', /** Malicious signature on Blur order */ blurFarming = 'blur_farming', - /** A known malicous site invoked that transaction */ + /** A known malicious site invoked that transaction */ maliciousDomain = 'malicious_domain', /** Malicious signature on a Permit order */ permitFarming = 'permit_farming', @@ -57,6 +57,8 @@ export enum BlockaidReason { errored = 'Error', notApplicable = 'NotApplicable', inProgress = 'validation_in_progress', + checkingChain = 'CheckingChain', + chainNotSupported = 'ChainNotSupported', } export enum BlockaidResultType { @@ -117,6 +119,17 @@ export const LOADING_SECURITY_ALERT_RESPONSE: SecurityAlertResponse = { reason: BlockaidReason.inProgress, }; +export const SECURITY_ALERT_RESPONSE_CHECKING_CHAIN: SecurityAlertResponse = { + result_type: BlockaidResultType.Loading, + reason: BlockaidReason.checkingChain, +}; + +export const SECURITY_ALERT_RESPONSE_CHAIN_NOT_SUPPORTED: SecurityAlertResponse = + { + result_type: BlockaidResultType.Benign, + reason: BlockaidReason.chainNotSupported, + }; + export enum SecurityAlertSource { /** Validation performed remotely using the Security Alerts API. */ API = 'api', diff --git a/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts b/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts index fc8a6d0ab240..328ad7811e1b 100644 --- a/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts +++ b/test/e2e/tests/confirmations/signatures/malicious-signatures.spec.ts @@ -10,6 +10,10 @@ import { withRedesignConfirmationFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import { + BlockaidReason, + BlockaidResultType, +} from '../../../../../shared/constants/security-provider'; import { assertSignatureRejectedMetrics, openDappAndTriggerSignature, @@ -80,6 +84,8 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this alert_visualized: [], alert_visualized_count: 0, }, + securityAlertReason: BlockaidReason.notApplicable, + securityAlertResponse: BlockaidResultType.NotApplicable, }); }, mockSignatureRejected, @@ -130,6 +136,8 @@ describe('Malicious Confirmation Signature - Bad Domain @no-mmi', function (this alert_visualized: ['requestFrom'], alert_visualized_count: 1, }, + securityAlertReason: BlockaidReason.notApplicable, + securityAlertResponse: BlockaidResultType.NotApplicable, }); }, mockSignatureRejected, diff --git a/test/e2e/tests/confirmations/signatures/signature-helpers.ts b/test/e2e/tests/confirmations/signatures/signature-helpers.ts index 9b87e5b4e9cc..27cc92fe27cf 100644 --- a/test/e2e/tests/confirmations/signatures/signature-helpers.ts +++ b/test/e2e/tests/confirmations/signatures/signature-helpers.ts @@ -8,6 +8,10 @@ import { unlockWallet, } from '../../../helpers'; import { Driver } from '../../../webdriver/driver'; +import { + BlockaidReason, + BlockaidResultType, +} from '../../../../../shared/constants/security-provider'; export const WALLET_ADDRESS = '0x5CfE73b6021E818B776b421B1c4Db2474086a7e1'; export const WALLET_ETH_BALANCE = '25'; @@ -30,6 +34,8 @@ type AssertSignatureMetricsOptions = { location?: string; expectedProps?: Record; withAnonEvents?: boolean; + securityAlertReason?: string; + securityAlertResponse?: string; }; type SignatureEventProperty = { @@ -39,7 +45,7 @@ type SignatureEventProperty = { environment_type: 'background'; locale: 'en'; security_alert_reason: string; - security_alert_response: 'NotApplicable'; + security_alert_response: string; signature_type: string; eip712_primary_type?: string; ui_customizations?: string[]; @@ -58,11 +64,15 @@ const signatureAnonProperties = { * @param signatureType * @param primaryType * @param uiCustomizations + * @param securityAlertReason + * @param securityAlertResponse */ function getSignatureEventProperty( signatureType: string, primaryType: string, uiCustomizations: string[], + securityAlertReason: string = BlockaidReason.checkingChain, + securityAlertResponse: string = BlockaidResultType.Loading, ): SignatureEventProperty { const signatureEventProperty: SignatureEventProperty = { account_type: 'MetaMask', @@ -71,8 +81,8 @@ function getSignatureEventProperty( chain_id: '0x539', environment_type: 'background', locale: 'en', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', + security_alert_reason: securityAlertReason, + security_alert_response: securityAlertResponse, ui_customizations: uiCustomizations, }; @@ -89,15 +99,15 @@ function assertSignatureRequestedMetrics( signatureEventProperty: SignatureEventProperty, withAnonEvents = false, ) { - assertEventPropertiesMatch(events, 'Signature Requested', { - ...signatureEventProperty, - security_alert_reason: 'NotApplicable', - }); + assertEventPropertiesMatch( + events, + 'Signature Requested', + signatureEventProperty, + ); if (withAnonEvents) { assertEventPropertiesMatch(events, 'Signature Requested Anon', { ...signatureEventProperty, - security_alert_reason: 'NotApplicable', ...signatureAnonProperties, }); } @@ -110,12 +120,16 @@ export async function assertSignatureConfirmedMetrics({ primaryType = '', uiCustomizations = ['redesigned_confirmation'], withAnonEvents = false, + securityAlertReason, + securityAlertResponse, }: AssertSignatureMetricsOptions) { const events = await getEventPayloads(driver, mockedEndpoints); const signatureEventProperty = getSignatureEventProperty( signatureType, primaryType, uiCustomizations, + securityAlertReason, + securityAlertResponse, ); assertSignatureRequestedMetrics( @@ -147,12 +161,16 @@ export async function assertSignatureRejectedMetrics({ location, expectedProps = {}, withAnonEvents = false, + securityAlertReason, + securityAlertResponse, }: AssertSignatureMetricsOptions) { const events = await getEventPayloads(driver, mockedEndpoints); const signatureEventProperty = getSignatureEventProperty( signatureType, primaryType, uiCustomizations, + securityAlertReason, + securityAlertResponse, ); assertSignatureRequestedMetrics( @@ -200,14 +218,44 @@ function assertEventPropertiesMatch( expectedProperties: object, ) { const event = events.find((e) => e.event === eventName); + + const actualProperties = { ...event.properties }; + const expectedProps = { ...expectedProperties }; + + compareSecurityAlertResponse(actualProperties, expectedProps, eventName); + assert(event, `${eventName} event not found`); assert.deepStrictEqual( - event.properties, - expectedProperties, + actualProperties, + expectedProps, `${eventName} event properties do not match`, ); } +function compareSecurityAlertResponse( + actualProperties: Record, + expectedProperties: Record, + eventName: string, +) { + if ( + expectedProperties.security_alert_response && + (expectedProperties.security_alert_response === 'loading' || + expectedProperties.security_alert_response === 'Benign') + ) { + if ( + actualProperties.security_alert_response !== 'loading' && + actualProperties.security_alert_response !== 'Benign' + ) { + assert.fail( + `${eventName} event properties do not match: security_alert_response is ${actualProperties.security_alert_response}`, + ); + } + // Remove the property from both objects to avoid comparison + delete actualProperties.security_alert_response; + delete expectedProperties.security_alert_response; + } +} + export async function clickHeaderInfoBtn(driver: Driver) { await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); diff --git a/test/e2e/tests/confirmations/signatures/siwe.spec.ts b/test/e2e/tests/confirmations/signatures/siwe.spec.ts index 2a7b43ba1099..889122eb22b8 100644 --- a/test/e2e/tests/confirmations/signatures/siwe.spec.ts +++ b/test/e2e/tests/confirmations/signatures/siwe.spec.ts @@ -11,6 +11,10 @@ import { withRedesignConfirmationFixtures, } from '../helpers'; import { TestSuiteArguments } from '../transactions/shared'; +import { + BlockaidReason, + BlockaidResultType, +} from '../../../../../shared/constants/security-provider'; import { assertAccountDetailsMetrics, assertHeaderInfoBalance, @@ -60,6 +64,8 @@ describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) { 'redesigned_confirmation', 'sign_in_with_ethereum', ], + securityAlertReason: BlockaidReason.notApplicable, + securityAlertResponse: BlockaidResultType.NotApplicable, }); }, mockSignatureApproved, @@ -95,6 +101,8 @@ describe('Confirmation Signature - SIWE @no-mmi', function (this: Suite) { 'sign_in_with_ethereum', ], location: 'confirmation', + securityAlertReason: BlockaidReason.notApplicable, + securityAlertResponse: BlockaidResultType.NotApplicable, }); }, mockSignatureRejected, diff --git a/test/e2e/tests/metrics/signature-approved.spec.js b/test/e2e/tests/metrics/signature-approved.spec.js index c6990820af8b..2ea84d281b50 100644 --- a/test/e2e/tests/metrics/signature-approved.spec.js +++ b/test/e2e/tests/metrics/signature-approved.spec.js @@ -1,4 +1,5 @@ const { strict: assert } = require('assert'); + const { defaultGanacheOptions, switchToNotificationWindow, @@ -47,6 +48,16 @@ async function mockSegment(mockServer) { ]; } +const expectedEventPropertiesBase = { + account_type: 'MetaMask', + category: 'inpage_provider', + locale: 'en', + chain_id: '0x539', + environment_type: 'background', + security_alert_reason: 'CheckingChain', + security_alert_response: 'loading', +}; + describe('Signature Approved Event @no-mmi', function () { it('Successfully tracked for signTypedData_v4', async function () { await withFixtures( @@ -76,31 +87,21 @@ describe('Signature Approved Event @no-mmi', function () { const events = await getEventPayloads(driver, mockedEndpoints); assert.deepStrictEqual(events[0].properties, { - account_type: 'MetaMask', + ...expectedEventPropertiesBase, signature_type: 'eth_signTypedData_v4', - category: 'inpage_provider', - locale: 'en', - chain_id: '0x539', eip712_primary_type: 'Mail', - environment_type: 'background', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', }); assert.deepStrictEqual(events[1].properties, { - account_type: 'MetaMask', + ...expectedEventPropertiesBase, signature_type: 'eth_signTypedData_v4', - category: 'inpage_provider', - locale: 'en', - chain_id: '0x539', eip712_primary_type: 'Mail', - environment_type: 'background', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', + security_alert_response: 'Benign', }); }, ); }); + it('Successfully tracked for signTypedData_v3', async function () { await withFixtures( { @@ -127,29 +128,21 @@ describe('Signature Approved Event @no-mmi', function () { await validateContractDetails(driver); await clickSignOnSignatureConfirmation({ driver }); const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { - account_type: 'MetaMask', + ...expectedEventPropertiesBase, signature_type: 'eth_signTypedData_v3', - category: 'inpage_provider', - locale: 'en', - chain_id: '0x539', - environment_type: 'background', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', }); + assert.deepStrictEqual(events[1].properties, { - account_type: 'MetaMask', + ...expectedEventPropertiesBase, signature_type: 'eth_signTypedData_v3', - category: 'inpage_provider', - locale: 'en', - chain_id: '0x539', - environment_type: 'background', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', + security_alert_response: 'Benign', }); }, ); }); + it('Successfully tracked for signTypedData', async function () { await withFixtures( { @@ -175,29 +168,21 @@ describe('Signature Approved Event @no-mmi', function () { await switchToNotificationWindow(driver); await clickSignOnSignatureConfirmation({ driver }); const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { - account_type: 'MetaMask', + ...expectedEventPropertiesBase, signature_type: 'eth_signTypedData', - category: 'inpage_provider', - locale: 'en', - chain_id: '0x539', - environment_type: 'background', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', }); + assert.deepStrictEqual(events[1].properties, { - account_type: 'MetaMask', + ...expectedEventPropertiesBase, signature_type: 'eth_signTypedData', - category: 'inpage_provider', - locale: 'en', - chain_id: '0x539', - environment_type: 'background', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', + security_alert_response: 'Benign', }); }, ); }); + it('Successfully tracked for personalSign', async function () { await withFixtures( { @@ -223,25 +208,16 @@ describe('Signature Approved Event @no-mmi', function () { await switchToNotificationWindow(driver); await clickSignOnSignatureConfirmation({ driver }); const events = await getEventPayloads(driver, mockedEndpoints); + assert.deepStrictEqual(events[0].properties, { - account_type: 'MetaMask', + ...expectedEventPropertiesBase, signature_type: 'personal_sign', - category: 'inpage_provider', - locale: 'en', - chain_id: '0x539', - environment_type: 'background', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', }); + assert.deepStrictEqual(events[1].properties, { - account_type: 'MetaMask', + ...expectedEventPropertiesBase, signature_type: 'personal_sign', - category: 'inpage_provider', - locale: 'en', - chain_id: '0x539', - environment_type: 'background', - security_alert_reason: 'NotApplicable', - security_alert_response: 'NotApplicable', + security_alert_response: 'Benign', }); }, ); From 5e7e9c233f62b691ab6399374a50161f08a0c526 Mon Sep 17 00:00:00 2001 From: Salim TOUBAL Date: Wed, 13 Nov 2024 16:21:46 +0100 Subject: [PATCH 17/32] fix: fix network client ID used on the useGasFeeInputs hook (#28391) (#28426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** cherry-pick for the PR #28391 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28426?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/pages/confirmations/hooks/test-utils.js | 4 ++++ ui/pages/confirmations/hooks/useGasFeeInputs.js | 10 +++++++++- ui/pages/confirmations/hooks/useGasFeeInputs.test.js | 12 ++++++++++++ ui/selectors/selectors.js | 10 ++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ui/pages/confirmations/hooks/test-utils.js b/ui/pages/confirmations/hooks/test-utils.js index 908f600564f8..9ad1101e6de9 100644 --- a/ui/pages/confirmations/hooks/test-utils.js +++ b/ui/pages/confirmations/hooks/test-utils.js @@ -14,6 +14,7 @@ import { getCurrentKeyring, getTokenExchangeRates, getPreferences, + selectNetworkConfigurationByChainId, } from '../../../selectors'; import { @@ -127,6 +128,9 @@ export const generateUseSelectorRouter = ) { return 'USD'; } + if (selector === selectNetworkConfigurationByChainId) { + return '2'; + } if ( selector === getMultichainShouldShowFiat || selector === getShouldShowFiat diff --git a/ui/pages/confirmations/hooks/useGasFeeInputs.js b/ui/pages/confirmations/hooks/useGasFeeInputs.js index 27a60ad5f043..8675b726038a 100644 --- a/ui/pages/confirmations/hooks/useGasFeeInputs.js +++ b/ui/pages/confirmations/hooks/useGasFeeInputs.js @@ -11,6 +11,7 @@ import { GAS_FORM_ERRORS } from '../../../helpers/constants/gas'; import { checkNetworkAndAccountSupports1559, getAdvancedInlineGasShown, + selectNetworkConfigurationByChainId, } from '../../../selectors'; import { isLegacyTransaction } from '../../../helpers/utils/transactions.util'; import { useGasFeeEstimates } from '../../../hooks/useGasFeeEstimates'; @@ -118,6 +119,13 @@ export function useGasFeeInputs( ? retryTxMeta : _transaction; + const network = useSelector((state) => + selectNetworkConfigurationByChainId(state, transaction?.chainId), + ); + + const networkClientId = + network?.rpcEndpoints?.[network?.defaultRpcEndpointIndex]?.networkClientId; + const supportsEIP1559 = useSelector(checkNetworkAndAccountSupports1559) && !isLegacyTransaction(transaction?.txParams); @@ -130,7 +138,7 @@ export function useGasFeeInputs( gasFeeEstimates, isGasEstimatesLoading, isNetworkBusy, - } = useGasFeeEstimates(transaction?.networkClientId); + } = useGasFeeEstimates(networkClientId); const userPrefersAdvancedGas = useSelector(getAdvancedInlineGasShown); diff --git a/ui/pages/confirmations/hooks/useGasFeeInputs.test.js b/ui/pages/confirmations/hooks/useGasFeeInputs.test.js index c7477d548735..48f78f42977f 100644 --- a/ui/pages/confirmations/hooks/useGasFeeInputs.test.js +++ b/ui/pages/confirmations/hooks/useGasFeeInputs.test.js @@ -50,6 +50,7 @@ jest.mock('../../../hooks/useMultichainSelector', () => ({ const mockTransaction = { status: TransactionStatus.unapproved, type: TransactionType.simpleSend, + networkClientId: '2', txParams: { from: '0x000000000000000000000000000000000000dead', type: '0x2', @@ -94,6 +95,7 @@ describe('useGasFeeInputs', () => { checkNetworkAndAccountSupports1559Response: false, }), ); + const { result } = renderHook(() => useGasFeeInputs()); expect(result.current.gasPrice).toBe( LEGACY_GAS_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium, @@ -187,9 +189,19 @@ describe('useGasFeeInputs', () => { ); expect(result.current.balanceError).toBe(true); }); + + it('should call useGasFeeEstimates with correct networkClientId', () => { + renderHook(() => useGasFeeInputs(null, mockTransaction)); + expect(useGasFeeEstimates).not.toHaveBeenCalledWith('2'); + }); }); describe('editGasMode', () => { + beforeEach(() => { + useGasFeeEstimates.mockImplementation( + () => HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE, + ); + }); it('should return editGasMode passed', () => { const { result } = renderHook(() => useGasFeeInputs(undefined, undefined, undefined, EditGasModes.swaps), diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index bb7ef5f796d7..dd246290bb93 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -720,6 +720,16 @@ export function getRequestingNetworkInfo(state, chainIds) { ); } +/** + * @type (state: any, chainId: string) => import('@metamask/network-controller').NetworkConfiguration + */ +export const selectNetworkConfigurationByChainId = createSelector( + getNetworkConfigurationsByChainId, + (_state, chainId) => chainId, + (networkConfigurationsByChainId, chainId) => + networkConfigurationsByChainId[chainId], +); + /** * Provides information about the last network change if present * From f0d25884a2c8bd63ee063191818a07afa166b867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Tavares?= Date: Thu, 14 Nov 2024 16:00:00 +0000 Subject: [PATCH 18/32] fix: ui customizations for redesigned transactions (#28443) (#28457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry pick: https://github.com/MetaMask/metamask-extension/commit/072c8c708913fc6f94b36eac84ed2848dbc4c1b2 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28443?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28322 Fixes: https://github.com/MetaMask/metamask-extension/issues/28339 ## **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: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> --- app/scripts/lib/transaction/metrics.ts | 2 +- app/scripts/metamask-controller.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index 1e0e26dc1c20..3cdc14619b0d 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -998,7 +998,7 @@ async function buildEventFragmentProperties({ } const isRedesignedConfirmationsDeveloperSettingEnabled = transactionMetricsRequest.getIsRedesignedConfirmationsDeveloperEnabled() || - Boolean(process.env.ENABLE_CONFIRMATION_REDESIGN); + process.env.ENABLE_CONFIRMATION_REDESIGN === 'true'; const isRedesignedTransactionsUserSettingEnabled = transactionMetricsRequest.getRedesignedTransactionsEnabled(); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 830b66d38a06..a275c4d17e5a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -6529,10 +6529,12 @@ export default class MetamaskController extends EventEmitter { ); }, getRedesignedConfirmationsEnabled: () => { - return this.preferencesController.getRedesignedConfirmationsEnabled; + return this.preferencesController.state.preferences + .redesignedConfirmationsEnabled; }, getRedesignedTransactionsEnabled: () => { - return this.preferencesController.getRedesignedTransactionsEnabled; + return this.preferencesController.state.preferences + .redesignedTransactionsEnabled; }, getMethodData: (data) => { if (!data) { From b86383d13ce31dc9ae0b1b3fbca3ec20c14949bc Mon Sep 17 00:00:00 2001 From: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:23:46 +0000 Subject: [PATCH 19/32] fix (cherry-pick): add simulation metrics when simulation UI is not visible (#28427) (#28461) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** cherry pick PR #28427 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28461?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28369 ## **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. Co-authored-by: digiwand <20778143+digiwand@users.noreply.github.com> --- .../confirm/info/native-transfer/native-transfer.tsx | 5 +++-- .../info/nft-token-transfer/nft-token-transfer.tsx | 5 +++-- .../confirm/info/token-transfer/token-transfer.tsx | 5 +++-- .../simulation-details/simulation-details.test.tsx | 11 ++++++++++- .../simulation-details/simulation-details.tsx | 7 +++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx b/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx index c098936989dd..f3433511fd01 100644 --- a/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx +++ b/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx @@ -19,15 +19,16 @@ const NativeTransferInfo = () => { <> - {!isWalletInitiated && ( + { - )} + } diff --git a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx index 113920a6aec2..2911e32f7696 100644 --- a/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx +++ b/ui/pages/confirmations/components/confirm/info/nft-token-transfer/nft-token-transfer.tsx @@ -19,15 +19,16 @@ const NFTTokenTransferInfo = () => { <> - {!isWalletInitiated && ( + { - )} + } diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx index 50e9d85936f0..dd95e841c2c0 100644 --- a/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx +++ b/ui/pages/confirmations/components/confirm/info/token-transfer/token-transfer.tsx @@ -19,15 +19,16 @@ const TokenTransferInfo = () => { <> - {!isWalletInitiated && ( + { - )} + } diff --git a/ui/pages/confirmations/components/simulation-details/simulation-details.test.tsx b/ui/pages/confirmations/components/simulation-details/simulation-details.test.tsx index 339ffbdd6a1a..129e89134159 100644 --- a/ui/pages/confirmations/components/simulation-details/simulation-details.test.tsx +++ b/ui/pages/confirmations/components/simulation-details/simulation-details.test.tsx @@ -41,12 +41,16 @@ jest.mock('../../context/confirm', () => ({ })), })); -const renderSimulationDetails = (simulationData?: Partial) => +const renderSimulationDetails = ( + simulationData?: Partial, + metricsOnly?: boolean, +) => renderWithProvider( , store, ); @@ -145,4 +149,9 @@ describe('SimulationDetails', () => { {}, ); }); + + it('does not render any UI elements when metricsOnly is true', () => { + const { container } = renderSimulationDetails({}, true); + expect(container).toBeEmptyDOMElement(); + }); }); diff --git a/ui/pages/confirmations/components/simulation-details/simulation-details.tsx b/ui/pages/confirmations/components/simulation-details/simulation-details.tsx index 382fcb180f82..f950c1846968 100644 --- a/ui/pages/confirmations/components/simulation-details/simulation-details.tsx +++ b/ui/pages/confirmations/components/simulation-details/simulation-details.tsx @@ -34,6 +34,7 @@ import { useSimulationMetrics } from './useSimulationMetrics'; export type SimulationDetailsProps = { enableMetrics?: boolean; isTransactionsRedesign?: boolean; + metricsOnly?: boolean; transaction: TransactionMeta; }; @@ -219,11 +220,13 @@ const SimulationDetailsLayout: React.FC<{ * @param props.enableMetrics - Whether to enable simulation metrics. * @param props.isTransactionsRedesign - Whether or not the component is being * used inside the transaction redesign flow. + * @param props.metricsOnly - Whether to only track metrics and not render the UI. */ export const SimulationDetails: React.FC = ({ transaction, enableMetrics = false, isTransactionsRedesign = false, + metricsOnly = false, }: SimulationDetailsProps) => { const t = useI18nContext(); const { chainId, id: transactionId, simulationData } = transaction; @@ -238,6 +241,10 @@ export const SimulationDetails: React.FC = ({ transactionId, }); + if (metricsOnly) { + return null; + } + if (loading) { return ( Date: Thu, 14 Nov 2024 14:30:31 -0500 Subject: [PATCH 20/32] Fixed lint --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9399969242bb..5685d8152ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -274,6 +274,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: Confirm Page test failing in CI/CD ([#27423](https://github.com/MetaMask/metamask-extension/pull/27423)) - feat: Add performance metrics for signature requests ([#26967](https://github.com/MetaMask/metamask-extension/pull/26967)) - fix(NOTIFY-1166): rename account sync event names ([#27413](https://github.com/MetaMask/metamask-extension/pull/27413)) + ## [12.6.1] ### Fixed - Fixed gas limit estimation on Base and BNB chains ([#28327](https://github.com/MetaMask/metamask-extension/pull/28327)) @@ -5576,8 +5577,7 @@ Update styles and spacing on the critical error page ([#20350](https://github.c [Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.7.0...HEAD -[12.7.0]: https://github.com/MetaMask/metamask-extension/compare/v12.6.0...v12.7.0 -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.6.1...HEAD +[12.7.0]: https://github.com/MetaMask/metamask-extension/compare/v12.6.1...v12.7.0 [12.6.1]: https://github.com/MetaMask/metamask-extension/compare/v12.6.0...v12.6.1 [12.6.0]: https://github.com/MetaMask/metamask-extension/compare/v12.5.1...v12.6.0 [12.5.1]: https://github.com/MetaMask/metamask-extension/compare/v12.5.0...v12.5.1 From 76d48fa70f8c4de2dadf0a4892097bead86147f2 Mon Sep 17 00:00:00 2001 From: Marina Boboc <120041701+benjisclowder@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:04:26 +0200 Subject: [PATCH 21/32] chore: V12.7.0 changelog (#28458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Adding RC 12.7.0 changelog. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28458?quickstart=1) ## **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. --- CHANGELOG.md | 301 ++++++--------------------------------------------- 1 file changed, 34 insertions(+), 267 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5685d8152ed1..e849728f7781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,273 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [12.7.0] -### Fixed -- chore: Master sync ([#28222](https://github.com/MetaMask/metamask-extension/pull/28222)) -- Merge origin/develop into master-sync -- Version v12.6.0 ([#27996](https://github.com/MetaMask/metamask-extension/pull/27996)) -- refactor: move `getSelectedInternalAccount` from `selectors.js` to `accounts.ts` ([#27644](https://github.com/MetaMask/metamask-extension/pull/27644)) -- fix: cherry-pick: Prevent coercing small spending caps to zero (#28179) ([#28179](https://github.com/MetaMask/metamask-extension/pull/28179)) -- [cherry pick] Fix left aligned fullscreen (#28218) ([#28218](https://github.com/MetaMask/metamask-extension/pull/28218)) -- v12.6.0 changelog lint fix ([#28228](https://github.com/MetaMask/metamask-extension/pull/28228)) -- feat: Improve provider method metrics for add/switch chain ([#28214](https://github.com/MetaMask/metamask-extension/pull/28214)) -- V12.6.0 Changelog ([#28166](https://github.com/MetaMask/metamask-extension/pull/28166)) -- Merge master to v12.6.0 after the merge of 12.5.1 to master ([#28217](https://github.com/MetaMask/metamask-extension/pull/28217)) -- fix: Fix left-aligned fullscreen UI ([#28218](https://github.com/MetaMask/metamask-extension/pull/28218)) -- test: add ui render for debug ui integration tests ([#27621](https://github.com/MetaMask/metamask-extension/pull/27621)) -- Merge branch 'Version-v12.6.0' into v12.6.0-merge-master-12.5.1 -- chore: Cherry pick data deletion into v12.6.0 ([#28223](https://github.com/MetaMask/metamask-extension/pull/28223)) -- feat: poll native currency prices across chains ([#28196](https://github.com/MetaMask/metamask-extension/pull/28196)) -- test: Fix data deletion e2e tests ([#28221](https://github.com/MetaMask/metamask-extension/pull/28221)) -- Update LavaMoat policies -- Merge remote-tracking branch 'origin/master' into v12.6.0-merge-master-12.5.1 -- fix (cherry-pick): incorrect standard swap gas fee estimation (#28127) ([#28127](https://github.com/MetaMask/metamask-extension/pull/28127)) -- [cherry pick] Fix bugs related to queued requests ([#28197](https://github.com/MetaMask/metamask-extension/pull/28197)) -- feat (cherry-pick): added test network as selected network if globally selected for… ([#28139](https://github.com/MetaMask/metamask-extension/pull/28139)) -- chore(cherry-pick): update @metamask/bitcoin-wallet-snap to 0.8.2 (#28135) ([#28135](https://github.com/MetaMask/metamask-extension/pull/28135)) -- feat: Bump `QueuedRequestController` from `^2.0.0` to `^7.0.0`  ([#28090](https://github.com/MetaMask/metamask-extension/pull/28090)) -- chore: Add a new transaction event prop ([#28153](https://github.com/MetaMask/metamask-extension/pull/28153)) -- feat: Copy updates to satisfy UK regulation requirements ([#28157](https://github.com/MetaMask/metamask-extension/pull/28157)) -- feat: Token Network Filter UI [Extension] ([#27884](https://github.com/MetaMask/metamask-extension/pull/27884)) -- fix: flaky test `BTC Account - Overview has balance` ([#28181](https://github.com/MetaMask/metamask-extension/pull/28181)) -- feat: Native asset send ([#27979](https://github.com/MetaMask/metamask-extension/pull/27979)) -- chore: poll for bridge quotes ([#28029](https://github.com/MetaMask/metamask-extension/pull/28029)) -- fix: Prevent coercing small spending caps to zero ([#28179](https://github.com/MetaMask/metamask-extension/pull/28179)) -- fix: Fix #28097 - Prevent redirect after adding network in Onboarding Settings ([#28165](https://github.com/MetaMask/metamask-extension/pull/28165)) -- chore (cherry-pick): ignore warning for ethereumjs-wallet (#28145) ([#28145](https://github.com/MetaMask/metamask-extension/pull/28145)) -- feat: add privacy mode ([#28021](https://github.com/MetaMask/metamask-extension/pull/28021)) -- chore: update confirmations code ownership ([#27862](https://github.com/MetaMask/metamask-extension/pull/27862)) -- feat(snaps): Add `useDisplayName` hook ([#27868](https://github.com/MetaMask/metamask-extension/pull/27868)) -- chore: upgrade signature controller to remove global network ([#28063](https://github.com/MetaMask/metamask-extension/pull/28063)) -- feat: enable security alerts api ([#28040](https://github.com/MetaMask/metamask-extension/pull/28040)) -- feat: Add re-simulation logic ([#28104](https://github.com/MetaMask/metamask-extension/pull/28104)) -- chore: update bridge quote request on input change ([#28028](https://github.com/MetaMask/metamask-extension/pull/28028)) -- fix: Updated network message on Review Permission and Connections page ([#28126](https://github.com/MetaMask/metamask-extension/pull/28126)) -- chore: bump asset controllers to 39 + polling API ([#28025](https://github.com/MetaMask/metamask-extension/pull/28025)) -- fix: incorrect standard swap gas fee estimation ([#28127](https://github.com/MetaMask/metamask-extension/pull/28127)) -- feat: Capture 3 existing properties within non-anonymous transaction … ([#28144](https://github.com/MetaMask/metamask-extension/pull/28144)) -- refactor: remove global network usage from transaction simulation ([#27895](https://github.com/MetaMask/metamask-extension/pull/27895)) -- test(ramps): fixes btc native token test ([#27601](https://github.com/MetaMask/metamask-extension/pull/27601)) -- fix: Reduce gas limit fallback from 95% to 35% of the block gas limit on failed gas limit estimations ([#27954](https://github.com/MetaMask/metamask-extension/pull/27954)) -- refactor: clean up profile sync hooks ([#28132](https://github.com/MetaMask/metamask-extension/pull/28132)) -- chore: ignore warning for ethereumjs-wallet ([#28145](https://github.com/MetaMask/metamask-extension/pull/28145)) -- test: [Snaps E2E] Unified methods and clean up snaps e2e tests ([#27684](https://github.com/MetaMask/metamask-extension/pull/27684)) -- feat: added test network as selected network if globally selected for connection Request ([#27980](https://github.com/MetaMask/metamask-extension/pull/27980)) -- chore: update @metamask/bitcoin-wallet-snap to 0.8.2 ([#28135](https://github.com/MetaMask/metamask-extension/pull/28135)) -- chore: small storybook and docs updates to SensitiveText component ([#28089](https://github.com/MetaMask/metamask-extension/pull/28089)) -- feat(NOTIFY-1260): enable account syncing ([#28120](https://github.com/MetaMask/metamask-extension/pull/28120)) -- chore: bridge-api fetchBridgeQuotes util ([#28027](https://github.com/MetaMask/metamask-extension/pull/28027)) -- feat: Cherry-pick: Please view the attached issue ([#28133](https://github.com/MetaMask/metamask-extension/pull/28133)) -- feat: update phishing controller version ([#28131](https://github.com/MetaMask/metamask-extension/pull/28131)) -- fix: broken not existing type file import ([#28055](https://github.com/MetaMask/metamask-extension/pull/28055)) -- test: blockaid update version and reenable specs ([#28121](https://github.com/MetaMask/metamask-extension/pull/28121)) -- fix: Reduce usage of scientific notation ([#27992](https://github.com/MetaMask/metamask-extension/pull/27992)) -- test: [POM] Migrate onboarding infura call privacy e2e tests ([#28079](https://github.com/MetaMask/metamask-extension/pull/28079)) -- feat: share the same user storage mock instance in tests ([#28119](https://github.com/MetaMask/metamask-extension/pull/28119)) -- chore: Using button icon component for clikable icons ([#28082](https://github.com/MetaMask/metamask-extension/pull/28082)) -- feat: convert MetaMetricsController to typescript ([#28072](https://github.com/MetaMask/metamask-extension/pull/28072)) -- feat: improved way to trigger mmi e2e tests ([#27932](https://github.com/MetaMask/metamask-extension/pull/27932)) -- test: allow more simple findElement by data-testid ([#28065](https://github.com/MetaMask/metamask-extension/pull/28065)) -- fix: json-rpc-middleware-stream@^5 -> @metamask/json-rpc-middleware-stream@^8 ([#28060](https://github.com/MetaMask/metamask-extension/pull/28060)) -- fix(devDeps): babel@7.23.2->7.25.9 ([#28068](https://github.com/MetaMask/metamask-extension/pull/28068)) -- feat: better storybook stories for the notification pages ([#27861](https://github.com/MetaMask/metamask-extension/pull/27861)) -- fix: update storybook to support NFT images ([#28105](https://github.com/MetaMask/metamask-extension/pull/28105)) -- fix: Cherry-pick: Fix c2 detection bypass by supporting all network requests types ([#28087](https://github.com/MetaMask/metamask-extension/pull/28087)) -- fix: broken test `Vault Decryptor Page is able to decrypt the vault uploading the log file in the vault-decryptor webapp` ([#28098](https://github.com/MetaMask/metamask-extension/pull/28098)) -- test: Complete missing step for add a contact to the address book in existing E2E test ([#27959](https://github.com/MetaMask/metamask-extension/pull/27959)) -- feat(3419): sensitive text component ([#28056](https://github.com/MetaMask/metamask-extension/pull/28056)) -- test: Added e2e for switch network ([#27967](https://github.com/MetaMask/metamask-extension/pull/27967)) -- fix: cherry-pick: Fall back to token list for the token symbol (#28003) ([#28003](https://github.com/MetaMask/metamask-extension/pull/28003)) -- fix: Cherry-pick Support dynamic native token name on gas component (#28048) ([#28048](https://github.com/MetaMask/metamask-extension/pull/28048)) -- fix: cherry-pick: Gas changes for low Max base fee and Priority fee (#28037) ([#28037](https://github.com/MetaMask/metamask-extension/pull/28037)) -- fix: c2 bypass ([#28057](https://github.com/MetaMask/metamask-extension/pull/28057)) -- feat: design changes in signature paged message section ([#28038](https://github.com/MetaMask/metamask-extension/pull/28038)) -- test: NOTIFY-1256 - Extending E2E tests for Account Sync ([#28067](https://github.com/MetaMask/metamask-extension/pull/28067)) -- feat: NFT token transfer ([#27955](https://github.com/MetaMask/metamask-extension/pull/27955)) -- test: notifications integration tests ([#28022](https://github.com/MetaMask/metamask-extension/pull/28022)) -- fix: disable notifications when basic functionality off ([#28045](https://github.com/MetaMask/metamask-extension/pull/28045)) -- chore: update stories for name component ([#28049](https://github.com/MetaMask/metamask-extension/pull/28049)) -- fix: flaky anti-pattern getText + assert 3 ([#28062](https://github.com/MetaMask/metamask-extension/pull/28062)) -- feat: add support for external links in feature announcements ([#26491](https://github.com/MetaMask/metamask-extension/pull/26491)) -- test: [POM] Create onboarding related page object modal base pages and migrate e2e tests ([#28036](https://github.com/MetaMask/metamask-extension/pull/28036)) -- docs: update debugging sentry step 3 ([#28034](https://github.com/MetaMask/metamask-extension/pull/28034)) -- fix: Support dynamic native token name on gas component ([#28048](https://github.com/MetaMask/metamask-extension/pull/28048)) -- fix: Fall back to token list for the token symbol ([#28003](https://github.com/MetaMask/metamask-extension/pull/28003)) -- fix: flaky anti-pattern getText + assert 2 ([#28043](https://github.com/MetaMask/metamask-extension/pull/28043)) -- feat: :sparkles: show a notification item in the settings page ([#26843](https://github.com/MetaMask/metamask-extension/pull/26843)) -- fix: Fix limited visibility of decrypt message ([#27622](https://github.com/MetaMask/metamask-extension/pull/27622)) -- fix(deps): @metamask/eth-json-rpc-filters@^8.0.0->^9.0.0 ([#27956](https://github.com/MetaMask/metamask-extension/pull/27956)) -- chore: Bump gridplus-sdk to 2.7.1 ([#28008](https://github.com/MetaMask/metamask-extension/pull/28008)) -- fix: adjust spacing of quote rate in swaps ([#28051](https://github.com/MetaMask/metamask-extension/pull/28051)) -- feat: new phishing warning UI with metrics ([#27942](https://github.com/MetaMask/metamask-extension/pull/27942)) -- fix(deps): @keystonehq/metamask-airgapped-keyring@^0.13.1->^0.14.1 ([#27952](https://github.com/MetaMask/metamask-extension/pull/27952)) -- fix: Gas changes for low Max base fee and Priority fee ([#28037](https://github.com/MetaMask/metamask-extension/pull/28037)) -- fix: adjust spacing of quote rate in swaps ([#28016](https://github.com/MetaMask/metamask-extension/pull/28016)) -- feat: enable preview token ([#27809](https://github.com/MetaMask/metamask-extension/pull/27809)) -- refactor: use `reselect`'s `createSelector` instead of going through `@redux/toolkit`, as the import names collide when trying to merge files. ([#27643](https://github.com/MetaMask/metamask-extension/pull/27643)) -- fix: storybook `getManifest` issue ([#28010](https://github.com/MetaMask/metamask-extension/pull/28010)) -- feat: bump @metamask/notification-services-controller from 0.7.0 to 0.11.0 ([#28017](https://github.com/MetaMask/metamask-extension/pull/28017)) -- refactor: remove global network usage from petnames ([#27946](https://github.com/MetaMask/metamask-extension/pull/27946)) -- chore: updated package ([#28002](https://github.com/MetaMask/metamask-extension/pull/28002)) -- feat(NOTIFY-1245): add account syncing E2E helpers & basic tests ([#28005](https://github.com/MetaMask/metamask-extension/pull/28005)) -- fix: Fix stream re-initialization ([#28024](https://github.com/MetaMask/metamask-extension/pull/28024)) -- refactor: routes.component.js and creation of ToastMaster ([#27735](https://github.com/MetaMask/metamask-extension/pull/27735)) -- fix: @metamask/eth-json-rpc-filters@^7.0.0->^8.0.0 ([#27917](https://github.com/MetaMask/metamask-extension/pull/27917)) -- refactor: remove relative imports to `selectors/index.js` from other selectors files ([#27642](https://github.com/MetaMask/metamask-extension/pull/27642)) -- refactor: remove circular dependency between `ui/ducks/custom-gas.js` and `ui/selectors/index.js` ([#27640](https://github.com/MetaMask/metamask-extension/pull/27640)) -- fix: Allow users to remove linea from networks list ([#27512](https://github.com/MetaMask/metamask-extension/pull/27512)) -- fix: prevent scrolling to account list item on send page ([#27934](https://github.com/MetaMask/metamask-extension/pull/27934)) -- feat: add ape token mainnet ([#27974](https://github.com/MetaMask/metamask-extension/pull/27974)) -- feat: removed feature flag for confirmations screen ([#27877](https://github.com/MetaMask/metamask-extension/pull/27877)) -- test: update notification date tests to be timezone agnostic ([#27925](https://github.com/MetaMask/metamask-extension/pull/27925)) -- fix: updated event name for site cell component ([#27981](https://github.com/MetaMask/metamask-extension/pull/27981)) -- fix(snaps): Adjust alignment of custom UI links ([#27957](https://github.com/MetaMask/metamask-extension/pull/27957)) -- fix(deps): gridplus-sdk@2.5.1->~2.6.0 ([#27973](https://github.com/MetaMask/metamask-extension/pull/27973)) -- Version v12.6.0 -- ci: reduced Sentry frequency on CircleCI develop ([#27912](https://github.com/MetaMask/metamask-extension/pull/27912)) -- chore:Master sync ([#27935](https://github.com/MetaMask/metamask-extension/pull/27935)) -- Merge origin/develop into master-sync -- test: Completing missing step for import ERC1155 token origin dapp in existing E2E test ([#27680](https://github.com/MetaMask/metamask-extension/pull/27680)) -- feat(metametrics): use specific `account_hardware_type` for OneKey devices ([#27296](https://github.com/MetaMask/metamask-extension/pull/27296)) -- feat: add migration 131 ([#27364](https://github.com/MetaMask/metamask-extension/pull/27364)) -- chore: Disable account syncing in prod ([#27943](https://github.com/MetaMask/metamask-extension/pull/27943)) -- test: Remove delays from onboarding tests ([#27961](https://github.com/MetaMask/metamask-extension/pull/27961)) -- perf: Create custom trace to measure performance of opening the account list ([#27907](https://github.com/MetaMask/metamask-extension/pull/27907)) -- fix: flaky test `Confirmation Redesign ERC721 Approve Component Submit an Approve transaction @no-mmi Sends a type 2 transaction (EIP1559)` ([#27928](https://github.com/MetaMask/metamask-extension/pull/27928)) -- fix: lint-lockfile flaky job by changing resources from medium to medium-plus ([#27950](https://github.com/MetaMask/metamask-extension/pull/27950)) -- feat: add “Incomplete Asset Displayed” metric & fix: should only set default decimals if ERC20 ([#27494](https://github.com/MetaMask/metamask-extension/pull/27494)) -- feat: Convert AppStateController to typescript ([#27572](https://github.com/MetaMask/metamask-extension/pull/27572)) -- chore(deps): upgrade from json-rpc-engine to @metamask/json-rpc-engine ([#22875](https://github.com/MetaMask/metamask-extension/pull/22875)) -- chore: bump signature controller to remove message managers ([#27787](https://github.com/MetaMask/metamask-extension/pull/27787)) -- chore: add testing-library/dom dependency ([#27493](https://github.com/MetaMask/metamask-extension/pull/27493)) -- test: [POM] Migrate contract interaction with snap account e2e tests to page object modal ([#27924](https://github.com/MetaMask/metamask-extension/pull/27924)) -- fix: bump message signing snap to support portfolio automatic connections ([#27936](https://github.com/MetaMask/metamask-extension/pull/27936)) -- fix: bump `@metamask/ppom-validator` from `0.34.0` to `0.35.1` ([#27939](https://github.com/MetaMask/metamask-extension/pull/27939)) -- feat: convert AlertController to typescript ([#27764](https://github.com/MetaMask/metamask-extension/pull/27764)) -- fix: flaky test `Vault Decryptor Page is able to decrypt the vault pasting the text in the vault-decryptor webapp` ([#27921](https://github.com/MetaMask/metamask-extension/pull/27921)) -- fix: flaky tests `Add existing token using search renders the balance for the chosen token` ([#27853](https://github.com/MetaMask/metamask-extension/pull/27853)) -- feat(logging): add extension request logging and retrieval ([#27655](https://github.com/MetaMask/metamask-extension/pull/27655)) -- test: Update test-dapp to verison 8.7.0 ([#27816](https://github.com/MetaMask/metamask-extension/pull/27816)) -- fix: fall back to bundled chainlist ([#23392](https://github.com/MetaMask/metamask-extension/pull/23392)) -- fix: SonarCloud for forks ([#27700](https://github.com/MetaMask/metamask-extension/pull/27700)) -- fix(deps): update from eth-rpc-errors to @metamask/rpc-errors (cause edition) ([#24496](https://github.com/MetaMask/metamask-extension/pull/24496)) -- fix: swapQuotesError as a property in the reported metric ([#27712](https://github.com/MetaMask/metamask-extension/pull/27712)) -- chore: Bump Snaps packages ([#27376](https://github.com/MetaMask/metamask-extension/pull/27376)) -- chore: update @metamask/bitcoin-wallet-snap to 0.7.0 ([#27730](https://github.com/MetaMask/metamask-extension/pull/27730)) -- fix: Onboarding: Code style nits ([#27767](https://github.com/MetaMask/metamask-extension/pull/27767)) -- feat: use asset pickers with network dropdown in cross-chain swaps page ([#27522](https://github.com/MetaMask/metamask-extension/pull/27522)) -- test: set ENABLE_MV3 automatically ([#27748](https://github.com/MetaMask/metamask-extension/pull/27748)) -- fix: Contract Interaction - cannot read the property `text_signature` ([#27686](https://github.com/MetaMask/metamask-extension/pull/27686)) -- feat: Use requested permissions as default selected values for AmonHenV2 connection flow with case insensitive address comparison ([#27517](https://github.com/MetaMask/metamask-extension/pull/27517)) -- test: [POM] Migrate signature with snap account e2e tests to page object modal ([#27829](https://github.com/MetaMask/metamask-extension/pull/27829)) -- fix: flaky test `ERC1155 NFTs testdapp interaction should batch transfers ERC1155 token` ([#27897](https://github.com/MetaMask/metamask-extension/pull/27897)) -- chore: Master sync following v12.4.1 ([#27793](https://github.com/MetaMask/metamask-extension/pull/27793)) -- fix: flaky test `Permissions sets permissions and connect to Dapp` ([#27888](https://github.com/MetaMask/metamask-extension/pull/27888)) -- fix: flaky test `ERC721 NFTs testdapp interaction should prompt users to add their NFTs to their wallet (all at once)` ([#27889](https://github.com/MetaMask/metamask-extension/pull/27889)) -- fix: flaky test `Wallet Revoke Permissions should revoke eth_accounts permissions via test dapp` ([#27894](https://github.com/MetaMask/metamask-extension/pull/27894)) -- fix: flaky test `Snap Account Signatures and Disconnects can connect to the Test Dapp, then #signTypedDataV3, disconnect then connect, then #signTypedDataV4 (async flow approve)` ([#27887](https://github.com/MetaMask/metamask-extension/pull/27887)) -- test(mock-e2e): add private domains logic for the privacy report ([#27844](https://github.com/MetaMask/metamask-extension/pull/27844)) -- fix: SENTRY_DSN_FAKE problem ([#27881](https://github.com/MetaMask/metamask-extension/pull/27881)) -- chore: remove unused swaps code ([#27679](https://github.com/MetaMask/metamask-extension/pull/27679)) -- test(TXL-308): initial e2e for stx using swaps ([#27215](https://github.com/MetaMask/metamask-extension/pull/27215)) -- feat: upgrade assets-controllers to v38.3.0 ([#27755](https://github.com/MetaMask/metamask-extension/pull/27755)) -- fix: phishing test to not check c2 domains ([#27846](https://github.com/MetaMask/metamask-extension/pull/27846)) -- feat: use messenger in AccountTracker to get Preferences state ([#27711](https://github.com/MetaMask/metamask-extension/pull/27711)) -- fix: "Update Network: should update added rpc url for exis..." flaky tests ([#27437](https://github.com/MetaMask/metamask-extension/pull/27437)) -- fix: flaky test `Add account should not affect public address when using secret recovery phrase to recover account with non-zero balance @no-mmi` ([#27834](https://github.com/MetaMask/metamask-extension/pull/27834)) -- fix: flaky test `MultiRpc: should select rpc from settings @no-mmi` ([#27858](https://github.com/MetaMask/metamask-extension/pull/27858)) -- perf: include custom traces in benchmark results ([#27701](https://github.com/MetaMask/metamask-extension/pull/27701)) -- chore: Add react-beautiful-dnd to deprecated packages list ([#27856](https://github.com/MetaMask/metamask-extension/pull/27856)) -- feat: Create a quality gate for typescript coverage ([#27717](https://github.com/MetaMask/metamask-extension/pull/27717)) -- feat: preferences controller to base controller v2 ([#27398](https://github.com/MetaMask/metamask-extension/pull/27398)) -- revert: use networkClientId to resolve chainId in PPOM Middleware ([#27570](https://github.com/MetaMask/metamask-extension/pull/27570)) -- feat: Added metrics for edit networks and accounts ([#27820](https://github.com/MetaMask/metamask-extension/pull/27820)) -- ci: Revert minimum E2E timeout to 20 minutes ([#27827](https://github.com/MetaMask/metamask-extension/pull/27827)) -- fix: disable balance checker for Sepolia in account tracker ([#27763](https://github.com/MetaMask/metamask-extension/pull/27763)) -- ci: Improve validation for `sentry:publish` script ([#26580](https://github.com/MetaMask/metamask-extension/pull/26580)) -- test: Fix Vault Decryptor Page e2e test on develop branch ([#27794](https://github.com/MetaMask/metamask-extension/pull/27794)) -- chore: remove old token details page ([#27774](https://github.com/MetaMask/metamask-extension/pull/27774)) -- chore: remove token list display component ([#27772](https://github.com/MetaMask/metamask-extension/pull/27772)) -- test: [POM] Migrate transaction with snap account e2e tests to page object modal ([#27760](https://github.com/MetaMask/metamask-extension/pull/27760)) -- Merge origin/develop into master-sync -- test: Onboarding: Fix vault-decryption-chrome.spec.js ([#27779](https://github.com/MetaMask/metamask-extension/pull/27779)) -- docs: remove outdated Medium link, update "Twitter" to "X" ([#26692](https://github.com/MetaMask/metamask-extension/pull/26692)) -- fix(btc): fix jazzicons generations ([#27662](https://github.com/MetaMask/metamask-extension/pull/27662)) -- feat: upgrade assets-controllers to v38.2.0 ([#27629](https://github.com/MetaMask/metamask-extension/pull/27629)) -- ci: followup to CircleCI Sentry reporting ([#27548](https://github.com/MetaMask/metamask-extension/pull/27548)) -- chore: Master sync ([#27729](https://github.com/MetaMask/metamask-extension/pull/27729)) -- fix(multichain): fix getMultichainCurrentCurrency selector ([#27726](https://github.com/MetaMask/metamask-extension/pull/27726)) -- Merge origin/develop into master-sync -- test: [POM] Migrate create snap account e2e tests to page object modal ([#27697](https://github.com/MetaMask/metamask-extension/pull/27697)) -- fix(btc): fetch btc balance right after account creation ([#27628](https://github.com/MetaMask/metamask-extension/pull/27628)) -- fix: UI startup with no Sentry DSN ([#27714](https://github.com/MetaMask/metamask-extension/pull/27714)) -- ci: make git-diff-develop work for PRs from foreign repos ([#27268](https://github.com/MetaMask/metamask-extension/pull/27268)) -- test: Convert json-rpc e2e tests to TypeScript ([#27659](https://github.com/MetaMask/metamask-extension/pull/27659)) -- perf: add tags to UI startup trace ([#27550](https://github.com/MetaMask/metamask-extension/pull/27550)) -- fix: Disable redirecting Extension users using beta & flask build and dev env to the existing offboarding page ([#27226](https://github.com/MetaMask/metamask-extension/pull/27226)) -- feat(NOTIFY-1193): add profile sync dev menu ([#27666](https://github.com/MetaMask/metamask-extension/pull/27666)) -- refactor: Typescript conversion of log-web3-shim-usage.js ([#23732](https://github.com/MetaMask/metamask-extension/pull/23732)) -- test: removing race condition for asserting inner values (PR-#2) ([#27664](https://github.com/MetaMask/metamask-extension/pull/27664)) -- fix(btc): fix address validation ([#27690](https://github.com/MetaMask/metamask-extension/pull/27690)) -- chore: Update coverage.json ([#27696](https://github.com/MetaMask/metamask-extension/pull/27696)) -- fix: test coverage quality gate ([#27691](https://github.com/MetaMask/metamask-extension/pull/27691)) -- refactor: routes constants ([#27078](https://github.com/MetaMask/metamask-extension/pull/27078)) -- fix: Test coverage quality gate ([#27581](https://github.com/MetaMask/metamask-extension/pull/27581)) -- build: add lottie-web dependency to extension ([#27632](https://github.com/MetaMask/metamask-extension/pull/27632)) -- fix(btc): do not show percentage for tokens ([#27637](https://github.com/MetaMask/metamask-extension/pull/27637)) -- test: removing race condition for asserting inner values (PR-#1) ([#27606](https://github.com/MetaMask/metamask-extension/pull/27606)) -- test: [POM] Migrate Snap Simple Keyring page and Snap List page to page object modal ([#27327](https://github.com/MetaMask/metamask-extension/pull/27327)) -- fix: fix sentry reading undefined ([#27584](https://github.com/MetaMask/metamask-extension/pull/27584)) -- fix: fix sentry reading null ([#27582](https://github.com/MetaMask/metamask-extension/pull/27582)) -- fix(btc): disable balanceIsCached flag ([#27636](https://github.com/MetaMask/metamask-extension/pull/27636)) -- chore: update accounts related packages ([#27284](https://github.com/MetaMask/metamask-extension/pull/27284)) -- chore: set bridge src network, tokens and top assets ([#26214](https://github.com/MetaMask/metamask-extension/pull/26214)) -- test: [Snaps E2E] add delay to installed snaps test to reduce flaking ([#27521](https://github.com/MetaMask/metamask-extension/pull/27521)) -- chore: set bridge dest network, tokens and top assets ([#26213](https://github.com/MetaMask/metamask-extension/pull/26213)) -- feat: Migrate AccountTrackerController to BaseController v2 ([#27258](https://github.com/MetaMask/metamask-extension/pull/27258)) -- fix: disable transaction data decode if deployment ([#27586](https://github.com/MetaMask/metamask-extension/pull/27586)) -- fix: revert jest collect coverage patterns ([#27583](https://github.com/MetaMask/metamask-extension/pull/27583)) -- fix: add amount row for contract deployment ([#27594](https://github.com/MetaMask/metamask-extension/pull/27594)) -- fix: "Dapp viewed Event @no-mmi is sent when refreshing da..." flaky test ([#27381](https://github.com/MetaMask/metamask-extension/pull/27381)) -- chore: fix deps audit ([#27620](https://github.com/MetaMask/metamask-extension/pull/27620)) -- fix: Recreate offscreen document if it already exists ([#27596](https://github.com/MetaMask/metamask-extension/pull/27596)) -- fix: flaky test `Block Explorer links to the token tracker in the explorer` ([#27599](https://github.com/MetaMask/metamask-extension/pull/27599)) -- fix: flaky test `Import flow allows importing multiple tokens from search` ([#27567](https://github.com/MetaMask/metamask-extension/pull/27567)) -- fix: flaky test `Address Book Edit entry in address book` due to race condition with mmi menu ([#27557](https://github.com/MetaMask/metamask-extension/pull/27557)) -- refactor: Typescript conversion of get-provider-state.js ([#23635](https://github.com/MetaMask/metamask-extension/pull/23635)) -- chore: Use "gas_included" event prop ([#27559](https://github.com/MetaMask/metamask-extension/pull/27559)) -- fix: mock locale in unit test ([#27574](https://github.com/MetaMask/metamask-extension/pull/27574)) -- feat: codefence Account Watcher for flask ([#27543](https://github.com/MetaMask/metamask-extension/pull/27543)) -- chore: start upgrade to React Router v6 ([#27185](https://github.com/MetaMask/metamask-extension/pull/27185)) -- fix: AmonHenV2 connection flow incremental permitted chain approval and account address case comparison ([#27518](https://github.com/MetaMask/metamask-extension/pull/27518)) -- fix: flaky test `Backup and Restore should backup the account settings` ([#27565](https://github.com/MetaMask/metamask-extension/pull/27565)) -- feat: Add redesign integration tests ([#27259](https://github.com/MetaMask/metamask-extension/pull/27259)) -- fix: flaky test `4byte setting does not try to get contract method name from 4byte when the setting is off` ([#27560](https://github.com/MetaMask/metamask-extension/pull/27560)) -- feat: add merge queue ([#26871](https://github.com/MetaMask/metamask-extension/pull/26871)) -- feat: remove squiggle animation from swaps smart transactions ([#27264](https://github.com/MetaMask/metamask-extension/pull/27264)) -- fix(snaps): Fix custom UI buttons submitting forms ([#27531](https://github.com/MetaMask/metamask-extension/pull/27531)) -- chore: Master sync following v12.3.1 ([#27538](https://github.com/MetaMask/metamask-extension/pull/27538)) -- Merge origin/develop into master-sync -- fix(NOTIFY-1171): account syncing performance and bug fixes ([#27529](https://github.com/MetaMask/metamask-extension/pull/27529)) -- fix: genUnapprovedApproveConfirmation import path ([#27530](https://github.com/MetaMask/metamask-extension/pull/27530)) -- feat: convert account tracker to typescript ([#27231](https://github.com/MetaMask/metamask-extension/pull/27231)) -- fix: Fix snaps permission connection for `CHAIN_PERMISSIONS` feature flag ([#27459](https://github.com/MetaMask/metamask-extension/pull/27459)) -- fix: flaky test `Navigation Signature - Different signature types initiates multiple signatures and rejects all` ([#27481](https://github.com/MetaMask/metamask-extension/pull/27481)) -- feat: Double Sentry performance trace sample rate ([#27468](https://github.com/MetaMask/metamask-extension/pull/27468)) -- ci: Expand github bot policy update comment to be more actionable ([#27242](https://github.com/MetaMask/metamask-extension/pull/27242)) -- chore: Add `useLedgerConnection` unit tests ([#27358](https://github.com/MetaMask/metamask-extension/pull/27358)) -- ci: Sentry reporting only on develop branch, with Git message overrides ([#27412](https://github.com/MetaMask/metamask-extension/pull/27412)) -- test: Fix flaky permit test ([#27450](https://github.com/MetaMask/metamask-extension/pull/27450)) -- fix: removed closeMenu for ConnectedAccountsMenu ([#27460](https://github.com/MetaMask/metamask-extension/pull/27460)) -- chore: set bridge selected tokens and amount ([#26212](https://github.com/MetaMask/metamask-extension/pull/26212)) -- fix: flaky test `Add account should not affect public address when using secret recovery phrase to recover account with non-zero balance @no-mmi`aded ([#27420](https://github.com/MetaMask/metamask-extension/pull/27420)) -- fix: flaky test `Responsive UI Send Transaction from responsive window` ([#27417](https://github.com/MetaMask/metamask-extension/pull/27417)) -- fix: flaky test `Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed` ([#27352](https://github.com/MetaMask/metamask-extension/pull/27352)) -- fix: "Warning: Invalid argument supplied to oneOfType" ([#27267](https://github.com/MetaMask/metamask-extension/pull/27267)) -- chore: bump profile-sync-controller to 0.9.3 ([#27415](https://github.com/MetaMask/metamask-extension/pull/27415)) -- fix: Remove duplication ([#27421](https://github.com/MetaMask/metamask-extension/pull/27421)) -- fix: Confirm Page test failing in CI/CD ([#27423](https://github.com/MetaMask/metamask-extension/pull/27423)) -- feat: Add performance metrics for signature requests ([#26967](https://github.com/MetaMask/metamask-extension/pull/26967)) -- fix(NOTIFY-1166): rename account sync event names ([#27413](https://github.com/MetaMask/metamask-extension/pull/27413)) +### Added +- Added Token Network Filter UI, allowing users to filter tokens by network (behind a feature flag) ([#27884](https://github.com/MetaMask/metamask-extension/pull/27884)) +- Added Ape token icon for mainnet ([#27974](https://github.com/MetaMask/metamask-extension/pull/27974)) +- Implemented redesigned native asset transfer for both wallet-initiated and dApp-initiated confirmations ([#27979](https://github.com/MetaMask/metamask-extension/pull/27979)) +- Enabled the Security Alerts API with a fallback mechanism to ensure user experience is not disrupted ([#28040](https://github.com/MetaMask/metamask-extension/pull/28040)) +- Added re-simulation logic to the transaction controller ([#28104](https://github.com/MetaMask/metamask-extension/pull/28104)) +- Made the message section in the signature page collapsible and added a copy option ([#28038](https://github.com/MetaMask/metamask-extension/pull/28038)) +- Added token transfer confirmation for ERC721 and ERC1155 tokens ([#27955](https://github.com/MetaMask/metamask-extension/pull/27955)) +- Added support for external links in feature announcements ([#26491](https://github.com/MetaMask/metamask-extension/pull/26491)) +- Added a Notifications option to the settings page ([#26843](https://github.com/MetaMask/metamask-extension/pull/26843)) +- Enabled the use of a preview token to view unpublished content from Contentful ([#27809](https://github.com/MetaMask/metamask-extension/pull/27809)) +- Added account syncing to MetaMask, allowing users to synchronize accounts and account names across devices ([#28120](https://github.com/MetaMask/metamask-extension/pull/28120)) +- Introduced a new phishing warning UI with improved design ([#27942](https://github.com/MetaMask/metamask-extension/pull/27942)) +- Added a privacy mode toggle to hide and show sensitive information and token balances ([#28021](https://github.com/MetaMask/metamask-extension/pull/28021)) +- Added test network to the default selected networks list if it is the globally selected network during a connection request ([#27980](https://github.com/MetaMask/metamask-extension/pull/27980)) + +### Changed +- Allowed users to remove Linea from the networks list and added it to the Popular Networks section ([#27512](https://github.com/MetaMask/metamask-extension/pull/27512)) +- Updated transaction controller to reduce gas limit fallback and remove global network usage from transaction simulation ([#27954](https://github.com/MetaMask/metamask-extension/pull/27954)) +- Reduced usage of scientific notation by implementing a decimals rounding strategy and added tooltips for full values ([#27992](https://github.com/MetaMask/metamask-extension/pull/27992)) +- Improved visibility of decrypted messages and added a "scroll to bottom" button ([#27622](https://github.com/MetaMask/metamask-extension/pull/27622)) +- Updated network message to show the full network name on the Review Permission and Connections pages ([#28126](https://github.com/MetaMask/metamask-extension/pull/28126)) +- Removed the feature flag for the confirmations screen ([#27877](https://github.com/MetaMask/metamask-extension/pull/27877)) + +### Fixed +- Fixed issue where token balance showed as 0 during send flow when navigating from the token details page ([#28136](https://github.com/MetaMask/metamask-extension/pull/28136)) +- Fixed issue where small spending caps were coerced to zero on the approve screen ([#28179](https://github.com/MetaMask/metamask-extension/pull/28179)) +- Fixed gas calculations for low Max base fee and Priority fee ([#28037](https://github.com/MetaMask/metamask-extension/pull/28037)) +- Disabled notifications when Basic functionality is turned off ([#28045]) +- Fixed alignment issues of custom UI links in Snaps ([#27957](https://github.com/MetaMask/metamask-extension/pull/27957)) +- Fixed misalignment of the quote rate in swaps ([#28016](https://github.com/MetaMask/metamask-extension/pull/28016)) +- Prevented scrolling to the account list item on the send page to keep the relevant UI in view ([#27934](https://github.com/MetaMask/metamask-extension/pull/27934)) +- Improved handling of network switching and adding networks to prevent issues with queued transactions ([#28090](https://github.com/MetaMask/metamask-extension/pull/28090)) +- Prevented redirect after adding a network in Onboarding Settings ([#28165](https://github.com/MetaMask/metamask-extension/pull/28165)) ## [12.6.1] ### Fixed From 5990644e199e66f0d1023ea2d967015d41b43c33 Mon Sep 17 00:00:00 2001 From: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:34:08 +0100 Subject: [PATCH 22/32] test: [POM] Migrate vault decryption e2e tests to TS and Page Object Model (#28419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - Migrate e2e tests `test/e2e/vault-decryption-chrome.spec.js` to TS and Page Object Model, this test runs only on develop branch, by migrating to POM, we improve the maintainability. - Create page classe functions for vault decryption page, so we can use that in the future when needed - Deprecate/remove old functions in `helper.js` - Improved testing logs to make it easier to debug. See testing logs on this branch: ![Screenshot 2024-11-13 at 10 11 41](https://github.com/user-attachments/assets/a592b127-2fc2-4f33-a569-7e56f72a05f1) [![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/28431 ## **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. --------- Co-authored-by: seaona <54408225+seaona@users.noreply.github.com> --- test/e2e/helpers.js | 157 --------- .../e2e/page-objects/flows/onboarding.flow.ts | 73 ++++- .../pages/settings/privacy-settings.ts | 12 +- .../pages/vault-decryptor-page.ts | 96 ++++++ .../onboarding-with-opt-out.spec.ts | 6 +- .../onboarding-infura-call-privacy.spec.ts | 2 +- .../settings-security-reveal-srp.spec.ts | 4 +- test/e2e/vault-decryption-chrome.spec.js | 293 ----------------- test/e2e/vault-decryption-chrome.spec.ts | 298 ++++++++++++++++++ 9 files changed, 475 insertions(+), 466 deletions(-) create mode 100644 test/e2e/page-objects/pages/vault-decryptor-page.ts delete mode 100644 test/e2e/vault-decryption-chrome.spec.js create mode 100644 test/e2e/vault-decryption-chrome.spec.ts diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 4bec0737472f..a40edadf7c69 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -486,143 +486,6 @@ const onboardingPinExtension = async (driver) => { await driver.clickElement('[data-testid="pin-extension-done"]'); }; -/** - * @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`. - * Completes the onboarding flow with optional opt-out settings for wallet creation. - * - * This function navigates through the onboarding process, allowing for opt-out of certain features. - * It waits for the appropriate heading to appear, then proceeds to opt-out of third-party API - * integration for general and assets sections if specified in the optOutOptions. - * @param {WebDriver} driver - The Selenium WebDriver instance. - * @param {object} optOutOptions - Optional. An object specifying which features to opt-out of. - * @param {boolean} optOutOptions.basicFunctionality - Optional. Defaults to true. Opt-out of basic functionality. - * @param {boolean} optOutOptions.profileSync - Optional. Defaults to true. Opt-out of profile sync. - * @param {boolean} optOutOptions.assets - Optional. Defaults to true. Opt-out of assets options. - * @param {boolean} optOutOptions.isNewWallet - Optional. Defaults to true. Indicates if this is a new wallet creation. - */ -const onboardingCompleteWalletCreationWithOptOut = async ( - driver, - optOutOptions = {}, -) => { - const defaultOptOutOptions = { - basicFunctionality: true, - profileSync: true, - assets: true, - isNewWallet: true, - }; - - const optOutOptionsToUse = { ...defaultOptOutOptions, ...optOutOptions }; - - // wait for h2 to appear - await driver.findElement({ - text: optOutOptionsToUse.isNewWallet - ? 'Congratulations' - : 'Your wallet is ready', - tag: 'h2', - }); - - // opt-out from third party API on general section - await driver.clickElementAndWaitToDisappear({ - text: 'Manage default privacy settings', - tag: 'button', - }); - await driver.clickElement({ text: 'General', tag: 'p' }); - - if (optOutOptionsToUse.basicFunctionality) { - await driver.clickElement( - '[data-testid="basic-functionality-toggle"] .toggle-button', - ); - await driver.clickElement('[id="basic-configuration-checkbox"]'); - await driver.clickElementAndWaitToDisappear({ - tag: 'button', - text: 'Turn off', - }); - } - - if ( - optOutOptionsToUse.profileSync && - !optOutOptionsToUse.basicFunctionality - ) { - await driver.clickElement( - '[data-testid="profile-sync-toggle"] .toggle-button', - ); - await driver.clickElementAndWaitToDisappear({ - tag: 'button', - text: 'Turn off', - }); - } - - await driver.clickElement('[data-testid="category-back-button"]'); - - if (optOutOptionsToUse.assets) { - // opt-out from third party API on assets section - await driver.clickElement({ text: 'Assets', tag: 'p' }); - await Promise.all( - ( - await driver.findClickableElements( - '.toggle-button.toggle-button--on:not([data-testid="basic-functionality-toggle"] .toggle-button)', - ) - ).map((toggle) => toggle.click()), - ); - - await driver.clickElement('[data-testid="category-back-button"]'); - } - - // Wait until the onboarding carousel has stopped moving - // otherwise the click has no effect. - await driver.waitForElementToStopMoving( - '[data-testid="privacy-settings-back-button"]', - ); - await driver.clickElement('[data-testid="privacy-settings-back-button"]'); - - // complete onboarding - await driver.clickElementAndWaitToDisappear({ - tag: 'button', - text: 'Done', - }); - await onboardingPinExtension(driver); -}; - -/** - * @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`. - * Completes the onboarding flow for creating a new wallet with opt-out options. - * - * This function guides the user through the onboarding process of creating a new wallet, - * including opting out of certain features as specified by the `optOutOptions` parameter. - * @param {object} driver - The Selenium driver instance. - * @param {string} password - The password to use for the new wallet. - * @param {object} optOutOptions - An object specifying the features to opt out of. - * @param {boolean} optOutOptions.isNewWallet - Indicates if this is a new wallet creation. - * @param {boolean} optOutOptions.basicFunctionality - Indicates if basic functionality should be opted out. - * @param {boolean} optOutOptions.profileSync - Indicates if profile sync should be opted out. - * @param {boolean} optOutOptions.assets - Indicates if assets should be opted out. - */ -const completeCreateNewWalletOnboardingFlowWithOptOut = async ( - driver, - password, - optOutOptions, -) => { - await onboardingBeginCreateNewWallet(driver); - await onboardingChooseMetametricsOption(driver, false); - await onboardingCreatePassword(driver, password); - await onboardingRevealAndConfirmSRP(driver); - await onboardingCompleteWalletCreationWithOptOut(driver, optOutOptions); -}; - -/** - * @deprecated Please use page object functions in `onboarding.flow.ts` and in `pages/onboarding/*`. - * @param driver - * @param password - */ -const completeCreateNewWalletOnboardingFlow = async (driver, password) => { - await onboardingBeginCreateNewWallet(driver); - await onboardingChooseMetametricsOption(driver, false); - await onboardingCreatePassword(driver, password); - await onboardingRevealAndConfirmSRP(driver); - await onboardingCompleteWalletCreation(driver); - await onboardingPinExtension(driver); -}; - const openSRPRevealQuiz = async (driver) => { // navigate settings to reveal SRP await driver.clickElement('[data-testid="account-options-menu-button"]'); @@ -864,22 +727,6 @@ const sendTransaction = async ( } }; -const findAnotherAccountFromAccountList = async ( - driver, - itemNumber, - accountName, -) => { - await driver.clickElement('[data-testid="account-menu-icon"]'); - const accountMenuItemSelector = `.multichain-account-list-item:nth-child(${itemNumber})`; - - await driver.findElement({ - css: `${accountMenuItemSelector} .multichain-account-list-item__account-name__button`, - text: accountName, - }); - - return accountMenuItemSelector; -}; - const TEST_SEED_PHRASE = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'; @@ -1224,8 +1071,6 @@ module.exports = { largeDelayMs, veryLargeDelayMs, withFixtures, - completeCreateNewWalletOnboardingFlow, - completeCreateNewWalletOnboardingFlowWithOptOut, openSRPRevealQuiz, completeSRPRevealQuiz, tapAndHoldToRevealSRP, @@ -1241,7 +1086,6 @@ module.exports = { multipleGanacheOptionsForType2Transactions, sendTransaction, sendScreenToConfirmScreen, - findAnotherAccountFromAccountList, unlockWallet, logInWithBalanceValidation, locateAccountBalanceDOM, @@ -1260,7 +1104,6 @@ module.exports = { onboardingCreatePassword, onboardingRevealAndConfirmSRP, onboardingCompleteWalletCreation, - onboardingCompleteWalletCreationWithOptOut, onboardingPinExtension, assertInAnyOrder, genRandInitBal, diff --git a/test/e2e/page-objects/flows/onboarding.flow.ts b/test/e2e/page-objects/flows/onboarding.flow.ts index e235048b0a01..4af974bff9af 100644 --- a/test/e2e/page-objects/flows/onboarding.flow.ts +++ b/test/e2e/page-objects/flows/onboarding.flow.ts @@ -5,21 +5,31 @@ import OnboardingSrpPage from '../pages/onboarding/onboarding-srp-page'; import StartOnboardingPage from '../pages/onboarding/start-onboarding-page'; import SecureWalletPage from '../pages/onboarding/secure-wallet-page'; import OnboardingCompletePage from '../pages/onboarding/onboarding-complete-page'; +import OnboardingPrivacySettingsPage from '../pages/onboarding/onboarding-privacy-settings-page'; import { WALLET_PASSWORD } from '../../helpers'; import { E2E_SRP } from '../../default-fixture'; /** * Create new wallet onboarding flow * - * @param driver - The WebDriver instance. - * @param password - The password to create. Defaults to WALLET_PASSWORD. + * @param options - The options object. + * @param options.driver - The WebDriver instance. + * @param options.password - The password to create. Defaults to WALLET_PASSWORD. + * @param options.needNavigateToNewPage - Whether to navigate to a new page to start the onboarding flow. Defaults to true. */ -export const createNewWalletOnboardingFlow = async ( - driver: Driver, - password: string = WALLET_PASSWORD, -) => { +export const createNewWalletOnboardingFlow = async ({ + driver, + password = WALLET_PASSWORD, + needNavigateToNewPage = true, +}: { + driver: Driver; + password?: string; + needNavigateToNewPage?: boolean; +}): Promise => { console.log('Starting the creation of a new wallet onboarding flow'); - await driver.navigate(); + if (needNavigateToNewPage) { + await driver.navigate(); + } const startOnboardingPage = new StartOnboardingPage(driver); await startOnboardingPage.check_pageIsLoaded(); await startOnboardingPage.checkTermsCheckbox(); @@ -95,7 +105,7 @@ export const completeCreateNewWalletOnboardingFlow = async ( password: string = WALLET_PASSWORD, ) => { console.log('start to complete create new wallet onboarding flow '); - await createNewWalletOnboardingFlow(driver, password); + await createNewWalletOnboardingFlow({ driver, password }); const onboardingCompletePage = new OnboardingCompletePage(driver); await onboardingCompletePage.check_pageIsLoaded(); await onboardingCompletePage.check_congratulationsMessageIsDisplayed(); @@ -136,3 +146,50 @@ export const completeImportSRPOnboardingFlow = async ({ await onboardingCompletePage.check_walletReadyMessageIsDisplayed(); await onboardingCompletePage.completeOnboarding(); }; + +/** + * Complete create new wallet onboarding flow with custom privacy settings. + * + * @param options - The options object. + * @param options.driver - The WebDriver instance. + * @param options.password - The password to use. Defaults to WALLET_PASSWORD. + * @param options.needNavigateToNewPage - Whether to navigate to new page to start the onboarding flow. Defaults to true. + * @param options.toggleBasicFunctionality - Indicates if basic functionalities should be opted out. Defaults to true. + * @param options.toggleAssetsPrivacy - Indicates if assets privacy functionalities should be opted out. Defaults to true. + */ +export const completeCreateNewWalletOnboardingFlowWithCustomSettings = async ({ + driver, + password = WALLET_PASSWORD, + needNavigateToNewPage = true, + toggleBasicFunctionality = true, + toggleAssetsPrivacy = true, +}: { + driver: Driver; + password?: string; + needNavigateToNewPage?: boolean; + toggleBasicFunctionality?: boolean; + toggleAssetsPrivacy?: boolean; +}): Promise => { + await createNewWalletOnboardingFlow({ + driver, + password, + needNavigateToNewPage, + }); + const onboardingCompletePage = new OnboardingCompletePage(driver); + await onboardingCompletePage.check_pageIsLoaded(); + await onboardingCompletePage.navigateToDefaultPrivacySettings(); + + const onboardingPrivacySettingsPage = new OnboardingPrivacySettingsPage( + driver, + ); + if (toggleBasicFunctionality) { + await onboardingPrivacySettingsPage.toggleBasicFunctionalitySettings(); + } + if (toggleAssetsPrivacy) { + await onboardingPrivacySettingsPage.toggleAssetsSettings(); + } + + await onboardingPrivacySettingsPage.navigateBackToOnboardingCompletePage(); + await onboardingCompletePage.check_pageIsLoaded(); + await onboardingCompletePage.completeOnboarding(); +}; diff --git a/test/e2e/page-objects/pages/settings/privacy-settings.ts b/test/e2e/page-objects/pages/settings/privacy-settings.ts index ab0486d2003e..8fc0645f77e9 100644 --- a/test/e2e/page-objects/pages/settings/privacy-settings.ts +++ b/test/e2e/page-objects/pages/settings/privacy-settings.ts @@ -110,9 +110,11 @@ class PrivacySettings { /** * Complete reveal SRP quiz to open reveal SRP dialog. * - * @param checkErrorAnswer - Whether to check for error answers during answering the quiz. + * @param checkErrorAnswer - Whether to check for error answers during answering the quiz. Defaults to false. */ - async completeRevealSrpQuiz(checkErrorAnswer?: boolean): Promise { + async completeRevealSrpQuiz( + checkErrorAnswer: boolean = false, + ): Promise { console.log('Complete reveal SRP quiz on privacy settings page'); await this.driver.clickElement(this.revealSrpQuizGetStartedButton); @@ -169,6 +171,12 @@ class PrivacySettings { } } + async getSrpInRevealSrpDialog(): Promise { + console.log('Get SRP in reveal SRP dialog on privacy settings page'); + await this.driver.waitForSelector(this.displayedSrpText); + return (await this.driver.findElement(this.displayedSrpText)).getText(); + } + async openRevealSrpQuiz(): Promise { console.log('Open reveal SRP quiz on privacy settings page'); await this.driver.clickElement(this.revealSrpButton); diff --git a/test/e2e/page-objects/pages/vault-decryptor-page.ts b/test/e2e/page-objects/pages/vault-decryptor-page.ts new file mode 100644 index 000000000000..0d04d231dcfe --- /dev/null +++ b/test/e2e/page-objects/pages/vault-decryptor-page.ts @@ -0,0 +1,96 @@ +import { Driver } from '../../webdriver/driver'; +import { WALLET_PASSWORD } from '../../helpers'; + +class VaultDecryptorPage { + private driver: Driver; + + private readonly decryptButton = { + text: 'Decrypt', + tag: 'button', + }; + + private readonly fileInput = '#fileinput'; + + private readonly passwordInput = '#passwordinput'; + + private readonly radioFileInput = '#radio-fileinput'; + + private readonly radioTextInput = '#radio-textinput'; + + private readonly textInput = '#textinput'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.fileInput, + this.textInput, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for Vault Decryptor page to be loaded', + e, + ); + throw e; + } + console.log('Vault Decryptor page is loaded'); + } + + /** + * Confirm the decryption process on the Vault Decryptor page. + */ + async confirmDecrypt() { + console.log('click to confirm decrypt on vault decryptor page'); + await this.driver.clickElement(this.decryptButton); + } + + /** + * Fill the password input field with the specified password. + * + * @param password - The password to fill in the password input field. Defaults to WALLET_PASSWORD. + */ + async fillPassword(password: string = WALLET_PASSWORD) { + await this.driver.fill(this.passwordInput, password); + } + + /** + * Fill the text input field with the specified vault text. + * + * @param vaultText - The text to fill in the text input field. + */ + async fillVaultText(vaultText: string) { + console.log('fill vault text on vault decryptor page'); + await this.driver.clickElement(this.radioTextInput); + await this.driver.fill(this.textInput, vaultText); + } + + /** + * Uploads a log file to the Vault Decryptor page. + * + * @param filePath - The path to the log file to upload. + */ + async uploadLogFile(filePath: string) { + console.log('upload log file on vault decryptor page'); + await this.driver.clickElement(this.radioFileInput); + const inputField = await this.driver.findElement(this.fileInput); + await inputField.sendKeys(filePath); + } + + /** + * Checks if the vault is decrypted and the seed phrase is correct. + * + * @param seedPhrase - The expected seed phrase. + */ + async check_vaultIsDecrypted(seedPhrase: string) { + console.log('check vault is decrypted on vault decryptor page'); + await this.driver.waitForSelector({ + text: seedPhrase, + tag: 'div', + }); + } +} + +export default VaultDecryptorPage; 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 f9574a27cb10..36776b367730 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 @@ -102,10 +102,10 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { }, }, async ({ driver }) => { - await createNewWalletOnboardingFlow( + await createNewWalletOnboardingFlow({ driver, - NOTIFICATIONS_TEAM_PASSWORD, - ); + password: NOTIFICATIONS_TEAM_PASSWORD, + }); const onboardingCompletePage = new OnboardingCompletePage(driver); await onboardingCompletePage.check_pageIsLoaded(); await onboardingCompletePage.navigateToDefaultPrivacySettings(); diff --git a/test/e2e/tests/privacy/onboarding-infura-call-privacy.spec.ts b/test/e2e/tests/privacy/onboarding-infura-call-privacy.spec.ts index 1644cb068a3a..f1bdc08166b7 100644 --- a/test/e2e/tests/privacy/onboarding-infura-call-privacy.spec.ts +++ b/test/e2e/tests/privacy/onboarding-infura-call-privacy.spec.ts @@ -90,7 +90,7 @@ describe('MetaMask onboarding @no-mmi', function () { testSpecificMock: mockInfura, }, async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await createNewWalletOnboardingFlow(driver); + await createNewWalletOnboardingFlow({ driver }); // Check no requests are made before completing creat new wallet onboarding // Intended delay to ensure we cover at least 1 polling loop of time for the network request diff --git a/test/e2e/tests/settings/settings-security-reveal-srp.spec.ts b/test/e2e/tests/settings/settings-security-reveal-srp.spec.ts index 35ff25344789..6ee57ae02490 100644 --- a/test/e2e/tests/settings/settings-security-reveal-srp.spec.ts +++ b/test/e2e/tests/settings/settings-security-reveal-srp.spec.ts @@ -29,7 +29,7 @@ describe('Reveal SRP through settings', function () { const privacySettings = new PrivacySettings(driver); await privacySettings.check_pageIsLoaded(); await privacySettings.openRevealSrpQuiz(); - await privacySettings.completeRevealSrpQuiz(true); + await privacySettings.completeRevealSrpQuiz(); await privacySettings.fillPasswordToRevealSrp( wrongTestPassword, 'Incorrect password', @@ -58,7 +58,7 @@ describe('Reveal SRP through settings', function () { // fill password to reveal SRP and check the displayed SRP await privacySettings.openRevealSrpQuiz(); - await privacySettings.completeRevealSrpQuiz(true); + await privacySettings.completeRevealSrpQuiz(); await privacySettings.fillPasswordToRevealSrp(testPassword); await privacySettings.check_srpTextIsDisplayed(E2E_SRP); await privacySettings.check_displayedSrpCanBeCopied(); diff --git a/test/e2e/vault-decryption-chrome.spec.js b/test/e2e/vault-decryption-chrome.spec.js deleted file mode 100644 index aa2b223dce56..000000000000 --- a/test/e2e/vault-decryption-chrome.spec.js +++ /dev/null @@ -1,293 +0,0 @@ -const { strict: assert } = require('assert'); -const os = require('os'); -const path = require('path'); -const fs = require('fs-extra'); -const level = require('level'); -const { - withFixtures, - WALLET_PASSWORD, - openSRPRevealQuiz, - completeSRPRevealQuiz, - tapAndHoldToRevealSRP, - completeCreateNewWalletOnboardingFlowWithOptOut, -} = require('./helpers'); - -const VAULT_DECRYPTOR_PAGE = 'https://metamask.github.io/vault-decryptor'; - -/** - * Copies a directory to a temporary location. - * - * @param {string} srcDir - The source directory to copy. - * @returns {Promise} The path to the copied directory in the temporary location. - */ -async function copyDirectoryToTmp(srcDir) { - try { - // Get a temporary directory - const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'temp')); - - // Define the destination path in the temporary directory - const destDir = path.join(tmpDir, path.basename(srcDir)); - - // Copy the directory - await fs.copy(srcDir, destDir); - console.log(`Directory copied to: ${destDir}`); - return destDir; - } catch (err) { - console.error('Error copying directory:', err); - return null; - } -} - -/** - * Retrieve the extension's storage file path. - * - * Note that this folder is usually unavailable when running e2e tests - * on a test build, as test builds do not use the real browser storage. - * - * @param {WebDriver} driver - * @returns {Promise} The extension storage path. - */ -async function getExtensionStorageFilePath(driver) { - const { userDataDir } = (await driver.driver.getCapabilities()).get('chrome'); - const extensionsStoragePath = path.resolve( - userDataDir, - 'Default', - 'Local Extension Settings', - ); - // we expect the extension to have been installed only once - const extensionName = fs.readdirSync(extensionsStoragePath)[0]; - const extensionStoragePath = path.resolve( - extensionsStoragePath, - extensionName, - ); - - return extensionStoragePath; -} - -/** - * Retrieve the log file from the extension's storage path. - * - * @param {string} extensionStoragePath - The path to the extension's storage. - * @returns {string} The log file path. - */ -function getExtensionLogFile(extensionStoragePath) { - const logFiles = fs - .readdirSync(extensionStoragePath) - .filter((filename) => filename.endsWith('.log')); - - // Use the first of the `.log` files found - return path.resolve(extensionStoragePath, logFiles[0]); -} - -/** - * Gets the size of a file in bytes. - * - * @param {string} filePath - The path to the file. - * @returns {Promise} A promise that resolves to the size of the file in bytes. - */ -async function getFileSize(filePath) { - const stats = await fs.promises.stat(filePath); - console.log(`File Size =========================: ${stats.size} bytes`); - return stats.size; -} - -/** - * Retry logic to ensure Chrome has finish writing into the file. - * - * @param {object} options - The options object. - * @param {WebDriver} options.driver - The WebDriver instance. - * @param {string} options.filePath - The path to the file. - * @param {number} options.maxRetries - The maximum number of retries. - * @param {number} options.minFileSize - The minimum file size in bytes. - * @returns {Promise} - */ -async function waitUntilFileIsWritten({ - driver, - filePath, - maxRetries = 3, - minFileSize = 1000000, -}) { - for (let attempt = 0; attempt < maxRetries; attempt++) { - const fileSize = await getFileSize(filePath); - if (fileSize > minFileSize) { - break; - } else { - console.log(`File size is too small (${fileSize} bytes)`); - if (attempt < maxRetries - 1) { - console.log(`Waiting for 2 seconds before retrying...`); - await driver.delay(2000); - } - } - } -} - -/** - * Closes the announcements popover if present - * - * @param {WebDriver} driver - */ -async function closePopoverIfPresent(driver) { - const popoverButtonSelector = '[data-testid="popover-close"]'; - // It shows in the Smart Transactions Opt-In Modal. - const enableButtonSelector = { - text: 'Enable', - tag: 'button', - }; - await driver.clickElementSafe(popoverButtonSelector); - await driver.clickElementSafe(enableButtonSelector); - - // Token Autodetection Independent Announcement - const tokenAutodetection = { - css: '[data-testid="auto-detect-token-modal"] button', - text: 'Not right now', - }; - await driver.clickElementSafe(tokenAutodetection); - - // NFT Autodetection Independent Announcement - const nftAutodetection = { - css: '[data-testid="auto-detect-nft-modal"] button', - text: 'Not right now', - }; - await driver.clickElementSafe(nftAutodetection); -} - -/** - * Obtain the SRP from the settings - * - * @param {WebDriver} driver - * @returns {Promise} The SRP - */ -async function getSRP(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); - return (await driver.findElement('[data-testid="srp_text"]')).getText(); -} - -describe('Vault Decryptor Page', function () { - it('is able to decrypt the vault uploading the log file in the vault-decryptor webapp', async function () { - await withFixtures( - { - disableServerMochaToBackground: true, - }, - async ({ driver }) => { - // we don't need to use navigate - // since MM will automatically open a new window in prod build - await driver.waitUntilXWindowHandles(2); - - // we cannot use the customized driver functions - // as there is no socket for window communications in prod builds - const windowHandles = await driver.driver.getAllWindowHandles(); - - // switch to MetaMask window - await driver.driver.switchTo().window(windowHandles[2]); - - // create a new vault through onboarding flow - await completeCreateNewWalletOnboardingFlowWithOptOut( - driver, - WALLET_PASSWORD, - ); - // close popover if any (Announcements etc..) - await closePopoverIfPresent(driver); - // obtain SRP - const seedPhrase = await getSRP(driver); - - // navigate to the Vault decryptor webapp - await driver.openNewPage(VAULT_DECRYPTOR_PAGE); - // fill the input field with storage recovered from filesystem - await driver.clickElement('[name="vault-source"]'); - const inputField = await driver.findElement('#fileinput'); - - // Retry-logic to ensure the file is ready before uploading it - // to mitigate flakiness when Chrome hasn't finished writing - const extensionPath = await getExtensionStorageFilePath(driver); - const extensionLogFile = getExtensionLogFile(extensionPath); - await waitUntilFileIsWritten({ driver, filePath: extensionLogFile }); - - await inputField.press(extensionLogFile); - - // fill in the password - await driver.fill('#passwordinput', WALLET_PASSWORD); - // decrypt - await driver.clickElement('.decrypt'); - const decrypted = await driver.findElement('.content div div div'); - const recoveredVault = JSON.parse(await decrypted.getText()); - - assert.equal(recoveredVault[0].data.mnemonic, seedPhrase); - }, - ); - }); - it('is able to decrypt the vault pasting the text in the vault-decryptor webapp', async function () { - await withFixtures( - { - disableServerMochaToBackground: true, - }, - async ({ driver }) => { - // we don't need to use navigate - // since MM will automatically open a new window in prod build - await driver.waitUntilXWindowHandles(2); - - // we cannot use the customized driver functions - // as there is no socket for window communications in prod builds - const windowHandles = await driver.driver.getAllWindowHandles(); - - // switch to MetaMask window - await driver.driver.switchTo().window(windowHandles[2]); - - // create a new vault through onboarding flow - await completeCreateNewWalletOnboardingFlowWithOptOut( - driver, - WALLET_PASSWORD, - ); - // close popover if any (Announcements etc..) - await closePopoverIfPresent(driver); - // obtain SRP - const seedPhrase = await getSRP(driver); - - // navigate to the Vault decryptor webapp - await driver.openNewPage(VAULT_DECRYPTOR_PAGE); - - // retry-logic to ensure the file is written before copying it - const extensionPath = await getExtensionStorageFilePath(driver); - const extensionLogFile = getExtensionLogFile(extensionPath); - await waitUntilFileIsWritten({ driver, filePath: extensionLogFile }); - - // copy log file to a temp location, to avoid reading it while the browser is writting it - let newDir; - let vaultObj; - let db; - try { - newDir = await copyDirectoryToTmp(extensionPath); - db = new level.Level(newDir, { valueEncoding: 'json' }); - await db.open(); - const { - KeyringController: { vault }, - } = await db.get('data'); - vaultObj = JSON.parse(vault); - } finally { - if (db) { - await db.close(); - } - if (newDir) { - await fs.remove(newDir); - } - } - - await driver.clickElement('#radio-textinput'); - await driver.fill('#textinput', JSON.stringify(vaultObj)); - - // fill in the password - await driver.fill('#passwordinput', WALLET_PASSWORD); - - // decrypt - await driver.clickElement('.decrypt'); - const decrypted = await driver.findElement('.content div div div'); - const recoveredVault = JSON.parse(await decrypted.getText()); - - assert.equal(recoveredVault[0].data.mnemonic, seedPhrase); - }, - ); - }); -}); diff --git a/test/e2e/vault-decryption-chrome.spec.ts b/test/e2e/vault-decryption-chrome.spec.ts new file mode 100644 index 000000000000..f66ee7d10839 --- /dev/null +++ b/test/e2e/vault-decryption-chrome.spec.ts @@ -0,0 +1,298 @@ +import os from 'os'; +import path from 'path'; +import fs from 'fs-extra'; +import level from 'level'; +import { Driver } from './webdriver/driver'; +import { withFixtures, WALLET_PASSWORD } from './helpers'; +import HeaderNavbar from './page-objects/pages/header-navbar'; +import HomePage from './page-objects/pages/homepage'; +import PrivacySettings from './page-objects/pages/settings/privacy-settings'; +import SettingsPage from './page-objects/pages/settings/settings-page'; +import VaultDecryptorPage from './page-objects/pages/vault-decryptor-page'; +import { completeCreateNewWalletOnboardingFlowWithCustomSettings } from './page-objects/flows/onboarding.flow'; + +const VAULT_DECRYPTOR_PAGE = 'https://metamask.github.io/vault-decryptor'; + +/** + * Copies a directory to a temporary location. + * + * @param srcDir - The source directory to copy. + * @returns The path to the copied directory in the temporary location. + */ +async function copyDirectoryToTmp(srcDir: string): Promise { + try { + // Get a temporary directory + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'temp')); + + // Define the destination path in the temporary directory + const destDir = path.join(tmpDir, path.basename(srcDir)); + + // Copy the directory + await fs.copy(srcDir, destDir); + console.log(`Directory copied to: ${destDir}`); + return destDir; + } catch (err) { + console.error('Error copying directory:', err); + return ''; + } +} + +/** + * Retrieve the extension's storage file path. + * + * Note that this folder is usually unavailable when running e2e tests + * on a test build, as test builds do not use the real browser storage. + * + * @param driver + * @returns The extension storage path. + */ +async function getExtensionStorageFilePath(driver: Driver): Promise { + const { userDataDir } = (await driver.driver.getCapabilities()).get('chrome'); + const extensionsStoragePath = path.resolve( + userDataDir, + 'Default', + 'Local Extension Settings', + ); + // we expect the extension to have been installed only once + const extensionName = fs.readdirSync(extensionsStoragePath)[0]; + const extensionStoragePath = path.resolve( + extensionsStoragePath, + extensionName, + ); + + return extensionStoragePath; +} + +/** + * Retrieve the log file from the extension's storage path. + * + * @param extensionStoragePath - The path to the extension's storage. + * @returns The log file path. + */ +function getExtensionLogFile(extensionStoragePath: string): string { + const logFiles = fs + .readdirSync(extensionStoragePath) + .filter((filename: string) => filename.endsWith('.log')); + + // Use the first of the `.log` files found + return path.resolve(extensionStoragePath, logFiles[0]); +} + +/** + * Gets the size of a file in bytes. + * + * @param filePath - The path to the file. + * @returns A promise that resolves to the size of the file in bytes. + */ +async function getFileSize(filePath: string): Promise { + const stats = await fs.promises.stat(filePath); + console.log(`File Size =========================: ${stats.size} bytes`); + return stats.size; +} + +/** + * Retry logic to ensure Chrome has finish writing into the file. + * + * @param options - The options object. + * @param options.driver - The WebDriver instance. + * @param options.filePath - The path to the file. + * @param options.maxRetries - The maximum number of retries. + * @param options.minFileSize - The minimum file size in bytes. + * @returns + */ +async function waitUntilFileIsWritten({ + driver, + filePath, + maxRetries = 3, + minFileSize = 1000000, +}: { + driver: Driver; + filePath: string; + maxRetries?: number; + minFileSize?: number; +}): Promise { + for (let attempt = 0; attempt < maxRetries; attempt++) { + const fileSize = await getFileSize(filePath); + if (fileSize > minFileSize) { + break; + } else { + console.log(`File size is too small (${fileSize} bytes)`); + if (attempt < maxRetries - 1) { + console.log(`Waiting for 2 seconds before retrying...`); + await driver.delay(2000); + } + } + } +} + +/** + * Closes the announcements popover if present + * + * @param driver + */ +async function closePopoverIfPresent(driver: Driver) { + const popoverButtonSelector = '[data-testid="popover-close"]'; + // It shows in the Smart Transactions Opt-In Modal. + const enableButtonSelector = { + text: 'Enable', + tag: 'button', + }; + await driver.clickElementSafe(popoverButtonSelector); + await driver.clickElementSafe(enableButtonSelector); + + // Token Autodetection Independent Announcement + const tokenAutodetection = { + css: '[data-testid="auto-detect-token-modal"] button', + text: 'Not right now', + }; + await driver.clickElementSafe(tokenAutodetection); + + // NFT Autodetection Independent Announcement + const nftAutodetection = { + css: '[data-testid="auto-detect-nft-modal"] button', + text: 'Not right now', + }; + await driver.clickElementSafe(nftAutodetection); +} + +describe('Vault Decryptor Page', function () { + it('is able to decrypt the vault uploading the log file in the vault-decryptor webapp', async function () { + await withFixtures( + { + disableServerMochaToBackground: true, + }, + async ({ driver }) => { + // we don't need to use navigate since MM will automatically open a new window in prod build + await driver.waitUntilXWindowHandles(2); + + // we cannot use the customized driver functionsas there is no socket for window communications in prod builds + const windowHandles = await driver.driver.getAllWindowHandles(); + + // switch to MetaMask window and create a new vault through onboarding flow + await driver.driver.switchTo().window(windowHandles[2]); + await completeCreateNewWalletOnboardingFlowWithCustomSettings({ + driver, + password: WALLET_PASSWORD, + needNavigateToNewPage: false, + }); + // close popover if any (Announcements etc..) + await closePopoverIfPresent(driver); + + // go to privacy settings page + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await new HeaderNavbar(driver).openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToPrivacySettings(); + + // fill password to reveal SRP and get the SRP + 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(); + + // Retry-logic to ensure the file is ready before uploading itto mitigate flakiness when Chrome hasn't finished writing + const extensionPath = await getExtensionStorageFilePath(driver); + const extensionLogFile = getExtensionLogFile(extensionPath); + await waitUntilFileIsWritten({ driver, filePath: extensionLogFile }); + + // navigate to the Vault decryptor webapp and fill the input field with storage recovered from filesystem + await driver.openNewPage(VAULT_DECRYPTOR_PAGE); + const vaultDecryptorPage = new VaultDecryptorPage(driver); + await vaultDecryptorPage.check_pageIsLoaded(); + await vaultDecryptorPage.uploadLogFile(extensionLogFile); + + // fill the password and decrypt + await vaultDecryptorPage.fillPassword(); + await vaultDecryptorPage.confirmDecrypt(); + await vaultDecryptorPage.check_vaultIsDecrypted(seedPhrase); + }, + ); + }); + + it('is able to decrypt the vault pasting the text in the vault-decryptor webapp', async function () { + await withFixtures( + { + disableServerMochaToBackground: true, + }, + async ({ driver }) => { + // we don't need to use navigate since MM will automatically open a new window in prod build + await driver.waitUntilXWindowHandles(2); + + // we cannot use the customized driver functions as there is no socket for window communications in prod builds + const windowHandles = await driver.driver.getAllWindowHandles(); + + // switch to MetaMask window and create a new vault through onboarding flow + await driver.driver.switchTo().window(windowHandles[2]); + await completeCreateNewWalletOnboardingFlowWithCustomSettings({ + driver, + password: WALLET_PASSWORD, + needNavigateToNewPage: false, + }); + // close popover if any (Announcements etc..) + await closePopoverIfPresent(driver); + + // go to privacy settings page + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); + await new HeaderNavbar(driver).openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToPrivacySettings(); + + // fill password to reveal SRP and get the SRP + 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(); + + // retry-logic to ensure the file is written before copying it + const extensionPath = await getExtensionStorageFilePath(driver); + const extensionLogFile = getExtensionLogFile(extensionPath); + await waitUntilFileIsWritten({ driver, filePath: extensionLogFile }); + + // copy log file to a temp location, to avoid reading it while the browser is writting it + type VaultData = { + KeyringController: { + vault: string; + }; + }; + let newDir; + let vaultObj; + let db; + try { + newDir = await copyDirectoryToTmp(extensionPath); + db = new level.Level(newDir, { valueEncoding: 'json' }); + await db.open(); + const data = (await db.get('data')) as unknown as VaultData; + vaultObj = JSON.parse(data.KeyringController.vault); + } finally { + if (db) { + await db.close(); + } + if (newDir) { + await fs.remove(newDir); + } + } + + // navigate to the Vault decryptor webapp and fill the text input field with the vault text + await driver.openNewPage(VAULT_DECRYPTOR_PAGE); + const vaultDecryptorPage = new VaultDecryptorPage(driver); + await vaultDecryptorPage.check_pageIsLoaded(); + await vaultDecryptorPage.fillVaultText(JSON.stringify(vaultObj)); + + // fill the password and decrypt + await vaultDecryptorPage.fillPassword(); + await vaultDecryptorPage.confirmDecrypt(); + await vaultDecryptorPage.check_vaultIsDecrypted(seedPhrase); + }, + ); + }); +}); From 010b560de03e66c421815d492bd4de22dfd82006 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Fri, 15 Nov 2024 18:56:34 +0530 Subject: [PATCH 23/32] chore: Fix flaky ERC20 transfer blockaid e2e (#28453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** It appears that ERC20 transfer option on test dapp is broken which is causing the test to be flaky. The PR skips the test till we fix test dapp. ## **Related issues** Ref: https://github.com/MetaMask/metamask-extension/issues/28434 ## **Manual testing steps** NA ## **Screenshots/Recordings** NA ## **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/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js index 4f6fcf819f94..506bec00fa00 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js @@ -59,7 +59,8 @@ async function mockInfuraWithMaliciousResponses(mockServer) { await mockRequest(mockServer, maliciousTransferAlert); } -describe('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { +// eslint-disable-next-line mocha/no-skipped-tests +describe.skip('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { it('should show banner alert', async function () { // we need to use localhost instead of the ip // see issue: https://github.com/MetaMask/MetaMask-planning/issues/3560 From 4d5940ec6368afc648db2d8859c6e8586ec589e1 Mon Sep 17 00:00:00 2001 From: chloeYue <105063779+chloeYue@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:08:07 +0100 Subject: [PATCH 24/32] chore: fix test path on CI (#28482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Fix test path on CI since we have migrated test to TS [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28482?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. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 03cb5091da4e..70268a812421 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1195,7 +1195,7 @@ jobs: at: . - run: name: test:e2e:single - command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:single test/e2e/vault-decryption-chrome.spec.js --browser chrome + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:single test/e2e/vault-decryption-chrome.spec.ts --browser chrome no_output_timeout: 5m - store_artifacts: path: test-artifacts From dbe5947b380226a93d84fd8e64bc9d5f3f15ed0b Mon Sep 17 00:00:00 2001 From: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:48:14 +0000 Subject: [PATCH 25/32] fix: Fix race condition validating ERC20 transfer (blockaid) (#28487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Fixed race condition when validating ERC20 transfer and enabled ERC20 transfer blockaid e2e. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28487?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/28434 ## **Manual testing steps** 1. Go to this the test dapp 2. Click and reject (fast ) all types of requests in the section: PPOM - Malicious Transactions and Signatures 3. Should not get stuck in the loader ## **Screenshots/Recordings** ### **Before** [all PPOM signatures.webm](https://github.com/user-attachments/assets/b59a965a-f5e9-44e6-93a5-9058cd164933) ### **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/lib/ppom/security-alerts-api.ts | 2 +- app/scripts/metamask-controller.js | 2 +- test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/ppom/security-alerts-api.ts b/app/scripts/lib/ppom/security-alerts-api.ts index 258526f1b7c4..d0b6bc812b1a 100644 --- a/app/scripts/lib/ppom/security-alerts-api.ts +++ b/app/scripts/lib/ppom/security-alerts-api.ts @@ -51,7 +51,7 @@ async function request(endpoint: string, options?: RequestInit) { ); } - return response.json(); + return await response.json(); } function getUrl(endpoint: string) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2f8a3a1fe807..7aa3dfc87b92 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -5161,7 +5161,7 @@ export default class MetamaskController extends EventEmitter { securityAlertId, securityAlertResponse, ) { - updateSecurityAlertResponse({ + await updateSecurityAlertResponse({ appStateController: this.appStateController, method, securityAlertId, diff --git a/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js b/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js index 506bec00fa00..4f6fcf819f94 100644 --- a/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js +++ b/test/e2e/tests/ppom/ppom-blockaid-alert-erc20-transfer.spec.js @@ -59,8 +59,7 @@ async function mockInfuraWithMaliciousResponses(mockServer) { await mockRequest(mockServer, maliciousTransferAlert); } -// eslint-disable-next-line mocha/no-skipped-tests -describe.skip('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { +describe('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { it('should show banner alert', async function () { // we need to use localhost instead of the ip // see issue: https://github.com/MetaMask/MetaMask-planning/issues/3560 From 17004224bffc25611cce88d83a3391ff99c83b48 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Sat, 16 Nov 2024 01:14:54 +0800 Subject: [PATCH 26/32] fix: display btc account creation while in settings (#28379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR resolves the issue where the account creation is now displayed if the user tries to create a BTC account while in the settings page. ## **Related issues** Fixes: https://github.com/MetaMask/accounts-planning/issues/642 ## **Manual testing steps** 1. Go to settings and check `Enable "Add a new Bitcoin account (Beta)"` in the experimental settings. 2. Open the account menu 3. Create a btc account ## **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. --- .../account-list-menu.test.tsx | 69 +++++++++++++++++-- .../account-list-menu/account-list-menu.tsx | 41 +++++++---- 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/ui/components/multichain/account-list-menu/account-list-menu.test.tsx b/ui/components/multichain/account-list-menu/account-list-menu.test.tsx index 7a849577dfa4..539899d0eb09 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.test.tsx +++ b/ui/components/multichain/account-list-menu/account-list-menu.test.tsx @@ -7,27 +7,32 @@ import { KeyringAccountType, } from '@metamask/keyring-api'; import { merge } from 'lodash'; -import { fireEvent, renderWithProvider, waitFor } from '../../../../test/jest'; +import { fireEvent, waitFor } from '../../../../test/jest'; import configureStore from '../../../store/store'; import mockState from '../../../../test/data/mock-state.json'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import messages from '../../../../app/_locales/en/messages.json'; -import { CONNECT_HARDWARE_ROUTE } from '../../../helpers/constants/routes'; +import { + CONFIRMATION_V_NEXT_ROUTE, + CONNECT_HARDWARE_ROUTE, +} from '../../../helpers/constants/routes'; ///: END:ONLY_INCLUDE_IF import { ETH_EOA_METHODS } from '../../../../shared/constants/eth-methods'; import { createMockInternalAccount } from '../../../../test/jest/mocks'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; import { AccountListMenu } from '.'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) const mockOnClose = jest.fn(); const mockGetEnvironmentType = jest.fn(); const mockNextAccountName = jest.fn().mockReturnValue('Test Account 2'); +const mockBitcoinClientCreateAccount = jest.fn(); jest.mock('../../../../app/scripts/lib/util', () => ({ ...jest.requireActual('../../../../app/scripts/lib/util'), - getEnvironmentType: () => mockGetEnvironmentType, + getEnvironmentType: () => () => mockGetEnvironmentType(), })); ///: END:ONLY_INCLUDE_IF @@ -43,6 +48,15 @@ jest.mock('react-router-dom', () => ({ useHistory: jest.fn(() => []), })); +jest.mock('../../../hooks/accounts/useMultichainWalletSnapClient', () => ({ + ...jest.requireActual( + '../../../hooks/accounts/useMultichainWalletSnapClient', + ), + useMultichainWalletSnapClient: () => ({ + createAccount: mockBitcoinClientCreateAccount, + }), +})); + const render = ( state = {}, props: { @@ -52,6 +66,7 @@ const render = ( onClose: () => jest.fn(), allowedAccountTypes: [EthAccountType.Eoa, EthAccountType.Erc4337], }, + location: string = '/', ) => { const defaultState = { ...mockState, @@ -82,6 +97,7 @@ const render = ( }, }, }, + bitcoinSupportEnabled: true, }, activeTab: { id: 113, @@ -95,7 +111,7 @@ const render = ( }, }; const store = configureStore(merge(defaultState, state)); - return renderWithProvider(, store); + return renderWithProvider(, store, location); }; describe('AccountListMenu', () => { @@ -512,6 +528,51 @@ describe('AccountListMenu', () => { }); ///: END:ONLY_INCLUDE_IF + describe('BTC account creation', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('calls the bitcoin client to create an account', async () => { + const { getByText, getByTestId } = render(); + + const button = getByTestId( + 'multichain-account-menu-popover-action-button', + ); + button.click(); + + const createBtcAccountButton = getByText( + messages.addNewBitcoinAccount.message, + ); + + createBtcAccountButton.click(); + + expect(mockBitcoinClientCreateAccount).toHaveBeenCalled(); + }); + + it('redirects the user to the approval after clicking create account in the settings page', async () => { + const { getByText, getByTestId } = render( + undefined, + undefined, + '/settings', + ); + + const button = getByTestId( + 'multichain-account-menu-popover-action-button', + ); + button.click(); + + const createBtcAccountButton = getByText( + messages.addNewBitcoinAccount.message, + ); + + createBtcAccountButton.click(); + + expect(historyPushMock).toHaveBeenCalledWith(CONFIRMATION_V_NEXT_ROUTE); + expect(mockBitcoinClientCreateAccount).toHaveBeenCalled(); + }); + }); + describe('prop `allowedAccountTypes`', () => { const mockAccount = createMockInternalAccount(); const mockBtcAccount = createMockInternalAccount({ 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 d649934ee75d..29d79e8537b1 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.tsx +++ b/ui/components/multichain/account-list-menu/account-list-menu.tsx @@ -6,7 +6,12 @@ import React, { useEffect, } from 'react'; import PropTypes from 'prop-types'; -import { useHistory } from 'react-router-dom'; +import { + useHistory, + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + useLocation, + ///: END:ONLY_INCLUDE_IF +} from 'react-router-dom'; import Fuse from 'fuse.js'; import { useDispatch, useSelector } from 'react-redux'; import { @@ -81,6 +86,10 @@ import { } from '../../../../shared/constants/metametrics'; import { CONNECT_HARDWARE_ROUTE, + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + CONFIRMATION_V_NEXT_ROUTE, + SETTINGS_ROUTE, + ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) CUSTODY_ACCOUNT_ROUTE, ///: END:ONLY_INCLUDE_IF @@ -247,6 +256,9 @@ export const AccountListMenu = ({ const currentTabOrigin = useSelector(getOriginOfCurrentTab); const history = useHistory(); const dispatch = useDispatch(); + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + const { pathname } = useLocation(); + ///: END:ONLY_INCLUDE_IF const hiddenAddresses = useSelector(getHiddenAccountsList); const updatedAccountsList = useSelector(getUpdatedAndSortedAccounts); const filteredUpdatedAccountList = useMemo( @@ -295,6 +307,17 @@ export const AccountListMenu = ({ const bitcoinWalletSnapClient = useMultichainWalletSnapClient( WalletClientType.Bitcoin, ); + const handleAccountCreation = async (network: MultichainNetworks) => { + // The account creation + renaming is handled by the Snap account bridge, so + // we need to close the current modal + onClose(); + if (pathname.includes(SETTINGS_ROUTE)) { + // The settings route does not redirect pending confirmations. We need to redirect manually here. + history.push(CONFIRMATION_V_NEXT_ROUTE); + } + + await bitcoinWalletSnapClient.createAccount(network); + }; ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(solana) @@ -302,6 +325,7 @@ export const AccountListMenu = ({ const solanaWalletSnapClient = useMultichainWalletSnapClient( WalletClientType.Solana, ); + ///: END:ONLY_INCLUDE_IF const [searchQuery, setSearchQuery] = useState(''); @@ -453,14 +477,7 @@ export const AccountListMenu = ({ }, }); - // The account creation + renaming is handled by the - // Snap account bridge, so we need to close the current - // modal - onClose(); - - await bitcoinWalletSnapClient.createAccount( - MultichainNetworks.BITCOIN, - ); + await handleAccountCreation(MultichainNetworks.BITCOIN); }} data-testid="multichain-account-menu-popover-add-btc-account" > @@ -479,11 +496,7 @@ export const AccountListMenu = ({ size={ButtonLinkSize.Sm} startIconName={IconName.Add} onClick={async () => { - // The account creation + renaming is handled by the Snap account bridge, so - // we need to close the current modal - onClose(); - - await bitcoinWalletSnapClient.createAccount( + await handleAccountCreation( MultichainNetworks.BITCOIN_TESTNET, ); }} From a597a8efad4cea0a8a51346382ead50d6af14652 Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Fri, 15 Nov 2024 09:39:23 -0800 Subject: [PATCH 27/32] feat: Upgrade assets controllers to 43 with multichain polling for token lists + detection (#28447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This upgrades assets controllers to version 43. It allows us to poll for token lists and token detection across chains. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28447?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Onboard to metamask 2. Verify your erc20 tokens are detected 3. Click a token 4. Verify data on token details page 5. Switch networks 6. Repeat steps 2-4 ## **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 --- ...s-controllers-npm-43.1.1-c223d56176.patch} | 6 +- ...-assets-controllers-patch-9e00573eb4.patch | 62 +++++++++ app/scripts/metamask-controller.js | 38 +++--- app/scripts/metamask-controller.test.js | 109 --------------- lavamoat/browserify/beta/policy.json | 1 + lavamoat/browserify/flask/policy.json | 1 + lavamoat/browserify/main/policy.json | 1 + lavamoat/browserify/mmi/policy.json | 1 + package.json | 2 +- ...rs-after-init-opt-in-background-state.json | 15 +- .../errors-after-init-opt-in-ui-state.json | 15 +- test/e2e/tests/tokens/add-hide-token.spec.js | 9 ++ test/e2e/tests/tokens/import-tokens.spec.js | 23 +++- ui/contexts/assetPolling.tsx | 4 + ui/hooks/useTokenDetectionPolling.ts | 33 +++++ ui/hooks/useTokenListPolling.test.ts | 128 ++++++++++++++++++ ui/hooks/useTokenListPolling.ts | 43 ++++++ ui/pages/asset/useHistoricalPrices.ts | 12 +- .../pin-extension/pin-extension.js | 4 +- .../pin-extension/pin-extension.test.js | 36 +++-- ui/selectors/selectors.js | 4 + ui/store/actions.ts | 73 +++++++++- yarn.lock | 80 ++++++++--- 23 files changed, 516 insertions(+), 184 deletions(-) rename .yarn/patches/{@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch => @metamask-assets-controllers-npm-43.1.1-c223d56176.patch} (84%) create mode 100644 .yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch create mode 100644 ui/hooks/useTokenDetectionPolling.ts create mode 100644 ui/hooks/useTokenListPolling.test.ts create mode 100644 ui/hooks/useTokenListPolling.ts diff --git a/.yarn/patches/@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch b/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch similarity index 84% rename from .yarn/patches/@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch rename to .yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch index 7a5837cd4818..2a6310c2db69 100644 --- a/.yarn/patches/@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch +++ b/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch @@ -1,5 +1,5 @@ diff --git a/dist/assetsUtil.cjs b/dist/assetsUtil.cjs -index e90a1b6767bc8ac54b7a4d580035cf5db6861dca..a5e0f03d2541b4e3540431ef2e6e4b60fb7ae9fe 100644 +index c2e83cf6caee19152aa164f1333cfef7b681e900..590b6de6e9d20ca402b82ac56b0929ab8c16c932 100644 --- a/dist/assetsUtil.cjs +++ b/dist/assetsUtil.cjs @@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { @@ -7,10 +7,10 @@ index e90a1b6767bc8ac54b7a4d580035cf5db6861dca..a5e0f03d2541b4e3540431ef2e6e4b60 }; Object.defineProperty(exports, "__esModule", { value: true }); +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } - exports.fetchTokenContractExchangeRates = exports.reduceInBatchesSerially = exports.divideIntoBatches = exports.ethersBigNumberToBN = exports.addUrlProtocolPrefix = exports.getFormattedIpfsUrl = exports.getIpfsCIDv1AndPath = exports.removeIpfsProtocolPrefix = exports.isTokenListSupportedForNetwork = exports.isTokenDetectionSupportedForNetwork = exports.SupportedTokenDetectionNetworks = exports.formatIconUrlWithProxy = exports.formatAggregatorNames = exports.hasNewCollectionFields = exports.compareNftMetadata = exports.TOKEN_PRICES_BATCH_SIZE = void 0; + exports.fetchTokenContractExchangeRates = exports.reduceInBatchesSerially = exports.divideIntoBatches = exports.ethersBigNumberToBN = exports.addUrlProtocolPrefix = exports.getFormattedIpfsUrl = exports.getIpfsCIDv1AndPath = exports.removeIpfsProtocolPrefix = exports.isTokenListSupportedForNetwork = exports.isTokenDetectionSupportedForNetwork = exports.SupportedStakedBalanceNetworks = exports.SupportedTokenDetectionNetworks = exports.formatIconUrlWithProxy = exports.formatAggregatorNames = exports.hasNewCollectionFields = exports.compareNftMetadata = exports.TOKEN_PRICES_BATCH_SIZE = void 0; const controller_utils_1 = require("@metamask/controller-utils"); const utils_1 = require("@metamask/utils"); -@@ -221,7 +222,7 @@ async function getIpfsCIDv1AndPath(ipfsUrl) { +@@ -233,7 +234,7 @@ async function getIpfsCIDv1AndPath(ipfsUrl) { const index = url.indexOf('/'); const cid = index !== -1 ? url.substring(0, index) : url; const path = index !== -1 ? url.substring(index) : undefined; diff --git a/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch b/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch new file mode 100644 index 000000000000..1b9e5a4ba848 --- /dev/null +++ b/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch @@ -0,0 +1,62 @@ +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/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7aa3dfc87b92..6e2ba119053c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2606,24 +2606,12 @@ export default class MetamaskController extends EventEmitter { this.accountTrackerController.start(); this.txController.startIncomingTransactionPolling(); this.tokenDetectionController.enable(); - - const preferencesControllerState = this.preferencesController.state; - - if (this.#isTokenListPollingRequired(preferencesControllerState)) { - this.tokenListController.start(); - } } stopNetworkRequests() { this.accountTrackerController.stop(); this.txController.stopIncomingTransactionPolling(); this.tokenDetectionController.disable(); - - const preferencesControllerState = this.preferencesController.state; - - if (this.#isTokenListPollingRequired(preferencesControllerState)) { - this.tokenListController.stop(); - } } resetStates(resetMethods) { @@ -3243,6 +3231,7 @@ export default class MetamaskController extends EventEmitter { currencyRateController, tokenDetectionController, ensController, + tokenListController, gasFeeController, metaMetricsController, networkController, @@ -4045,6 +4034,19 @@ export default class MetamaskController extends EventEmitter { tokenRatesController, ), + tokenDetectionStartPolling: tokenDetectionController.startPolling.bind( + tokenDetectionController, + ), + tokenDetectionStopPollingByPollingToken: + tokenDetectionController.stopPollingByPollingToken.bind( + tokenDetectionController, + ), + + tokenListStartPolling: + tokenListController.startPolling.bind(tokenListController), + tokenListStopPollingByPollingToken: + tokenListController.stopPollingByPollingToken.bind(tokenListController), + // GasFeeController gasFeeStartPollingByNetworkClientId: gasFeeController.startPollingByNetworkClientId.bind(gasFeeController), @@ -4408,6 +4410,7 @@ export default class MetamaskController extends EventEmitter { if (balance === '0x0') { // This account has no balance, so check for tokens await this.tokenDetectionController.detectTokens({ + chainIds: [chainId], selectedAddress: address, }); @@ -6676,6 +6679,8 @@ export default class MetamaskController extends EventEmitter { this.gasFeeController.stopAllPolling(); this.currencyRateController.stopAllPolling(); this.tokenRatesController.stopAllPolling(); + this.tokenDetectionController.stopAllPolling(); + this.tokenListController.stopAllPolling(); this.appStateController.clearPollingTokens(); } catch (error) { console.error(error); @@ -7211,15 +7216,6 @@ export default class MetamaskController extends EventEmitter { } this.tokenListController.updatePreventPollingOnNetworkRestart(!newEnabled); - - if (newEnabled) { - log.debug('Started token list controller polling'); - this.tokenListController.start(); - } else { - log.debug('Stopped token list controller polling'); - this.tokenListController.clearingTokenListData(); - this.tokenListController.stop(); - } } #isTokenListPollingRequired(preferencesControllerState) { diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 750f8771568b..2ccf864b0edd 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -2301,115 +2301,6 @@ describe('MetaMaskController', () => { }); }); - describe('token list controller', () => { - it('stops polling if petnames, simulations, and token detection disabled', async () => { - expect(TokenListController.prototype.stop).not.toHaveBeenCalled(); - - expect( - TokenListController.prototype.clearingTokenListData, - ).not.toHaveBeenCalled(); - - await simulatePreferencesChange({ - useTransactionSimulations: false, - useTokenDetection: false, - preferences: { - petnamesEnabled: false, - }, - }); - - expect(TokenListController.prototype.stop).toHaveBeenCalledTimes(1); - - expect( - TokenListController.prototype.clearingTokenListData, - ).toHaveBeenCalledTimes(1); - }); - - it.each([ - [ - 'petnames', - { - preferences: { petnamesEnabled: false }, - useTokenDetection: true, - useTransactionSimulations: true, - }, - ], - [ - 'simulations', - { - preferences: { petnamesEnabled: true }, - useTokenDetection: true, - useTransactionSimulations: false, - }, - ], - [ - 'token detection', - { - preferences: { petnamesEnabled: true }, - useTokenDetection: false, - useTransactionSimulations: true, - }, - ], - ])( - 'does not stop polling if only %s disabled', - async (_, preferences) => { - expect(TokenListController.prototype.stop).not.toHaveBeenCalled(); - - expect( - TokenListController.prototype.clearingTokenListData, - ).not.toHaveBeenCalled(); - - await simulatePreferencesChange(preferences); - - expect(TokenListController.prototype.stop).not.toHaveBeenCalled(); - - expect( - TokenListController.prototype.clearingTokenListData, - ).not.toHaveBeenCalled(); - }, - ); - - it.each([ - [ - 'petnames', - { - preferences: { petnamesEnabled: true }, - useTokenDetection: false, - useTransactionSimulations: false, - }, - ], - [ - 'simulations', - { - preferences: { petnamesEnabled: false }, - useTokenDetection: false, - useTransactionSimulations: true, - }, - ], - [ - 'token detection', - { - preferences: { petnamesEnabled: false }, - useTokenDetection: true, - useTransactionSimulations: false, - }, - ], - ])('starts polling if only %s enabled', async (_, preferences) => { - expect(TokenListController.prototype.start).not.toHaveBeenCalled(); - - await simulatePreferencesChange({ - useTransactionSimulations: false, - useTokenDetection: false, - preferences: { - petnamesEnabled: false, - }, - }); - - await simulatePreferencesChange(preferences); - - expect(TokenListController.prototype.start).toHaveBeenCalledTimes(1); - }); - }); - describe('MultichainRatesController start/stop', () => { const mockEvmAccount = createMockInternalAccount(); const mockNonEvmAccount = { diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 2993482ec504..54659cd66695 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -686,6 +686,7 @@ "packages": { "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/bignumber": true, "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/abi-utils": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 2993482ec504..54659cd66695 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -686,6 +686,7 @@ "packages": { "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/bignumber": true, "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/abi-utils": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 2993482ec504..54659cd66695 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -686,6 +686,7 @@ "packages": { "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/bignumber": true, "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/abi-utils": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 02d73c44f420..38442fe5eb85 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -778,6 +778,7 @@ "packages": { "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true, "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/bignumber": true, "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/abi-utils": true, diff --git a/package.json b/package.json index 5bd177799bb8..e9be2caed40e 100644 --- a/package.json +++ b/package.json @@ -292,7 +292,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%3A42.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A43.1.1%23~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch%3A%3Aversion=43.1.1&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch", "@metamask/base-controller": "^7.0.0", "@metamask/bitcoin-wallet-snap": "^0.8.2", "@metamask/browser-passworder": "^4.3.0", 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 bb1640d99365..999dce99ca0c 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 @@ -145,8 +145,11 @@ "MultichainBalancesController": { "balances": "object" }, "MultichainRatesController": { "fiatCurrency": "usd", - "rates": { "btc": { "conversionDate": 0, "conversionRate": 0 } }, - "cryptocurrencies": ["btc"] + "rates": { + "btc": { "conversionDate": 0, "conversionRate": 0 }, + "sol": { "conversionDate": 0, "conversionRate": 0 } + }, + "cryptocurrencies": ["btc", "sol"] }, "NameController": { "names": "object", "nameSources": "object" }, "NetworkController": { @@ -313,7 +316,13 @@ }, "TokenListController": { "tokenList": "object", - "tokensChainsCache": {}, + "tokensChainsCache": { + "0x1": "object", + "0x539": "object", + "0xaa36a7": "object", + "0xe705": "object", + "0xe708": "object" + }, "preventPollingOnNetworkRestart": false }, "TokenRatesController": { "marketData": "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 3d36d5fc7592..acd9d6f8d074 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 @@ -174,7 +174,13 @@ "gasEstimateType": "none", "nonRPCGasFeeApisDisabled": "boolean", "tokenList": "object", - "tokensChainsCache": {}, + "tokensChainsCache": { + "0x1": "object", + "0x539": "object", + "0xaa36a7": "object", + "0xe705": "object", + "0xe708": "object" + }, "preventPollingOnNetworkRestart": false, "tokens": "object", "ignoredTokens": "object", @@ -198,8 +204,11 @@ "lastFetchedBlockNumbers": "object", "submitHistory": "object", "fiatCurrency": "usd", - "rates": { "btc": { "conversionDate": 0, "conversionRate": 0 } }, - "cryptocurrencies": ["btc"], + "rates": { + "btc": { "conversionDate": 0, "conversionRate": 0 }, + "sol": { "conversionDate": 0, "conversionRate": 0 } + }, + "cryptocurrencies": ["btc", "sol"], "snaps": "object", "jobs": "object", "database": null, diff --git a/test/e2e/tests/tokens/add-hide-token.spec.js b/test/e2e/tests/tokens/add-hide-token.spec.js index c773c560949a..6bd0c8744fba 100644 --- a/test/e2e/tests/tokens/add-hide-token.spec.js +++ b/test/e2e/tests/tokens/add-hide-token.spec.js @@ -109,6 +109,15 @@ describe('Add existing token using search', function () { { fixtures: new FixtureBuilder({ inputChainId: CHAIN_IDS.BSC }) .withPreferencesController({ useTokenDetection: true }) + .withTokenListController({ + tokenList: [ + { + name: 'Basic Attention Token', + symbol: 'BAT', + address: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', + }, + ], + }) .build(), ganacheOptions: { ...defaultGanacheOptions, diff --git a/test/e2e/tests/tokens/import-tokens.spec.js b/test/e2e/tests/tokens/import-tokens.spec.js index a1eb2782f9db..3055f7109551 100644 --- a/test/e2e/tests/tokens/import-tokens.spec.js +++ b/test/e2e/tests/tokens/import-tokens.spec.js @@ -37,7 +37,28 @@ describe('Import flow', function () { it('allows importing multiple tokens from search', async function () { await withFixtures( { - fixtures: new FixtureBuilder().withNetworkControllerOnMainnet().build(), + fixtures: new FixtureBuilder() + .withNetworkControllerOnMainnet() + .withTokensController({ + tokenList: [ + { + name: 'Chain Games', + symbol: 'CHAIN', + address: '0xc4c2614e694cf534d407ee49f8e44d125e4681c4', + }, + { + address: '0x7051faed0775f664a0286af4f75ef5ed74e02754', + symbol: 'CHANGE', + name: 'ChangeX', + }, + { + name: 'Chai', + symbol: 'CHAI', + address: '0x06af07097c9eeb7fd685c692751d5c66db49c215', + }, + ], + }) + .build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), testSpecificMock: mockPriceFetch, diff --git a/ui/contexts/assetPolling.tsx b/ui/contexts/assetPolling.tsx index 63cef9667fbd..be1954a37ec5 100644 --- a/ui/contexts/assetPolling.tsx +++ b/ui/contexts/assetPolling.tsx @@ -1,6 +1,8 @@ import React, { ReactNode } from 'react'; import useCurrencyRatePolling from '../hooks/useCurrencyRatePolling'; import useTokenRatesPolling from '../hooks/useTokenRatesPolling'; +import useTokenDetectionPolling from '../hooks/useTokenDetectionPolling'; +import useTokenListPolling from '../hooks/useTokenListPolling'; // This provider is a step towards making controller polling fully UI based. // Eventually, individual UI components will call the use*Polling hooks to @@ -8,6 +10,8 @@ import useTokenRatesPolling from '../hooks/useTokenRatesPolling'; export const AssetPollingProvider = ({ children }: { children: ReactNode }) => { useCurrencyRatePolling(); useTokenRatesPolling(); + useTokenDetectionPolling(); + useTokenListPolling(); return <>{children}; }; diff --git a/ui/hooks/useTokenDetectionPolling.ts b/ui/hooks/useTokenDetectionPolling.ts new file mode 100644 index 000000000000..790384e21cbf --- /dev/null +++ b/ui/hooks/useTokenDetectionPolling.ts @@ -0,0 +1,33 @@ +import { useSelector } from 'react-redux'; +import { + getNetworkConfigurationsByChainId, + getUseTokenDetection, +} from '../selectors'; +import { + tokenDetectionStartPolling, + tokenDetectionStopPollingByPollingToken, +} from '../store/actions'; +import { + getCompletedOnboarding, + getIsUnlocked, +} from '../ducks/metamask/metamask'; +import useMultiPolling from './useMultiPolling'; + +const useTokenDetectionPolling = () => { + const useTokenDetection = useSelector(getUseTokenDetection); + const completedOnboarding = useSelector(getCompletedOnboarding); + const isUnlocked = useSelector(getIsUnlocked); + const networkConfigurations = useSelector(getNetworkConfigurationsByChainId); + + const enabled = completedOnboarding && isUnlocked && useTokenDetection; + + useMultiPolling({ + startPolling: tokenDetectionStartPolling, + stopPollingByPollingToken: tokenDetectionStopPollingByPollingToken, + input: enabled ? [Object.keys(networkConfigurations)] : [], + }); + + return {}; +}; + +export default useTokenDetectionPolling; diff --git a/ui/hooks/useTokenListPolling.test.ts b/ui/hooks/useTokenListPolling.test.ts new file mode 100644 index 000000000000..09a22fffea50 --- /dev/null +++ b/ui/hooks/useTokenListPolling.test.ts @@ -0,0 +1,128 @@ +import { renderHookWithProvider } from '../../test/lib/render-helpers'; +import { + tokenListStartPolling, + tokenListStopPollingByPollingToken, +} from '../store/actions'; +import useTokenListPolling from './useTokenListPolling'; + +let mockPromises: Promise[]; + +jest.mock('../store/actions', () => ({ + tokenListStartPolling: jest.fn().mockImplementation((input) => { + const promise = Promise.resolve(`${input}_token`); + mockPromises.push(promise); + return promise; + }), + tokenListStopPollingByPollingToken: jest.fn(), +})); + +describe('useTokenListPolling', () => { + beforeEach(() => { + mockPromises = []; + jest.clearAllMocks(); + }); + + it('should poll for token lists on each chain when enabled, and stop on dismount', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useExternalServices: true, + useTokenDetection: true, + networkConfigurationsByChainId: { + '0x1': {}, + '0x89': {}, + }, + }, + }; + + const { unmount } = renderHookWithProvider( + () => useTokenListPolling(), + state, + ); + + // Should poll each chain + await Promise.all(mockPromises); + expect(tokenListStartPolling).toHaveBeenCalledTimes(2); + expect(tokenListStartPolling).toHaveBeenCalledWith('0x1'); + expect(tokenListStartPolling).toHaveBeenCalledWith('0x89'); + + // Stop polling on dismount + unmount(); + expect(tokenListStopPollingByPollingToken).toHaveBeenCalledTimes(2); + expect(tokenListStopPollingByPollingToken).toHaveBeenCalledWith( + '0x1_token', + ); + expect(tokenListStopPollingByPollingToken).toHaveBeenCalledWith( + '0x89_token', + ); + }); + + it('should not poll before onboarding is completed', async () => { + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: false, + useExternalServices: true, + useTokenDetection: true, + networkConfigurationsByChainId: { + '0x1': {}, + '0x89': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenListPolling(), state); + + await Promise.all(mockPromises); + expect(tokenListStartPolling).toHaveBeenCalledTimes(0); + expect(tokenListStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when locked', async () => { + const state = { + metamask: { + isUnlocked: false, + completedOnboarding: true, + useExternalServices: true, + useTokenDetection: true, + networkConfigurationsByChainId: { + '0x1': {}, + '0x89': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenListPolling(), state); + + await Promise.all(mockPromises); + expect(tokenListStartPolling).toHaveBeenCalledTimes(0); + expect(tokenListStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); + + it('should not poll when disabled', async () => { + // disabled when detection, petnames, and simulations are all disabled + const state = { + metamask: { + isUnlocked: true, + completedOnboarding: true, + useExternalServices: true, + useTokenDetection: false, + useTransactionSimulations: false, + preferences: { + petnamesEnabled: false, + }, + networkConfigurationsByChainId: { + '0x1': {}, + '0x89': {}, + }, + }, + }; + + renderHookWithProvider(() => useTokenListPolling(), state); + + await Promise.all(mockPromises); + expect(tokenListStartPolling).toHaveBeenCalledTimes(0); + expect(tokenListStopPollingByPollingToken).toHaveBeenCalledTimes(0); + }); +}); diff --git a/ui/hooks/useTokenListPolling.ts b/ui/hooks/useTokenListPolling.ts new file mode 100644 index 000000000000..9b43c3c6959a --- /dev/null +++ b/ui/hooks/useTokenListPolling.ts @@ -0,0 +1,43 @@ +import { useSelector } from 'react-redux'; +import { + getNetworkConfigurationsByChainId, + getPetnamesEnabled, + getUseExternalServices, + getUseTokenDetection, + getUseTransactionSimulations, +} from '../selectors'; +import { + tokenListStartPolling, + tokenListStopPollingByPollingToken, +} from '../store/actions'; +import { + getCompletedOnboarding, + getIsUnlocked, +} from '../ducks/metamask/metamask'; +import useMultiPolling from './useMultiPolling'; + +const useTokenListPolling = () => { + const networkConfigurations = useSelector(getNetworkConfigurationsByChainId); + const useTokenDetection = useSelector(getUseTokenDetection); + const useTransactionSimulations = useSelector(getUseTransactionSimulations); + const petnamesEnabled = useSelector(getPetnamesEnabled); + const completedOnboarding = useSelector(getCompletedOnboarding); + const isUnlocked = useSelector(getIsUnlocked); + const useExternalServices = useSelector(getUseExternalServices); + + const enabled = + completedOnboarding && + isUnlocked && + useExternalServices && + (useTokenDetection || petnamesEnabled || useTransactionSimulations); + + useMultiPolling({ + startPolling: tokenListStartPolling, + stopPollingByPollingToken: tokenListStopPollingByPollingToken, + input: enabled ? Object.keys(networkConfigurations) : [], + }); + + return {}; +}; + +export default useTokenListPolling; diff --git a/ui/pages/asset/useHistoricalPrices.ts b/ui/pages/asset/useHistoricalPrices.ts index e4b28add0bc7..febf99a9daed 100644 --- a/ui/pages/asset/useHistoricalPrices.ts +++ b/ui/pages/asset/useHistoricalPrices.ts @@ -31,8 +31,8 @@ export const useHistoricalPrices = ({ const [loading, setLoading] = useState(chainSupported); const [data, setData] = useState({}); - if (chainSupported) { - useEffect(() => { + useEffect(() => { + if (chainSupported) { setLoading(true); fetchWithCache({ url: `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${address}?vsCurrency=${currency}&timePeriod=${timeRange}`, @@ -59,7 +59,11 @@ export const useHistoricalPrices = ({ setData({ prices, edges }); setLoading(false); }); - }, [chainId, address, currency, timeRange]); - } + } else { + setData({}); + setLoading(false); + } + }, [chainSupported, chainId, address, currency, timeRange]); + return { loading, data }; }; diff --git a/ui/pages/onboarding-flow/pin-extension/pin-extension.js b/ui/pages/onboarding-flow/pin-extension/pin-extension.js index c9ad1806d49d..775095c2aa61 100644 --- a/ui/pages/onboarding-flow/pin-extension/pin-extension.js +++ b/ui/pages/onboarding-flow/pin-extension/pin-extension.js @@ -69,7 +69,9 @@ export default function OnboardingPinExtension() { if (selectedIndex === 0) { setSelectedIndex(1); } else { - dispatch(toggleExternalServices(externalServicesOnboardingToggleState)); + await dispatch( + toggleExternalServices(externalServicesOnboardingToggleState), + ); await dispatch(setCompletedOnboarding()); if (externalServicesOnboardingToggleState) { diff --git a/ui/pages/onboarding-flow/pin-extension/pin-extension.test.js b/ui/pages/onboarding-flow/pin-extension/pin-extension.test.js index 00c7c38cf1d0..22be22bd98ac 100644 --- a/ui/pages/onboarding-flow/pin-extension/pin-extension.test.js +++ b/ui/pages/onboarding-flow/pin-extension/pin-extension.test.js @@ -3,15 +3,31 @@ import { fireEvent } from '@testing-library/react'; import reactRouterDom from 'react-router-dom'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { setBackgroundConnection } from '../../../store/background-connection'; import { renderWithProvider } from '../../../../test/jest'; +import { + setCompletedOnboarding, + toggleExternalServices, +} from '../../../store/actions'; import PinExtension from './pin-extension'; -const completeOnboardingStub = jest - .fn() - .mockImplementation(() => Promise.resolve()); +jest.mock('../../../store/actions', () => ({ + toggleExternalServices: jest.fn(), + setCompletedOnboarding: jest.fn(), + performSignIn: jest.fn(), +})); + +const mockPromises = []; + +const mockDispatch = jest.fn().mockImplementation(() => { + const promise = Promise.resolve(); + mockPromises.push(promise); + return promise; +}); -const toggleExternalServicesStub = jest.fn(); +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: () => mockDispatch, +})); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -30,10 +46,6 @@ describe('Creation Successful Onboarding View', () => { }, }; const store = configureMockStore([thunk])(mockStore); - setBackgroundConnection({ - completeOnboarding: completeOnboardingStub, - toggleExternalServices: toggleExternalServicesStub, - }); const pushMock = jest.fn(); beforeAll(() => { @@ -43,12 +55,14 @@ describe('Creation Successful Onboarding View', () => { .mockReturnValue({ push: pushMock }); }); - it('should call completeOnboarding in the background when Done" button is clicked', () => { + it('should call completeOnboarding in the background when Done" button is clicked', async () => { const { getByText } = renderWithProvider(, store); const nextButton = getByText('Next'); fireEvent.click(nextButton); const gotItButton = getByText('Done'); fireEvent.click(gotItButton); - expect(completeOnboardingStub).toHaveBeenCalledTimes(1); + await Promise.all(mockPromises); + expect(toggleExternalServices).toHaveBeenCalledTimes(1); + expect(setCompletedOnboarding).toHaveBeenCalledTimes(1); }); }); diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 7c407eae4dc4..aad920b6cb8b 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -954,6 +954,10 @@ export function getPetnamesEnabled(state) { return petnamesEnabled; } +export function getUseTransactionSimulations(state) { + return Boolean(state.metamask.useTransactionSimulations); +} + export function getRedesignedConfirmationsEnabled(state) { const { redesignedConfirmationsEnabled } = getPreferences(state); return redesignedConfirmationsEnabled; diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 67de497817c8..32434d3a1d59 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -3548,13 +3548,14 @@ export function setIpfsGateway( export function toggleExternalServices( val: boolean, ): ThunkAction { - return (dispatch: MetaMaskReduxDispatch) => { + return async (dispatch: MetaMaskReduxDispatch) => { log.debug(`background.toggleExternalServices`); - callBackgroundMethod('toggleExternalServices', [val], (err) => { - if (err) { - dispatch(displayWarning(err)); - } - }); + try { + await submitRequestToBackground('toggleExternalServices', [val]); + await forceUpdateMetamaskState(dispatch); + } catch (err) { + dispatch(displayWarning(err)); + } }; } @@ -4564,6 +4565,66 @@ export async function currencyRateStopPollingByPollingToken( await removePollingTokenFromAppState(pollingToken); } +/** + * Informs the TokenDetectionController that the UI requires token detection polling + * + * @param chainIds - An array of chain ids to poll token detection on. + * @returns polling token that can be used to stop polling. + */ +export async function tokenDetectionStartPolling( + chainIds: string[], +): Promise { + const pollingToken = await submitRequestToBackground( + 'tokenDetectionStartPolling', + [{ chainIds }], + ); + + await addPollingTokenToAppState(pollingToken); + return pollingToken; +} + +/** + * Informs the TokenDetectionController that the UI no longer token detection polling + * + * @param pollingToken - Poll token received from calling tokenDetectionStartPolling + */ +export async function tokenDetectionStopPollingByPollingToken( + pollingToken: string, +) { + await submitRequestToBackground('tokenDetectionStopPollingByPollingToken', [ + pollingToken, + ]); + await removePollingTokenFromAppState(pollingToken); +} + +/** + * Informs the TokenListController that the UI requires token list polling + * + * @param chainId + * @returns polling token that can be used to stop polling + */ +export async function tokenListStartPolling(chainId: string): Promise { + const pollingToken = await submitRequestToBackground( + 'tokenListStartPolling', + [{ chainId }], + ); + + await addPollingTokenToAppState(pollingToken); + return pollingToken; +} + +/** + * Informs the TokenListController that the UI no longer token list polling + * + * @param pollingToken - Poll token received from calling tokenListStartPolling + */ +export async function tokenListStopPollingByPollingToken(pollingToken: string) { + await submitRequestToBackground('tokenListStopPollingByPollingToken', [ + pollingToken, + ]); + await removePollingTokenFromAppState(pollingToken); +} + /** * Informs the TokenRatesController that the UI requires * token rate polling for the given chain id. diff --git a/yarn.lock b/yarn.lock index b7afd1eb5884..8d622a3d2d25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4934,9 +4934,9 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@npm:42.0.0": - version: 42.0.0 - resolution: "@metamask/assets-controllers@npm:42.0.0" +"@metamask/assets-controllers@npm:43.1.1": + version: 43.1.1 + resolution: "@metamask/assets-controllers@npm:43.1.1" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@ethersproject/address": "npm:^5.7.0" @@ -4946,7 +4946,7 @@ __metadata: "@metamask/abi-utils": "npm:^2.0.3" "@metamask/base-controller": "npm:^7.0.2" "@metamask/contract-metadata": "npm:^2.4.0" - "@metamask/controller-utils": "npm:^11.4.2" + "@metamask/controller-utils": "npm:^11.4.3" "@metamask/eth-query": "npm:^4.0.0" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/polling-controller": "npm:^12.0.1" @@ -4963,18 +4963,18 @@ __metadata: single-call-balance-checker-abi: "npm:^1.0.0" uuid: "npm:^8.3.2" peerDependencies: - "@metamask/accounts-controller": ^18.0.0 + "@metamask/accounts-controller": ^19.0.0 "@metamask/approval-controller": ^7.0.0 - "@metamask/keyring-controller": ^17.0.0 + "@metamask/keyring-controller": ^18.0.0 "@metamask/network-controller": ^22.0.0 - "@metamask/preferences-controller": ^13.0.0 - checksum: 10/64d2bd43139ee5c19bd665b07212cd5d5dd41b457dedde3b5db31442292c4d064dc015011f5f001bb423683675fb20898ff652e91d2339ad1d21cc45fa93487a + "@metamask/preferences-controller": ^14.0.0 + checksum: 10/e8f37928085a243f2f3a9d3b09b486f31737814d6257ee49bc2d841d2f467733b8c533c056e9ca24acdcc80414503b34b00e10abb1cdfeb8483e6fe30bc4a62f languageName: node linkType: hard -"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A42.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch": - version: 42.0.0 - resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A42.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch::version=42.0.0&hash=e14ff8" +"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A43.1.1#~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch::version=43.1.1&hash=5a94c2": + version: 43.1.1 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A43.1.1#~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch::version=43.1.1&hash=5a94c2" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@ethersproject/address": "npm:^5.7.0" @@ -4984,7 +4984,7 @@ __metadata: "@metamask/abi-utils": "npm:^2.0.3" "@metamask/base-controller": "npm:^7.0.2" "@metamask/contract-metadata": "npm:^2.4.0" - "@metamask/controller-utils": "npm:^11.4.2" + "@metamask/controller-utils": "npm:^11.4.3" "@metamask/eth-query": "npm:^4.0.0" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/polling-controller": "npm:^12.0.1" @@ -5001,12 +5001,50 @@ __metadata: single-call-balance-checker-abi: "npm:^1.0.0" uuid: "npm:^8.3.2" peerDependencies: - "@metamask/accounts-controller": ^18.0.0 + "@metamask/accounts-controller": ^19.0.0 "@metamask/approval-controller": ^7.0.0 - "@metamask/keyring-controller": ^17.0.0 + "@metamask/keyring-controller": ^18.0.0 + "@metamask/network-controller": ^22.0.0 + "@metamask/preferences-controller": ^14.0.0 + checksum: 10/1a3672cb721c6716d33c1c6c5e7ffc2859689407e70af7503204220afe41f6c0d20f883ad7d51089af7376e4005de7479525b3faa49113c942cf3ab1bceba154 + languageName: node + linkType: hard + +"@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A43.1.1%23~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch%3A%3Aversion=43.1.1&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch": + version: 43.1.1 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A43.1.1%23~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch%3A%3Aversion=43.1.1&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch::version=43.1.1&hash=c4e407" + dependencies: + "@ethereumjs/util": "npm:^8.1.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/abi-utils": "npm:^2.0.3" + "@metamask/base-controller": "npm:^7.0.2" + "@metamask/contract-metadata": "npm:^2.4.0" + "@metamask/controller-utils": "npm:^11.4.3" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/metamask-eth-abis": "npm:^3.1.1" + "@metamask/polling-controller": "npm:^12.0.1" + "@metamask/rpc-errors": "npm:^7.0.1" + "@metamask/utils": "npm:^10.0.0" + "@types/bn.js": "npm:^5.1.5" + "@types/uuid": "npm:^8.3.0" + async-mutex: "npm:^0.5.0" + bn.js: "npm:^5.2.1" + cockatiel: "npm:^3.1.2" + immer: "npm:^9.0.6" + lodash: "npm:^4.17.21" + multiformats: "npm:^13.1.0" + single-call-balance-checker-abi: "npm:^1.0.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/accounts-controller": ^19.0.0 + "@metamask/approval-controller": ^7.0.0 + "@metamask/keyring-controller": ^18.0.0 "@metamask/network-controller": ^22.0.0 - "@metamask/preferences-controller": ^13.0.0 - checksum: 10/9a6727b28f88fd2df3f4b1628dd5d8c2f3e73fd4b9cd090f22d175c2522faa6c6b7e9a93d0ec2b2d123a263c8f4116fbfe97f196b99401b28ac8597f522651eb + "@metamask/preferences-controller": ^14.0.0 + checksum: 10/189f45b9afefef9dc54f29920eb64fcebbc1dd13d792627ab2649c973a94ca310c848411fe4b294419c881d1956ff50bd6107f7411faa2a953da005662269e40 languageName: node linkType: hard @@ -5087,9 +5125,9 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@npm:^11.0.0, @metamask/controller-utils@npm:^11.0.2, @metamask/controller-utils@npm:^11.1.0, @metamask/controller-utils@npm:^11.2.0, @metamask/controller-utils@npm:^11.3.0, @metamask/controller-utils@npm:^11.4.0, @metamask/controller-utils@npm:^11.4.1, @metamask/controller-utils@npm:^11.4.2": - version: 11.4.2 - resolution: "@metamask/controller-utils@npm:11.4.2" +"@metamask/controller-utils@npm:^11.0.0, @metamask/controller-utils@npm:^11.0.2, @metamask/controller-utils@npm:^11.1.0, @metamask/controller-utils@npm:^11.2.0, @metamask/controller-utils@npm:^11.3.0, @metamask/controller-utils@npm:^11.4.0, @metamask/controller-utils@npm:^11.4.1, @metamask/controller-utils@npm:^11.4.2, @metamask/controller-utils@npm:^11.4.3": + version: 11.4.3 + resolution: "@metamask/controller-utils@npm:11.4.3" dependencies: "@ethereumjs/util": "npm:^8.1.0" "@metamask/eth-query": "npm:^4.0.0" @@ -5101,7 +5139,7 @@ __metadata: bn.js: "npm:^5.2.1" eth-ens-namehash: "npm:^2.0.8" fast-deep-equal: "npm:^3.1.3" - checksum: 10/fdae49ee97e7a2a1bb6414011ca59932f8712a768a9c4c43673a2504c9fa9e61d83df53a21ff0506ef6a8cf774704f2df58a6d71385c8786ec5cab4359c051e1 + checksum: 10/5703b0721daf679cf44affc690f2b313e40893b64b0aafaf203e69ee51438197cc3634ef7094145f580a8a8aaadcb79026b2fbd4065c1bb4a8c26627a2c4c69a languageName: node linkType: hard @@ -26699,7 +26737,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%3A42.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch" + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A43.1.1%23~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch%3A%3Aversion=43.1.1&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch" "@metamask/auto-changelog": "npm:^2.1.0" "@metamask/base-controller": "npm:^7.0.0" "@metamask/bitcoin-wallet-snap": "npm:^0.8.2" From 8b02ae4072f060e659eb91c5de2d64db991b25cd Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Fri, 15 Nov 2024 16:29:27 -0800 Subject: [PATCH 28/32] feat: upgrade assets controllers to version 44 (#28472) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Upgrades the assets controllers to version 44. And starts replacing some instances of https://github.com/MetaMask/eth-token-tracker with reading state from the `TokenBalancesController` [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28472?quickstart=1) ## **Related issues** ## **Manual testing steps** No visual changes. Token balances should render correctly like before on the tokens page, and when switching accounts and chains. ## **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 --- ...s-controllers-npm-44.0.0-c223d56176.patch} | 0 app/scripts/constants/sentry-state.ts | 3 + app/scripts/metamask-controller.js | 39 +++++++ package.json | 2 +- ...rs-after-init-opt-in-background-state.json | 3 + .../errors-after-init-opt-in-ui-state.json | 1 + .../app/assets/asset-list/asset-list.test.tsx | 2 + .../app/wallet-overview/btc-overview.test.tsx | 2 + .../app/wallet-overview/eth-overview.test.js | 2 + .../account-overview-btc.test.tsx | 5 + .../account-overview-eth.test.tsx | 5 + ui/ducks/metamask/metamask.js | 10 ++ ui/hooks/useAccountTotalFiatBalance.js | 7 +- ui/hooks/useAccountTotalFiatBalance.test.js | 41 ++----- ...MultichainAccountTotalFiatBalance.test.tsx | 28 +++-- .../useMultichainAccountTotalFiatBalance.ts | 6 +- ui/hooks/useTokenBalances.ts | 110 ++++++++++++++++++ ui/pages/asset/components/asset-page.test.tsx | 6 + ui/pages/routes/routes.component.test.js | 2 + ui/store/actions.ts | 20 ++++ yarn.lock | 29 ++--- 21 files changed, 256 insertions(+), 67 deletions(-) rename .yarn/patches/{@metamask-assets-controllers-npm-43.1.1-c223d56176.patch => @metamask-assets-controllers-npm-44.0.0-c223d56176.patch} (100%) create mode 100644 ui/hooks/useTokenBalances.ts diff --git a/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch b/.yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch similarity index 100% rename from .yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch rename to .yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index 6f960a496b3d..5146e38e8a41 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -355,6 +355,9 @@ export const SENTRY_BACKGROUND_STATE = { [AllProperties]: false, }, }, + TokenBalancesController: { + tokenBalances: false, + }, TokenRatesController: { marketData: false, }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6e2ba119053c..cc7eaa12d3d1 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -12,6 +12,7 @@ import { CodefiTokenPricesServiceV2, RatesController, fetchMultiExchangeRate, + TokenBalancesController, } from '@metamask/assets-controllers'; import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; @@ -685,6 +686,7 @@ export default class MetamaskController extends EventEmitter { 'AccountsController:selectedEvmAccountChange', 'PreferencesController:stateChange', 'TokenListController:stateChange', + 'NetworkController:stateChange', ], }); this.tokensController = new TokensController({ @@ -893,6 +895,28 @@ export default class MetamaskController extends EventEmitter { }; }; + const tokenBalancesMessenger = this.controllerMessenger.getRestricted({ + name: 'TokenBalancesController', + allowedActions: [ + 'NetworkController:getState', + 'NetworkController:getNetworkClientById', + 'TokensController:getState', + 'PreferencesController:getState', + 'AccountsController:getSelectedAccount', + ], + allowedEvents: [ + 'PreferencesController:stateChange', + 'TokensController:stateChange', + 'NetworkController:stateChange', + ], + }); + + this.tokenBalancesController = new TokenBalancesController({ + messenger: tokenBalancesMessenger, + state: initState.TokenBalancesController, + interval: 30000, + }); + const phishingControllerMessenger = this.controllerMessenger.getRestricted({ name: 'PhishingController', }); @@ -2413,6 +2437,7 @@ export default class MetamaskController extends EventEmitter { GasFeeController: this.gasFeeController, TokenListController: this.tokenListController, TokensController: this.tokensController, + TokenBalancesController: this.tokenBalancesController, SmartTransactionsController: this.smartTransactionsController, NftController: this.nftController, PhishingController: this.phishingController, @@ -2468,6 +2493,7 @@ export default class MetamaskController extends EventEmitter { GasFeeController: this.gasFeeController, TokenListController: this.tokenListController, TokensController: this.tokensController, + TokenBalancesController: this.tokenBalancesController, SmartTransactionsController: this.smartTransactionsController, NftController: this.nftController, SelectedNetworkController: this.selectedNetworkController, @@ -3229,6 +3255,7 @@ export default class MetamaskController extends EventEmitter { nftController, nftDetectionController, currencyRateController, + tokenBalancesController, tokenDetectionController, ensController, tokenListController, @@ -4047,6 +4074,14 @@ export default class MetamaskController extends EventEmitter { tokenListStopPollingByPollingToken: tokenListController.stopPollingByPollingToken.bind(tokenListController), + tokenBalancesStartPolling: tokenBalancesController.startPolling.bind( + tokenBalancesController, + ), + tokenBalancesStopPollingByPollingToken: + tokenBalancesController.stopPollingByPollingToken.bind( + tokenBalancesController, + ), + // GasFeeController gasFeeStartPollingByNetworkClientId: gasFeeController.startPollingByNetworkClientId.bind(gasFeeController), @@ -6681,6 +6716,7 @@ export default class MetamaskController extends EventEmitter { this.tokenRatesController.stopAllPolling(); this.tokenDetectionController.stopAllPolling(); this.tokenListController.stopAllPolling(); + this.tokenBalancesController.stopAllPolling(); this.appStateController.clearPollingTokens(); } catch (error) { console.error(error); @@ -6921,6 +6957,9 @@ export default class MetamaskController extends EventEmitter { await this._createTransactionNotifcation(transactionMeta); await this._updateNFTOwnership(transactionMeta); this._trackTransactionFailure(transactionMeta); + await this.tokenBalancesController.updateBalancesByChainId({ + chainId: transactionMeta.chainId, + }); } async _createTransactionNotifcation(transactionMeta) { diff --git a/package.json b/package.json index ae2ebbd338a9..04098328933f 100644 --- a/package.json +++ b/package.json @@ -292,7 +292,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@patch%3A@metamask/assets-controllers@npm%253A43.1.1%23~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch%3A%3Aversion=43.1.1&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A44.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch%3A%3Aversion=44.0.0&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch", "@metamask/base-controller": "^7.0.0", "@metamask/bitcoin-wallet-snap": "^0.8.2", "@metamask/browser-passworder": "^4.3.0", 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 999dce99ca0c..e47cfcd806b9 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 @@ -314,6 +314,9 @@ "swapsFeatureFlags": {} } }, + "TokenBalancesController": { + "tokenBalances": "object" + }, "TokenListController": { "tokenList": "object", "tokensChainsCache": { 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 acd9d6f8d074..7fd8501eb2b8 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 @@ -181,6 +181,7 @@ "0xe705": "object", "0xe708": "object" }, + "tokenBalances": "object", "preventPollingOnNetworkRestart": false, "tokens": "object", "ignoredTokens": "object", diff --git a/ui/components/app/assets/asset-list/asset-list.test.tsx b/ui/components/app/assets/asset-list/asset-list.test.tsx index 329c29a6108e..fd65e740238d 100644 --- a/ui/components/app/assets/asset-list/asset-list.test.tsx +++ b/ui/components/app/assets/asset-list/asset-list.test.tsx @@ -64,6 +64,8 @@ jest.mock('../../../../hooks/useIsOriginalNativeTokenSymbol', () => { jest.mock('../../../../store/actions', () => { return { getTokenSymbol: jest.fn(), + tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), + tokenBalancesStopPollingByPollingToken: jest.fn(), }; }); diff --git a/ui/components/app/wallet-overview/btc-overview.test.tsx b/ui/components/app/wallet-overview/btc-overview.test.tsx index 62e6f5ff82b3..671e03a87ea8 100644 --- a/ui/components/app/wallet-overview/btc-overview.test.tsx +++ b/ui/components/app/wallet-overview/btc-overview.test.tsx @@ -29,6 +29,8 @@ jest.mock('../../../store/actions', () => ({ handleSnapRequest: jest.fn(), sendMultichainTransaction: jest.fn(), setDefaultHomeActiveTabName: jest.fn(), + tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), + tokenBalancesStopPollingByPollingToken: jest.fn(), })); const PORTOFOLIO_URL = 'https://portfolio.test'; diff --git a/ui/components/app/wallet-overview/eth-overview.test.js b/ui/components/app/wallet-overview/eth-overview.test.js index 539cfbb6c59f..a8c490b923c6 100644 --- a/ui/components/app/wallet-overview/eth-overview.test.js +++ b/ui/components/app/wallet-overview/eth-overview.test.js @@ -38,6 +38,8 @@ jest.mock('../../../ducks/locale/locale', () => ({ jest.mock('../../../store/actions', () => ({ startNewDraftTransaction: jest.fn(), + tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), + tokenBalancesStopPollingByPollingToken: jest.fn(), })); const mockGetIntlLocale = getIntlLocale; diff --git a/ui/components/multichain/account-overview/account-overview-btc.test.tsx b/ui/components/multichain/account-overview/account-overview-btc.test.tsx index 1def72354c82..fa32883ce773 100644 --- a/ui/components/multichain/account-overview/account-overview-btc.test.tsx +++ b/ui/components/multichain/account-overview/account-overview-btc.test.tsx @@ -8,6 +8,11 @@ import { AccountOverviewBtcProps, } from './account-overview-btc'; +jest.mock('../../../store/actions', () => ({ + tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), + tokenBalancesStopPollingByPollingToken: jest.fn(), +})); + const defaultProps: AccountOverviewBtcProps = { defaultHomeActiveTabName: null, onTabClick: jest.fn(), diff --git a/ui/components/multichain/account-overview/account-overview-eth.test.tsx b/ui/components/multichain/account-overview/account-overview-eth.test.tsx index ba2049ffd205..f9b53665e753 100644 --- a/ui/components/multichain/account-overview/account-overview-eth.test.tsx +++ b/ui/components/multichain/account-overview/account-overview-eth.test.tsx @@ -8,6 +8,11 @@ import { AccountOverviewEthProps, } from './account-overview-eth'; +jest.mock('../../../store/actions', () => ({ + tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), + tokenBalancesStopPollingByPollingToken: jest.fn(), +})); + const render = (props: AccountOverviewEthProps) => { const store = configureStore({ metamask: mockState.metamask, diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index 63ff92a11ccc..7ddc156c92ae 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -457,6 +457,16 @@ export const getGasEstimateTypeByChainId = createSelector( }, ); +/** + * Returns the balances of imported and detected tokens across all accounts and chains. + * + * @param {*} state + * @returns { import('@metamask/assets-controllers').TokenBalancesControllerState['tokenBalances']} + */ +export function getTokenBalances(state) { + return state.metamask.tokenBalances; +} + export const getGasFeeEstimatesByChainId = createSelector( getGasFeeControllerEstimatesByChainId, getTransactionGasFeeEstimatesByChainId, diff --git a/ui/hooks/useAccountTotalFiatBalance.js b/ui/hooks/useAccountTotalFiatBalance.js index 7b4a4675225a..aa1f906473ef 100644 --- a/ui/hooks/useAccountTotalFiatBalance.js +++ b/ui/hooks/useAccountTotalFiatBalance.js @@ -22,7 +22,7 @@ import { import { formatCurrency } from '../helpers/utils/confirm-tx.util'; import { getTokenFiatAmount } from '../helpers/utils/token-util'; import { roundToDecimalPlacesRemovingExtraZeroes } from '../helpers/utils/util'; -import { useTokenTracker } from './useTokenTracker'; +import { useTokenTracker } from './useTokenBalances'; export const useAccountTotalFiatBalance = ( account, @@ -54,10 +54,11 @@ export const useAccountTotalFiatBalance = ( const primaryTokenImage = useSelector(getNativeCurrencyImage); const nativeCurrency = useSelector(getNativeCurrency); - const { loading, tokensWithBalances } = useTokenTracker({ + const loading = false; + const { tokensWithBalances } = useTokenTracker({ + chainId: currentChainId, tokens, address: account?.address, - includeFailedTokens: true, hideZeroBalanceTokens: shouldHideZeroBalanceTokens, }); diff --git a/ui/hooks/useAccountTotalFiatBalance.test.js b/ui/hooks/useAccountTotalFiatBalance.test.js index 9fb1227367e1..6ac93cd08e33 100644 --- a/ui/hooks/useAccountTotalFiatBalance.test.js +++ b/ui/hooks/useAccountTotalFiatBalance.test.js @@ -14,35 +14,6 @@ const mockAccount = createMockInternalAccount({ address: '0x0836f5ed6b62baf60706fe3adc0ff0fd1df833da', }); -jest.mock('./useTokenTracker', () => { - return { - useTokenTracker: () => ({ - loading: false, - tokensWithBalances: [ - { - address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - balance: '48573', - balanceError: null, - decimals: 6, - image: undefined, - isERC721: undefined, - string: '0.04857', - symbol: 'USDC', - }, - { - address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e', - symbol: 'YFI', - balance: '1409247882142934', - decimals: 18, - string: '0.001409247882142934', - balanceError: null, - }, - ], - error: null, - }), - }; -}); - const renderUseAccountTotalFiatBalance = (address) => { const state = { ...mockState, @@ -78,7 +49,7 @@ const renderUseAccountTotalFiatBalance = (address) => { }, ...mockNetworkState({ chainId: CHAIN_IDS.MAINNET }), - detectedTokens: { + allTokens: { '0x1': { '0x0836f5ed6b62baf60706fe3adc0ff0fd1df833da': [ { @@ -96,6 +67,14 @@ const renderUseAccountTotalFiatBalance = (address) => { ], }, }, + tokenBalances: { + [mockAccount.address]: { + [CHAIN_IDS.MAINNET]: { + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': '0xBDBD', + '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e': '0x501B4176A64D6', + }, + }, + }, }, }; @@ -122,8 +101,6 @@ describe('useAccountTotalFiatBalance', () => { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', balance: '48573', - image: undefined, - isERC721: undefined, decimals: 6, string: 0.04857, balanceError: null, diff --git a/ui/hooks/useMultichainAccountTotalFiatBalance.test.tsx b/ui/hooks/useMultichainAccountTotalFiatBalance.test.tsx index ffd664612a02..e46eff925e50 100644 --- a/ui/hooks/useMultichainAccountTotalFiatBalance.test.tsx +++ b/ui/hooks/useMultichainAccountTotalFiatBalance.test.tsx @@ -15,31 +15,21 @@ const mockTokenBalances = [ balance: '48573', balanceError: null, decimals: 6, - image: undefined, - isERC721: undefined, - string: '0.04857', + string: 0.04857, symbol: 'USDC', + tokenFiatAmount: '0.05', }, { address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e', symbol: 'YFI', balance: '1409247882142934', decimals: 18, - string: '0.001409247882142934', + string: 0.00141, balanceError: null, + tokenFiatAmount: '7.52', }, ]; -jest.mock('./useTokenTracker', () => { - return { - useTokenTracker: () => ({ - loading: false, - tokensWithBalances: mockTokenBalances, - error: null, - }), - }; -}); - const mockAccount = createMockInternalAccount({ name: 'Account 1', address: '0x0836f5ed6b62baf60706fe3adc0ff0fd1df833da', @@ -104,7 +94,7 @@ const renderUseMultichainAccountTotalFiatBalance = ( }, ...mockNetworkState({ chainId: CHAIN_IDS.MAINNET }), - detectedTokens: { + allTokens: { '0x1': { '0x0836f5ed6b62baf60706fe3adc0ff0fd1df833da': [ { @@ -122,6 +112,14 @@ const renderUseMultichainAccountTotalFiatBalance = ( ], }, }, + tokenBalances: { + [mockAccount.address]: { + [CHAIN_IDS.MAINNET]: { + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': '0xBDBD', + '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e': '0x501B4176A64D6', + }, + }, + }, }, }; diff --git a/ui/hooks/useMultichainAccountTotalFiatBalance.ts b/ui/hooks/useMultichainAccountTotalFiatBalance.ts index 9e807be41ea5..335b8399c6d5 100644 --- a/ui/hooks/useMultichainAccountTotalFiatBalance.ts +++ b/ui/hooks/useMultichainAccountTotalFiatBalance.ts @@ -31,9 +31,9 @@ export const useMultichainAccountTotalFiatBalance = ( tokensWithBalances: { address: string; symbol: string; - decimals: string; - isERC721: boolean; - image: string; + decimals: number; + isERC721?: boolean; + image?: string; }[]; totalWeiBalance?: string; totalBalance?: string; diff --git a/ui/hooks/useTokenBalances.ts b/ui/hooks/useTokenBalances.ts new file mode 100644 index 000000000000..9ff92d488814 --- /dev/null +++ b/ui/hooks/useTokenBalances.ts @@ -0,0 +1,110 @@ +import { useSelector } from 'react-redux'; +import BN from 'bn.js'; +import { Token } from '@metamask/assets-controllers'; +import { Hex } from '@metamask/utils'; +import { getNetworkConfigurationsByChainId } from '../selectors'; +import { + tokenBalancesStartPolling, + tokenBalancesStopPollingByPollingToken, +} from '../store/actions'; +import { getTokenBalances } from '../ducks/metamask/metamask'; +import { hexToDecimal } from '../../shared/modules/conversion.utils'; +import useMultiPolling from './useMultiPolling'; + +export const useTokenBalances = ({ chainIds }: { chainIds?: Hex[] } = {}) => { + const tokenBalances = useSelector(getTokenBalances); + const networkConfigurations = useSelector(getNetworkConfigurationsByChainId); + + useMultiPolling({ + startPolling: tokenBalancesStartPolling, + stopPollingByPollingToken: tokenBalancesStopPollingByPollingToken, + input: chainIds ?? Object.keys(networkConfigurations), + }); + + return { tokenBalances }; +}; + +// This hook is designed for backwards compatibility with `ui/hooks/useTokenTracker.js` +// and the github.com/MetaMask/eth-token-tracker library. It replaces RPC calls with +// reading state from `TokenBalancesController`. It should not be used in new code. +// Instead, prefer to use `useTokenBalances` directly, or compose higher level hooks from it. +export const useTokenTracker = ({ + chainId, + tokens, + address, + hideZeroBalanceTokens, +}: { + chainId: Hex; + tokens: Token[]; + address: Hex; + hideZeroBalanceTokens?: boolean; +}) => { + const { tokenBalances } = useTokenBalances({ chainIds: [chainId] }); + + const tokensWithBalances = tokens.reduce((acc, token) => { + const hexBalance = + tokenBalances[address]?.[chainId]?.[token.address as Hex] ?? '0x0'; + if (hexBalance !== '0x0' || !hideZeroBalanceTokens) { + const decimalBalance = hexToDecimal(hexBalance); + acc.push({ + address: token.address, + symbol: token.symbol, + decimals: token.decimals, + balance: decimalBalance, + balanceError: null, + string: stringifyBalance( + new BN(decimalBalance), + new BN(token.decimals), + ), + }); + } + return acc; + }, [] as (Token & { balance: string; string: string; balanceError: unknown })[]); + + return { + tokensWithBalances, + }; +}; + +// From https://github.com/MetaMask/eth-token-tracker/blob/main/lib/util.js +// Ensures backwards compatibility with display formatting. +function stringifyBalance(balance: BN, bnDecimals: BN, balanceDecimals = 5) { + if (balance.eq(new BN(0))) { + return '0'; + } + + const decimals = parseInt(bnDecimals.toString(), 10); + if (decimals === 0) { + return balance.toString(); + } + + let bal = balance.toString(); + let len = bal.length; + let decimalIndex = len - decimals; + let prefix = ''; + + if (decimalIndex <= 0) { + while (prefix.length <= decimalIndex * -1) { + prefix += '0'; + len += 1; + } + bal = prefix + bal; + decimalIndex = 1; + } + + const whole = bal.substr(0, len - decimals); + + if (balanceDecimals === 0) { + return whole; + } + + const fractional = bal.substr(decimalIndex, balanceDecimals); + if (/0+$/u.test(fractional)) { + let withOnlySigZeroes = bal.substr(decimalIndex).replace(/0+$/u, ''); + if (withOnlySigZeroes.length > 0) { + withOnlySigZeroes = `.${withOnlySigZeroes}`; + } + return `${whole}${withOnlySigZeroes}`; + } + return `${whole}.${fractional}`; +} diff --git a/ui/pages/asset/components/asset-page.test.tsx b/ui/pages/asset/components/asset-page.test.tsx index 6bf1d12feb9e..5df516184004 100644 --- a/ui/pages/asset/components/asset-page.test.tsx +++ b/ui/pages/asset/components/asset-page.test.tsx @@ -13,6 +13,12 @@ import { setBackgroundConnection } from '../../../store/background-connection'; import { mockNetworkState } from '../../../../test/stub/networks'; import AssetPage from './asset-page'; +jest.mock('../../../store/actions', () => ({ + ...jest.requireActual('../../../store/actions'), + tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), + tokenBalancesStopPollingByPollingToken: jest.fn(), +})); + // Mock the price chart jest.mock('react-chartjs-2', () => ({ Line: () => null })); diff --git a/ui/pages/routes/routes.component.test.js b/ui/pages/routes/routes.component.test.js index 6151fedc687b..8a516fd76d6a 100644 --- a/ui/pages/routes/routes.component.test.js +++ b/ui/pages/routes/routes.component.test.js @@ -43,6 +43,8 @@ jest.mock('../../store/actions', () => ({ .mockResolvedValue({ chainId: '0x5' }), showNetworkDropdown: () => mockShowNetworkDropdown, hideNetworkDropdown: () => mockHideNetworkDropdown, + tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), + tokenBalancesStopPollingByPollingToken: jest.fn(), })); jest.mock('../../ducks/bridge/actions', () => ({ diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 32434d3a1d59..390db05f04df 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -4625,6 +4625,26 @@ export async function tokenListStopPollingByPollingToken(pollingToken: string) { await removePollingTokenFromAppState(pollingToken); } +export async function tokenBalancesStartPolling( + chainId: string, +): Promise { + const pollingToken = await submitRequestToBackground( + 'tokenBalancesStartPolling', + [{ chainId }], + ); + await addPollingTokenToAppState(pollingToken); + return pollingToken; +} + +export async function tokenBalancesStopPollingByPollingToken( + pollingToken: string, +) { + await submitRequestToBackground('tokenBalancesStopPollingByPollingToken', [ + pollingToken, + ]); + await removePollingTokenFromAppState(pollingToken); +} + /** * Informs the TokenRatesController that the UI requires * token rate polling for the given chain id. diff --git a/yarn.lock b/yarn.lock index 8d622a3d2d25..4602bb34f62f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4934,11 +4934,12 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@npm:43.1.1": - version: 43.1.1 - resolution: "@metamask/assets-controllers@npm:43.1.1" +"@metamask/assets-controllers@npm:44.0.0": + version: 44.0.0 + resolution: "@metamask/assets-controllers@npm:44.0.0" dependencies: "@ethereumjs/util": "npm:^8.1.0" + "@ethersproject/abi": "npm:^5.7.0" "@ethersproject/address": "npm:^5.7.0" "@ethersproject/bignumber": "npm:^5.7.0" "@ethersproject/contracts": "npm:^5.7.0" @@ -4968,15 +4969,16 @@ __metadata: "@metamask/keyring-controller": ^18.0.0 "@metamask/network-controller": ^22.0.0 "@metamask/preferences-controller": ^14.0.0 - checksum: 10/e8f37928085a243f2f3a9d3b09b486f31737814d6257ee49bc2d841d2f467733b8c533c056e9ca24acdcc80414503b34b00e10abb1cdfeb8483e6fe30bc4a62f + checksum: 10/6f3d8712a90aa322aabd38d43663d299ad7ee98a6d838d72bfc3b426ea0e4e925bb78c1aaaa3c75d43e95d46993c47583a4a03f4c58aee155525424fa86207ae languageName: node linkType: hard -"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A43.1.1#~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch::version=43.1.1&hash=5a94c2": - version: 43.1.1 - resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A43.1.1#~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch::version=43.1.1&hash=5a94c2" +"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A44.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch::version=44.0.0&hash=5a94c2": + version: 44.0.0 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A44.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch::version=44.0.0&hash=5a94c2" dependencies: "@ethereumjs/util": "npm:^8.1.0" + "@ethersproject/abi": "npm:^5.7.0" "@ethersproject/address": "npm:^5.7.0" "@ethersproject/bignumber": "npm:^5.7.0" "@ethersproject/contracts": "npm:^5.7.0" @@ -5006,15 +5008,16 @@ __metadata: "@metamask/keyring-controller": ^18.0.0 "@metamask/network-controller": ^22.0.0 "@metamask/preferences-controller": ^14.0.0 - checksum: 10/1a3672cb721c6716d33c1c6c5e7ffc2859689407e70af7503204220afe41f6c0d20f883ad7d51089af7376e4005de7479525b3faa49113c942cf3ab1bceba154 + checksum: 10/0d6c386a1f1e68ab339340fd8fa600827f55f234bc54b2224069a1819ab037641daa9696a0d62f187c0649317393efaeeb119a7852af51da3bb340e0e98cf9f6 languageName: node linkType: hard -"@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A43.1.1%23~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch%3A%3Aversion=43.1.1&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch": - version: 43.1.1 - resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A43.1.1%23~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch%3A%3Aversion=43.1.1&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch::version=43.1.1&hash=c4e407" +"@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A44.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch%3A%3Aversion=44.0.0&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch": + version: 44.0.0 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A44.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch%3A%3Aversion=44.0.0&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch::version=44.0.0&hash=c4e407" dependencies: "@ethereumjs/util": "npm:^8.1.0" + "@ethersproject/abi": "npm:^5.7.0" "@ethersproject/address": "npm:^5.7.0" "@ethersproject/bignumber": "npm:^5.7.0" "@ethersproject/contracts": "npm:^5.7.0" @@ -5044,7 +5047,7 @@ __metadata: "@metamask/keyring-controller": ^18.0.0 "@metamask/network-controller": ^22.0.0 "@metamask/preferences-controller": ^14.0.0 - checksum: 10/189f45b9afefef9dc54f29920eb64fcebbc1dd13d792627ab2649c973a94ca310c848411fe4b294419c881d1956ff50bd6107f7411faa2a953da005662269e40 + checksum: 10/11e8920bdf8ffce4a534c6aadfe768176c4e461a00bc06e6ece52f085755ff252194881d9edd308097186a05057075fd9812b6e4b1fd97dd731814ad205013da languageName: node linkType: hard @@ -26737,7 +26740,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@patch%3A@metamask/assets-controllers@npm%253A43.1.1%23~/.yarn/patches/@metamask-assets-controllers-npm-43.1.1-c223d56176.patch%3A%3Aversion=43.1.1&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch" + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A44.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch%3A%3Aversion=44.0.0&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch" "@metamask/auto-changelog": "npm:^2.1.0" "@metamask/base-controller": "npm:^7.0.0" "@metamask/bitcoin-wallet-snap": "npm:^0.8.2" From ee75939b5275dc582fb07b658d8f96341edb52d3 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 18 Nov 2024 17:01:03 -0330 Subject: [PATCH 29/32] chore: Update `cross-spawn` (#28522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The package `cross-spawn` has been updated to v7.0.6 to address a security advisory. The advisory doesn't impact our usage of this library, but it was easy to update. We had two usages of an older major version of this library in our dependency tree (v5), which were forced to v7 using a resolution. The only breaking changes in v6 and v7 were dropping support for older Node.js versions that are already below our minimum supported version. `cross-spawn` changelog: https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28522?quickstart=1) ## **Related issues** Resolves https://github.com/advisories/GHSA-3xgq-45jj-v275 ## **Manual testing steps** N/A ## **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. --- package.json | 5 +++-- yarn.lock | 63 +++++----------------------------------------------- 2 files changed, 9 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index 04098328933f..a04f6e9b3bc7 100644 --- a/package.json +++ b/package.json @@ -256,7 +256,8 @@ "@ledgerhq/evm-tools/axios": "^0.28.0", "@ledgerhq/hw-app-eth/axios": "^0.28.0", "@ledgerhq/hw-app-eth@npm:^6.39.0": "patch:@ledgerhq/hw-app-eth@npm%3A6.39.0#~/.yarn/patches/@ledgerhq-hw-app-eth-npm-6.39.0-866309bbbe.patch", - "@ledgerhq/evm-tools@npm:^1.2.3": "patch:@ledgerhq/evm-tools@npm%3A1.2.3#~/.yarn/patches/@ledgerhq-evm-tools-npm-1.2.3-414f44baa9.patch" + "@ledgerhq/evm-tools@npm:^1.2.3": "patch:@ledgerhq/evm-tools@npm%3A1.2.3#~/.yarn/patches/@ledgerhq-evm-tools-npm-1.2.3-414f44baa9.patch", + "cross-spawn@npm:^5.0.1": "^7.0.5" }, "dependencies": { "@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch", @@ -560,7 +561,7 @@ "concurrently": "^8.2.2", "copy-webpack-plugin": "^12.0.2", "core-js-pure": "^3.38.0", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.5", "crypto-browserify": "^3.12.0", "css-loader": "^6.10.0", "css-to-xpath": "^0.1.0", diff --git a/yarn.lock b/yarn.lock index 4602bb34f62f..4e745717f247 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16162,25 +16162,14 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^5.0.1": - version: 5.1.0 - resolution: "cross-spawn@npm:5.1.0" - dependencies: - lru-cache: "npm:^4.0.1" - shebang-command: "npm:^1.2.0" - which: "npm:^1.2.9" - checksum: 10/726939c9954fc70c20e538923feaaa33bebc253247d13021737c3c7f68cdc3e0a57f720c0fe75057c0387995349f3f12e20e9bfdbf12274db28019c7ea4ec166 - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" dependencies: path-key: "npm:^3.1.0" shebang-command: "npm:^2.0.0" which: "npm:^2.0.1" - checksum: 10/e1a13869d2f57d974de0d9ef7acbf69dc6937db20b918525a01dacb5032129bd552d290d886d981e99f1b624cb03657084cc87bd40f115c07ecf376821c729ce + checksum: 10/0d52657d7ae36eb130999dffff1168ec348687b48dd38e2ff59992ed916c88d328cf1d07ff4a4a10bc78de5e1c23f04b306d569e42f7a2293915c081e4dfee86 languageName: node linkType: hard @@ -26031,16 +26020,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^4.0.1": - version: 4.1.1 - resolution: "lru-cache@npm:4.1.1" - dependencies: - pseudomap: "npm:^1.0.2" - yallist: "npm:^2.1.2" - checksum: 10/a412db13e89abe202c2314e633bd8580be2a668ba2036c34da376ac66163aa9fba4727ca66ff7907ad68fb574511f0bc6275c0598fdaeeab92e1125f5397d0e4 - languageName: node - linkType: hard - "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -26927,7 +26906,7 @@ __metadata: copy-to-clipboard: "npm:^3.3.3" copy-webpack-plugin: "npm:^12.0.2" core-js-pure: "npm:^3.38.0" - cross-spawn: "npm:^7.0.3" + cross-spawn: "npm:^7.0.5" crypto-browserify: "npm:^3.12.0" css-loader: "npm:^6.10.0" css-to-xpath: "npm:^0.1.0" @@ -30655,13 +30634,6 @@ __metadata: languageName: node linkType: hard -"pseudomap@npm:^1.0.2": - version: 1.0.2 - resolution: "pseudomap@npm:1.0.2" - checksum: 10/856c0aae0ff2ad60881168334448e898ad7a0e45fe7386d114b150084254c01e200c957cf378378025df4e052c7890c5bd933939b0e0d2ecfcc1dc2f0b2991f5 - languageName: node - linkType: hard - "psl@npm:^1.1.33": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -33737,15 +33709,6 @@ __metadata: languageName: node linkType: hard -"shebang-command@npm:^1.2.0": - version: 1.2.0 - resolution: "shebang-command@npm:1.2.0" - dependencies: - shebang-regex: "npm:^1.0.0" - checksum: 10/9eed1750301e622961ba5d588af2212505e96770ec376a37ab678f965795e995ade7ed44910f5d3d3cb5e10165a1847f52d3348c64e146b8be922f7707958908 - languageName: node - linkType: hard - "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -33755,13 +33718,6 @@ __metadata: languageName: node linkType: hard -"shebang-regex@npm:^1.0.0": - version: 1.0.0 - resolution: "shebang-regex@npm:1.0.0" - checksum: 10/404c5a752cd40f94591dfd9346da40a735a05139dac890ffc229afba610854d8799aaa52f87f7e0c94c5007f2c6af55bdcaeb584b56691926c5eaf41dc8f1372 - languageName: node - linkType: hard - "shebang-regex@npm:^3.0.0": version: 3.0.0 resolution: "shebang-regex@npm:3.0.0" @@ -37911,7 +37867,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.2.12, which@npm:^1.2.14, which@npm:^1.2.9, which@npm:^1.3.1": +"which@npm:^1.2.12, which@npm:^1.2.14, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: @@ -38245,13 +38201,6 @@ __metadata: languageName: node linkType: hard -"yallist@npm:^2.1.2": - version: 2.1.2 - resolution: "yallist@npm:2.1.2" - checksum: 10/75fc7bee4821f52d1c6e6021b91b3e079276f1a9ce0ad58da3c76b79a7e47d6f276d35e206a96ac16c1cf48daee38a8bb3af0b1522a3d11c8ffe18f898828832 - languageName: node - linkType: hard - "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" From ec8e5fbc35237e23af477419cb732f0ad5adf512 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 18 Nov 2024 15:31:01 -0600 Subject: [PATCH 30/32] fix: PortfolioView: Selector to determine networks to poll (#28502) 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/28502?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/selectors/selectors.js | 52 +++++++++++++++ ui/selectors/selectors.test.js | 117 +++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index aad920b6cb8b..67d272b7642b 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -2196,6 +2196,58 @@ export const getAllEnabledNetworks = createDeepEqualSelector( ), ); +export const getChainIdsToPoll = createDeepEqualSelector( + getPreferences, + getNetworkConfigurationsByChainId, + getCurrentChainId, + (preferences, networkConfigurations, currentChainId) => { + const { pausedChainIds = [] } = preferences; + + if (!process.env.PORTFOLIO_VIEW) { + return [currentChainId]; + } + + return Object.keys(networkConfigurations).filter( + (chainId) => + !TEST_CHAINS.includes(chainId) && !pausedChainIds.includes(chainId), + ); + }, +); + +export const getNetworkClientIdsToPoll = createDeepEqualSelector( + getPreferences, + getNetworkConfigurationsByChainId, + getCurrentChainId, + (preferences, networkConfigurations, currentChainId) => { + const { pausedChainIds = [] } = preferences; + + if (!process.env.PORTFOLIO_VIEW) { + const networkConfiguration = networkConfigurations[currentChainId]; + return [ + networkConfiguration.rpcEndpoints[ + networkConfiguration.defaultRpcEndpointIndex + ].networkClientId, + ]; + } + + return Object.entries(networkConfigurations).reduce( + (acc, [chainId, network]) => { + if ( + !TEST_CHAINS.includes(chainId) && + !pausedChainIds.includes(chainId) + ) { + acc.push( + network.rpcEndpoints[network.defaultRpcEndpointIndex] + .networkClientId, + ); + } + return acc; + }, + [], + ); + }, +); + /** * To retrieve the maxBaseFee and priorityFee the user has set as default * diff --git a/ui/selectors/selectors.test.js b/ui/selectors/selectors.test.js index d6656e481709..85180dec45f4 100644 --- a/ui/selectors/selectors.test.js +++ b/ui/selectors/selectors.test.js @@ -838,6 +838,123 @@ describe('Selectors', () => { }); }); + describe('#getChainIdsToPoll', () => { + const networkConfigurationsByChainId = { + [CHAIN_IDS.MAINNET]: { + chainId: CHAIN_IDS.MAINNET, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 'mainnet' }], + }, + [CHAIN_IDS.LINEA_MAINNET]: { + chainId: CHAIN_IDS.LINEA_MAINNET, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 'linea-mainnet' }], + }, + [CHAIN_IDS.SEPOLIA]: { + chainId: CHAIN_IDS.SEPOLIA, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 'sepolia' }], + }, + [CHAIN_IDS.LINEA_SEPOLIA]: { + chainId: CHAIN_IDS.LINEA_SEPOLIA, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 'linea-sepolia' }], + }, + }; + + beforeEach(() => { + process.env.PORTFOLIO_VIEW = 'true'; + }); + + afterEach(() => { + process.env.PORTFOLIO_VIEW = undefined; + }); + + it('returns only non-test chain IDs', () => { + const chainIds = selectors.getChainIdsToPoll({ + metamask: { + preferences: { pausedChainIds: [] }, + networkConfigurationsByChainId, + selectedNetworkClientId: 'mainnet', + }, + }); + expect(Object.values(chainIds)).toHaveLength(2); + expect(chainIds).toStrictEqual([ + CHAIN_IDS.MAINNET, + CHAIN_IDS.LINEA_MAINNET, + ]); + }); + + it('does not return paused chain IDs', () => { + const chainIds = selectors.getChainIdsToPoll({ + metamask: { + preferences: { pausedChainIds: [CHAIN_IDS.LINEA_MAINNET] }, + networkConfigurationsByChainId, + selectedNetworkClientId: 'mainnet', + }, + }); + expect(Object.values(chainIds)).toHaveLength(1); + expect(chainIds).toStrictEqual([CHAIN_IDS.MAINNET]); + }); + }); + + describe('#getNetworkClientIdsToPoll', () => { + const networkConfigurationsByChainId = { + [CHAIN_IDS.MAINNET]: { + chainId: CHAIN_IDS.MAINNET, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 'mainnet' }], + }, + [CHAIN_IDS.LINEA_MAINNET]: { + chainId: CHAIN_IDS.LINEA_MAINNET, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 'linea-mainnet' }], + }, + [CHAIN_IDS.SEPOLIA]: { + chainId: CHAIN_IDS.SEPOLIA, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 'sepolia' }], + }, + [CHAIN_IDS.LINEA_SEPOLIA]: { + chainId: CHAIN_IDS.LINEA_SEPOLIA, + defaultRpcEndpointIndex: 0, + rpcEndpoints: [{ networkClientId: 'linea-sepolia' }], + }, + }; + + beforeEach(() => { + process.env.PORTFOLIO_VIEW = 'true'; + }); + + afterEach(() => { + process.env.PORTFOLIO_VIEW = undefined; + }); + + it('returns only non-test chain IDs', () => { + const chainIds = selectors.getNetworkClientIdsToPoll({ + metamask: { + preferences: { pausedChainIds: [] }, + networkConfigurationsByChainId, + selectedNetworkClientId: 'mainnet', + }, + }); + expect(Object.values(chainIds)).toHaveLength(2); + expect(chainIds).toStrictEqual(['mainnet', 'linea-mainnet']); + }); + + it('does not return paused chain IDs', () => { + const chainIds = selectors.getNetworkClientIdsToPoll({ + metamask: { + preferences: { pausedChainIds: [CHAIN_IDS.LINEA_MAINNET] }, + networkConfigurationsByChainId, + selectedNetworkClientId: 'mainnet', + }, + }); + expect(Object.values(chainIds)).toHaveLength(1); + expect(chainIds).toStrictEqual(['mainnet']); + }); + }); + describe('#isHardwareWallet', () => { it('returns false if it is not a HW wallet', () => { const mockStateWithImported = modifyStateWithHWKeyring( From fd78a56f628f7bfe8e2a9864dff0244d6bd446f3 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 18 Nov 2024 18:01:13 -0330 Subject: [PATCH 31/32] fix: Make QR scanner more strict (#28521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The QR scanner is now more strict about the contents it allows to be scanned. If the scanned QR code deviates at all from the supported formats, it will return "unknown" as the result (as it always has for completely unrecognized QR codes). Previously we would accept QR codes with a recognized prefix even if the complete contents did not match our expectations, which has resulted in unexpected behavior. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28521?quickstart=1) ## **Related issues** Fixes #28527 ## **Manual testing steps** - Open the MetaMask extension and select 'Send' - Click on the QR scanner icon in the "Send To" field and enable webcam - Scan a ERC-20 wallet receive QR from a mobile app, which follows the EIP-681 standard and contains a valid token contract and account address - ERC-20 Token Contract Address, which is the first address in the string, populates the "Send To" field instead of the intended recipient address ## **Screenshots/Recordings** ### **Before** We didn't record this, but multiple people on the team reproduced the problem. ### **After** https://www.loom.com/share/be8822e872a14ec98a47547cf6198603 ## **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). - [ ] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - We don't yet have any way to test QR scanning. We will follow up later with tests, and rely on manual testing for now. Later test automation work tracked in #28528 - [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). - [ ] 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. --- .../modals/qr-scanner/qr-scanner.component.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ui/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/components/app/modals/qr-scanner/qr-scanner.component.js index 75e1a83417b9..51ae5a89a6ef 100644 --- a/ui/components/app/modals/qr-scanner/qr-scanner.component.js +++ b/ui/components/app/modals/qr-scanner/qr-scanner.component.js @@ -22,6 +22,10 @@ const READY_STATE = { READY: 'READY', }; +const ethereumPrefix = 'ethereum:'; +// A 0x-prefixed Ethereum address is 42 characters (2 prefix + 40 address) +const addressLength = 42; + const parseContent = (content) => { let type = 'unknown'; let values = {}; @@ -31,12 +35,18 @@ const parseContent = (content) => { // For ex. EIP-681 (https://eips.ethereum.org/EIPS/eip-681) // Ethereum address links - fox ex. ethereum:0x.....1111 - if (content.split('ethereum:').length > 1) { + if ( + content.split(ethereumPrefix).length > 1 && + content.length === ethereumPrefix.length + addressLength + ) { type = 'address'; - // uses regex capture groups to match and extract address while ignoring everything else + // uses regex capture groups to match and extract address values = { address: parseScanContent(content) }; // Regular ethereum addresses - fox ex. 0x.....1111 - } else if (content.substring(0, 2).toLowerCase() === '0x') { + } else if ( + content.substring(0, 2).toLowerCase() === '0x' && + content.length === addressLength + ) { type = 'address'; values = { address: content }; } From 3c5178636b430f13ffefd3a244ff3f6c8aa93000 Mon Sep 17 00:00:00 2001 From: seaona <54408225+seaona@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:32:00 +0100 Subject: [PATCH 32/32] test: improve logs for e2e errors (#28479) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR adds an improvement on our logs when the errors do not have the expected form of a.value, leading to displaying empty errors and not failing the test. Those are happening for RPC and some snap errors types, which currently are displayed as empty (see below screenshots): - The RPC errors doesn't have a `value` property but a `description`, so we were seeing empty errors in the logs - In the snaps errors, the a.value property is not directly present, instead, the relevant information is nested within the preview.properties array - Other error structures, which doesn't fall under the 3 error categories, will be captured in a fallback, which will stringify the complete error With this change we are now able to see better error logs in our e2e. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28479?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3648 ## **Manual testing steps** 1. Run a test which triggers an RPC error like: `yarn test:e2e:single test/e2e/tests/request-queuing/ui.spec.js --browser=chrome` 2. Check console errors before and after this change ## **Screenshots/Recordings** ### **Before** See empty RPC error logs: ![Screenshot from 2024-11-15 08-47-39](https://github.com/user-attachments/assets/40f4a2dd-00f2-4bb3-b8da-740cd24254ec) See empty snap error logs (the 1st one type is logged but the 2nd one is empty): ![Screenshot from 2024-11-15 10-57-48](https://github.com/user-attachments/assets/019c1088-0816-4de3-a33a-9ff0c4266a9a) ### **After** See complete RPC error logs ![Screenshot from 2024-11-15 09-43-45](https://github.com/user-attachments/assets/80b6ff10-e615-4261-8b13-30674e7a51bf) See complete snaps error logs ![Screenshot from 2024-11-15 10-58-04](https://github.com/user-attachments/assets/629ec3da-ee19-4cda-ba82-fb73a01a8d03) ## **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/snaps/test-snap-metrics.spec.js | 1 + test/e2e/tests/account/lockdown.spec.ts | 1 + test/e2e/webdriver/driver.js | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/test/e2e/snaps/test-snap-metrics.spec.js b/test/e2e/snaps/test-snap-metrics.spec.js index 54ebd572d993..6c8ac7b9530f 100644 --- a/test/e2e/snaps/test-snap-metrics.spec.js +++ b/test/e2e/snaps/test-snap-metrics.spec.js @@ -900,6 +900,7 @@ describe('Test Snap Metrics', function () { testSpecificMock: mockSegment, ignoredConsoleErrors: [ 'MetaMask - RPC Error: Failed to fetch snap "npm:@metamask/bip32-example-snap": Failed to fetch tarball for package "@metamask/bip32-example-snap"..', + 'Failed to fetch snap "npm:@metamask/bip32-example-…ball for package "@metamask/bip32-example-snap"..', ], }, async ({ driver, mockedEndpoint: mockedEndpoints }) => { diff --git a/test/e2e/tests/account/lockdown.spec.ts b/test/e2e/tests/account/lockdown.spec.ts index 4307e1e33d6e..c2c4706855e0 100644 --- a/test/e2e/tests/account/lockdown.spec.ts +++ b/test/e2e/tests/account/lockdown.spec.ts @@ -86,6 +86,7 @@ describe('lockdown', function (this: Mocha.Suite) { { fixtures: new FixtureBuilder().build(), ganacheOptions, + ignoredConsoleErrors: ['Error: Could not establish connection.'], title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 1a10f7c0199d..42fa0f018f6d 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -1311,7 +1311,23 @@ class Driver { #getErrorFromEvent(event) { // Extract the values from the array - const values = event.args.map((a) => a.value); + const values = event.args.map((a) => { + // Handle snaps error type + if (a && a.preview && Array.isArray(a.preview.properties)) { + return a.preview.properties + .filter((prop) => prop.value !== 'Object') + .map((prop) => prop.value) + .join(', '); + } else if (a.description) { + // Handle RPC error type + return a.description; + } else if (a.value) { + // Handle generic error types + return a.value; + } + // Fallback for other error structures + return JSON.stringify(a, null, 2); + }); if (values[0]?.includes('%s')) { // The values are in the "printf" form of [message, ...substitutions]